Commit fe8d65556d

Andrew Kelley <andrew@ziglang.org>
2019-12-08 18:13:34
add syntax for comptime struct fields
1 parent 119ed12
src/all_types.hpp
@@ -1006,6 +1006,7 @@ struct AstNodeStructField {
     // populated if the "align(A)" is present
     AstNode *align_expr;
     Buf doc_comments;
+    Token *comptime_token;
 };
 
 struct AstNodeStringLiteral {
src/analyze.cpp
@@ -2736,6 +2736,16 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
             field_node = decl_node->data.container_decl.fields.at(i);
             type_struct_field->name = field_node->data.struct_field.name;
             type_struct_field->decl_node = field_node;
+            if (field_node->data.struct_field.comptime_token != nullptr) {
+                if (field_node->data.struct_field.value == nullptr) {
+                    add_token_error(g, field_node->owner,
+                        field_node->data.struct_field.comptime_token,
+                        buf_sprintf("comptime struct field missing initialization value"));
+                    struct_type->data.structure.resolve_status = ResolveStatusInvalid;
+                    return ErrorSemanticAnalyzeFail;
+                }
+                type_struct_field->is_comptime = true;
+            }
 
             if (field_node->data.struct_field.type == nullptr) {
                 add_node_error(g, field_node, buf_sprintf("struct field missing type"));
src/ir.cpp
@@ -19514,6 +19514,18 @@ static IrInstruction *ir_analyze_container_member_access_inner(IrAnalyze *ira,
     return ira->codegen->invalid_instruction;
 }
 
+static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field) {
+    if (field->init_val != nullptr) return;
+    if (field->decl_node->type != NodeTypeStructField) return;
+    AstNode *init_node = field->decl_node->data.struct_field.value;
+    if (init_node == nullptr) return;
+    // scope is not the scope of the struct init, it's the scope of the struct type decl
+    Scope *analyze_scope = &get_container_scope(container_type)->base;
+    // memoize it
+    field->init_val = analyze_const_value(codegen, analyze_scope, init_node,
+            field->type_entry, nullptr, UndefOk);
+}
+
 static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction *source_instr,
         TypeStructField *field, IrInstruction *struct_ptr, ZigType *struct_type, bool initializing)
 {
@@ -19523,6 +19535,7 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
         return ira->codegen->invalid_instruction;
     if (field->is_comptime) {
         IrInstruction *elem = ir_const(ira, source_instr, field_type);
+        memoize_field_init_val(ira->codegen, struct_type, field);
         copy_const_val(elem->value, field->init_val);
         return ir_get_ref(ira, source_instr, elem, true, false);
     }
@@ -21556,25 +21569,12 @@ static IrInstruction *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruc
 
         // look for a default field value
         TypeStructField *field = container_type->data.structure.fields[i];
+        memoize_field_init_val(ira->codegen, container_type, field);
         if (field->init_val == nullptr) {
-            // it's not memoized. time to go analyze it
-            AstNode *init_node;
-            if (field->decl_node->type == NodeTypeStructField) {
-                init_node = field->decl_node->data.struct_field.value;
-            } else {
-                init_node = nullptr;
-            }
-            if (init_node == nullptr) {
-                ir_add_error_node(ira, instruction->source_node,
-                    buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name)));
-                any_missing = true;
-                continue;
-            }
-            // scope is not the scope of the struct init, it's the scope of the struct type decl
-            Scope *analyze_scope = &get_container_scope(container_type)->base;
-            // memoize it
-            field->init_val = analyze_const_value(ira->codegen, analyze_scope, init_node,
-                    field->type_entry, nullptr, UndefOk);
+            ir_add_error_node(ira, instruction->source_node,
+                buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name)));
+            any_missing = true;
+            continue;
         }
         if (type_is_invalid(field->init_val->type))
             return ira->codegen->invalid_instruction;
src/parser.cpp
@@ -536,8 +536,8 @@ static void ast_parse_container_doc_comments(ParseContext *pc, Buf *buf) {
 //     <- TestDecl ContainerMembers
 //      / TopLevelComptime ContainerMembers
 //      / KEYWORD_pub? TopLevelDecl ContainerMembers
-//      / ContainerField COMMA ContainerMembers
-//      / ContainerField
+//      / KEYWORD_comptime? ContainerField COMMA ContainerMembers
+//      / KEYWORD_comptime? ContainerField
 //      /
 static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) {
     AstNodeContainerDecl res = {};
@@ -574,10 +574,13 @@ static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) {
             ast_error(pc, peek_token(pc), "expected function or variable declaration after pub");
         }
 
+        Token *comptime_token = eat_token_if(pc, TokenIdKeywordCompTime);
+
         AstNode *container_field = ast_parse_container_field(pc);
         if (container_field != nullptr) {
             assert(container_field->type == NodeTypeStructField);
             container_field->data.struct_field.doc_comments = doc_comment_buf;
+            container_field->data.struct_field.comptime_token = comptime_token;
             res.fields.append(container_field);
             if (eat_token_if(pc, TokenIdComma) != nullptr) {
                 continue;
@@ -612,6 +615,13 @@ static AstNode *ast_parse_top_level_comptime(ParseContext *pc) {
     if (comptime == nullptr)
         return nullptr;
 
+    // 1 token lookahead because it could be a comptime struct field
+    Token *lbrace = peek_token(pc);
+    if (lbrace->id != TokenIdLBrace) {
+        put_back_token(pc);
+        return nullptr;
+    }
+
     AstNode *block = ast_expect(pc, ast_parse_block_expr);
     AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
     res->data.comptime_expr.expr = block;
test/stage1/behavior/struct.zig
@@ -789,3 +789,13 @@ test "struct with var field" {
     expect(pt.x == 1);
     expect(pt.y == 2);
 }
+
+test "comptime struct field" {
+    const T = struct {
+        a: i32,
+        comptime b: i32 = 1234,
+    };
+
+    var foo: T = undefined;
+    comptime expect(foo.b == 1234);
+}
test/compile_errors.zig
@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add("comptime struct field, no init value",
+        \\const Foo = struct {
+        \\    comptime b: i32,
+        \\};
+        \\export fn entry() void {
+        \\    var f: Foo = undefined;
+        \\}
+    , "tmp.zig:2:5: error: comptime struct field missing initialization value");
+
     cases.add(
         "bad usage of @call",
         \\export fn entry1() void {
@@ -32,7 +41,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "tmp.zig:15:43: error: unable to evaluate constant expression",
     );
 
-    cases.add(
+    cases.add("exported async function",
         \\export async fn foo() void {}
     , "tmp.zig:1:1: error: exported function cannot be async");