From 069fbb3c01d8be0940d60e3324213a0cde4a42d5 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Fri, 25 Sep 2020 14:12:11 -0600 Subject: [PATCH] Add opaque type syntax --- doc/langref.html.in | 5 +-- lib/std/builtin.zig | 8 ++++- lib/std/zig/ast.zig | 2 +- lib/std/zig/parse.zig | 3 +- lib/std/zig/render.zig | 14 +++++++- lib/std/zig/tokenizer.zig | 3 ++ src/stage1/all_types.hpp | 4 +++ src/stage1/analyze.cpp | 54 ++++++++++++++++++++++++------ src/stage1/ir.cpp | 37 ++++++++++++++++++-- src/stage1/parser.cpp | 9 +++++ src/stage1/tokenizer.cpp | 2 ++ src/stage1/tokenizer.hpp | 1 + src/translate_c.zig | 16 ++++----- test/stage1/behavior/type.zig | 13 +++++-- test/stage1/behavior/type_info.zig | 17 +++++++++- 15 files changed, 159 insertions(+), 29 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 22faf7fd8f..690614ff99 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -11193,7 +11193,7 @@ PtrTypeStart ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE ContainerDeclType - <- (KEYWORD_struct / KEYWORD_enum) (LPAREN Expr RPAREN)? + <- (KEYWORD_struct / KEYWORD_enum / KEYWORD_opaque) (LPAREN Expr RPAREN)? / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? # Alignment @@ -11340,6 +11340,7 @@ KEYWORD_inline <- 'inline' end_of_word KEYWORD_noalias <- 'noalias' end_of_word KEYWORD_nosuspend <- 'nosuspend' end_of_word KEYWORD_null <- 'null' end_of_word +KEYWORD_opaque <- 'opaque' end_of_word KEYWORD_or <- 'or' end_of_word KEYWORD_orelse <- 'orelse' end_of_word KEYWORD_packed <- 'packed' end_of_word @@ -11368,7 +11369,7 @@ keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_anyframe / KEYWORD_anytype / KEYWORD_defer / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer / KEYWORD_error / KEYWORD_export / KEYWORD_extern / KEYWORD_false / KEYWORD_fn / KEYWORD_for / KEYWORD_if / KEYWORD_inline - / KEYWORD_noalias / KEYWORD_null / KEYWORD_or + / KEYWORD_noalias / KEYWORD_null / KEYWORD_opaque / KEYWORD_or / KEYWORD_orelse / KEYWORD_packed / KEYWORD_pub / KEYWORD_resume / KEYWORD_return / KEYWORD_linksection / KEYWORD_struct / KEYWORD_suspend diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 68bbbe3b2d..fe72046e36 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -199,7 +199,7 @@ pub const TypeInfo = union(enum) { Union: Union, Fn: Fn, BoundFn: Fn, - Opaque: void, + Opaque: Opaque, Frame: Frame, AnyFrame: AnyFrame, Vector: Vector, @@ -360,6 +360,12 @@ pub const TypeInfo = union(enum) { args: []const FnArg, }; + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Opaque = struct { + decls: []const Declaration, + }; + /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const Frame = struct { diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index d8943adde0..0973877aa8 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -288,7 +288,7 @@ pub const Error = union(enum) { pub const ExpectedVarDecl = SingleTokenError("Expected variable declaration, found '{}'"); pub const ExpectedFn = SingleTokenError("Expected function, found '{}'"); pub const ExpectedReturnType = SingleTokenError("Expected 'var' or return type expression, found '{}'"); - pub const ExpectedAggregateKw = SingleTokenError("Expected '" ++ Token.Id.Keyword_struct.symbol() ++ "', '" ++ Token.Id.Keyword_union.symbol() ++ "', or '" ++ Token.Id.Keyword_enum.symbol() ++ "', found '{}'"); + pub const ExpectedAggregateKw = SingleTokenError("Expected '" ++ Token.Id.Keyword_struct.symbol() ++ "', '" ++ Token.Id.Keyword_union.symbol() ++ "', '" ++ Token.Id.Keyword_enum.symbol() ++ "', or '" ++ Token.Id.Keyword_opaque.symbol() ++ "', found '{}'"); pub const ExpectedEqOrSemi = SingleTokenError("Expected '=' or ';', found '{}'"); pub const ExpectedSemiOrLBrace = SingleTokenError("Expected ';' or '{{', found '{}'"); pub const ExpectedSemiOrElse = SingleTokenError("Expected ';' or 'else', found '{}'"); diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 2af2ee4a45..467b06a5ca 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -2896,11 +2896,12 @@ const Parser = struct { /// <- KEYWORD_struct /// / KEYWORD_enum (LPAREN Expr RPAREN)? /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? + /// / KEYWORD_opaque fn parseContainerDeclType(p: *Parser) !?ContainerDeclType { const kind_token = p.nextToken(); const init_arg_expr = switch (p.token_ids[kind_token]) { - .Keyword_struct => Node.ContainerDecl.InitArg{ .None = {} }, + .Keyword_struct, .Keyword_opaque => Node.ContainerDecl.InitArg{ .None = {} }, .Keyword_enum => blk: { if (p.eatToken(.LParen) != null) { const expr = try p.expectNode(parseExpr, .{ diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 67afbb77d9..8c8a2fc50b 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1492,7 +1492,19 @@ fn renderExpression( // TODO remove after 0.7.0 release if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType")) - return ais.writer().writeAll("@Type(.Opaque)"); + return ais.writer().writeAll("opaque {}"); + + // TODO remove after 0.7.0 release + { + const params = builtin_call.paramsConst(); + if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@Type") and + params.len == 1) + { + if (params[0].castTag(.EnumLiteral)) |enum_literal| + if (mem.eql(u8, tree.tokenSlice(enum_literal.name), "Opaque")) + return ais.writer().writeAll("opaque {}"); + } + } try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index e40483c022..c8f33dbfaa 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -47,6 +47,7 @@ pub const Token = struct { .{ "noinline", .Keyword_noinline }, .{ "nosuspend", .Keyword_nosuspend }, .{ "null", .Keyword_null }, + .{ "opaque", .Keyword_opaque }, .{ "or", .Keyword_or }, .{ "orelse", .Keyword_orelse }, .{ "packed", .Keyword_packed }, @@ -173,6 +174,7 @@ pub const Token = struct { Keyword_noinline, Keyword_nosuspend, Keyword_null, + Keyword_opaque, Keyword_or, Keyword_orelse, Keyword_packed, @@ -296,6 +298,7 @@ pub const Token = struct { .Keyword_noinline => "noinline", .Keyword_nosuspend => "nosuspend", .Keyword_null => "null", + .Keyword_opaque => "opaque", .Keyword_or => "or", .Keyword_orelse => "orelse", .Keyword_packed => "packed", diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index c9d7755942..f43f28a85d 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1054,6 +1054,7 @@ enum ContainerKind { ContainerKindStruct, ContainerKindEnum, ContainerKindUnion, + ContainerKindOpaque, }; enum ContainerLayout { @@ -1571,7 +1572,10 @@ enum OnePossibleValue { }; struct ZigTypeOpaque { + AstNode *decl_node; Buf *bare_name; + + ScopeDecls *decls_scope; }; struct ZigTypeFnFrame { diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 99af1db098..6008496fe2 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -86,14 +86,18 @@ ZigType *new_type_table_entry(ZigTypeId id) { } static ScopeDecls **get_container_scope_ptr(ZigType *type_entry) { - if (type_entry->id == ZigTypeIdStruct) { - return &type_entry->data.structure.decls_scope; - } else if (type_entry->id == ZigTypeIdEnum) { - return &type_entry->data.enumeration.decls_scope; - } else if (type_entry->id == ZigTypeIdUnion) { - return &type_entry->data.unionation.decls_scope; + switch (type_entry->id) { + case ZigTypeIdStruct: + return &type_entry->data.structure.decls_scope; + case ZigTypeIdEnum: + return &type_entry->data.enumeration.decls_scope; + case ZigTypeIdUnion: + return &type_entry->data.unionation.decls_scope; + case ZigTypeIdOpaque: + return &type_entry->data.opaque.decls_scope; + default: + zig_unreachable(); } - zig_unreachable(); } static ScopeExpr *find_expr_scope(Scope *scope) { @@ -912,13 +916,17 @@ ZigType *get_opaque_type(CodeGen *g, Scope *scope, AstNode *source_node, const c ZigType *import = scope ? get_scope_import(scope) : nullptr; unsigned line = source_node ? (unsigned)(source_node->line + 1) : 0; + // Note: duplicated in get_partial_container_type entry->llvm_type = LLVMInt8Type(); entry->llvm_di_type = ZigLLVMCreateDebugForwardDeclType(g->dbuilder, ZigLLVMTag_DW_structure_type(), full_name, import ? ZigLLVMFileToScope(import->data.structure.root_struct->di_file) : nullptr, import ? import->data.structure.root_struct->di_file : nullptr, line); + entry->data.opaque.decl_node = source_node; entry->data.opaque.bare_name = bare_name; + entry->data.opaque.decls_scope = create_decls_scope( + g, source_node, scope, entry, import, &entry->name); // The actual size is unknown, but the value must not be 0 because that // is how type_has_bits is determined. @@ -1080,6 +1088,8 @@ static ZigTypeId container_to_type(ContainerKind kind) { return ZigTypeIdEnum; case ContainerKindUnion: return ZigTypeIdUnion; + case ContainerKindOpaque: + return ZigTypeIdOpaque; } zig_unreachable(); } @@ -1121,6 +1131,22 @@ ZigType *get_partial_container_type(CodeGen *g, Scope *scope, ContainerKind kind entry->data.unionation.decl_node = decl_node; entry->data.unionation.layout = layout; break; + case ContainerKindOpaque: { + ZigType *import = scope ? get_scope_import(scope) : nullptr; + unsigned line = decl_node ? (unsigned)(decl_node->line + 1) : 0; + // Note: duplicated in get_opaque_type + entry->llvm_type = LLVMInt8Type(); + entry->llvm_di_type = ZigLLVMCreateDebugForwardDeclType(g->dbuilder, + ZigLLVMTag_DW_structure_type(), full_name, + import ? ZigLLVMFileToScope(import->data.structure.root_struct->di_file) : nullptr, + import ? import->data.structure.root_struct->di_file : nullptr, + line); + entry->data.opaque.decl_node = decl_node; + entry->abi_size = SIZE_MAX; + entry->size_in_bits = SIZE_MAX; + entry->abi_align = 1; + break; + } } buf_init_from_str(&entry->name, full_name); @@ -3430,6 +3456,13 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { return ErrorNone; } +static Error resolve_opaque_type(CodeGen *g, ZigType *opaque_type) { + opaque_type->abi_align = UINT32_MAX; + opaque_type->abi_size = SIZE_MAX; + opaque_type->size_in_bits = SIZE_MAX; + return ErrorNone; +} + void append_namespace_qualification(CodeGen *g, Buf *buf, ZigType *container_type) { if (g->root_import == container_type || buf_len(&container_type->name) == 0) return; buf_append_buf(buf, &container_type->name); @@ -3896,6 +3929,8 @@ static Error resolve_decl_container(CodeGen *g, TldContainer *tld_container) { return resolve_enum_zero_bits(g, tld_container->type_entry); case ZigTypeIdUnion: return resolve_union_type(g, tld_container->type_entry); + case ZigTypeIdOpaque: + return resolve_opaque_type(g, tld_container->type_entry); default: zig_unreachable(); } @@ -4462,6 +4497,7 @@ bool is_container(ZigType *type_entry) { return type_entry->data.structure.special != StructSpecialSlice; case ZigTypeIdEnum: case ZigTypeIdUnion: + case ZigTypeIdOpaque: return true; case ZigTypeIdPointer: case ZigTypeIdMetaType: @@ -4481,7 +4517,6 @@ bool is_container(ZigType *type_entry) { case ZigTypeIdErrorSet: case ZigTypeIdFn: case ZigTypeIdBoundFn: - case ZigTypeIdOpaque: case ZigTypeIdVector: case ZigTypeIdFnFrame: case ZigTypeIdAnyFrame: @@ -8168,6 +8203,7 @@ const char *container_string(ContainerKind kind) { case ContainerKindEnum: return "enum"; case ContainerKindStruct: return "struct"; case ContainerKindUnion: return "union"; + case ContainerKindOpaque: return "opaque"; } zig_unreachable(); } @@ -8186,8 +8222,6 @@ Buf *type_bare_name(ZigType *type_entry) { return &type_entry->name; } else if (is_container(type_entry)) { return get_container_scope(type_entry)->bare_name; - } else if (type_entry->id == ZigTypeIdOpaque) { - return type_entry->data.opaque.bare_name; } else { return &type_entry->name; } diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index ebda9b88f6..ad58f23ec3 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -22587,7 +22587,7 @@ static IrInstGen *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name } } - if (bare_type->id == ZigTypeIdEnum) { + if (bare_type->id == ZigTypeIdEnum || bare_type->id == ZigTypeIdOpaque) { return ir_analyze_container_member_access_inner(ira, bare_type, field_name, source_instr, container_ptr, container_ptr_src, container_type); } @@ -25183,7 +25183,6 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy case ZigTypeIdEnumLiteral: case ZigTypeIdUndefined: case ZigTypeIdNull: - case ZigTypeIdOpaque: result = ira->codegen->intern.for_void(); break; case ZigTypeIdInt: @@ -25737,6 +25736,25 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy if ((err = ir_make_type_info_value(ira, source_instr, fn_type, &result))) return err; + break; + } + case ZigTypeIdOpaque: + { + result = ira->codegen->pass1_arena->create(); + result->special = ConstValSpecialStatic; + result->type = ir_type_info_get_type(ira, "Opaque", nullptr); + + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 1); + result->data.x_struct.fields = fields; + + // decls: []TypeInfo.Declaration + ensure_field_index(result->type, "decls", 0); + if ((err = ir_make_type_info_decls(ira, source_instr, fields[0], + type_entry->data.opaque.decls_scope, false))) + { + return err; + } + break; } case ZigTypeIdFnFrame: @@ -26044,6 +26062,21 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI return get_error_union_type(ira->codegen, err_set_type, payload_type); } case ZigTypeIdOpaque: { + assert(payload->special == ConstValSpecialStatic); + assert(payload->type == ir_type_info_get_type(ira, "Opaque", nullptr)); + + ZigValue *decls_value = get_const_field(ira, source_instr->source_node, payload, "decls", 0); + if (decls_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(decls_value->special == ConstValSpecialStatic); + assert(is_slice(decls_value->type)); + ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index]; + size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint); + if (decls_len != 0) { + ir_add_error(ira, source_instr, buf_create_from_str("TypeInfo.Struct.decls must be empty for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + Buf *bare_name = buf_alloc(); Buf *full_name = get_anon_type_name(ira->codegen, ira->old_irb.exec, "opaque", source_instr->scope, source_instr->source_node, bare_name); diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index 1253baf9ea..219f767a9c 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2920,6 +2920,7 @@ static AstNode *ast_parse_container_decl_auto(ParseContext *pc) { // <- KEYWORD_struct // / KEYWORD_enum (LPAREN Expr RPAREN)? // / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? +// / KEYWORD_opaque static AstNode *ast_parse_container_decl_type(ParseContext *pc) { Token *first = eat_token_if(pc, TokenIdKeywordStruct); if (first != nullptr) { @@ -2929,6 +2930,14 @@ static AstNode *ast_parse_container_decl_type(ParseContext *pc) { return res; } + first = eat_token_if(pc, TokenIdKeywordOpaque); + if (first != nullptr) { + AstNode *res = ast_create_node(pc, NodeTypeContainerDecl, first); + res->data.container_decl.init_arg_expr = nullptr; + res->data.container_decl.kind = ContainerKindOpaque; + return res; + } + first = eat_token_if(pc, TokenIdKeywordEnum); if (first != nullptr) { AstNode *init_arg_expr = nullptr; diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp index fa14dd40fa..f597acb701 100644 --- a/src/stage1/tokenizer.cpp +++ b/src/stage1/tokenizer.cpp @@ -133,6 +133,7 @@ static const struct ZigKeyword zig_keywords[] = { {"noinline", TokenIdKeywordNoInline}, {"nosuspend", TokenIdKeywordNoSuspend}, {"null", TokenIdKeywordNull}, + {"opaque", TokenIdKeywordOpaque}, {"or", TokenIdKeywordOr}, {"orelse", TokenIdKeywordOrElse}, {"packed", TokenIdKeywordPacked}, @@ -1595,6 +1596,7 @@ const char * token_name(TokenId id) { case TokenIdKeywordNoInline: return "noinline"; case TokenIdKeywordNoSuspend: return "nosuspend"; case TokenIdKeywordNull: return "null"; + case TokenIdKeywordOpaque: return "opaque"; case TokenIdKeywordOr: return "or"; case TokenIdKeywordOrElse: return "orelse"; case TokenIdKeywordPacked: return "packed"; diff --git a/src/stage1/tokenizer.hpp b/src/stage1/tokenizer.hpp index d8af21ee00..dc12242268 100644 --- a/src/stage1/tokenizer.hpp +++ b/src/stage1/tokenizer.hpp @@ -81,6 +81,7 @@ enum TokenId { TokenIdKeywordNoAlias, TokenIdKeywordNoSuspend, TokenIdKeywordNull, + TokenIdKeywordOpaque, TokenIdKeywordOr, TokenIdKeywordOrElse, TokenIdKeywordPacked, diff --git a/src/translate_c.zig b/src/translate_c.zig index 66b5752930..5e2c412f19 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -930,9 +930,9 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* const init_node = blk: { const rp = makeRestorePoint(c); const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse { - const opaque = try transCreateNodeOpaqueType(c); + const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); - break :blk opaque; + break :blk opaque_type; }; const layout_tok = try if (ZigClangRecordDecl_getPackedAttribute(record_decl)) @@ -954,17 +954,17 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* const field_qt = ZigClangFieldDecl_getType(field_decl); if (ZigClangFieldDecl_isBitField(field_decl)) { - const opaque = try transCreateNodeOpaqueType(c); + const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); try emitWarning(c, field_loc, "{} demoted to opaque type - has bitfield", .{container_kind_name}); - break :blk opaque; + break :blk opaque_type; } if (ZigClangType_isIncompleteOrZeroLengthArrayType(qualTypeCanon(field_qt), c.clang_context)) { - const opaque = try transCreateNodeOpaqueType(c); + const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); try emitWarning(c, field_loc, "{} demoted to opaque type - has variable length array", .{container_kind_name}); - break :blk opaque; + break :blk opaque_type; } var is_anon = false; @@ -979,10 +979,10 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* _ = try appendToken(c, .Colon, ":"); const field_type = transQualType(rp, field_qt, field_loc) catch |err| switch (err) { error.UnsupportedType => { - const opaque = try transCreateNodeOpaqueType(c); + const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); try emitWarning(c, record_loc, "{} demoted to opaque type - unable to translate type of field {}", .{ container_kind_name, raw_name }); - break :blk opaque; + break :blk opaque_type; }, else => |e| return e, }; diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig index 60a23ffa94..53a47228db 100644 --- a/test/stage1/behavior/type.zig +++ b/test/stage1/behavior/type.zig @@ -190,8 +190,17 @@ test "Type.ErrorUnion" { } test "Type.Opaque" { - testing.expect(@Type(.Opaque) != @Type(.Opaque)); - testing.expect(@typeInfo(@Type(.Opaque)) == .Opaque); + const Opaque = @Type(.{ + .Opaque = .{ + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + testing.expect(Opaque != opaque {}); + testing.expectEqualSlices( + TypeInfo.Declaration, + &[_]TypeInfo.Declaration{}, + @typeInfo(Opaque).Opaque.decls, + ); } test "Type.Vector" { diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 8b413bf031..e663284d0d 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -199,7 +199,7 @@ fn testUnion() void { expect(typeinfo_info.Union.tag_type.? == TypeId); expect(typeinfo_info.Union.fields.len == 25); expect(typeinfo_info.Union.fields[4].field_type == @TypeOf(@typeInfo(u8).Int)); - expect(typeinfo_info.Union.decls.len == 21); + expect(typeinfo_info.Union.decls.len == 22); const TestNoTagUnion = union { Foo: void, @@ -265,6 +265,21 @@ const TestStruct = packed struct { const Self = @This(); }; +test "type info: opaque info" { + testOpaque(); + comptime testOpaque(); +} + +fn testOpaque() void { + const Foo = opaque { + const A = 1; + fn b() void {} + }; + + const foo_info = @typeInfo(Foo); + expect(foo_info.Opaque.decls.len == 2); +} + test "type info: function type info" { // wasm doesn't support align attributes on functions if (builtin.arch == .wasm32 or builtin.arch == .wasm64) return error.SkipZigTest;