Commit 069fbb3c01

Tadeo Kondrak <me@tadeo.ca>
2020-09-25 22:12:11
Add opaque type syntax
1 parent 7f7e2d6
doc/langref.html.in
@@ -11193,7 +11193,7 @@ PtrTypeStart
 ContainerDeclAuto &lt;- ContainerDeclType LBRACE ContainerMembers RBRACE
 
 ContainerDeclType
-    &lt;- (KEYWORD_struct / KEYWORD_enum) (LPAREN Expr RPAREN)?
+    &lt;- (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      &lt;- 'inline'      end_of_word
 KEYWORD_noalias     &lt;- 'noalias'     end_of_word
 KEYWORD_nosuspend   &lt;- 'nosuspend'   end_of_word
 KEYWORD_null        &lt;- 'null'        end_of_word
+KEYWORD_opaque      &lt;- 'opaque'      end_of_word
 KEYWORD_or          &lt;- 'or'          end_of_word
 KEYWORD_orelse      &lt;- 'orelse'      end_of_word
 KEYWORD_packed      &lt;- 'packed'      end_of_word
@@ -11368,7 +11369,7 @@ keyword &lt;- 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
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 '{}'");
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, .{
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
 
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",
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 {
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 {
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;
     }
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<ZigValue>();
+                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);
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;
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";
src/stage1/tokenizer.hpp
@@ -81,6 +81,7 @@ enum TokenId {
     TokenIdKeywordNoAlias,
     TokenIdKeywordNoSuspend,
     TokenIdKeywordNull,
+    TokenIdKeywordOpaque,
     TokenIdKeywordOr,
     TokenIdKeywordOrElse,
     TokenIdKeywordPacked,
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,
             };
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" {
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;