From 78f32259daa79aaced1c49bcdace936121a2ccc5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 May 2019 15:35:30 -0400 Subject: [PATCH] default struct field initialization expressions closes #485 --- doc/langref.html.in | 22 +++++++++++++++++++++ src/all_types.hpp | 1 + src/analyze.cpp | 8 +------- src/analyze.hpp | 1 + src/ir.cpp | 35 +++++++++++++++++++++++++++++---- test/compile_errors.zig | 27 ++++++++++++++----------- test/stage1/behavior/struct.zig | 18 +++++++++++++++++ 7 files changed, 89 insertions(+), 23 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 68f3926839..622a3aeb60 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2263,6 +2263,28 @@ test "linked list" { } {#code_end#} + {#header_open|Default Field Values#} +

+ 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: +

+ {#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#}

An {#syntax#}extern struct{#endsyntax#} has in-memory layout guaranteed to match the C ABI for the target.

diff --git a/src/all_types.hpp b/src/all_types.hpp index 4bb76ff64f..2a6bb1feb5 100644 --- a/src/all_types.hpp +++ b/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 }; diff --git a/src/analyze.cpp b/src/analyze.cpp index 3769ae47c2..a62c24460e 100644 --- a/src/analyze.cpp +++ b/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")); diff --git a/src/analyze.hpp b/src/analyze.hpp index d89bb91126..57f1072355 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -251,5 +251,6 @@ void add_cc_args(CodeGen *g, ZigList &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 diff --git a/src/ir.cpp b/src/ir.cpp index c4c3808545..fb9e7b51c7 100644 --- a/src/ir.cpp +++ b/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) diff --git a/test/compile_errors.zig b/test/compile_errors.zig index fbecacb94b..7ee5896568 100644 --- a/test/compile_errors.zig +++ 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 { diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 7a84fcc763..3c1db4989c 100644 --- a/test/stage1/behavior/struct.zig +++ b/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); +}