Commit 78f32259da

Andrew Kelley <andrew@ziglang.org>
2019-05-30 21:35:30
default struct field initialization expressions
closes #485
1 parent 7878f96
doc/langref.html.in
@@ -2263,6 +2263,28 @@ test "linked list" {
 }
       {#code_end#}
 
+      {#header_open|Default Field Values#}
+      <p>
+      Each struct field may have an expression indicating the default field value. Such expressions
+      are executed at {#link|comptime#}, and allow the field to be omitted in a struct literal expression:
+      </p>
+      {#code_begin|test#}
+const Foo = struct {
+    a: i32 = 1234,
+    b: i32,
+};
+
+test "default struct initialization fields" {
+    const x = Foo{
+        .b = 5,
+    };
+    if (x.a + x.b != 1239) {
+        @compileError("it's even comptime known!");
+    }
+}
+      {#code_end#}
+      {#header_close#}
+
       {#header_open|extern struct#}
       <p>An {#syntax#}extern struct{#endsyntax#} has in-memory layout guaranteed to match the
       C ABI for the target.</p>
src/all_types.hpp
@@ -1069,6 +1069,7 @@ struct TypeStructField {
     size_t gen_index;
     size_t offset; // byte offset from beginning of struct
     AstNode *decl_node;
+    ConstExprValue *init_val; // null and then memoized
     uint32_t bit_offset_in_host; // offset from the memory at gen_index
     uint32_t host_int_bytes; // size of host integer
 };
src/analyze.cpp
@@ -965,9 +965,7 @@ ZigType *get_partial_container_type(CodeGen *g, Scope *scope, ContainerKind kind
     return entry;
 }
 
-static ConstExprValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType *type_entry,
-        Buf *type_name)
-{
+ConstExprValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType *type_entry, Buf *type_name) {
     size_t backward_branch_count = 0;
     size_t backward_branch_quota = default_backward_branch_quota;
     return ir_eval_const_value(g, scope, node, type_entry,
@@ -2189,10 +2187,6 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
         type_struct_field->src_index = i;
         type_struct_field->gen_index = SIZE_MAX;
 
-        if (field_node->data.struct_field.value != nullptr) {
-            add_node_error(g, field_node->data.struct_field.value,
-                    buf_sprintf("enums, not structs, support field assignment"));
-        }
         if (field_type->id == ZigTypeIdOpaque) {
             add_node_error(g, field_node->data.struct_field.type,
                 buf_sprintf("opaque types have unknown size and therefore cannot be directly embedded in structs"));
src/analyze.hpp
@@ -251,5 +251,6 @@ void add_cc_args(CodeGen *g, ZigList<const char *> &args, const char *out_dep_pa
 
 void src_assert(bool ok, AstNode *source_node);
 bool is_container(ZigType *type_entry);
+ConstExprValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType *type_entry, Buf *type_name);
 
 #endif
src/ir.cpp
@@ -17887,10 +17887,37 @@ static IrInstruction *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruc
 
     bool any_missing = false;
     for (size_t i = 0; i < actual_field_count; i += 1) {
-        if (!field_assign_nodes[i]) {
-            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;
+        if (field_assign_nodes[i]) continue;
+
+        // look for a default field value
+        TypeStructField *field = &container_type->data.structure.fields[i];
+        if (field->init_val == nullptr) {
+            // it's not memoized. time to go analyze it
+            assert(field->decl_node->type == NodeTypeStructField);
+            AstNode *init_node = field->decl_node->data.struct_field.value;
+            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);
+        }
+        if (type_is_invalid(field->init_val->type))
+            return ira->codegen->invalid_instruction;
+
+        IrInstruction *runtime_inst = ir_const(ira, instruction, field->init_val->type);
+        copy_const_val(&runtime_inst->value, field->init_val, true);
+
+        new_fields[i].value = runtime_inst;
+        new_fields[i].type_struct_field = field;
+
+        if (const_val.special == ConstValSpecialStatic) {
+            copy_const_val(&const_val.data.x_struct.fields[i], field->init_val, true);
         }
     }
     if (any_missing)
test/stage1/behavior/struct.zig
@@ -560,3 +560,21 @@ test "use within struct scope" {
     };
     expectEqual(i32(42), S.inner());
 }
+
+test "default struct initialization fields" {
+    const S = struct {
+        a: i32 = 1234,
+        b: i32,
+    };
+    const x = S{
+        .b = 5,
+    };
+    if (x.a + x.b != 1239) {
+        @compileError("it should be comptime known");
+    }
+    var five: i32 = 5;
+    const y = S{
+        .b = five,
+    };
+    expectEqual(1239, x.a + x.b);
+}
test/compile_errors.zig
@@ -2,6 +2,21 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "compile error in struct init expression",
+        \\const Foo = struct {
+        \\    a: i32 = crap,
+        \\    b: i32,
+        \\};
+        \\export fn entry() void {
+        \\    var x = Foo{
+        \\        .b = 5,
+        \\    };
+        \\}
+    ,
+        "tmp.zig:2:14: error: use of undeclared identifier 'crap'",
+    );
+
     cases.add(
         "undefined as field type is rejected",
         \\const Foo = struct {
@@ -5484,18 +5499,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "tmp.zig:10:31: error: expected type 'u2', found 'u3'",
     );
 
-    cases.add(
-        "struct fields with value assignments",
-        \\const MultipleChoice = struct {
-        \\    A: i32 = 20,
-        \\};
-        \\export fn entry() void {
-        \\        var x: MultipleChoice = undefined;
-        \\}
-    ,
-        "tmp.zig:2:14: error: enums, not structs, support field assignment",
-    );
-
     cases.add(
         "union fields with value assignments",
         \\const MultipleChoice = union {