Commit 78f32259da
Changed files (7)
test
stage1
behavior
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 {