From 119ed128c05e95a4779a00cd87d3e2d5b01f9f17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Dec 2019 11:40:17 -0500 Subject: [PATCH 1/3] implement comptime struct fields --- src/all_types.hpp | 1 + src/analyze.cpp | 10 +++++++--- src/ir.cpp | 20 ++++++++++++++++++++ test/stage1/behavior/call.zig | 8 ++++---- test/stage1/behavior/struct.zig | 2 -- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index a5b0804985..ebccbaa37f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1263,6 +1263,7 @@ struct TypeStructField { uint32_t bit_offset_in_host; // offset from the memory at gen_index uint32_t host_int_bytes; // size of host integer uint32_t align; + bool is_comptime; }; enum ResolveStatus { diff --git a/src/analyze.cpp b/src/analyze.cpp index c3e24ecb46..37c93c638a 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2788,6 +2788,9 @@ 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 (type_struct_field->is_comptime) + continue; + switch (type_val_resolve_requires_comptime(g, field_type_val)) { case ReqCompTimeYes: struct_type->data.structure.requires_comptime = true; @@ -7987,8 +7990,9 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS // inserting padding bytes where LLVM would do it automatically. size_t llvm_struct_abi_align = 0; for (size_t i = 0; i < field_count; i += 1) { - ZigType *field_type = struct_type->data.structure.fields[i]->type_entry; - if (!type_has_bits(field_type)) + TypeStructField *field = struct_type->data.structure.fields[i]; + ZigType *field_type = field->type_entry; + if (field->is_comptime || !type_has_bits(field_type)) continue; LLVMTypeRef field_llvm_type = get_llvm_type(g, field_type); size_t llvm_field_abi_align = LLVMABIAlignmentOfType(g->target_data_ref, field_llvm_type); @@ -7999,7 +8003,7 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS TypeStructField *field = struct_type->data.structure.fields[i]; ZigType *field_type = field->type_entry; - if (!type_has_bits(field_type)) { + if (field->is_comptime || !type_has_bits(field_type)) { field->gen_index = SIZE_MAX; continue; } diff --git a/src/ir.cpp b/src/ir.cpp index 9b99fed42c..5f398bb11a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -655,6 +655,10 @@ static ZigValue *const_ptr_pointee_unchecked(CodeGen *g, ZigValue *const_val) { if (isf != nullptr) { TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name); assert(field != nullptr); + if (field->is_comptime) { + assert(field->init_val != nullptr); + return field->init_val; + } assert(const_val->data.x_ptr.special == ConstPtrSpecialRef); ZigValue *struct_val = const_val->data.x_ptr.data.ref.pointee; return struct_val->data.x_struct.fields[field->src_index]; @@ -17373,6 +17377,13 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source field->type_val = create_const_type(ira->codegen, field->type_entry); field->src_index = old_field_count; field->decl_node = uncasted_value->source_node; + if (instr_is_comptime(uncasted_value)) { + ZigValue *uncasted_val = ir_resolve_const(ira, uncasted_value, UndefOk); + field->is_comptime = true; + field->init_val = create_const_vals(1); + copy_const_val(field->init_val, uncasted_val); + return ir_const_void(ira, source_instr); + } ZigType *struct_ptr_type = get_pointer_to_type(ira->codegen, isf->inferred_struct_type, false); IrInstruction *casted_ptr; @@ -19510,6 +19521,11 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction ZigType *field_type = resolve_struct_field_type(ira->codegen, field); if (field_type == nullptr) return ira->codegen->invalid_instruction; + if (field->is_comptime) { + IrInstruction *elem = ir_const(ira, source_instr, field_type); + copy_const_val(elem->value, field->init_val); + return ir_get_ref(ira, source_instr, elem, true, false); + } switch (type_has_one_possible_value(ira->codegen, field_type)) { case OnePossibleValueInvalid: return ira->codegen->invalid_instruction; @@ -21716,6 +21732,10 @@ static IrInstruction *ir_analyze_instruction_container_init_list(IrAnalyze *ira, for (size_t i = 0; i < const_ptrs.length; i += 1) { IrInstruction *elem_result_loc = const_ptrs.at(i); assert(elem_result_loc->value->special == ConstValSpecialStatic); + if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) { + // This field will be generated comptime; no need to do this. + continue; + } IrInstruction *deref = ir_get_deref(ira, elem_result_loc, elem_result_loc, nullptr); elem_result_loc->value->special = ConstValSpecialRuntime; ir_analyze_store_ptr(ira, elem_result_loc, elem_result_loc, deref, false); diff --git a/test/stage1/behavior/call.zig b/test/stage1/behavior/call.zig index d68fb64d02..1280224fa3 100644 --- a/test/stage1/behavior/call.zig +++ b/test/stage1/behavior/call.zig @@ -37,12 +37,12 @@ test "tuple parameters" { comptime expect(@call(.{}, add, .{ 12, 34 }) == 46); { const separate_args0 = .{ a, b }; - //TODO const separate_args1 = .{ a, 34 }; + const separate_args1 = .{ a, 34 }; const separate_args2 = .{ 12, 34 }; - //TODO const separate_args3 = .{ 12, b }; + const separate_args3 = .{ 12, b }; expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46); - // TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46); + expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46); expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46); - // TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46); + expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46); } } diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 408f5da687..8ac14583c5 100644 --- a/test/stage1/behavior/struct.zig +++ b/test/stage1/behavior/struct.zig @@ -775,8 +775,6 @@ test "anonymous struct literal assigned to variable" { expect(vec.@"0" == 22); expect(vec.@"1" == 55); expect(vec.@"2" == 99); - vec.@"1" += 1; - expect(vec.@"1" == 56); } test "struct with var field" { From fe8d65556dc49bb0da7ee621597c3b24f0e879f6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Dec 2019 12:13:34 -0500 Subject: [PATCH 2/3] add syntax for comptime struct fields --- src/all_types.hpp | 1 + src/analyze.cpp | 10 +++++++++ src/ir.cpp | 36 ++++++++++++++++----------------- src/parser.cpp | 14 +++++++++++-- test/compile_errors.zig | 11 +++++++++- test/stage1/behavior/struct.zig | 10 +++++++++ 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index ebccbaa37f..6e1bfb5ad5 100644 --- a/src/all_types.hpp +++ b/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 { diff --git a/src/analyze.cpp b/src/analyze.cpp index 37c93c638a..cf7296a9f8 100644 --- a/src/analyze.cpp +++ b/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")); diff --git a/src/ir.cpp b/src/ir.cpp index 5f398bb11a..cd9d895fd4 100644 --- a/src/ir.cpp +++ b/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; diff --git a/src/parser.cpp b/src/parser.cpp index 665e048a89..1786e38d4b 100644 --- a/src/parser.cpp +++ b/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; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 13e666f49e..3cdf8723ac 100644 --- a/test/compile_errors.zig +++ b/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"); diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 8ac14583c5..91f00006da 100644 --- a/test/stage1/behavior/struct.zig +++ b/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); +} From 64d700bfa6cea1d9a440a7431ec8d64964cdd6c1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Dec 2019 12:24:45 -0500 Subject: [PATCH 3/3] zig fmt: support comptime fields --- lib/std/zig/ast.zig | 5 +++-- lib/std/zig/parse.zig | 15 ++++++++++++--- lib/std/zig/parser_test.zig | 10 ++++++++++ lib/std/zig/render.zig | 3 +++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 63c6168ed9..bedb980463 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -754,8 +754,9 @@ pub const Node = struct { }; pub const ContainerField = struct { - base: Node, + base: Node = Node{ .id = .ContainerField }, doc_comments: ?*DocComment, + comptime_token: ?TokenIndex, name_token: TokenIndex, type_expr: ?*Node, value_expr: ?*Node, @@ -778,7 +779,7 @@ pub const Node = struct { } pub fn firstToken(self: *const ContainerField) TokenIndex { - return self.name_token; + return self.comptime_token orelse self.name_token; } pub fn lastToken(self: *const ContainerField) TokenIndex { diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 269586f8b1..2e95a4605d 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -206,6 +206,11 @@ fn parseTestDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { /// TopLevelComptime <- KEYWORD_comptime BlockExpr fn parseTopLevelComptime(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { const tok = eatToken(it, .Keyword_comptime) orelse return null; + const lbrace = eatToken(it, .LBrace) orelse { + putBackToken(it, tok); + return null; + }; + putBackToken(it, lbrace); const block_node = try expectNode(arena, it, tree, parseBlockExpr, AstError{ .ExpectedLabelOrLBrace = AstError.ExpectedLabelOrLBrace{ .token = it.index }, }); @@ -403,9 +408,13 @@ fn parseVarDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { return &node.base; } -/// ContainerField <- IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)? +/// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)? fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { - const name_token = eatToken(it, .Identifier) orelse return null; + const comptime_token = eatToken(it, .Keyword_comptime); + const name_token = eatToken(it, .Identifier) orelse { + if (comptime_token) |t| putBackToken(it, t); + return null; + }; var align_expr: ?*Node = null; var type_expr: ?*Node = null; @@ -431,8 +440,8 @@ fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*No const node = try arena.create(Node.ContainerField); node.* = Node.ContainerField{ - .base = Node{ .id = .ContainerField }, .doc_comments = null, + .comptime_token = comptime_token, .name_token = name_token, .type_expr = type_expr, .value_expr = value_expr, diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 616cf8a75a..7f3cc3694a 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,3 +1,13 @@ +test "zig fmt: comptime struct field" { + try testCanonical( + \\const Foo = struct { + \\ a: i32, + \\ comptime b: i32 = 1234, + \\}; + \\ + ); +} + test "zig fmt: c pointer type" { try testCanonical( \\pub extern fn repro() [*c]const u8; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index b01718cdab..8b0ce7ee1a 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -251,6 +251,9 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); try renderDocComments(tree, stream, field, indent, start_col); + if (field.comptime_token) |t| { + try renderToken(tree, stream, t, indent, start_col, Space.Space); // comptime + } if (field.type_expr == null and field.value_expr == null) { return renderToken(tree, stream, field.name_token, indent, start_col, Space.Comma); // name,