| author | |
| committer | |
| log | d57370d3ca28db17a183157b0497c0ab25e22c19 |
| tree | 0dfbe27beb71eabdce5add8e423f5ad5d50f8b09 |
| parent | 19c1b5a33a21bdddfbbca3c65b1c0e6419c4629f |
| parent | 64d700bfa6cea1d9a440a7431ec8d64964cdd6c1 |
| signature | Commit is signed but in an unrecognized format. |
closes #367711 files changed, 121 insertions(+), 35 deletions(-)
lib/std/zig/ast.zig+3-2| ... | ... | @@ -754,8 +754,9 @@ pub const Node = struct { |
| 754 | 754 | }; |
| 755 | 755 | |
| 756 | 756 | pub const ContainerField = struct { |
| 757 | base: Node, | |
| 757 | base: Node = Node{ .id = .ContainerField }, | |
| 758 | 758 | doc_comments: ?*DocComment, |
| 759 | comptime_token: ?TokenIndex, | |
| 759 | 760 | name_token: TokenIndex, |
| 760 | 761 | type_expr: ?*Node, |
| 761 | 762 | value_expr: ?*Node, |
| ... | ... | @@ -778,7 +779,7 @@ pub const Node = struct { |
| 778 | 779 | } |
| 779 | 780 | |
| 780 | 781 | pub fn firstToken(self: *const ContainerField) TokenIndex { |
| 781 | return self.name_token; | |
| 782 | return self.comptime_token orelse self.name_token; | |
| 782 | 783 | } |
| 783 | 784 | |
| 784 | 785 | pub fn lastToken(self: *const ContainerField) TokenIndex { |
lib/std/zig/parse.zig+12-3| ... | ... | @@ -206,6 +206,11 @@ fn parseTestDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { |
| 206 | 206 | /// TopLevelComptime <- KEYWORD_comptime BlockExpr |
| 207 | 207 | fn parseTopLevelComptime(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { |
| 208 | 208 | const tok = eatToken(it, .Keyword_comptime) orelse return null; |
| 209 | const lbrace = eatToken(it, .LBrace) orelse { | |
| 210 | putBackToken(it, tok); | |
| 211 | return null; | |
| 212 | }; | |
| 213 | putBackToken(it, lbrace); | |
| 209 | 214 | const block_node = try expectNode(arena, it, tree, parseBlockExpr, AstError{ |
| 210 | 215 | .ExpectedLabelOrLBrace = AstError.ExpectedLabelOrLBrace{ .token = it.index }, |
| 211 | 216 | }); |
| ... | ... | @@ -403,9 +408,13 @@ fn parseVarDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { |
| 403 | 408 | return &node.base; |
| 404 | 409 | } |
| 405 | 410 | |
| 406 | /// ContainerField <- IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)? | |
| 411 | /// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)? | |
| 407 | 412 | fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { |
| 408 | const name_token = eatToken(it, .Identifier) orelse return null; | |
| 413 | const comptime_token = eatToken(it, .Keyword_comptime); | |
| 414 | const name_token = eatToken(it, .Identifier) orelse { | |
| 415 | if (comptime_token) |t| putBackToken(it, t); | |
| 416 | return null; | |
| 417 | }; | |
| 409 | 418 | |
| 410 | 419 | var align_expr: ?*Node = null; |
| 411 | 420 | var type_expr: ?*Node = null; |
| ... | ... | @@ -431,8 +440,8 @@ fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*No |
| 431 | 440 | |
| 432 | 441 | const node = try arena.create(Node.ContainerField); |
| 433 | 442 | node.* = Node.ContainerField{ |
| 434 | .base = Node{ .id = .ContainerField }, | |
| 435 | 443 | .doc_comments = null, |
| 444 | .comptime_token = comptime_token, | |
| 436 | 445 | .name_token = name_token, |
| 437 | 446 | .type_expr = type_expr, |
| 438 | 447 | .value_expr = value_expr, |
lib/std/zig/parser_test.zig+10| ... | ... | @@ -1,3 +1,13 @@ |
| 1 | test "zig fmt: comptime struct field" { | |
| 2 | try testCanonical( | |
| 3 | \\const Foo = struct { | |
| 4 | \\ a: i32, | |
| 5 | \\ comptime b: i32 = 1234, | |
| 6 | \\}; | |
| 7 | \\ | |
| 8 | ); | |
| 9 | } | |
| 10 | ||
| 1 | 11 | test "zig fmt: c pointer type" { |
| 2 | 12 | try testCanonical( |
| 3 | 13 | \\pub extern fn repro() [*c]const u8; |
lib/std/zig/render.zig+3| ... | ... | @@ -251,6 +251,9 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i |
| 251 | 251 | const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); |
| 252 | 252 | |
| 253 | 253 | try renderDocComments(tree, stream, field, indent, start_col); |
| 254 | if (field.comptime_token) |t| { | |
| 255 | try renderToken(tree, stream, t, indent, start_col, Space.Space); // comptime | |
| 256 | } | |
| 254 | 257 | |
| 255 | 258 | if (field.type_expr == null and field.value_expr == null) { |
| 256 | 259 | return renderToken(tree, stream, field.name_token, indent, start_col, Space.Comma); // name, |
src/all_types.hpp+2| ... | ... | @@ -1006,6 +1006,7 @@ struct AstNodeStructField { |
| 1006 | 1006 | // populated if the "align(A)" is present |
| 1007 | 1007 | AstNode *align_expr; |
| 1008 | 1008 | Buf doc_comments; |
| 1009 | Token *comptime_token; | |
| 1009 | 1010 | }; |
| 1010 | 1011 | |
| 1011 | 1012 | struct AstNodeStringLiteral { |
| ... | ... | @@ -1263,6 +1264,7 @@ struct TypeStructField { |
| 1263 | 1264 | uint32_t bit_offset_in_host; // offset from the memory at gen_index |
| 1264 | 1265 | uint32_t host_int_bytes; // size of host integer |
| 1265 | 1266 | uint32_t align; |
| 1267 | bool is_comptime; | |
| 1266 | 1268 | }; |
| 1267 | 1269 | |
| 1268 | 1270 | enum ResolveStatus { |
src/analyze.cpp+17-3| ... | ... | @@ -2736,6 +2736,16 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) { |
| 2736 | 2736 | field_node = decl_node->data.container_decl.fields.at(i); |
| 2737 | 2737 | type_struct_field->name = field_node->data.struct_field.name; |
| 2738 | 2738 | type_struct_field->decl_node = field_node; |
| 2739 | if (field_node->data.struct_field.comptime_token != nullptr) { | |
| 2740 | if (field_node->data.struct_field.value == nullptr) { | |
| 2741 | add_token_error(g, field_node->owner, | |
| 2742 | field_node->data.struct_field.comptime_token, | |
| 2743 | buf_sprintf("comptime struct field missing initialization value")); | |
| 2744 | struct_type->data.structure.resolve_status = ResolveStatusInvalid; | |
| 2745 | return ErrorSemanticAnalyzeFail; | |
| 2746 | } | |
| 2747 | type_struct_field->is_comptime = true; | |
| 2748 | } | |
| 2739 | 2749 | |
| 2740 | 2750 | if (field_node->data.struct_field.type == nullptr) { |
| 2741 | 2751 | add_node_error(g, field_node, buf_sprintf("struct field missing type")); |
| ... | ... | @@ -2788,6 +2798,9 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) { |
| 2788 | 2798 | type_struct_field->src_index = i; |
| 2789 | 2799 | type_struct_field->gen_index = SIZE_MAX; |
| 2790 | 2800 | |
| 2801 | if (type_struct_field->is_comptime) | |
| 2802 | continue; | |
| 2803 | ||
| 2791 | 2804 | switch (type_val_resolve_requires_comptime(g, field_type_val)) { |
| 2792 | 2805 | case ReqCompTimeYes: |
| 2793 | 2806 | struct_type->data.structure.requires_comptime = true; |
| ... | ... | @@ -7987,8 +8000,9 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS |
| 7987 | 8000 | // inserting padding bytes where LLVM would do it automatically. |
| 7988 | 8001 | size_t llvm_struct_abi_align = 0; |
| 7989 | 8002 | for (size_t i = 0; i < field_count; i += 1) { |
| 7990 | ZigType *field_type = struct_type->data.structure.fields[i]->type_entry; | |
| 7991 | if (!type_has_bits(field_type)) | |
| 8003 | TypeStructField *field = struct_type->data.structure.fields[i]; | |
| 8004 | ZigType *field_type = field->type_entry; | |
| 8005 | if (field->is_comptime || !type_has_bits(field_type)) | |
| 7992 | 8006 | continue; |
| 7993 | 8007 | LLVMTypeRef field_llvm_type = get_llvm_type(g, field_type); |
| 7994 | 8008 | size_t llvm_field_abi_align = LLVMABIAlignmentOfType(g->target_data_ref, field_llvm_type); |
| ... | ... | @@ -7999,7 +8013,7 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS |
| 7999 | 8013 | TypeStructField *field = struct_type->data.structure.fields[i]; |
| 8000 | 8014 | ZigType *field_type = field->type_entry; |
| 8001 | 8015 | |
| 8002 | if (!type_has_bits(field_type)) { | |
| 8016 | if (field->is_comptime || !type_has_bits(field_type)) { | |
| 8003 | 8017 | field->gen_index = SIZE_MAX; |
| 8004 | 8018 | continue; |
| 8005 | 8019 | } |
src/ir.cpp+38-18| ... | ... | @@ -655,6 +655,10 @@ static ZigValue *const_ptr_pointee_unchecked(CodeGen *g, ZigValue *const_val) { |
| 655 | 655 | if (isf != nullptr) { |
| 656 | 656 | TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name); |
| 657 | 657 | assert(field != nullptr); |
| 658 | if (field->is_comptime) { | |
| 659 | assert(field->init_val != nullptr); | |
| 660 | return field->init_val; | |
| 661 | } | |
| 658 | 662 | assert(const_val->data.x_ptr.special == ConstPtrSpecialRef); |
| 659 | 663 | ZigValue *struct_val = const_val->data.x_ptr.data.ref.pointee; |
| 660 | 664 | return struct_val->data.x_struct.fields[field->src_index]; |
| ... | ... | @@ -17373,6 +17377,13 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source |
| 17373 | 17377 | field->type_val = create_const_type(ira->codegen, field->type_entry); |
| 17374 | 17378 | field->src_index = old_field_count; |
| 17375 | 17379 | field->decl_node = uncasted_value->source_node; |
| 17380 | if (instr_is_comptime(uncasted_value)) { | |
| 17381 | ZigValue *uncasted_val = ir_resolve_const(ira, uncasted_value, UndefOk); | |
| 17382 | field->is_comptime = true; | |
| 17383 | field->init_val = create_const_vals(1); | |
| 17384 | copy_const_val(field->init_val, uncasted_val); | |
| 17385 | return ir_const_void(ira, source_instr); | |
| 17386 | } | |
| 17376 | 17387 | |
| 17377 | 17388 | ZigType *struct_ptr_type = get_pointer_to_type(ira->codegen, isf->inferred_struct_type, false); |
| 17378 | 17389 | IrInstruction *casted_ptr; |
| ... | ... | @@ -19503,6 +19514,18 @@ static IrInstruction *ir_analyze_container_member_access_inner(IrAnalyze *ira, |
| 19503 | 19514 | return ira->codegen->invalid_instruction; |
| 19504 | 19515 | } |
| 19505 | 19516 | |
| 19517 | static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field) { | |
| 19518 | if (field->init_val != nullptr) return; | |
| 19519 | if (field->decl_node->type != NodeTypeStructField) return; | |
| 19520 | AstNode *init_node = field->decl_node->data.struct_field.value; | |
| 19521 | if (init_node == nullptr) return; | |
| 19522 | // scope is not the scope of the struct init, it's the scope of the struct type decl | |
| 19523 | Scope *analyze_scope = &get_container_scope(container_type)->base; | |
| 19524 | // memoize it | |
| 19525 | field->init_val = analyze_const_value(codegen, analyze_scope, init_node, | |
| 19526 | field->type_entry, nullptr, UndefOk); | |
| 19527 | } | |
| 19528 | ||
| 19506 | 19529 | static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction *source_instr, |
| 19507 | 19530 | TypeStructField *field, IrInstruction *struct_ptr, ZigType *struct_type, bool initializing) |
| 19508 | 19531 | { |
| ... | ... | @@ -19510,6 +19533,12 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction |
| 19510 | 19533 | ZigType *field_type = resolve_struct_field_type(ira->codegen, field); |
| 19511 | 19534 | if (field_type == nullptr) |
| 19512 | 19535 | return ira->codegen->invalid_instruction; |
| 19536 | if (field->is_comptime) { | |
| 19537 | IrInstruction *elem = ir_const(ira, source_instr, field_type); | |
| 19538 | memoize_field_init_val(ira->codegen, struct_type, field); | |
| 19539 | copy_const_val(elem->value, field->init_val); | |
| 19540 | return ir_get_ref(ira, source_instr, elem, true, false); | |
| 19541 | } | |
| 19513 | 19542 | switch (type_has_one_possible_value(ira->codegen, field_type)) { |
| 19514 | 19543 | case OnePossibleValueInvalid: |
| 19515 | 19544 | return ira->codegen->invalid_instruction; |
| ... | ... | @@ -21540,25 +21569,12 @@ static IrInstruction *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruc |
| 21540 | 21569 | |
| 21541 | 21570 | // look for a default field value |
| 21542 | 21571 | TypeStructField *field = container_type->data.structure.fields[i]; |
| 21572 | memoize_field_init_val(ira->codegen, container_type, field); | |
| 21543 | 21573 | if (field->init_val == nullptr) { |
| 21544 | // it's not memoized. time to go analyze it | |
| 21545 | AstNode *init_node; | |
| 21546 | if (field->decl_node->type == NodeTypeStructField) { | |
| 21547 | init_node = field->decl_node->data.struct_field.value; | |
| 21548 | } else { | |
| 21549 | init_node = nullptr; | |
| 21550 | } | |
| 21551 | if (init_node == nullptr) { | |
| 21552 | ir_add_error_node(ira, instruction->source_node, | |
| 21553 | buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name))); | |
| 21554 | any_missing = true; | |
| 21555 | continue; | |
| 21556 | } | |
| 21557 | // scope is not the scope of the struct init, it's the scope of the struct type decl | |
| 21558 | Scope *analyze_scope = &get_container_scope(container_type)->base; | |
| 21559 | // memoize it | |
| 21560 | field->init_val = analyze_const_value(ira->codegen, analyze_scope, init_node, | |
| 21561 | field->type_entry, nullptr, UndefOk); | |
| 21574 | ir_add_error_node(ira, instruction->source_node, | |
| 21575 | buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name))); | |
| 21576 | any_missing = true; | |
| 21577 | continue; | |
| 21562 | 21578 | } |
| 21563 | 21579 | if (type_is_invalid(field->init_val->type)) |
| 21564 | 21580 | return ira->codegen->invalid_instruction; |
| ... | ... | @@ -21716,6 +21732,10 @@ static IrInstruction *ir_analyze_instruction_container_init_list(IrAnalyze *ira, |
| 21716 | 21732 | for (size_t i = 0; i < const_ptrs.length; i += 1) { |
| 21717 | 21733 | IrInstruction *elem_result_loc = const_ptrs.at(i); |
| 21718 | 21734 | assert(elem_result_loc->value->special == ConstValSpecialStatic); |
| 21735 | if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) { | |
| 21736 | // This field will be generated comptime; no need to do this. | |
| 21737 | continue; | |
| 21738 | } | |
| 21719 | 21739 | IrInstruction *deref = ir_get_deref(ira, elem_result_loc, elem_result_loc, nullptr); |
| 21720 | 21740 | elem_result_loc->value->special = ConstValSpecialRuntime; |
| 21721 | 21741 | ir_analyze_store_ptr(ira, elem_result_loc, elem_result_loc, deref, false); |
src/parser.cpp+12-2| ... | ... | @@ -536,8 +536,8 @@ static void ast_parse_container_doc_comments(ParseContext *pc, Buf *buf) { |
| 536 | 536 | // <- TestDecl ContainerMembers |
| 537 | 537 | // / TopLevelComptime ContainerMembers |
| 538 | 538 | // / KEYWORD_pub? TopLevelDecl ContainerMembers |
| 539 | // / ContainerField COMMA ContainerMembers | |
| 540 | // / ContainerField | |
| 539 | // / KEYWORD_comptime? ContainerField COMMA ContainerMembers | |
| 540 | // / KEYWORD_comptime? ContainerField | |
| 541 | 541 | // / |
| 542 | 542 | static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) { |
| 543 | 543 | AstNodeContainerDecl res = {}; |
| ... | ... | @@ -574,10 +574,13 @@ static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) { |
| 574 | 574 | ast_error(pc, peek_token(pc), "expected function or variable declaration after pub"); |
| 575 | 575 | } |
| 576 | 576 | |
| 577 | Token *comptime_token = eat_token_if(pc, TokenIdKeywordCompTime); | |
| 578 | ||
| 577 | 579 | AstNode *container_field = ast_parse_container_field(pc); |
| 578 | 580 | if (container_field != nullptr) { |
| 579 | 581 | assert(container_field->type == NodeTypeStructField); |
| 580 | 582 | container_field->data.struct_field.doc_comments = doc_comment_buf; |
| 583 | container_field->data.struct_field.comptime_token = comptime_token; | |
| 581 | 584 | res.fields.append(container_field); |
| 582 | 585 | if (eat_token_if(pc, TokenIdComma) != nullptr) { |
| 583 | 586 | continue; |
| ... | ... | @@ -612,6 +615,13 @@ static AstNode *ast_parse_top_level_comptime(ParseContext *pc) { |
| 612 | 615 | if (comptime == nullptr) |
| 613 | 616 | return nullptr; |
| 614 | 617 | |
| 618 | // 1 token lookahead because it could be a comptime struct field | |
| 619 | Token *lbrace = peek_token(pc); | |
| 620 | if (lbrace->id != TokenIdLBrace) { | |
| 621 | put_back_token(pc); | |
| 622 | return nullptr; | |
| 623 | } | |
| 624 | ||
| 615 | 625 | AstNode *block = ast_expect(pc, ast_parse_block_expr); |
| 616 | 626 | AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime); |
| 617 | 627 | res->data.comptime_expr.expr = block; |
test/compile_errors.zig+10-1| ... | ... | @@ -2,6 +2,15 @@ const tests = @import("tests.zig"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | |
| 4 | 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5 | cases.add("comptime struct field, no init value", | |
| 6 | \\const Foo = struct { | |
| 7 | \\ comptime b: i32, | |
| 8 | \\}; | |
| 9 | \\export fn entry() void { | |
| 10 | \\ var f: Foo = undefined; | |
| 11 | \\} | |
| 12 | , "tmp.zig:2:5: error: comptime struct field missing initialization value"); | |
| 13 | ||
| 5 | 14 | cases.add( |
| 6 | 15 | "bad usage of @call", |
| 7 | 16 | \\export fn entry1() void { |
| ... | ... | @@ -32,7 +41,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 32 | 41 | "tmp.zig:15:43: error: unable to evaluate constant expression", |
| 33 | 42 | ); |
| 34 | 43 | |
| 35 | cases.add( | |
| 44 | cases.add("exported async function", | |
| 36 | 45 | \\export async fn foo() void {} |
| 37 | 46 | , "tmp.zig:1:1: error: exported function cannot be async"); |
| 38 | 47 |
test/stage1/behavior/call.zig+4-4| ... | ... | @@ -37,12 +37,12 @@ test "tuple parameters" { |
| 37 | 37 | comptime expect(@call(.{}, add, .{ 12, 34 }) == 46); |
| 38 | 38 | { |
| 39 | 39 | const separate_args0 = .{ a, b }; |
| 40 | //TODO const separate_args1 = .{ a, 34 }; | |
| 40 | const separate_args1 = .{ a, 34 }; | |
| 41 | 41 | const separate_args2 = .{ 12, 34 }; |
| 42 | //TODO const separate_args3 = .{ 12, b }; | |
| 42 | const separate_args3 = .{ 12, b }; | |
| 43 | 43 | expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46); |
| 44 | // TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46); | |
| 44 | expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46); | |
| 45 | 45 | expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46); |
| 46 | // TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46); | |
| 46 | expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46); | |
| 47 | 47 | } |
| 48 | 48 | } |
test/stage1/behavior/struct.zig+10-2| ... | ... | @@ -775,8 +775,6 @@ test "anonymous struct literal assigned to variable" { |
| 775 | 775 | expect(vec.@"0" == 22); |
| 776 | 776 | expect(vec.@"1" == 55); |
| 777 | 777 | expect(vec.@"2" == 99); |
| 778 | vec.@"1" += 1; | |
| 779 | expect(vec.@"1" == 56); | |
| 780 | 778 | } |
| 781 | 779 | |
| 782 | 780 | test "struct with var field" { |
| ... | ... | @@ -791,3 +789,13 @@ test "struct with var field" { |
| 791 | 789 | expect(pt.x == 1); |
| 792 | 790 | expect(pt.y == 2); |
| 793 | 791 | } |
| 792 | ||
| 793 | test "comptime struct field" { | |
| 794 | const T = struct { | |
| 795 | a: i32, | |
| 796 | comptime b: i32 = 1234, | |
| 797 | }; | |
| 798 | ||
| 799 | var foo: T = undefined; | |
| 800 | comptime expect(foo.b == 1234); | |
| 801 | } |