Update `type_decl` and `primary_expression`
This CL updates the `type_decl` and `primary_expression` rules to the new WGSL grammar. Bug: tint:1633 Change-Id: Ifc457e01f43fe33e083fc8f9e316fdd02bfd87c3 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/99881 Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: Dan Sinclair <dsinclair@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
08d735e6a9
commit
64b09959f7
|
@ -1182,26 +1182,7 @@ Maybe<const ast::Type*> ParserImpl::type_decl_without_ident() {
|
||||||
|
|
||||||
// type_decl
|
// type_decl
|
||||||
// : IDENTIFIER
|
// : IDENTIFIER
|
||||||
// | BOOL
|
// | type_decl_without_ident
|
||||||
// | FLOAT32
|
|
||||||
// | INT32
|
|
||||||
// | UINT32
|
|
||||||
// | VEC2 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | VEC3 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | VEC4 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | PTR LESS_THAN storage_class, type_decl (COMMA access_mode)? GREATER_THAN
|
|
||||||
// | array_attribute_list* ARRAY LESS_THAN type_decl COMMA element_count_expression GREATER_THAN
|
|
||||||
// | array_attribute_list* ARRAY LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT2x2 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT2x3 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT2x4 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT3x2 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT3x3 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT3x4 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT4x2 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT4x3 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | MAT4x4 LESS_THAN type_decl GREATER_THAN
|
|
||||||
// | texture_and_sampler_types
|
|
||||||
Maybe<const ast::Type*> ParserImpl::type_decl() {
|
Maybe<const ast::Type*> ParserImpl::type_decl() {
|
||||||
auto& t = peek();
|
auto& t = peek();
|
||||||
Source source;
|
Source source;
|
||||||
|
@ -1209,57 +1190,7 @@ Maybe<const ast::Type*> ParserImpl::type_decl() {
|
||||||
return builder_.create<ast::TypeName>(source, builder_.Symbols().Register(t.to_str()));
|
return builder_.create<ast::TypeName>(source, builder_.Symbols().Register(t.to_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match(Token::Type::kBool, &source)) {
|
return type_decl_without_ident();
|
||||||
return builder_.ty.bool_(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kF16, &source)) {
|
|
||||||
return builder_.ty.f16(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kF32, &source)) {
|
|
||||||
return builder_.ty.f32(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kI32, &source)) {
|
|
||||||
return builder_.ty.i32(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kU32, &source)) {
|
|
||||||
return builder_.ty.u32(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.IsVector()) {
|
|
||||||
next(); // Consume the peek
|
|
||||||
return expect_type_decl_vector(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kPtr)) {
|
|
||||||
return expect_type_decl_pointer(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kAtomic)) {
|
|
||||||
return expect_type_decl_atomic(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kArray, &source)) {
|
|
||||||
return expect_type_decl_array(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.IsMatrix()) {
|
|
||||||
next(); // Consume the peek
|
|
||||||
return expect_type_decl_matrix(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto texture_or_sampler = texture_and_sampler_types();
|
|
||||||
if (texture_or_sampler.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
if (texture_or_sampler.matched) {
|
|
||||||
return texture_or_sampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Failure::kNoMatch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
|
Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
|
||||||
|
@ -1314,47 +1245,6 @@ Expect<const ast::Type*> ParserImpl::expect_type_decl_pointer(const Source& s) {
|
||||||
return builder_.ty.pointer(make_source_range_from(s), subtype.value, storage_class, access);
|
return builder_.ty.pointer(make_source_range_from(s), subtype.value, storage_class, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_pointer(const Token& t) {
|
|
||||||
const char* use = "ptr declaration";
|
|
||||||
|
|
||||||
auto storage_class = ast::StorageClass::kNone;
|
|
||||||
auto access = ast::Access::kUndefined;
|
|
||||||
|
|
||||||
auto subtype = expect_lt_gt_block(use, [&]() -> Expect<const ast::Type*> {
|
|
||||||
auto sc = expect_address_space(use);
|
|
||||||
if (sc.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
storage_class = sc.value;
|
|
||||||
|
|
||||||
if (!expect(use, Token::Type::kComma)) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto type = expect_type(use);
|
|
||||||
if (type.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kComma)) {
|
|
||||||
auto ac = expect_access_mode("access control");
|
|
||||||
if (ac.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
access = ac.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return type.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (subtype.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder_.ty.pointer(make_source_range_from(t.source()), subtype.value, storage_class,
|
|
||||||
access);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LESS_THAN type_decl GREATER_THAN
|
// LESS_THAN type_decl GREATER_THAN
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(const Source& s) {
|
Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(const Source& s) {
|
||||||
const char* use = "atomic declaration";
|
const char* use = "atomic declaration";
|
||||||
|
@ -1367,17 +1257,6 @@ Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(const Source& s) {
|
||||||
return builder_.ty.atomic(make_source_range_from(s), subtype.value);
|
return builder_.ty.atomic(make_source_range_from(s), subtype.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_atomic(const Token& t) {
|
|
||||||
const char* use = "atomic declaration";
|
|
||||||
|
|
||||||
auto subtype = expect_lt_gt_block(use, [&] { return expect_type(use); });
|
|
||||||
if (subtype.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder_.ty.atomic(make_source_range_from(t.source()), subtype.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LESS_THAN type_decl GREATER_THAN
|
// LESS_THAN type_decl GREATER_THAN
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(const Source& s, uint32_t count) {
|
Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(const Source& s, uint32_t count) {
|
||||||
const char* use = "vector";
|
const char* use = "vector";
|
||||||
|
@ -1389,27 +1268,6 @@ Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(const Source& s, ui
|
||||||
return builder_.ty.vec(make_source_range_from(s), ty.value, count);
|
return builder_.ty.vec(make_source_range_from(s), ty.value, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_vector(const Token& t) {
|
|
||||||
uint32_t count = 2;
|
|
||||||
if (t.Is(Token::Type::kVec3)) {
|
|
||||||
count = 3;
|
|
||||||
} else if (t.Is(Token::Type::kVec4)) {
|
|
||||||
count = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ast::Type* subtype = nullptr;
|
|
||||||
if (peek_is(Token::Type::kLessThan)) {
|
|
||||||
const char* use = "vector";
|
|
||||||
auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
|
|
||||||
if (ty.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
subtype = ty.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder_.ty.vec(make_source_range_from(t.source()), subtype, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LESS_THAN type_decl ( COMMA element_count_expression )? GREATER_THAN
|
// LESS_THAN type_decl ( COMMA element_count_expression )? GREATER_THAN
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_array(const Source& s) {
|
Expect<const ast::Type*> ParserImpl::expect_type_decl_array(const Source& s) {
|
||||||
const char* use = "array declaration";
|
const char* use = "array declaration";
|
||||||
|
@ -1451,46 +1309,6 @@ Expect<const ast::Type*> ParserImpl::expect_type_decl_array(const Source& s) {
|
||||||
return builder_.ty.array(make_source_range_from(s), type_size->type, type_size->size);
|
return builder_.ty.array(make_source_range_from(s), type_size->type, type_size->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_array(const Token& t) {
|
|
||||||
const char* use = "array declaration";
|
|
||||||
|
|
||||||
struct TypeAndSize {
|
|
||||||
const ast::Type* type = nullptr;
|
|
||||||
const ast::Expression* size = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!peek_is(Token::Type::kLessThan)) {
|
|
||||||
return builder_.ty.array(make_source_range_from(t.source()), nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto type_size = expect_lt_gt_block(use, [&]() -> Expect<TypeAndSize> {
|
|
||||||
auto type = expect_type(use);
|
|
||||||
if (type.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match(Token::Type::kComma)) {
|
|
||||||
return TypeAndSize{type.value, nullptr};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto size = element_count_expression();
|
|
||||||
if (size.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
if (!size.matched) {
|
|
||||||
return add_error(peek(), "expected array size expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypeAndSize{type.value, size.value};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type_size.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder_.ty.array(make_source_range_from(t.source()), type_size->type, type_size->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LESS_THAN type_decl GREATER_THAN
|
// LESS_THAN type_decl GREATER_THAN
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(const Source& s,
|
Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(const Source& s,
|
||||||
const MatrixDimensions& dims) {
|
const MatrixDimensions& dims) {
|
||||||
|
@ -1503,33 +1321,6 @@ Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(const Source& s,
|
||||||
return builder_.ty.mat(make_source_range_from(s), ty.value, dims.columns, dims.rows);
|
return builder_.ty.mat(make_source_range_from(s), ty.value, dims.columns, dims.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<const ast::Type*> ParserImpl::expect_type_decl_matrix(const Token& t) {
|
|
||||||
uint32_t rows = 2;
|
|
||||||
uint32_t columns = 2;
|
|
||||||
if (t.IsMat3xN()) {
|
|
||||||
columns = 3;
|
|
||||||
} else if (t.IsMat4xN()) {
|
|
||||||
columns = 4;
|
|
||||||
}
|
|
||||||
if (t.IsMatNx3()) {
|
|
||||||
rows = 3;
|
|
||||||
} else if (t.IsMatNx4()) {
|
|
||||||
rows = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ast::Type* subtype = nullptr;
|
|
||||||
if (peek_is(Token::Type::kLessThan)) {
|
|
||||||
const char* use = "matrix";
|
|
||||||
auto ty = expect_lt_gt_block(use, [&] { return expect_type(use); });
|
|
||||||
if (ty.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
subtype = ty.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns, rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
// address_space
|
// address_space
|
||||||
// : 'function'
|
// : 'function'
|
||||||
// | 'private'
|
// | 'private'
|
||||||
|
@ -2129,7 +1920,7 @@ Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_statement() {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto initializer = expression();
|
auto initializer = maybe_expression();
|
||||||
if (initializer.errored) {
|
if (initializer.errored) {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
@ -2156,7 +1947,7 @@ Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_statement() {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto initializer = expression();
|
auto initializer = maybe_expression();
|
||||||
if (initializer.errored) {
|
if (initializer.errored) {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
@ -2183,7 +1974,7 @@ Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_statement() {
|
||||||
|
|
||||||
const ast::Expression* initializer = nullptr;
|
const ast::Expression* initializer = nullptr;
|
||||||
if (match(Token::Type::kEqual)) {
|
if (match(Token::Type::kEqual)) {
|
||||||
auto initializer_expr = expression();
|
auto initializer_expr = maybe_expression();
|
||||||
if (initializer_expr.errored) {
|
if (initializer_expr.errored) {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
@ -2718,31 +2509,20 @@ Maybe<const ast::Type*> ParserImpl::callable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// primary_expression
|
// primary_expression
|
||||||
// : IDENT argument_expression_list?
|
// : BITCAST LESS_THAN type_decl GREATER_THAN paren_expression
|
||||||
// | type_decl argument_expression_list
|
// | callable argument_expression_list
|
||||||
// | const_literal
|
// | const_literal
|
||||||
|
// | IDENT argument_expression_list?
|
||||||
// | paren_expression
|
// | paren_expression
|
||||||
// | BITCAST LESS_THAN type_decl GREATER_THAN paren_expression
|
//
|
||||||
|
// Note, PAREN_LEFT ( expression ( COMMA expression ) * COMMA? )? PAREN_RIGHT is replaced
|
||||||
|
// with `argument_expression_list`.
|
||||||
|
//
|
||||||
|
// Note, this is matching the `callable` ident here instead of having to come from
|
||||||
|
// callable so we can return a `type` from callable.
|
||||||
Maybe<const ast::Expression*> ParserImpl::primary_expression() {
|
Maybe<const ast::Expression*> ParserImpl::primary_expression() {
|
||||||
auto& t = peek();
|
auto& t = peek();
|
||||||
|
|
||||||
auto lit = const_literal();
|
|
||||||
if (lit.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
if (lit.matched) {
|
|
||||||
return lit.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.Is(Token::Type::kParenLeft)) {
|
|
||||||
auto paren = expect_paren_expression();
|
|
||||||
if (paren.errored) {
|
|
||||||
return Failure::kErrored;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paren.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(Token::Type::kBitcast)) {
|
if (match(Token::Type::kBitcast)) {
|
||||||
const char* use = "bitcast expression";
|
const char* use = "bitcast expression";
|
||||||
|
|
||||||
|
@ -2759,6 +2539,27 @@ Maybe<const ast::Expression*> ParserImpl::primary_expression() {
|
||||||
return create<ast::BitcastExpression>(t.source(), type.value, params.value);
|
return create<ast::BitcastExpression>(t.source(), type.value, params.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto call = callable();
|
||||||
|
if (call.errored) {
|
||||||
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
|
if (call.matched) {
|
||||||
|
auto params = expect_argument_expression_list("type constructor");
|
||||||
|
if (params.errored) {
|
||||||
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder_.Construct(t.source(), call.value, std::move(params.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lit = const_literal();
|
||||||
|
if (lit.errored) {
|
||||||
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
|
if (lit.matched) {
|
||||||
|
return lit.value;
|
||||||
|
}
|
||||||
|
|
||||||
if (t.IsIdentifier()) {
|
if (t.IsIdentifier()) {
|
||||||
next();
|
next();
|
||||||
|
|
||||||
|
@ -2777,17 +2578,13 @@ Maybe<const ast::Expression*> ParserImpl::primary_expression() {
|
||||||
return ident;
|
return ident;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto type = type_decl();
|
if (t.Is(Token::Type::kParenLeft)) {
|
||||||
if (type.errored) {
|
auto paren = expect_paren_expression();
|
||||||
return Failure::kErrored;
|
if (paren.errored) {
|
||||||
}
|
|
||||||
if (type.matched) {
|
|
||||||
auto params = expect_argument_expression_list("type constructor");
|
|
||||||
if (params.errored) {
|
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder_.Construct(t.source(), type.value, std::move(params.value));
|
return paren.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
|
|
|
@ -950,11 +950,6 @@ class ParserImpl {
|
||||||
Expect<const ast::Type*> expect_type_decl_array(const Source& s);
|
Expect<const ast::Type*> expect_type_decl_array(const Source& s);
|
||||||
Expect<const ast::Type*> expect_type_decl_matrix(const Source& s, const MatrixDimensions& dims);
|
Expect<const ast::Type*> expect_type_decl_matrix(const Source& s, const MatrixDimensions& dims);
|
||||||
|
|
||||||
Expect<const ast::Type*> expect_type_decl_pointer(const Token& t);
|
|
||||||
Expect<const ast::Type*> expect_type_decl_atomic(const Token& t);
|
|
||||||
Expect<const ast::Type*> expect_type_decl_vector(const Token& t);
|
|
||||||
Expect<const ast::Type*> expect_type_decl_array(const Token& t);
|
|
||||||
Expect<const ast::Type*> expect_type_decl_matrix(const Token& t);
|
|
||||||
Expect<const ast::Type*> expect_type(std::string_view use);
|
Expect<const ast::Type*> expect_type(std::string_view use);
|
||||||
|
|
||||||
Maybe<const ast::Statement*> non_block_statement();
|
Maybe<const ast::Statement*> non_block_statement();
|
||||||
|
|
|
@ -528,22 +528,6 @@ TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Vec) {
|
||||||
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
|
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ParserImplTest, TypeDecl_Array_InferTypeAndSize) {
|
|
||||||
auto p = parser("array");
|
|
||||||
auto t = p->type_decl();
|
|
||||||
EXPECT_TRUE(t.matched);
|
|
||||||
EXPECT_FALSE(t.errored);
|
|
||||||
ASSERT_NE(t.value, nullptr) << p->error();
|
|
||||||
ASSERT_FALSE(p->has_error());
|
|
||||||
ASSERT_TRUE(t.value->Is<ast::Array>());
|
|
||||||
|
|
||||||
auto* a = t.value->As<ast::Array>();
|
|
||||||
EXPECT_FALSE(a->IsRuntimeArray());
|
|
||||||
EXPECT_EQ(a->type, nullptr);
|
|
||||||
EXPECT_EQ(a->count, nullptr);
|
|
||||||
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 6u}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
|
TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
|
||||||
auto p = parser("array<f32, !>");
|
auto p = parser("array<f32, !>");
|
||||||
auto t = p->type_decl();
|
auto t = p->type_decl();
|
||||||
|
|
Loading…
Reference in New Issue