| author | |
| committer | |
| log | 88f5315ddfc6eaf3e28433504ec046fb3252db7c |
| tree | 5cd6e8e16b285d136a1fbaa98d12739aeab34feb |
| parent | 50ef10eb4963167225f7153dc5165292dbac0046 |
This change implements the following syntax into the compiler:
```zig
const x: u32, var y, foo.bar = .{ 1, 2, 3 };
```
A destructure expression may only appear within a block (i.e. not at
comtainer scope). The LHS consists of a sequence of comma-separated var
decls and/or lvalue expressions. The RHS is a normal expression.
A new result location type, `destructure`, is used, which contains
result pointers for each component of the destructure. This means that
when the RHS is a more complicated expression, peer type resolution is
not used: each result value is individually destructured and written to
the result pointers. RLS is always used for destructure expressions,
meaning every `const` on the LHS of such an expression creates a true
stack allocation.
Aside from anonymous array literals, Sema is capable of destructuring
the following types:
* Tuples
* Arrays
* Vectors
A destructure may be prefixed with the `comptime` keyword, in which case
the entire destructure is evaluated at comptime: this means all `var`s
in the LHS are `comptime var`s, every lvalue expression is evaluated at
comptime, and the RHS is evaluated at comptime. If every LHS is a
`const`, this is not allowed: as with single declarations, the user
should instead mark the RHS as `comptime`.
There are a few subtleties in the grammar changes here. For one thing,
if every LHS is an lvalue expression (rather than a var decl), a
destructure is considered an expression. This makes, for instance,
`if (cond) x, y = .{ 1, 2 };` valid Zig code. A destructure is allowed
in almost every context where a standard assignment expression is
permitted. The exception is `switch` prongs, which cannot be
destructures as the comma is ambiguous with the end of the prong.
A follow-up commit will begin utilizing this syntax in the Zig compiler.
Resolves: #49816 files changed, 1083 insertions(+), 111 deletions(-)
lib/std/zig/Ast.zig+28| ... | @@ -241,6 +241,11 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { | ... | @@ -241,6 +241,11 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { |
| 241 | token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)].symbol(), | 241 | token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)].symbol(), |
| 242 | }); | 242 | }); |
| 243 | }, | 243 | }, |
| 244 | .expected_expr_or_var_decl => { | ||
| 245 | return stream.print("expected expression or var decl, found '{s}'", .{ | ||
| 246 | token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)].symbol(), | ||
| 247 | }); | ||
| 248 | }, | ||
| 244 | .expected_fn => { | 249 | .expected_fn => { |
| 245 | return stream.print("expected function, found '{s}'", .{ | 250 | return stream.print("expected function, found '{s}'", .{ |
| 246 | token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)].symbol(), | 251 | token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)].symbol(), |
| ... | @@ -584,6 +589,13 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex { | ... | @@ -584,6 +589,13 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex { |
| 584 | .error_union, | 589 | .error_union, |
| 585 | => n = datas[n].lhs, | 590 | => n = datas[n].lhs, |
| 586 | 591 | ||
| 592 | .assign_destructure => { | ||
| 593 | const extra_idx = datas[n].lhs; | ||
| 594 | const lhs_len = tree.extra_data[extra_idx]; | ||
| 595 | assert(lhs_len > 0); | ||
| 596 | n = tree.extra_data[extra_idx + 1]; | ||
| 597 | }, | ||
| 598 | |||
| 587 | .fn_decl, | 599 | .fn_decl, |
| 588 | .fn_proto_simple, | 600 | .fn_proto_simple, |
| 589 | .fn_proto_multi, | 601 | .fn_proto_multi, |
| ... | @@ -816,6 +828,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { | ... | @@ -816,6 +828,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { |
| 816 | .assign_add_sat, | 828 | .assign_add_sat, |
| 817 | .assign_sub_sat, | 829 | .assign_sub_sat, |
| 818 | .assign, | 830 | .assign, |
| 831 | .assign_destructure, | ||
| 819 | .merge_error_sets, | 832 | .merge_error_sets, |
| 820 | .mul, | 833 | .mul, |
| 821 | .div, | 834 | .div, |
| ... | @@ -2846,6 +2859,7 @@ pub const Error = struct { | ... | @@ -2846,6 +2859,7 @@ pub const Error = struct { |
| 2846 | expected_container_members, | 2859 | expected_container_members, |
| 2847 | expected_expr, | 2860 | expected_expr, |
| 2848 | expected_expr_or_assignment, | 2861 | expected_expr_or_assignment, |
| 2862 | expected_expr_or_var_decl, | ||
| 2849 | expected_fn, | 2863 | expected_fn, |
| 2850 | expected_inlinable, | 2864 | expected_inlinable, |
| 2851 | expected_labelable, | 2865 | expected_labelable, |
| ... | @@ -3006,6 +3020,20 @@ pub const Node = struct { | ... | @@ -3006,6 +3020,20 @@ pub const Node = struct { |
| 3006 | assign_sub_sat, | 3020 | assign_sub_sat, |
| 3007 | /// `lhs = rhs`. main_token is op. | 3021 | /// `lhs = rhs`. main_token is op. |
| 3008 | assign, | 3022 | assign, |
| 3023 | /// `a, b, ... = rhs`. main_token is op. lhs is index into `extra_data` | ||
| 3024 | /// of an lhs elem count followed by an array of that many `Node.Index`, | ||
| 3025 | /// with each node having one of the following types: | ||
| 3026 | /// * `global_var_decl` | ||
| 3027 | /// * `local_var_decl` | ||
| 3028 | /// * `simple_var_decl` | ||
| 3029 | /// * `aligned_var_decl` | ||
| 3030 | /// * Any expression node | ||
| 3031 | /// The first 3 types correspond to a `var` or `const` lhs node (note | ||
| 3032 | /// that their `rhs` is always 0). An expression node corresponds to a | ||
| 3033 | /// standard assignment LHS (which must be evaluated as an lvalue). | ||
| 3034 | /// There may be a preceding `comptime` token, which does not create a | ||
| 3035 | /// corresponding `comptime` node so must be manually detected. | ||
| 3036 | assign_destructure, | ||
| 3009 | /// `lhs || rhs`. main_token is the `||`. | 3037 | /// `lhs || rhs`. main_token is the `||`. |
| 3010 | merge_error_sets, | 3038 | merge_error_sets, |
| 3011 | /// `lhs * rhs`. main_token is the `*`. | 3039 | /// `lhs * rhs`. main_token is the `*`. |
lib/std/zig/Parse.zig+278-72| ... | @@ -658,9 +658,8 @@ fn expectTopLevelDecl(p: *Parse) !Node.Index { | ... | @@ -658,9 +658,8 @@ fn expectTopLevelDecl(p: *Parse) !Node.Index { |
| 658 | } | 658 | } |
| 659 | 659 | ||
| 660 | const thread_local_token = p.eatToken(.keyword_threadlocal); | 660 | const thread_local_token = p.eatToken(.keyword_threadlocal); |
| 661 | const var_decl = try p.parseVarDecl(); | 661 | const var_decl = try p.parseGlobalVarDecl(); |
| 662 | if (var_decl != 0) { | 662 | if (var_decl != 0) { |
| 663 | try p.expectSemicolon(.expected_semi_after_decl, false); | ||
| 664 | return var_decl; | 663 | return var_decl; |
| 665 | } | 664 | } |
| 666 | if (thread_local_token != null) { | 665 | if (thread_local_token != null) { |
| ... | @@ -792,8 +791,9 @@ fn parseFnProto(p: *Parse) !Node.Index { | ... | @@ -792,8 +791,9 @@ fn parseFnProto(p: *Parse) !Node.Index { |
| 792 | } | 791 | } |
| 793 | } | 792 | } |
| 794 | 793 | ||
| 795 | /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? (EQUAL Expr)? SEMICOLON | 794 | /// VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? |
| 796 | fn parseVarDecl(p: *Parse) !Node.Index { | 795 | /// Returns a `*_var_decl` node with its rhs (init expression) initialized to 0. |
| 796 | fn parseVarDeclProto(p: *Parse) !Node.Index { | ||
| 797 | const mut_token = p.eatToken(.keyword_const) orelse | 797 | const mut_token = p.eatToken(.keyword_const) orelse |
| 798 | p.eatToken(.keyword_var) orelse | 798 | p.eatToken(.keyword_var) orelse |
| 799 | return null_node; | 799 | return null_node; |
| ... | @@ -803,18 +803,7 @@ fn parseVarDecl(p: *Parse) !Node.Index { | ... | @@ -803,18 +803,7 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 803 | const align_node = try p.parseByteAlign(); | 803 | const align_node = try p.parseByteAlign(); |
| 804 | const addrspace_node = try p.parseAddrSpace(); | 804 | const addrspace_node = try p.parseAddrSpace(); |
| 805 | const section_node = try p.parseLinkSection(); | 805 | const section_node = try p.parseLinkSection(); |
| 806 | const init_node: Node.Index = switch (p.token_tags[p.tok_i]) { | 806 | |
| 807 | .equal_equal => blk: { | ||
| 808 | try p.warn(.wrong_equal_var_decl); | ||
| 809 | p.tok_i += 1; | ||
| 810 | break :blk try p.expectExpr(); | ||
| 811 | }, | ||
| 812 | .equal => blk: { | ||
| 813 | p.tok_i += 1; | ||
| 814 | break :blk try p.expectExpr(); | ||
| 815 | }, | ||
| 816 | else => 0, | ||
| 817 | }; | ||
| 818 | if (section_node == 0 and addrspace_node == 0) { | 807 | if (section_node == 0 and addrspace_node == 0) { |
| 819 | if (align_node == 0) { | 808 | if (align_node == 0) { |
| 820 | return p.addNode(.{ | 809 | return p.addNode(.{ |
| ... | @@ -822,31 +811,33 @@ fn parseVarDecl(p: *Parse) !Node.Index { | ... | @@ -822,31 +811,33 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 822 | .main_token = mut_token, | 811 | .main_token = mut_token, |
| 823 | .data = .{ | 812 | .data = .{ |
| 824 | .lhs = type_node, | 813 | .lhs = type_node, |
| 825 | .rhs = init_node, | 814 | .rhs = 0, |
| 826 | }, | 815 | }, |
| 827 | }); | 816 | }); |
| 828 | } else if (type_node == 0) { | 817 | } |
| 818 | |||
| 819 | if (type_node == 0) { | ||
| 829 | return p.addNode(.{ | 820 | return p.addNode(.{ |
| 830 | .tag = .aligned_var_decl, | 821 | .tag = .aligned_var_decl, |
| 831 | .main_token = mut_token, | 822 | .main_token = mut_token, |
| 832 | .data = .{ | 823 | .data = .{ |
| 833 | .lhs = align_node, | 824 | .lhs = align_node, |
| 834 | .rhs = init_node, | 825 | .rhs = 0, |
| 835 | }, | ||
| 836 | }); | ||
| 837 | } else { | ||
| 838 | return p.addNode(.{ | ||
| 839 | .tag = .local_var_decl, | ||
| 840 | .main_token = mut_token, | ||
| 841 | .data = .{ | ||
| 842 | .lhs = try p.addExtra(Node.LocalVarDecl{ | ||
| 843 | .type_node = type_node, | ||
| 844 | .align_node = align_node, | ||
| 845 | }), | ||
| 846 | .rhs = init_node, | ||
| 847 | }, | 826 | }, |
| 848 | }); | 827 | }); |
| 849 | } | 828 | } |
| 829 | |||
| 830 | return p.addNode(.{ | ||
| 831 | .tag = .local_var_decl, | ||
| 832 | .main_token = mut_token, | ||
| 833 | .data = .{ | ||
| 834 | .lhs = try p.addExtra(Node.LocalVarDecl{ | ||
| 835 | .type_node = type_node, | ||
| 836 | .align_node = align_node, | ||
| 837 | }), | ||
| 838 | .rhs = 0, | ||
| 839 | }, | ||
| 840 | }); | ||
| 850 | } else { | 841 | } else { |
| 851 | return p.addNode(.{ | 842 | return p.addNode(.{ |
| 852 | .tag = .global_var_decl, | 843 | .tag = .global_var_decl, |
| ... | @@ -858,12 +849,38 @@ fn parseVarDecl(p: *Parse) !Node.Index { | ... | @@ -858,12 +849,38 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 858 | .addrspace_node = addrspace_node, | 849 | .addrspace_node = addrspace_node, |
| 859 | .section_node = section_node, | 850 | .section_node = section_node, |
| 860 | }), | 851 | }), |
| 861 | .rhs = init_node, | 852 | .rhs = 0, |
| 862 | }, | 853 | }, |
| 863 | }); | 854 | }); |
| 864 | } | 855 | } |
| 865 | } | 856 | } |
| 866 | 857 | ||
| 858 | /// GlobalVarDecl <- VarDeclProto (EQUAL Expr?) SEMICOLON | ||
| 859 | fn parseGlobalVarDecl(p: *Parse) !Node.Index { | ||
| 860 | const var_decl = try p.parseVarDeclProto(); | ||
| 861 | if (var_decl == 0) { | ||
| 862 | return null_node; | ||
| 863 | } | ||
| 864 | |||
| 865 | const init_node: Node.Index = switch (p.token_tags[p.tok_i]) { | ||
| 866 | .equal_equal => blk: { | ||
| 867 | try p.warn(.wrong_equal_var_decl); | ||
| 868 | p.tok_i += 1; | ||
| 869 | break :blk try p.expectExpr(); | ||
| 870 | }, | ||
| 871 | .equal => blk: { | ||
| 872 | p.tok_i += 1; | ||
| 873 | break :blk try p.expectExpr(); | ||
| 874 | }, | ||
| 875 | else => 0, | ||
| 876 | }; | ||
| 877 | |||
| 878 | p.nodes.items(.data)[var_decl].rhs = init_node; | ||
| 879 | |||
| 880 | try p.expectSemicolon(.expected_semi_after_decl, false); | ||
| 881 | return var_decl; | ||
| 882 | } | ||
| 883 | |||
| 867 | /// ContainerField | 884 | /// ContainerField |
| 868 | /// <- doc_comment? KEYWORD_comptime? IDENTIFIER (COLON TypeExpr)? ByteAlign? (EQUAL Expr)? | 885 | /// <- doc_comment? KEYWORD_comptime? IDENTIFIER (COLON TypeExpr)? ByteAlign? (EQUAL Expr)? |
| 869 | /// / doc_comment? KEYWORD_comptime? (IDENTIFIER COLON)? !KEYWORD_fn TypeExpr ByteAlign? (EQUAL Expr)? | 886 | /// / doc_comment? KEYWORD_comptime? (IDENTIFIER COLON)? !KEYWORD_fn TypeExpr ByteAlign? (EQUAL Expr)? |
| ... | @@ -918,8 +935,7 @@ fn expectContainerField(p: *Parse) !Node.Index { | ... | @@ -918,8 +935,7 @@ fn expectContainerField(p: *Parse) !Node.Index { |
| 918 | } | 935 | } |
| 919 | 936 | ||
| 920 | /// Statement | 937 | /// Statement |
| 921 | /// <- KEYWORD_comptime? VarDecl | 938 | /// <- KEYWORD_comptime ComptimeStatement |
| 922 | /// / KEYWORD_comptime BlockExprStatement | ||
| 923 | /// / KEYWORD_nosuspend BlockExprStatement | 939 | /// / KEYWORD_nosuspend BlockExprStatement |
| 924 | /// / KEYWORD_suspend BlockExprStatement | 940 | /// / KEYWORD_suspend BlockExprStatement |
| 925 | /// / KEYWORD_defer BlockExprStatement | 941 | /// / KEYWORD_defer BlockExprStatement |
| ... | @@ -927,27 +943,28 @@ fn expectContainerField(p: *Parse) !Node.Index { | ... | @@ -927,27 +943,28 @@ fn expectContainerField(p: *Parse) !Node.Index { |
| 927 | /// / IfStatement | 943 | /// / IfStatement |
| 928 | /// / LabeledStatement | 944 | /// / LabeledStatement |
| 929 | /// / SwitchExpr | 945 | /// / SwitchExpr |
| 930 | /// / AssignExpr SEMICOLON | 946 | /// / VarDeclExprStatement |
| 931 | fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | 947 | fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { |
| 932 | const comptime_token = p.eatToken(.keyword_comptime); | 948 | if (p.eatToken(.keyword_comptime)) |comptime_token| { |
| 933 | 949 | const block_expr = try p.parseBlockExpr(); | |
| 934 | if (allow_defer_var) { | 950 | if (block_expr != 0) { |
| 935 | const var_decl = try p.parseVarDecl(); | 951 | return p.addNode(.{ |
| 936 | if (var_decl != 0) { | 952 | .tag = .@"comptime", |
| 937 | try p.expectSemicolon(.expected_semi_after_decl, true); | 953 | .main_token = comptime_token, |
| 938 | return var_decl; | 954 | .data = .{ |
| 955 | .lhs = block_expr, | ||
| 956 | .rhs = undefined, | ||
| 957 | }, | ||
| 958 | }); | ||
| 939 | } | 959 | } |
| 940 | } | ||
| 941 | 960 | ||
| 942 | if (comptime_token) |token| { | 961 | if (allow_defer_var) { |
| 943 | return p.addNode(.{ | 962 | return p.expectVarDeclExprStatement(comptime_token); |
| 944 | .tag = .@"comptime", | 963 | } else { |
| 945 | .main_token = token, | 964 | const assign = try p.expectAssignExpr(); |
| 946 | .data = .{ | 965 | try p.expectSemicolon(.expected_semi_after_stmt, true); |
| 947 | .lhs = try p.expectBlockExprStatement(), | 966 | return assign; |
| 948 | .rhs = undefined, | 967 | } |
| 949 | }, | ||
| 950 | }); | ||
| 951 | } | 968 | } |
| 952 | 969 | ||
| 953 | switch (p.token_tags[p.tok_i]) { | 970 | switch (p.token_tags[p.tok_i]) { |
| ... | @@ -1011,21 +1028,145 @@ fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | ... | @@ -1011,21 +1028,145 @@ fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { |
| 1011 | const labeled_statement = try p.parseLabeledStatement(); | 1028 | const labeled_statement = try p.parseLabeledStatement(); |
| 1012 | if (labeled_statement != 0) return labeled_statement; | 1029 | if (labeled_statement != 0) return labeled_statement; |
| 1013 | 1030 | ||
| 1014 | const assign_expr = try p.parseAssignExpr(); | 1031 | if (allow_defer_var) { |
| 1015 | if (assign_expr != 0) { | 1032 | return p.expectVarDeclExprStatement(null); |
| 1033 | } else { | ||
| 1034 | const assign = try p.expectAssignExpr(); | ||
| 1016 | try p.expectSemicolon(.expected_semi_after_stmt, true); | 1035 | try p.expectSemicolon(.expected_semi_after_stmt, true); |
| 1017 | return assign_expr; | 1036 | return assign; |
| 1018 | } | 1037 | } |
| 1038 | } | ||
| 1019 | 1039 | ||
| 1020 | return null_node; | 1040 | /// ComptimeStatement |
| 1041 | /// <- BlockExpr | ||
| 1042 | /// / VarDeclExprStatement | ||
| 1043 | fn expectComptimeStatement(p: *Parse, comptime_token: TokenIndex) !Node.Index { | ||
| 1044 | const block_expr = try p.parseBlockExpr(); | ||
| 1045 | if (block_expr != 0) { | ||
| 1046 | return p.addNode(.{ | ||
| 1047 | .tag = .@"comptime", | ||
| 1048 | .main_token = comptime_token, | ||
| 1049 | .data = .{ .lhs = block_expr, .rhs = undefined }, | ||
| 1050 | }); | ||
| 1051 | } | ||
| 1052 | return p.expectVarDeclExprStatement(comptime_token); | ||
| 1021 | } | 1053 | } |
| 1022 | 1054 | ||
| 1023 | fn expectStatement(p: *Parse, allow_defer_var: bool) !Node.Index { | 1055 | /// VarDeclExprStatement |
| 1024 | const statement = try p.parseStatement(allow_defer_var); | 1056 | /// <- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON |
| 1025 | if (statement == 0) { | 1057 | /// / Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON |
| 1026 | return p.fail(.expected_statement); | 1058 | fn expectVarDeclExprStatement(p: *Parse, comptime_token: ?TokenIndex) !Node.Index { |
| 1059 | const scratch_top = p.scratch.items.len; | ||
| 1060 | defer p.scratch.shrinkRetainingCapacity(scratch_top); | ||
| 1061 | |||
| 1062 | while (true) { | ||
| 1063 | const var_decl_proto = try p.parseVarDeclProto(); | ||
| 1064 | if (var_decl_proto != 0) { | ||
| 1065 | try p.scratch.append(p.gpa, var_decl_proto); | ||
| 1066 | } else { | ||
| 1067 | const expr = try p.parseExpr(); | ||
| 1068 | if (expr == 0) { | ||
| 1069 | if (p.scratch.items.len == scratch_top) { | ||
| 1070 | // We parsed nothing | ||
| 1071 | return p.fail(.expected_statement); | ||
| 1072 | } else { | ||
| 1073 | // We've had at least one LHS, but had a bad comma | ||
| 1074 | return p.fail(.expected_expr_or_var_decl); | ||
| 1075 | } | ||
| 1076 | } | ||
| 1077 | try p.scratch.append(p.gpa, expr); | ||
| 1078 | } | ||
| 1079 | _ = p.eatToken(.comma) orelse break; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | const lhs_count = p.scratch.items.len - scratch_top; | ||
| 1083 | assert(lhs_count > 0); | ||
| 1084 | |||
| 1085 | const equal_token = p.eatToken(.equal) orelse eql: { | ||
| 1086 | if (lhs_count > 1) { | ||
| 1087 | // Definitely a destructure, so allow recovering from == | ||
| 1088 | if (p.eatToken(.equal_equal)) |tok| { | ||
| 1089 | try p.warnMsg(.{ .tag = .wrong_equal_var_decl, .token = tok }); | ||
| 1090 | break :eql tok; | ||
| 1091 | } | ||
| 1092 | return p.failExpected(.equal); | ||
| 1093 | } | ||
| 1094 | const lhs = p.scratch.items[scratch_top]; | ||
| 1095 | switch (p.nodes.items(.tag)[lhs]) { | ||
| 1096 | .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => { | ||
| 1097 | // Definitely a var decl, so allow recovering from == | ||
| 1098 | if (p.eatToken(.equal_equal)) |tok| { | ||
| 1099 | try p.warnMsg(.{ .tag = .wrong_equal_var_decl, .token = tok }); | ||
| 1100 | break :eql tok; | ||
| 1101 | } | ||
| 1102 | return p.failExpected(.equal); | ||
| 1103 | }, | ||
| 1104 | else => {}, | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | const expr = try p.finishAssignExpr(lhs); | ||
| 1108 | try p.expectSemicolon(.expected_semi_after_stmt, true); | ||
| 1109 | if (comptime_token) |t| { | ||
| 1110 | return p.addNode(.{ | ||
| 1111 | .tag = .@"comptime", | ||
| 1112 | .main_token = t, | ||
| 1113 | .data = .{ | ||
| 1114 | .lhs = expr, | ||
| 1115 | .rhs = undefined, | ||
| 1116 | }, | ||
| 1117 | }); | ||
| 1118 | } else { | ||
| 1119 | return expr; | ||
| 1120 | } | ||
| 1121 | }; | ||
| 1122 | |||
| 1123 | const rhs = try p.expectExpr(); | ||
| 1124 | try p.expectSemicolon(.expected_semi_after_stmt, true); | ||
| 1125 | |||
| 1126 | if (lhs_count == 1) { | ||
| 1127 | const lhs = p.scratch.items[scratch_top]; | ||
| 1128 | switch (p.nodes.items(.tag)[lhs]) { | ||
| 1129 | .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => { | ||
| 1130 | p.nodes.items(.data)[lhs].rhs = rhs; | ||
| 1131 | // Don't need to wrap in comptime | ||
| 1132 | return lhs; | ||
| 1133 | }, | ||
| 1134 | else => {}, | ||
| 1135 | } | ||
| 1136 | const expr = try p.addNode(.{ | ||
| 1137 | .tag = .assign, | ||
| 1138 | .main_token = equal_token, | ||
| 1139 | .data = .{ .lhs = lhs, .rhs = rhs }, | ||
| 1140 | }); | ||
| 1141 | if (comptime_token) |t| { | ||
| 1142 | return p.addNode(.{ | ||
| 1143 | .tag = .@"comptime", | ||
| 1144 | .main_token = t, | ||
| 1145 | .data = .{ | ||
| 1146 | .lhs = expr, | ||
| 1147 | .rhs = undefined, | ||
| 1148 | }, | ||
| 1149 | }); | ||
| 1150 | } else { | ||
| 1151 | return expr; | ||
| 1152 | } | ||
| 1027 | } | 1153 | } |
| 1028 | return statement; | 1154 | |
| 1155 | // An actual destructure! No need for any `comptime` wrapper here. | ||
| 1156 | |||
| 1157 | const extra_start = p.extra_data.items.len; | ||
| 1158 | try p.extra_data.ensureUnusedCapacity(p.gpa, lhs_count + 1); | ||
| 1159 | p.extra_data.appendAssumeCapacity(@intCast(lhs_count)); | ||
| 1160 | p.extra_data.appendSliceAssumeCapacity(p.scratch.items[scratch_top..]); | ||
| 1161 | |||
| 1162 | return p.addNode(.{ | ||
| 1163 | .tag = .assign_destructure, | ||
| 1164 | .main_token = equal_token, | ||
| 1165 | .data = .{ | ||
| 1166 | .lhs = @intCast(extra_start), | ||
| 1167 | .rhs = rhs, | ||
| 1168 | }, | ||
| 1169 | }); | ||
| 1029 | } | 1170 | } |
| 1030 | 1171 | ||
| 1031 | /// If a parse error occurs, reports an error, but then finds the next statement | 1172 | /// If a parse error occurs, reports an error, but then finds the next statement |
| ... | @@ -1345,7 +1486,7 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index { | ... | @@ -1345,7 +1486,7 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index { |
| 1345 | } | 1486 | } |
| 1346 | } | 1487 | } |
| 1347 | 1488 | ||
| 1348 | /// AssignExpr <- Expr (AssignOp Expr)? | 1489 | /// AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)? |
| 1349 | /// | 1490 | /// |
| 1350 | /// AssignOp | 1491 | /// AssignOp |
| 1351 | /// <- ASTERISKEQUAL | 1492 | /// <- ASTERISKEQUAL |
| ... | @@ -1369,8 +1510,40 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index { | ... | @@ -1369,8 +1510,40 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index { |
| 1369 | fn parseAssignExpr(p: *Parse) !Node.Index { | 1510 | fn parseAssignExpr(p: *Parse) !Node.Index { |
| 1370 | const expr = try p.parseExpr(); | 1511 | const expr = try p.parseExpr(); |
| 1371 | if (expr == 0) return null_node; | 1512 | if (expr == 0) return null_node; |
| 1513 | return p.finishAssignExpr(expr); | ||
| 1514 | } | ||
| 1372 | 1515 | ||
| 1373 | const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { | 1516 | /// SingleAssignExpr <- Expr (AssignOp Expr)? |
| 1517 | fn parseSingleAssignExpr(p: *Parse) !Node.Index { | ||
| 1518 | const lhs = try p.parseExpr(); | ||
| 1519 | if (lhs == 0) return null_node; | ||
| 1520 | const tag = assignOpNode(p.token_tags[p.tok_i]) orelse return lhs; | ||
| 1521 | return p.addNode(.{ | ||
| 1522 | .tag = tag, | ||
| 1523 | .main_token = p.nextToken(), | ||
| 1524 | .data = .{ | ||
| 1525 | .lhs = lhs, | ||
| 1526 | .rhs = try p.expectExpr(), | ||
| 1527 | }, | ||
| 1528 | }); | ||
| 1529 | } | ||
| 1530 | |||
| 1531 | fn finishAssignExpr(p: *Parse, lhs: Node.Index) !Node.Index { | ||
| 1532 | const tok = p.token_tags[p.tok_i]; | ||
| 1533 | if (tok == .comma) return p.finishAssignDestructureExpr(lhs); | ||
| 1534 | const tag = assignOpNode(tok) orelse return lhs; | ||
| 1535 | return p.addNode(.{ | ||
| 1536 | .tag = tag, | ||
| 1537 | .main_token = p.nextToken(), | ||
| 1538 | .data = .{ | ||
| 1539 | .lhs = lhs, | ||
| 1540 | .rhs = try p.expectExpr(), | ||
| 1541 | }, | ||
| 1542 | }); | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | fn assignOpNode(tok: Token.Tag) ?Node.Tag { | ||
| 1546 | return switch (tok) { | ||
| 1374 | .asterisk_equal => .assign_mul, | 1547 | .asterisk_equal => .assign_mul, |
| 1375 | .slash_equal => .assign_div, | 1548 | .slash_equal => .assign_div, |
| 1376 | .percent_equal => .assign_mod, | 1549 | .percent_equal => .assign_mod, |
| ... | @@ -1389,18 +1562,51 @@ fn parseAssignExpr(p: *Parse) !Node.Index { | ... | @@ -1389,18 +1562,51 @@ fn parseAssignExpr(p: *Parse) !Node.Index { |
| 1389 | .plus_pipe_equal => .assign_add_sat, | 1562 | .plus_pipe_equal => .assign_add_sat, |
| 1390 | .minus_pipe_equal => .assign_sub_sat, | 1563 | .minus_pipe_equal => .assign_sub_sat, |
| 1391 | .equal => .assign, | 1564 | .equal => .assign, |
| 1392 | else => return expr, | 1565 | else => null, |
| 1393 | }; | 1566 | }; |
| 1567 | } | ||
| 1568 | |||
| 1569 | fn finishAssignDestructureExpr(p: *Parse, first_lhs: Node.Index) !Node.Index { | ||
| 1570 | const scratch_top = p.scratch.items.len; | ||
| 1571 | defer p.scratch.shrinkRetainingCapacity(scratch_top); | ||
| 1572 | |||
| 1573 | try p.scratch.append(p.gpa, first_lhs); | ||
| 1574 | |||
| 1575 | while (p.eatToken(.comma)) |_| { | ||
| 1576 | const expr = try p.expectExpr(); | ||
| 1577 | try p.scratch.append(p.gpa, expr); | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | const equal_token = try p.expectToken(.equal); | ||
| 1581 | |||
| 1582 | const rhs = try p.expectExpr(); | ||
| 1583 | |||
| 1584 | const lhs_count = p.scratch.items.len - scratch_top; | ||
| 1585 | assert(lhs_count > 1); // we already had first_lhs, and must have at least one more lvalue | ||
| 1586 | |||
| 1587 | const extra_start = p.extra_data.items.len; | ||
| 1588 | try p.extra_data.ensureUnusedCapacity(p.gpa, lhs_count + 1); | ||
| 1589 | p.extra_data.appendAssumeCapacity(@intCast(lhs_count)); | ||
| 1590 | p.extra_data.appendSliceAssumeCapacity(p.scratch.items[scratch_top..]); | ||
| 1591 | |||
| 1394 | return p.addNode(.{ | 1592 | return p.addNode(.{ |
| 1395 | .tag = tag, | 1593 | .tag = .assign_destructure, |
| 1396 | .main_token = p.nextToken(), | 1594 | .main_token = equal_token, |
| 1397 | .data = .{ | 1595 | .data = .{ |
| 1398 | .lhs = expr, | 1596 | .lhs = @intCast(extra_start), |
| 1399 | .rhs = try p.expectExpr(), | 1597 | .rhs = rhs, |
| 1400 | }, | 1598 | }, |
| 1401 | }); | 1599 | }); |
| 1402 | } | 1600 | } |
| 1403 | 1601 | ||
| 1602 | fn expectSingleAssignExpr(p: *Parse) !Node.Index { | ||
| 1603 | const expr = try p.parseSingleAssignExpr(); | ||
| 1604 | if (expr == 0) { | ||
| 1605 | return p.fail(.expected_expr_or_assignment); | ||
| 1606 | } | ||
| 1607 | return expr; | ||
| 1608 | } | ||
| 1609 | |||
| 1404 | fn expectAssignExpr(p: *Parse) !Node.Index { | 1610 | fn expectAssignExpr(p: *Parse) !Node.Index { |
| 1405 | const expr = try p.parseAssignExpr(); | 1611 | const expr = try p.parseAssignExpr(); |
| 1406 | if (expr == 0) { | 1612 | if (expr == 0) { |
| ... | @@ -3260,7 +3466,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { | ... | @@ -3260,7 +3466,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3260 | .main_token = arrow_token, | 3466 | .main_token = arrow_token, |
| 3261 | .data = .{ | 3467 | .data = .{ |
| 3262 | .lhs = 0, | 3468 | .lhs = 0, |
| 3263 | .rhs = try p.expectAssignExpr(), | 3469 | .rhs = try p.expectSingleAssignExpr(), |
| 3264 | }, | 3470 | }, |
| 3265 | }), | 3471 | }), |
| 3266 | 1 => return p.addNode(.{ | 3472 | 1 => return p.addNode(.{ |
| ... | @@ -3268,7 +3474,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { | ... | @@ -3268,7 +3474,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3268 | .main_token = arrow_token, | 3474 | .main_token = arrow_token, |
| 3269 | .data = .{ | 3475 | .data = .{ |
| 3270 | .lhs = items[0], | 3476 | .lhs = items[0], |
| 3271 | .rhs = try p.expectAssignExpr(), | 3477 | .rhs = try p.expectSingleAssignExpr(), |
| 3272 | }, | 3478 | }, |
| 3273 | }), | 3479 | }), |
| 3274 | else => return p.addNode(.{ | 3480 | else => return p.addNode(.{ |
| ... | @@ -3276,7 +3482,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { | ... | @@ -3276,7 +3482,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3276 | .main_token = arrow_token, | 3482 | .main_token = arrow_token, |
| 3277 | .data = .{ | 3483 | .data = .{ |
| 3278 | .lhs = try p.addExtra(try p.listToSpan(items)), | 3484 | .lhs = try p.addExtra(try p.listToSpan(items)), |
| 3279 | .rhs = try p.expectAssignExpr(), | 3485 | .rhs = try p.expectSingleAssignExpr(), |
| 3280 | }, | 3486 | }, |
| 3281 | }), | 3487 | }), |
| 3282 | } | 3488 | } |
lib/std/zig/parser_test.zig+7-7| ... | @@ -4348,12 +4348,12 @@ test "zig fmt: invalid else branch statement" { | ... | @@ -4348,12 +4348,12 @@ test "zig fmt: invalid else branch statement" { |
| 4348 | \\ for ("") |_| {} else defer {} | 4348 | \\ for ("") |_| {} else defer {} |
| 4349 | \\} | 4349 | \\} |
| 4350 | , &[_]Error{ | 4350 | , &[_]Error{ |
| 4351 | .expected_statement, | 4351 | .expected_expr_or_assignment, |
| 4352 | .expected_statement, | 4352 | .expected_expr_or_assignment, |
| 4353 | .expected_statement, | 4353 | .expected_expr_or_assignment, |
| 4354 | .expected_statement, | 4354 | .expected_expr_or_assignment, |
| 4355 | .expected_statement, | 4355 | .expected_expr_or_assignment, |
| 4356 | .expected_statement, | 4356 | .expected_expr_or_assignment, |
| 4357 | }); | 4357 | }); |
| 4358 | } | 4358 | } |
| 4359 | 4359 | ||
| ... | @@ -6078,7 +6078,7 @@ test "recovery: missing for payload" { | ... | @@ -6078,7 +6078,7 @@ test "recovery: missing for payload" { |
| 6078 | try testError( | 6078 | try testError( |
| 6079 | \\comptime { | 6079 | \\comptime { |
| 6080 | \\ const a = for(a) {}; | 6080 | \\ const a = for(a) {}; |
| 6081 | \\ const a: for(a) blk: {}; | 6081 | \\ const a: for(a) blk: {} = {}; |
| 6082 | \\ for(a) {} | 6082 | \\ for(a) {} |
| 6083 | \\} | 6083 | \\} |
| 6084 | , &[_]Error{ | 6084 | , &[_]Error{ |
lib/std/zig/render.zig+82-32| ... | @@ -164,7 +164,7 @@ fn renderMember( | ... | @@ -164,7 +164,7 @@ fn renderMember( |
| 164 | .local_var_decl, | 164 | .local_var_decl, |
| 165 | .simple_var_decl, | 165 | .simple_var_decl, |
| 166 | .aligned_var_decl, | 166 | .aligned_var_decl, |
| 167 | => return renderVarDecl(gpa, ais, tree, tree.fullVarDecl(decl).?), | 167 | => return renderVarDecl(gpa, ais, tree, tree.fullVarDecl(decl).?, false, .semicolon), |
| 168 | 168 | ||
| 169 | .test_decl => { | 169 | .test_decl => { |
| 170 | const test_token = main_tokens[decl]; | 170 | const test_token = main_tokens[decl]; |
| ... | @@ -427,6 +427,42 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, | ... | @@ -427,6 +427,42 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, |
| 427 | return renderExpression(gpa, ais, tree, infix.rhs, space); | 427 | return renderExpression(gpa, ais, tree, infix.rhs, space); |
| 428 | }, | 428 | }, |
| 429 | 429 | ||
| 430 | .assign_destructure => { | ||
| 431 | const lhs_count = tree.extra_data[datas[node].lhs]; | ||
| 432 | assert(lhs_count > 1); | ||
| 433 | const lhs_exprs = tree.extra_data[datas[node].lhs + 1 ..][0..lhs_count]; | ||
| 434 | const rhs = datas[node].rhs; | ||
| 435 | |||
| 436 | const maybe_comptime_token = tree.firstToken(node) - 1; | ||
| 437 | if (token_tags[maybe_comptime_token] == .keyword_comptime) { | ||
| 438 | try renderToken(ais, tree, maybe_comptime_token, .space); | ||
| 439 | } | ||
| 440 | |||
| 441 | for (lhs_exprs, 0..) |lhs_node, i| { | ||
| 442 | const lhs_space: Space = if (i == lhs_exprs.len - 1) .space else .comma_space; | ||
| 443 | switch (node_tags[lhs_node]) { | ||
| 444 | .global_var_decl, | ||
| 445 | .local_var_decl, | ||
| 446 | .simple_var_decl, | ||
| 447 | .aligned_var_decl, | ||
| 448 | => { | ||
| 449 | try renderVarDecl(gpa, ais, tree, tree.fullVarDecl(lhs_node).?, true, lhs_space); | ||
| 450 | }, | ||
| 451 | else => try renderExpression(gpa, ais, tree, lhs_node, lhs_space), | ||
| 452 | } | ||
| 453 | } | ||
| 454 | const equal_token = main_tokens[node]; | ||
| 455 | if (tree.tokensOnSameLine(equal_token, equal_token + 1)) { | ||
| 456 | try renderToken(ais, tree, equal_token, .space); | ||
| 457 | } else { | ||
| 458 | ais.pushIndent(); | ||
| 459 | try renderToken(ais, tree, equal_token, .newline); | ||
| 460 | ais.popIndent(); | ||
| 461 | } | ||
| 462 | ais.pushIndentOneShot(); | ||
| 463 | return renderExpression(gpa, ais, tree, rhs, space); | ||
| 464 | }, | ||
| 465 | |||
| 430 | .bit_not, | 466 | .bit_not, |
| 431 | .bool_not, | 467 | .bool_not, |
| 432 | .negation, | 468 | .negation, |
| ... | @@ -943,7 +979,16 @@ fn renderAsmInput( | ... | @@ -943,7 +979,16 @@ fn renderAsmInput( |
| 943 | return renderToken(ais, tree, datas[asm_input].rhs, space); // rparen | 979 | return renderToken(ais, tree, datas[asm_input].rhs, space); // rparen |
| 944 | } | 980 | } |
| 945 | 981 | ||
| 946 | fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDecl) Error!void { | 982 | fn renderVarDecl( |
| 983 | gpa: Allocator, | ||
| 984 | ais: *Ais, | ||
| 985 | tree: Ast, | ||
| 986 | var_decl: Ast.full.VarDecl, | ||
| 987 | /// Destructures intentionally ignore leading `comptime` tokens. | ||
| 988 | ignore_comptime_token: bool, | ||
| 989 | /// `comma_space` and `space` are used for destructure LHS decls. | ||
| 990 | space: Space, | ||
| 991 | ) Error!void { | ||
| 947 | if (var_decl.visib_token) |visib_token| { | 992 | if (var_decl.visib_token) |visib_token| { |
| 948 | try renderToken(ais, tree, visib_token, Space.space); // pub | 993 | try renderToken(ais, tree, visib_token, Space.space); // pub |
| 949 | } | 994 | } |
| ... | @@ -960,21 +1005,31 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec | ... | @@ -960,21 +1005,31 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 960 | try renderToken(ais, tree, thread_local_token, Space.space); // threadlocal | 1005 | try renderToken(ais, tree, thread_local_token, Space.space); // threadlocal |
| 961 | } | 1006 | } |
| 962 | 1007 | ||
| 963 | if (var_decl.comptime_token) |comptime_token| { | 1008 | if (!ignore_comptime_token) { |
| 964 | try renderToken(ais, tree, comptime_token, Space.space); // comptime | 1009 | if (var_decl.comptime_token) |comptime_token| { |
| 1010 | try renderToken(ais, tree, comptime_token, Space.space); // comptime | ||
| 1011 | } | ||
| 965 | } | 1012 | } |
| 966 | 1013 | ||
| 967 | try renderToken(ais, tree, var_decl.ast.mut_token, .space); // var | 1014 | try renderToken(ais, tree, var_decl.ast.mut_token, .space); // var |
| 968 | 1015 | ||
| 969 | const name_space = if (var_decl.ast.type_node == 0 and | 1016 | if (var_decl.ast.type_node != 0 or var_decl.ast.align_node != 0 or |
| 970 | (var_decl.ast.align_node != 0 or | 1017 | var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or |
| 971 | var_decl.ast.addrspace_node != 0 or | 1018 | var_decl.ast.init_node != 0) |
| 972 | var_decl.ast.section_node != 0 or | 1019 | { |
| 973 | var_decl.ast.init_node != 0)) | 1020 | const name_space = if (var_decl.ast.type_node == 0 and |
| 974 | Space.space | 1021 | (var_decl.ast.align_node != 0 or |
| 975 | else | 1022 | var_decl.ast.addrspace_node != 0 or |
| 976 | Space.none; | 1023 | var_decl.ast.section_node != 0 or |
| 977 | try renderIdentifier(ais, tree, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name | 1024 | var_decl.ast.init_node != 0)) |
| 1025 | Space.space | ||
| 1026 | else | ||
| 1027 | Space.none; | ||
| 1028 | |||
| 1029 | try renderIdentifier(ais, tree, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name | ||
| 1030 | } else { | ||
| 1031 | return renderIdentifier(ais, tree, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name | ||
| 1032 | } | ||
| 978 | 1033 | ||
| 979 | if (var_decl.ast.type_node != 0) { | 1034 | if (var_decl.ast.type_node != 0) { |
| 980 | try renderToken(ais, tree, var_decl.ast.mut_token + 2, Space.space); // : | 1035 | try renderToken(ais, tree, var_decl.ast.mut_token + 2, Space.space); // : |
| ... | @@ -983,9 +1038,7 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec | ... | @@ -983,9 +1038,7 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 983 | { | 1038 | { |
| 984 | try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .space); | 1039 | try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .space); |
| 985 | } else { | 1040 | } else { |
| 986 | try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .none); | 1041 | return renderExpression(gpa, ais, tree, var_decl.ast.type_node, space); |
| 987 | const semicolon = tree.lastToken(var_decl.ast.type_node) + 1; | ||
| 988 | return renderToken(ais, tree, semicolon, Space.newline); // ; | ||
| 989 | } | 1042 | } |
| 990 | } | 1043 | } |
| 991 | 1044 | ||
| ... | @@ -1001,8 +1054,7 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec | ... | @@ -1001,8 +1054,7 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 1001 | { | 1054 | { |
| 1002 | try renderToken(ais, tree, rparen, .space); // ) | 1055 | try renderToken(ais, tree, rparen, .space); // ) |
| 1003 | } else { | 1056 | } else { |
| 1004 | try renderToken(ais, tree, rparen, .none); // ) | 1057 | return renderToken(ais, tree, rparen, space); // ) |
| 1005 | return renderToken(ais, tree, rparen + 1, Space.newline); // ; | ||
| 1006 | } | 1058 | } |
| 1007 | } | 1059 | } |
| 1008 | 1060 | ||
| ... | @@ -1031,23 +1083,21 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec | ... | @@ -1031,23 +1083,21 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 1031 | if (var_decl.ast.init_node != 0) { | 1083 | if (var_decl.ast.init_node != 0) { |
| 1032 | try renderToken(ais, tree, rparen, .space); // ) | 1084 | try renderToken(ais, tree, rparen, .space); // ) |
| 1033 | } else { | 1085 | } else { |
| 1034 | try renderToken(ais, tree, rparen, .none); // ) | 1086 | return renderToken(ais, tree, rparen, space); // ) |
| 1035 | return renderToken(ais, tree, rparen + 1, Space.newline); // ; | ||
| 1036 | } | 1087 | } |
| 1037 | } | 1088 | } |
| 1038 | 1089 | ||
| 1039 | if (var_decl.ast.init_node != 0) { | 1090 | assert(var_decl.ast.init_node != 0); |
| 1040 | const eq_token = tree.firstToken(var_decl.ast.init_node) - 1; | 1091 | |
| 1041 | const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; | 1092 | const eq_token = tree.firstToken(var_decl.ast.init_node) - 1; |
| 1042 | { | 1093 | const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; |
| 1043 | ais.pushIndent(); | 1094 | { |
| 1044 | try renderToken(ais, tree, eq_token, eq_space); // = | 1095 | ais.pushIndent(); |
| 1045 | ais.popIndent(); | 1096 | try renderToken(ais, tree, eq_token, eq_space); // = |
| 1046 | } | 1097 | ais.popIndent(); |
| 1047 | ais.pushIndentOneShot(); | ||
| 1048 | return renderExpression(gpa, ais, tree, var_decl.ast.init_node, .semicolon); // ; | ||
| 1049 | } | 1098 | } |
| 1050 | return renderToken(ais, tree, var_decl.ast.mut_token + 2, .newline); // ; | 1099 | ais.pushIndentOneShot(); |
| 1100 | return renderExpression(gpa, ais, tree, var_decl.ast.init_node, space); // ; | ||
| 1051 | } | 1101 | } |
| 1052 | 1102 | ||
| 1053 | fn renderIf(gpa: Allocator, ais: *Ais, tree: Ast, if_node: Ast.full.If, space: Space) Error!void { | 1103 | fn renderIf(gpa: Allocator, ais: *Ais, tree: Ast, if_node: Ast.full.If, space: Space) Error!void { |
| ... | @@ -1825,7 +1875,7 @@ fn renderBlock( | ... | @@ -1825,7 +1875,7 @@ fn renderBlock( |
| 1825 | .local_var_decl, | 1875 | .local_var_decl, |
| 1826 | .simple_var_decl, | 1876 | .simple_var_decl, |
| 1827 | .aligned_var_decl, | 1877 | .aligned_var_decl, |
| 1828 | => try renderVarDecl(gpa, ais, tree, tree.fullVarDecl(stmt).?), | 1878 | => try renderVarDecl(gpa, ais, tree, tree.fullVarDecl(stmt).?, false, .semicolon), |
| 1829 | else => try renderExpression(gpa, ais, tree, stmt, .semicolon), | 1879 | else => try renderExpression(gpa, ais, tree, stmt, .semicolon), |
| 1830 | } | 1880 | } |
| 1831 | } | 1881 | } |
src/AstGen.zig+406| ... | @@ -280,6 +280,20 @@ const ResultInfo = struct { | ... | @@ -280,6 +280,20 @@ const ResultInfo = struct { |
| 280 | /// The result instruction from the expression must be ignored. | 280 | /// The result instruction from the expression must be ignored. |
| 281 | /// Always an instruction with tag `alloc_inferred`. | 281 | /// Always an instruction with tag `alloc_inferred`. |
| 282 | inferred_ptr: Zir.Inst.Ref, | 282 | inferred_ptr: Zir.Inst.Ref, |
| 283 | /// The expression has a sequence of pointers to store its results into due to a destructure | ||
| 284 | /// operation. Each of these pointers may or may not have an inferred type. | ||
| 285 | destructure: struct { | ||
| 286 | /// The AST node of the destructure operation itself. | ||
| 287 | src_node: Ast.Node.Index, | ||
| 288 | /// The pointers to store results into. | ||
| 289 | components: []const DestructureComponent, | ||
| 290 | }, | ||
| 291 | |||
| 292 | const DestructureComponent = union(enum) { | ||
| 293 | typed_ptr: PtrResultLoc, | ||
| 294 | inferred_ptr: Zir.Inst.Ref, | ||
| 295 | discard, | ||
| 296 | }; | ||
| 283 | 297 | ||
| 284 | const PtrResultLoc = struct { | 298 | const PtrResultLoc = struct { |
| 285 | inst: Zir.Inst.Ref, | 299 | inst: Zir.Inst.Ref, |
| ... | @@ -298,6 +312,12 @@ const ResultInfo = struct { | ... | @@ -298,6 +312,12 @@ const ResultInfo = struct { |
| 298 | const ptr_ty = try gz.addUnNode(.typeof, ptr.inst, node); | 312 | const ptr_ty = try gz.addUnNode(.typeof, ptr.inst, node); |
| 299 | return gz.addUnNode(.elem_type, ptr_ty, node); | 313 | return gz.addUnNode(.elem_type, ptr_ty, node); |
| 300 | }, | 314 | }, |
| 315 | .destructure => |destructure| { | ||
| 316 | return astgen.failNodeNotes(node, "{s} must have a known result type", .{builtin_name}, &.{ | ||
| 317 | try astgen.errNoteNode(destructure.src_node, "destructure expressions do not provide a single result type", .{}), | ||
| 318 | try astgen.errNoteNode(node, "use @as to provide explicit result type", .{}), | ||
| 319 | }); | ||
| 320 | }, | ||
| 301 | } | 321 | } |
| 302 | 322 | ||
| 303 | return astgen.failNodeNotes(node, "{s} must have a known result type", .{builtin_name}, &.{ | 323 | return astgen.failNodeNotes(node, "{s} must have a known result type", .{builtin_name}, &.{ |
| ... | @@ -399,6 +419,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins | ... | @@ -399,6 +419,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins |
| 399 | .asm_input => unreachable, | 419 | .asm_input => unreachable, |
| 400 | 420 | ||
| 401 | .assign, | 421 | .assign, |
| 422 | .assign_destructure, | ||
| 402 | .assign_bit_and, | 423 | .assign_bit_and, |
| 403 | .assign_bit_or, | 424 | .assign_bit_or, |
| 404 | .assign_shl, | 425 | .assign_shl, |
| ... | @@ -621,6 +642,13 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE | ... | @@ -621,6 +642,13 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 621 | return rvalue(gz, ri, .void_value, node); | 642 | return rvalue(gz, ri, .void_value, node); |
| 622 | }, | 643 | }, |
| 623 | 644 | ||
| 645 | .assign_destructure => { | ||
| 646 | // Note that this variant does not declare any new var/const: that | ||
| 647 | // variant is handled by `blockExprStmts`. | ||
| 648 | try assignDestructure(gz, scope, node); | ||
| 649 | return rvalue(gz, ri, .void_value, node); | ||
| 650 | }, | ||
| 651 | |||
| 624 | .assign_shl => { | 652 | .assign_shl => { |
| 625 | try assignShift(gz, scope, node, .shl); | 653 | try assignShift(gz, scope, node, .shl); |
| 626 | return rvalue(gz, ri, .void_value, node); | 654 | return rvalue(gz, ri, .void_value, node); |
| ... | @@ -1478,6 +1506,33 @@ fn arrayInitExpr( | ... | @@ -1478,6 +1506,33 @@ fn arrayInitExpr( |
| 1478 | return arrayInitExprRlPtr(gz, scope, node, ptr_inst, array_init.ast.elements, types.array); | 1506 | return arrayInitExprRlPtr(gz, scope, node, ptr_inst, array_init.ast.elements, types.array); |
| 1479 | } | 1507 | } |
| 1480 | }, | 1508 | }, |
| 1509 | .destructure => |destructure| { | ||
| 1510 | if (types.array != .none) { | ||
| 1511 | // We have a specific type, so there may be things like default | ||
| 1512 | // field values messing with us. Do this as a standard typed | ||
| 1513 | // init followed by an rvalue destructure. | ||
| 1514 | const result = try arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, .array_init); | ||
| 1515 | return rvalue(gz, ri, result, node); | ||
| 1516 | } | ||
| 1517 | // Untyped init - destructure directly into result pointers | ||
| 1518 | if (array_init.ast.elements.len != destructure.components.len) { | ||
| 1519 | return astgen.failNodeNotes(node, "expected {} elements for destructure, found {}", .{ | ||
| 1520 | destructure.components.len, | ||
| 1521 | array_init.ast.elements.len, | ||
| 1522 | }, &.{ | ||
| 1523 | try astgen.errNoteNode(destructure.src_node, "result destructured here", .{}), | ||
| 1524 | }); | ||
| 1525 | } | ||
| 1526 | for (array_init.ast.elements, destructure.components) |elem_init, ds_comp| { | ||
| 1527 | const elem_ri: ResultInfo = .{ .rl = switch (ds_comp) { | ||
| 1528 | .typed_ptr => |ptr_rl| .{ .ptr = ptr_rl }, | ||
| 1529 | .inferred_ptr => |ptr_inst| .{ .inferred_ptr = ptr_inst }, | ||
| 1530 | .discard => .discard, | ||
| 1531 | } }; | ||
| 1532 | _ = try expr(gz, scope, elem_ri, elem_init); | ||
| 1533 | } | ||
| 1534 | return .void_value; | ||
| 1535 | }, | ||
| 1481 | } | 1536 | } |
| 1482 | } | 1537 | } |
| 1483 | 1538 | ||
| ... | @@ -1707,6 +1762,23 @@ fn structInitExpr( | ... | @@ -1707,6 +1762,23 @@ fn structInitExpr( |
| 1707 | return structInitExprRlPtr(gz, scope, node, struct_init, ptr_inst); | 1762 | return structInitExprRlPtr(gz, scope, node, struct_init, ptr_inst); |
| 1708 | } | 1763 | } |
| 1709 | }, | 1764 | }, |
| 1765 | .destructure => |destructure| { | ||
| 1766 | if (struct_init.ast.type_expr == 0) { | ||
| 1767 | // This is an untyped init, so is an actual struct, which does | ||
| 1768 | // not support destructuring. | ||
| 1769 | return astgen.failNodeNotes(node, "struct value cannot be destructured", .{}, &.{ | ||
| 1770 | try astgen.errNoteNode(destructure.src_node, "result destructured here", .{}), | ||
| 1771 | }); | ||
| 1772 | } | ||
| 1773 | // You can init tuples using struct init syntax and numeric field | ||
| 1774 | // names, but as with array inits, we could be bitten by default | ||
| 1775 | // fields. Therefore, we do a normal typed init then an rvalue | ||
| 1776 | // destructure. | ||
| 1777 | const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); | ||
| 1778 | _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); | ||
| 1779 | const result = try structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); | ||
| 1780 | return rvalue(gz, ri, result, node); | ||
| 1781 | }, | ||
| 1710 | } | 1782 | } |
| 1711 | } | 1783 | } |
| 1712 | 1784 | ||
| ... | @@ -1968,6 +2040,7 @@ fn restoreErrRetIndex( | ... | @@ -1968,6 +2040,7 @@ fn restoreErrRetIndex( |
| 1968 | // TODO: Update this to do a proper load from the rl_ptr, once Sema can support it. | 2040 | // TODO: Update this to do a proper load from the rl_ptr, once Sema can support it. |
| 1969 | break :blk .none; | 2041 | break :blk .none; |
| 1970 | }, | 2042 | }, |
| 2043 | .destructure => return, // value must be a tuple or array, so never restore/pop | ||
| 1971 | else => result, | 2044 | else => result, |
| 1972 | }, | 2045 | }, |
| 1973 | else => .none, // always restore/pop | 2046 | else => .none, // always restore/pop |
| ... | @@ -2340,6 +2413,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod | ... | @@ -2340,6 +2413,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod |
| 2340 | .simple_var_decl, | 2413 | .simple_var_decl, |
| 2341 | .aligned_var_decl, => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.fullVarDecl(statement).?), | 2414 | .aligned_var_decl, => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.fullVarDecl(statement).?), |
| 2342 | 2415 | ||
| 2416 | .assign_destructure => scope = try assignDestructureMaybeDecls(gz, scope, statement, block_arena_allocator), | ||
| 2417 | |||
| 2343 | .@"defer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_normal), | 2418 | .@"defer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_normal), |
| 2344 | .@"errdefer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_error), | 2419 | .@"errdefer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_error), |
| 2345 | 2420 | ||
| ... | @@ -2481,6 +2556,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As | ... | @@ -2481,6 +2556,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2481 | .elem_ptr_node, | 2556 | .elem_ptr_node, |
| 2482 | .elem_ptr_imm, | 2557 | .elem_ptr_imm, |
| 2483 | .elem_val_node, | 2558 | .elem_val_node, |
| 2559 | .elem_val_imm, | ||
| 2484 | .field_ptr, | 2560 | .field_ptr, |
| 2485 | .field_ptr_init, | 2561 | .field_ptr_init, |
| 2486 | .field_val, | 2562 | .field_val, |
| ... | @@ -2686,6 +2762,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As | ... | @@ -2686,6 +2762,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2686 | .validate_array_init_ty, | 2762 | .validate_array_init_ty, |
| 2687 | .validate_struct_init_ty, | 2763 | .validate_struct_init_ty, |
| 2688 | .validate_deref, | 2764 | .validate_deref, |
| 2765 | .validate_destructure, | ||
| 2689 | .save_err_ret_index, | 2766 | .save_err_ret_index, |
| 2690 | .restore_err_ret_index, | 2767 | .restore_err_ret_index, |
| 2691 | => break :b true, | 2768 | => break :b true, |
| ... | @@ -3227,6 +3304,301 @@ fn assign(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!voi | ... | @@ -3227,6 +3304,301 @@ fn assign(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!voi |
| 3227 | } } }, rhs); | 3304 | } } }, rhs); |
| 3228 | } | 3305 | } |
| 3229 | 3306 | ||
| 3307 | /// Handles destructure assignments where no LHS is a `const` or `var` decl. | ||
| 3308 | fn assignDestructure(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!void { | ||
| 3309 | try emitDbgNode(gz, node); | ||
| 3310 | const astgen = gz.astgen; | ||
| 3311 | const tree = astgen.tree; | ||
| 3312 | const token_tags = tree.tokens.items(.tag); | ||
| 3313 | const node_datas = tree.nodes.items(.data); | ||
| 3314 | const main_tokens = tree.nodes.items(.main_token); | ||
| 3315 | const node_tags = tree.nodes.items(.tag); | ||
| 3316 | |||
| 3317 | const extra_index = node_datas[node].lhs; | ||
| 3318 | const lhs_count = tree.extra_data[extra_index]; | ||
| 3319 | const lhs_nodes: []const Ast.Node.Index = @ptrCast(tree.extra_data[extra_index + 1 ..][0..lhs_count]); | ||
| 3320 | const rhs = node_datas[node].rhs; | ||
| 3321 | |||
| 3322 | const maybe_comptime_token = tree.firstToken(node) - 1; | ||
| 3323 | const declared_comptime = token_tags[maybe_comptime_token] == .keyword_comptime; | ||
| 3324 | |||
| 3325 | if (declared_comptime and gz.is_comptime) { | ||
| 3326 | return astgen.failNode(node, "redundant comptime keyword in already comptime scope", .{}); | ||
| 3327 | } | ||
| 3328 | |||
| 3329 | // If this expression is marked comptime, we must wrap the whole thing in a comptime block. | ||
| 3330 | var gz_buf: GenZir = undefined; | ||
| 3331 | const inner_gz = if (declared_comptime) bs: { | ||
| 3332 | gz_buf = gz.makeSubBlock(scope); | ||
| 3333 | gz_buf.is_comptime = true; | ||
| 3334 | break :bs &gz_buf; | ||
| 3335 | } else gz; | ||
| 3336 | defer if (declared_comptime) inner_gz.unstack(); | ||
| 3337 | |||
| 3338 | const rl_components = try astgen.arena.alloc(ResultInfo.Loc.DestructureComponent, lhs_nodes.len); | ||
| 3339 | for (rl_components, lhs_nodes) |*lhs_rl, lhs_node| { | ||
| 3340 | if (node_tags[lhs_node] == .identifier) { | ||
| 3341 | // This intentionally does not support `@"_"` syntax. | ||
| 3342 | const ident_name = tree.tokenSlice(main_tokens[lhs_node]); | ||
| 3343 | if (mem.eql(u8, ident_name, "_")) { | ||
| 3344 | lhs_rl.* = .discard; | ||
| 3345 | continue; | ||
| 3346 | } | ||
| 3347 | } | ||
| 3348 | lhs_rl.* = .{ .typed_ptr = .{ | ||
| 3349 | .inst = try lvalExpr(inner_gz, scope, lhs_node), | ||
| 3350 | .src_node = lhs_node, | ||
| 3351 | } }; | ||
| 3352 | } | ||
| 3353 | |||
| 3354 | const ri: ResultInfo = .{ .rl = .{ .destructure = .{ | ||
| 3355 | .src_node = node, | ||
| 3356 | .components = rl_components, | ||
| 3357 | } } }; | ||
| 3358 | |||
| 3359 | _ = try expr(inner_gz, scope, ri, rhs); | ||
| 3360 | |||
| 3361 | if (declared_comptime) { | ||
| 3362 | const comptime_block_inst = try gz.makeBlockInst(.block_comptime, node); | ||
| 3363 | _ = try inner_gz.addBreak(.@"break", comptime_block_inst, .void_value); | ||
| 3364 | try inner_gz.setBlockBody(comptime_block_inst); | ||
| 3365 | try gz.instructions.append(gz.astgen.gpa, comptime_block_inst); | ||
| 3366 | } | ||
| 3367 | } | ||
| 3368 | |||
| 3369 | /// Handles destructure assignments where the LHS may contain `const` or `var` decls. | ||
| 3370 | fn assignDestructureMaybeDecls( | ||
| 3371 | gz: *GenZir, | ||
| 3372 | scope: *Scope, | ||
| 3373 | node: Ast.Node.Index, | ||
| 3374 | block_arena: Allocator, | ||
| 3375 | ) InnerError!*Scope { | ||
| 3376 | try emitDbgNode(gz, node); | ||
| 3377 | const astgen = gz.astgen; | ||
| 3378 | const tree = astgen.tree; | ||
| 3379 | const token_tags = tree.tokens.items(.tag); | ||
| 3380 | const node_datas = tree.nodes.items(.data); | ||
| 3381 | const main_tokens = tree.nodes.items(.main_token); | ||
| 3382 | const node_tags = tree.nodes.items(.tag); | ||
| 3383 | |||
| 3384 | const extra_index = node_datas[node].lhs; | ||
| 3385 | const lhs_count = tree.extra_data[extra_index]; | ||
| 3386 | const lhs_nodes: []const Ast.Node.Index = @ptrCast(tree.extra_data[extra_index + 1 ..][0..lhs_count]); | ||
| 3387 | const rhs = node_datas[node].rhs; | ||
| 3388 | |||
| 3389 | const maybe_comptime_token = tree.firstToken(node) - 1; | ||
| 3390 | const declared_comptime = token_tags[maybe_comptime_token] == .keyword_comptime; | ||
| 3391 | if (declared_comptime and gz.is_comptime) { | ||
| 3392 | return astgen.failNode(node, "redundant comptime keyword in already comptime scope", .{}); | ||
| 3393 | } | ||
| 3394 | |||
| 3395 | const is_comptime = declared_comptime or gz.is_comptime; | ||
| 3396 | const rhs_is_comptime = tree.nodes.items(.tag)[rhs] == .@"comptime"; | ||
| 3397 | |||
| 3398 | // When declaring consts via a destructure, we always use a result pointer. | ||
| 3399 | // This avoids the need to create tuple types, and is also likely easier to | ||
| 3400 | // optimize, since it's a bit tricky for the optimizer to "split up" the | ||
| 3401 | // value into individual pointer writes down the line. | ||
| 3402 | |||
| 3403 | // We know this rl information won't live past the evaluation of this | ||
| 3404 | // expression, so it may as well go in the block arena. | ||
| 3405 | const rl_components = try block_arena.alloc(ResultInfo.Loc.DestructureComponent, lhs_nodes.len); | ||
| 3406 | var any_non_const_lhs = false; | ||
| 3407 | var any_lvalue_expr = false; | ||
| 3408 | for (rl_components, lhs_nodes) |*lhs_rl, lhs_node| { | ||
| 3409 | switch (node_tags[lhs_node]) { | ||
| 3410 | .identifier => { | ||
| 3411 | // This intentionally does not support `@"_"` syntax. | ||
| 3412 | const ident_name = tree.tokenSlice(main_tokens[lhs_node]); | ||
| 3413 | if (mem.eql(u8, ident_name, "_")) { | ||
| 3414 | any_non_const_lhs = true; | ||
| 3415 | lhs_rl.* = .discard; | ||
| 3416 | continue; | ||
| 3417 | } | ||
| 3418 | }, | ||
| 3419 | .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => { | ||
| 3420 | const full = tree.fullVarDecl(lhs_node).?; | ||
| 3421 | |||
| 3422 | const name_token = full.ast.mut_token + 1; | ||
| 3423 | const ident_name_raw = tree.tokenSlice(name_token); | ||
| 3424 | if (mem.eql(u8, ident_name_raw, "_")) { | ||
| 3425 | return astgen.failTok(name_token, "'_' used as an identifier without @\"_\" syntax", .{}); | ||
| 3426 | } | ||
| 3427 | |||
| 3428 | // We detect shadowing in the second pass over these, while we're creating scopes. | ||
| 3429 | |||
| 3430 | if (full.ast.addrspace_node != 0) { | ||
| 3431 | return astgen.failTok(main_tokens[full.ast.addrspace_node], "cannot set address space of local variable '{s}'", .{ident_name_raw}); | ||
| 3432 | } | ||
| 3433 | if (full.ast.section_node != 0) { | ||
| 3434 | return astgen.failTok(main_tokens[full.ast.section_node], "cannot set section of local variable '{s}'", .{ident_name_raw}); | ||
| 3435 | } | ||
| 3436 | |||
| 3437 | const is_const = switch (token_tags[full.ast.mut_token]) { | ||
| 3438 | .keyword_var => false, | ||
| 3439 | .keyword_const => true, | ||
| 3440 | else => unreachable, | ||
| 3441 | }; | ||
| 3442 | if (!is_const) any_non_const_lhs = true; | ||
| 3443 | |||
| 3444 | // We also mark `const`s as comptime if the RHS is definitely comptime-known. | ||
| 3445 | const this_lhs_comptime = is_comptime or (is_const and rhs_is_comptime); | ||
| 3446 | |||
| 3447 | const align_inst: Zir.Inst.Ref = if (full.ast.align_node != 0) | ||
| 3448 | try expr(gz, scope, align_ri, full.ast.align_node) | ||
| 3449 | else | ||
| 3450 | .none; | ||
| 3451 | |||
| 3452 | if (full.ast.type_node != 0) { | ||
| 3453 | // Typed alloc | ||
| 3454 | const type_inst = try typeExpr(gz, scope, full.ast.type_node); | ||
| 3455 | const ptr = if (align_inst == .none) ptr: { | ||
| 3456 | const tag: Zir.Inst.Tag = if (is_const) | ||
| 3457 | .alloc | ||
| 3458 | else if (this_lhs_comptime) | ||
| 3459 | .alloc_comptime_mut | ||
| 3460 | else | ||
| 3461 | .alloc_mut; | ||
| 3462 | break :ptr try gz.addUnNode(tag, type_inst, node); | ||
| 3463 | } else try gz.addAllocExtended(.{ | ||
| 3464 | .node = node, | ||
| 3465 | .type_inst = type_inst, | ||
| 3466 | .align_inst = align_inst, | ||
| 3467 | .is_const = is_const, | ||
| 3468 | .is_comptime = this_lhs_comptime, | ||
| 3469 | }); | ||
| 3470 | lhs_rl.* = .{ .typed_ptr = .{ .inst = ptr } }; | ||
| 3471 | } else { | ||
| 3472 | // Inferred alloc | ||
| 3473 | const ptr = if (align_inst == .none) ptr: { | ||
| 3474 | const tag: Zir.Inst.Tag = if (is_const) tag: { | ||
| 3475 | break :tag if (this_lhs_comptime) .alloc_inferred_comptime else .alloc_inferred; | ||
| 3476 | } else tag: { | ||
| 3477 | break :tag if (this_lhs_comptime) .alloc_inferred_comptime_mut else .alloc_inferred_mut; | ||
| 3478 | }; | ||
| 3479 | break :ptr try gz.addNode(tag, node); | ||
| 3480 | } else try gz.addAllocExtended(.{ | ||
| 3481 | .node = node, | ||
| 3482 | .type_inst = .none, | ||
| 3483 | .align_inst = align_inst, | ||
| 3484 | .is_const = is_const, | ||
| 3485 | .is_comptime = this_lhs_comptime, | ||
| 3486 | }); | ||
| 3487 | lhs_rl.* = .{ .inferred_ptr = ptr }; | ||
| 3488 | } | ||
| 3489 | |||
| 3490 | continue; | ||
| 3491 | }, | ||
| 3492 | else => {}, | ||
| 3493 | } | ||
| 3494 | // This LHS is just an lvalue expression. | ||
| 3495 | // We will fill in its result pointer later, inside a comptime block. | ||
| 3496 | any_non_const_lhs = true; | ||
| 3497 | any_lvalue_expr = true; | ||
| 3498 | lhs_rl.* = .{ .typed_ptr = .{ | ||
| 3499 | .inst = undefined, | ||
| 3500 | .src_node = lhs_node, | ||
| 3501 | } }; | ||
| 3502 | } | ||
| 3503 | |||
| 3504 | if (declared_comptime and !any_non_const_lhs) { | ||
| 3505 | try astgen.appendErrorTok(maybe_comptime_token, "'comptime const' is redundant; instead wrap the initialization expression with 'comptime'", .{}); | ||
| 3506 | } | ||
| 3507 | |||
| 3508 | // If this expression is marked comptime, we must wrap it in a comptime block. | ||
| 3509 | var gz_buf: GenZir = undefined; | ||
| 3510 | const inner_gz = if (declared_comptime) bs: { | ||
| 3511 | gz_buf = gz.makeSubBlock(scope); | ||
| 3512 | gz_buf.is_comptime = true; | ||
| 3513 | break :bs &gz_buf; | ||
| 3514 | } else gz; | ||
| 3515 | defer if (declared_comptime) inner_gz.unstack(); | ||
| 3516 | |||
| 3517 | if (any_lvalue_expr) { | ||
| 3518 | // At least one LHS was an lvalue expr. Iterate again in order to | ||
| 3519 | // evaluate the lvalues from within the possible block_comptime. | ||
| 3520 | for (rl_components, lhs_nodes) |*lhs_rl, lhs_node| { | ||
| 3521 | if (lhs_rl.* != .typed_ptr) continue; | ||
| 3522 | switch (node_tags[lhs_node]) { | ||
| 3523 | .global_var_decl, .local_var_decl, .simple_var_decl, .aligned_var_decl => continue, | ||
| 3524 | else => {}, | ||
| 3525 | } | ||
| 3526 | lhs_rl.typed_ptr.inst = try lvalExpr(inner_gz, scope, lhs_node); | ||
| 3527 | } | ||
| 3528 | } | ||
| 3529 | |||
| 3530 | // We can't give a reasonable anon name strategy for destructured inits, so | ||
| 3531 | // leave it at its default of `.anon`. | ||
| 3532 | _ = try reachableExpr(inner_gz, scope, .{ .rl = .{ .destructure = .{ | ||
| 3533 | .src_node = node, | ||
| 3534 | .components = rl_components, | ||
| 3535 | } } }, rhs, node); | ||
| 3536 | |||
| 3537 | if (declared_comptime) { | ||
| 3538 | // Finish the block_comptime. Inferred alloc resolution etc will occur | ||
| 3539 | // in the parent block. | ||
| 3540 | const comptime_block_inst = try gz.makeBlockInst(.block_comptime, node); | ||
| 3541 | _ = try inner_gz.addBreak(.@"break", comptime_block_inst, .void_value); | ||
| 3542 | try inner_gz.setBlockBody(comptime_block_inst); | ||
| 3543 | try gz.instructions.append(gz.astgen.gpa, comptime_block_inst); | ||
| 3544 | } | ||
| 3545 | |||
| 3546 | // Now, iterate over the LHS exprs to construct any new scopes. | ||
| 3547 | // If there were any inferred allocations, resolve them. | ||
| 3548 | // If there were any `const` decls, make the pointer constant. | ||
| 3549 | var cur_scope = scope; | ||
| 3550 | for (rl_components, lhs_nodes) |lhs_rl, lhs_node| { | ||
| 3551 | switch (node_tags[lhs_node]) { | ||
| 3552 | .local_var_decl, .simple_var_decl, .aligned_var_decl => {}, | ||
| 3553 | else => continue, // We were mutating an existing lvalue - nothing to do | ||
| 3554 | } | ||
| 3555 | const full = tree.fullVarDecl(lhs_node).?; | ||
| 3556 | const raw_ptr = switch (lhs_rl) { | ||
| 3557 | .discard => unreachable, | ||
| 3558 | .typed_ptr => |typed_ptr| typed_ptr.inst, | ||
| 3559 | .inferred_ptr => |ptr_inst| ptr_inst, | ||
| 3560 | }; | ||
| 3561 | // If the alloc was inferred, resolve it. | ||
| 3562 | if (full.ast.type_node == 0) { | ||
| 3563 | _ = try gz.addUnNode(.resolve_inferred_alloc, raw_ptr, lhs_node); | ||
| 3564 | } | ||
| 3565 | const is_const = switch (token_tags[full.ast.mut_token]) { | ||
| 3566 | .keyword_var => false, | ||
| 3567 | .keyword_const => true, | ||
| 3568 | else => unreachable, | ||
| 3569 | }; | ||
| 3570 | // If the alloc was const, make it const. | ||
| 3571 | const var_ptr = if (is_const) make_const: { | ||
| 3572 | break :make_const try gz.addUnNode(.make_ptr_const, raw_ptr, node); | ||
| 3573 | } else raw_ptr; | ||
| 3574 | const name_token = full.ast.mut_token + 1; | ||
| 3575 | const ident_name_raw = tree.tokenSlice(name_token); | ||
| 3576 | const ident_name = try astgen.identAsString(name_token); | ||
| 3577 | try astgen.detectLocalShadowing( | ||
| 3578 | cur_scope, | ||
| 3579 | ident_name, | ||
| 3580 | name_token, | ||
| 3581 | ident_name_raw, | ||
| 3582 | if (is_const) .@"local constant" else .@"local variable", | ||
| 3583 | ); | ||
| 3584 | try gz.addDbgVar(.dbg_var_ptr, ident_name, var_ptr); | ||
| 3585 | // Finally, create the scope. | ||
| 3586 | const sub_scope = try block_arena.create(Scope.LocalPtr); | ||
| 3587 | sub_scope.* = .{ | ||
| 3588 | .parent = cur_scope, | ||
| 3589 | .gen_zir = gz, | ||
| 3590 | .name = ident_name, | ||
| 3591 | .ptr = var_ptr, | ||
| 3592 | .token_src = name_token, | ||
| 3593 | .maybe_comptime = is_const or is_comptime, | ||
| 3594 | .id_cat = if (is_const) .@"local constant" else .@"local variable", | ||
| 3595 | }; | ||
| 3596 | cur_scope = &sub_scope.base; | ||
| 3597 | } | ||
| 3598 | |||
| 3599 | return cur_scope; | ||
| 3600 | } | ||
| 3601 | |||
| 3230 | fn assignOp( | 3602 | fn assignOp( |
| 3231 | gz: *GenZir, | 3603 | gz: *GenZir, |
| 3232 | scope: *Scope, | 3604 | scope: *Scope, |
| ... | @@ -9059,6 +9431,7 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev | ... | @@ -9059,6 +9431,7 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev |
| 9059 | .array_cat, | 9431 | .array_cat, |
| 9060 | .array_mult, | 9432 | .array_mult, |
| 9061 | .assign, | 9433 | .assign, |
| 9434 | .assign_destructure, | ||
| 9062 | .assign_bit_and, | 9435 | .assign_bit_and, |
| 9063 | .assign_bit_or, | 9436 | .assign_bit_or, |
| 9064 | .assign_shl, | 9437 | .assign_shl, |
| ... | @@ -9237,6 +9610,7 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In | ... | @@ -9237,6 +9610,7 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In |
| 9237 | .array_cat, | 9610 | .array_cat, |
| 9238 | .array_mult, | 9611 | .array_mult, |
| 9239 | .assign, | 9612 | .assign, |
| 9613 | .assign_destructure, | ||
| 9240 | .assign_bit_and, | 9614 | .assign_bit_and, |
| 9241 | .assign_bit_or, | 9615 | .assign_bit_or, |
| 9242 | .assign_shl, | 9616 | .assign_shl, |
| ... | @@ -9483,6 +9857,7 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool { | ... | @@ -9483,6 +9857,7 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool { |
| 9483 | .array_cat, | 9857 | .array_cat, |
| 9484 | .array_mult, | 9858 | .array_mult, |
| 9485 | .assign, | 9859 | .assign, |
| 9860 | .assign_destructure, | ||
| 9486 | .assign_bit_and, | 9861 | .assign_bit_and, |
| 9487 | .assign_bit_or, | 9862 | .assign_bit_or, |
| 9488 | .assign_shl, | 9863 | .assign_shl, |
| ... | @@ -9830,6 +10205,37 @@ fn rvalue( | ... | @@ -9830,6 +10205,37 @@ fn rvalue( |
| 9830 | _ = try gz.addBin(.store_to_inferred_ptr, alloc, result); | 10205 | _ = try gz.addBin(.store_to_inferred_ptr, alloc, result); |
| 9831 | return .void_value; | 10206 | return .void_value; |
| 9832 | }, | 10207 | }, |
| 10208 | .destructure => |destructure| { | ||
| 10209 | const components = destructure.components; | ||
| 10210 | _ = try gz.addPlNode(.validate_destructure, src_node, Zir.Inst.ValidateDestructure{ | ||
| 10211 | .operand = result, | ||
| 10212 | .destructure_node = gz.nodeIndexToRelative(destructure.src_node), | ||
| 10213 | .expect_len = @intCast(components.len), | ||
| 10214 | }); | ||
| 10215 | for (components, 0..) |component, i| { | ||
| 10216 | if (component == .discard) continue; | ||
| 10217 | const elem_val = try gz.add(.{ | ||
| 10218 | .tag = .elem_val_imm, | ||
| 10219 | .data = .{ .elem_val_imm = .{ | ||
| 10220 | .operand = result, | ||
| 10221 | .idx = @intCast(i), | ||
| 10222 | } }, | ||
| 10223 | }); | ||
| 10224 | switch (component) { | ||
| 10225 | .typed_ptr => |ptr_res| { | ||
| 10226 | _ = try gz.addPlNode(.store_node, ptr_res.src_node orelse src_node, Zir.Inst.Bin{ | ||
| 10227 | .lhs = ptr_res.inst, | ||
| 10228 | .rhs = elem_val, | ||
| 10229 | }); | ||
| 10230 | }, | ||
| 10231 | .inferred_ptr => |ptr_inst| { | ||
| 10232 | _ = try gz.addBin(.store_to_inferred_ptr, ptr_inst, elem_val); | ||
| 10233 | }, | ||
| 10234 | .discard => unreachable, | ||
| 10235 | } | ||
| 10236 | } | ||
| 10237 | return .void_value; | ||
| 10238 | }, | ||
| 9833 | } | 10239 | } |
| 9834 | } | 10240 | } |
| 9835 | 10241 |
src/AstRlAnnotate.zig+10| ... | @@ -203,6 +203,16 @@ fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultI | ... | @@ -203,6 +203,16 @@ fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultI |
| 203 | else => unreachable, | 203 | else => unreachable, |
| 204 | } | 204 | } |
| 205 | }, | 205 | }, |
| 206 | .assign_destructure => { | ||
| 207 | const lhs_count = tree.extra_data[node_datas[node].lhs]; | ||
| 208 | const all_lhs = tree.extra_data[node_datas[node].lhs + 1 ..][0..lhs_count]; | ||
| 209 | for (all_lhs) |lhs| { | ||
| 210 | _ = try astrl.expr(lhs, block, ResultInfo.none); | ||
| 211 | } | ||
| 212 | // We don't need to gather any meaningful data here, because destructures always use RLS | ||
| 213 | _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none); | ||
| 214 | return false; | ||
| 215 | }, | ||
| 206 | .assign => { | 216 | .assign => { |
| 207 | _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none); | 217 | _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none); |
| 208 | _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.typed_ptr); | 218 | _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.typed_ptr); |
src/Sema.zig+54| ... | @@ -1018,6 +1018,7 @@ fn analyzeBodyInner( | ... | @@ -1018,6 +1018,7 @@ fn analyzeBodyInner( |
| 1018 | .elem_ptr_imm => try sema.zirElemPtrImm(block, inst), | 1018 | .elem_ptr_imm => try sema.zirElemPtrImm(block, inst), |
| 1019 | .elem_val => try sema.zirElemVal(block, inst), | 1019 | .elem_val => try sema.zirElemVal(block, inst), |
| 1020 | .elem_val_node => try sema.zirElemValNode(block, inst), | 1020 | .elem_val_node => try sema.zirElemValNode(block, inst), |
| 1021 | .elem_val_imm => try sema.zirElemValImm(block, inst), | ||
| 1021 | .elem_type_index => try sema.zirElemTypeIndex(block, inst), | 1022 | .elem_type_index => try sema.zirElemTypeIndex(block, inst), |
| 1022 | .elem_type => try sema.zirElemType(block, inst), | 1023 | .elem_type => try sema.zirElemType(block, inst), |
| 1023 | .indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst), | 1024 | .indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst), |
| ... | @@ -1379,6 +1380,11 @@ fn analyzeBodyInner( | ... | @@ -1379,6 +1380,11 @@ fn analyzeBodyInner( |
| 1379 | i += 1; | 1380 | i += 1; |
| 1380 | continue; | 1381 | continue; |
| 1381 | }, | 1382 | }, |
| 1383 | .validate_destructure => { | ||
| 1384 | try sema.zirValidateDestructure(block, inst); | ||
| 1385 | i += 1; | ||
| 1386 | continue; | ||
| 1387 | }, | ||
| 1382 | .@"export" => { | 1388 | .@"export" => { |
| 1383 | try sema.zirExport(block, inst); | 1389 | try sema.zirExport(block, inst); |
| 1384 | i += 1; | 1390 | i += 1; |
| ... | @@ -5178,6 +5184,43 @@ fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr | ... | @@ -5178,6 +5184,43 @@ fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr |
| 5178 | } | 5184 | } |
| 5179 | } | 5185 | } |
| 5180 | 5186 | ||
| 5187 | fn zirValidateDestructure(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { | ||
| 5188 | const mod = sema.mod; | ||
| 5189 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; | ||
| 5190 | const extra = sema.code.extraData(Zir.Inst.ValidateDestructure, inst_data.payload_index).data; | ||
| 5191 | const src = inst_data.src(); | ||
| 5192 | const destructure_src = LazySrcLoc.nodeOffset(extra.destructure_node); | ||
| 5193 | const operand = try sema.resolveInst(extra.operand); | ||
| 5194 | const operand_ty = sema.typeOf(operand); | ||
| 5195 | |||
| 5196 | const can_destructure = switch (operand_ty.zigTypeTag(mod)) { | ||
| 5197 | .Array => true, | ||
| 5198 | .Struct => operand_ty.isTuple(mod), | ||
| 5199 | else => false, | ||
| 5200 | }; | ||
| 5201 | |||
| 5202 | if (!can_destructure) { | ||
| 5203 | return sema.failWithOwnedErrorMsg(block, msg: { | ||
| 5204 | const msg = try sema.errMsg(block, src, "type '{}' cannot be destructured", .{operand_ty.fmt(mod)}); | ||
| 5205 | errdefer msg.destroy(sema.gpa); | ||
| 5206 | try sema.errNote(block, destructure_src, msg, "result destructured here", .{}); | ||
| 5207 | break :msg msg; | ||
| 5208 | }); | ||
| 5209 | } | ||
| 5210 | |||
| 5211 | if (operand_ty.arrayLen(mod) != extra.expect_len) { | ||
| 5212 | return sema.failWithOwnedErrorMsg(block, msg: { | ||
| 5213 | const msg = try sema.errMsg(block, src, "expected {} elements for destructure, found {}", .{ | ||
| 5214 | extra.expect_len, | ||
| 5215 | operand_ty.arrayLen(mod), | ||
| 5216 | }); | ||
| 5217 | errdefer msg.destroy(sema.gpa); | ||
| 5218 | try sema.errNote(block, destructure_src, msg, "result destructured here", .{}); | ||
| 5219 | break :msg msg; | ||
| 5220 | }); | ||
| 5221 | } | ||
| 5222 | } | ||
| 5223 | |||
| 5181 | fn failWithBadMemberAccess( | 5224 | fn failWithBadMemberAccess( |
| 5182 | sema: *Sema, | 5225 | sema: *Sema, |
| 5183 | block: *Block, | 5226 | block: *Block, |
| ... | @@ -10304,6 +10347,17 @@ fn zirElemValNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError | ... | @@ -10304,6 +10347,17 @@ fn zirElemValNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 10304 | return sema.elemVal(block, src, array, elem_index, elem_index_src, true); | 10347 | return sema.elemVal(block, src, array, elem_index, elem_index_src, true); |
| 10305 | } | 10348 | } |
| 10306 | 10349 | ||
| 10350 | fn zirElemValImm(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { | ||
| 10351 | const tracy = trace(@src()); | ||
| 10352 | defer tracy.end(); | ||
| 10353 | |||
| 10354 | const mod = sema.mod; | ||
| 10355 | const inst_data = sema.code.instructions.items(.data)[inst].elem_val_imm; | ||
| 10356 | const array = try sema.resolveInst(inst_data.operand); | ||
| 10357 | const elem_index = try mod.intRef(Type.usize, inst_data.idx); | ||
| 10358 | return sema.elemVal(block, .unneeded, array, elem_index, .unneeded, false); | ||
| 10359 | } | ||
| 10360 | |||
| 10307 | fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { | 10361 | fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 10308 | const tracy = trace(@src()); | 10362 | const tracy = trace(@src()); |
| 10309 | defer tracy.end(); | 10363 | defer tracy.end(); |
src/Zir.zig+29| ... | @@ -434,6 +434,10 @@ pub const Inst = struct { | ... | @@ -434,6 +434,10 @@ pub const Inst = struct { |
| 434 | /// Payload is `Bin`. | 434 | /// Payload is `Bin`. |
| 435 | /// No OOB safety check is emitted. | 435 | /// No OOB safety check is emitted. |
| 436 | elem_val, | 436 | elem_val, |
| 437 | /// Same as `elem_val` but takes the index as an immediate value. | ||
| 438 | /// No OOB safety check is emitted. A prior instruction must validate this operation. | ||
| 439 | /// Uses the `elem_val_imm` union field. | ||
| 440 | elem_val_imm, | ||
| 437 | /// Emits a compile error if the operand is not `void`. | 441 | /// Emits a compile error if the operand is not `void`. |
| 438 | /// Uses the `un_node` field. | 442 | /// Uses the `un_node` field. |
| 439 | ensure_result_used, | 443 | ensure_result_used, |
| ... | @@ -725,6 +729,9 @@ pub const Inst = struct { | ... | @@ -725,6 +729,9 @@ pub const Inst = struct { |
| 725 | /// Check that operand type supports the dereference operand (.*). | 729 | /// Check that operand type supports the dereference operand (.*). |
| 726 | /// Uses the `un_node` field. | 730 | /// Uses the `un_node` field. |
| 727 | validate_deref, | 731 | validate_deref, |
| 732 | /// Check that the operand's type is an array or tuple with the given number of elements. | ||
| 733 | /// Uses the `pl_node` field. Payload is `ValidateDestructure`. | ||
| 734 | validate_destructure, | ||
| 728 | /// A struct literal with a specified type, with no fields. | 735 | /// A struct literal with a specified type, with no fields. |
| 729 | /// Uses the `un_node` field. | 736 | /// Uses the `un_node` field. |
| 730 | struct_init_empty, | 737 | struct_init_empty, |
| ... | @@ -1069,6 +1076,7 @@ pub const Inst = struct { | ... | @@ -1069,6 +1076,7 @@ pub const Inst = struct { |
| 1069 | .elem_ptr_node, | 1076 | .elem_ptr_node, |
| 1070 | .elem_ptr_imm, | 1077 | .elem_ptr_imm, |
| 1071 | .elem_val_node, | 1078 | .elem_val_node, |
| 1079 | .elem_val_imm, | ||
| 1072 | .ensure_result_used, | 1080 | .ensure_result_used, |
| 1073 | .ensure_result_non_error, | 1081 | .ensure_result_non_error, |
| 1074 | .ensure_err_union_payload_void, | 1082 | .ensure_err_union_payload_void, |
| ... | @@ -1145,6 +1153,7 @@ pub const Inst = struct { | ... | @@ -1145,6 +1153,7 @@ pub const Inst = struct { |
| 1145 | .validate_struct_init, | 1153 | .validate_struct_init, |
| 1146 | .validate_array_init, | 1154 | .validate_array_init, |
| 1147 | .validate_deref, | 1155 | .validate_deref, |
| 1156 | .validate_destructure, | ||
| 1148 | .struct_init_empty, | 1157 | .struct_init_empty, |
| 1149 | .struct_init, | 1158 | .struct_init, |
| 1150 | .struct_init_ref, | 1159 | .struct_init_ref, |
| ... | @@ -1295,6 +1304,7 @@ pub const Inst = struct { | ... | @@ -1295,6 +1304,7 @@ pub const Inst = struct { |
| 1295 | .validate_struct_init, | 1304 | .validate_struct_init, |
| 1296 | .validate_array_init, | 1305 | .validate_array_init, |
| 1297 | .validate_deref, | 1306 | .validate_deref, |
| 1307 | .validate_destructure, | ||
| 1298 | .@"export", | 1308 | .@"export", |
| 1299 | .export_value, | 1309 | .export_value, |
| 1300 | .set_runtime_safety, | 1310 | .set_runtime_safety, |
| ... | @@ -1369,6 +1379,7 @@ pub const Inst = struct { | ... | @@ -1369,6 +1379,7 @@ pub const Inst = struct { |
| 1369 | .elem_ptr_node, | 1379 | .elem_ptr_node, |
| 1370 | .elem_ptr_imm, | 1380 | .elem_ptr_imm, |
| 1371 | .elem_val_node, | 1381 | .elem_val_node, |
| 1382 | .elem_val_imm, | ||
| 1372 | .field_ptr, | 1383 | .field_ptr, |
| 1373 | .field_ptr_init, | 1384 | .field_ptr_init, |
| 1374 | .field_val, | 1385 | .field_val, |
| ... | @@ -1615,6 +1626,7 @@ pub const Inst = struct { | ... | @@ -1615,6 +1626,7 @@ pub const Inst = struct { |
| 1615 | .elem_ptr_imm = .pl_node, | 1626 | .elem_ptr_imm = .pl_node, |
| 1616 | .elem_val = .pl_node, | 1627 | .elem_val = .pl_node, |
| 1617 | .elem_val_node = .pl_node, | 1628 | .elem_val_node = .pl_node, |
| 1629 | .elem_val_imm = .elem_val_imm, | ||
| 1618 | .ensure_result_used = .un_node, | 1630 | .ensure_result_used = .un_node, |
| 1619 | .ensure_result_non_error = .un_node, | 1631 | .ensure_result_non_error = .un_node, |
| 1620 | .ensure_err_union_payload_void = .un_node, | 1632 | .ensure_err_union_payload_void = .un_node, |
| ... | @@ -1689,6 +1701,7 @@ pub const Inst = struct { | ... | @@ -1689,6 +1701,7 @@ pub const Inst = struct { |
| 1689 | .validate_struct_init = .pl_node, | 1701 | .validate_struct_init = .pl_node, |
| 1690 | .validate_array_init = .pl_node, | 1702 | .validate_array_init = .pl_node, |
| 1691 | .validate_deref = .un_node, | 1703 | .validate_deref = .un_node, |
| 1704 | .validate_destructure = .pl_node, | ||
| 1692 | .struct_init_empty = .un_node, | 1705 | .struct_init_empty = .un_node, |
| 1693 | .field_type = .pl_node, | 1706 | .field_type = .pl_node, |
| 1694 | .field_type_ref = .pl_node, | 1707 | .field_type_ref = .pl_node, |
| ... | @@ -2295,6 +2308,12 @@ pub const Inst = struct { | ... | @@ -2295,6 +2308,12 @@ pub const Inst = struct { |
| 2295 | block: Ref, // If restored, the index is from this block's entrypoint | 2308 | block: Ref, // If restored, the index is from this block's entrypoint |
| 2296 | operand: Ref, // If non-error (or .none), then restore the index | 2309 | operand: Ref, // If non-error (or .none), then restore the index |
| 2297 | }, | 2310 | }, |
| 2311 | elem_val_imm: struct { | ||
| 2312 | /// The indexable value being accessed. | ||
| 2313 | operand: Ref, | ||
| 2314 | /// The index being accessed. | ||
| 2315 | idx: u32, | ||
| 2316 | }, | ||
| 2298 | 2317 | ||
| 2299 | // Make sure we don't accidentally add a field to make this union | 2318 | // Make sure we don't accidentally add a field to make this union |
| 2300 | // bigger than expected. Note that in Debug builds, Zig is allowed | 2319 | // bigger than expected. Note that in Debug builds, Zig is allowed |
| ... | @@ -2334,6 +2353,7 @@ pub const Inst = struct { | ... | @@ -2334,6 +2353,7 @@ pub const Inst = struct { |
| 2334 | defer_err_code, | 2353 | defer_err_code, |
| 2335 | save_err_ret_index, | 2354 | save_err_ret_index, |
| 2336 | restore_err_ret_index, | 2355 | restore_err_ret_index, |
| 2356 | elem_val_imm, | ||
| 2337 | }; | 2357 | }; |
| 2338 | }; | 2358 | }; |
| 2339 | 2359 | ||
| ... | @@ -3233,6 +3253,15 @@ pub const Inst = struct { | ... | @@ -3233,6 +3253,15 @@ pub const Inst = struct { |
| 3233 | index: u32, | 3253 | index: u32, |
| 3234 | len: u32, | 3254 | len: u32, |
| 3235 | }; | 3255 | }; |
| 3256 | |||
| 3257 | pub const ValidateDestructure = struct { | ||
| 3258 | /// The value being destructured. | ||
| 3259 | operand: Ref, | ||
| 3260 | /// The `destructure_assign` node. | ||
| 3261 | destructure_node: i32, | ||
| 3262 | /// The expected field count. | ||
| 3263 | expect_len: u32, | ||
| 3264 | }; | ||
| 3236 | }; | 3265 | }; |
| 3237 | 3266 | ||
| 3238 | pub const SpecialProng = enum { none, @"else", under }; | 3267 | pub const SpecialProng = enum { none, @"else", under }; |
src/print_zir.zig+23| ... | @@ -242,6 +242,7 @@ const Writer = struct { | ... | @@ -242,6 +242,7 @@ const Writer = struct { |
| 242 | .bool_br_or, | 242 | .bool_br_or, |
| 243 | => try self.writeBoolBr(stream, inst), | 243 | => try self.writeBoolBr(stream, inst), |
| 244 | 244 | ||
| 245 | .validate_destructure => try self.writeValidateDestructure(stream, inst), | ||
| 245 | .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst), | 246 | .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst), |
| 246 | .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), | 247 | .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), |
| 247 | .ptr_type => try self.writePtrType(stream, inst), | 248 | .ptr_type => try self.writePtrType(stream, inst), |
| ... | @@ -357,6 +358,8 @@ const Writer = struct { | ... | @@ -357,6 +358,8 @@ const Writer = struct { |
| 357 | 358 | ||
| 358 | .for_len => try self.writePlNodeMultiOp(stream, inst), | 359 | .for_len => try self.writePlNodeMultiOp(stream, inst), |
| 359 | 360 | ||
| 361 | .elem_val_imm => try self.writeElemValImm(stream, inst), | ||
| 362 | |||
| 360 | .elem_ptr_imm => try self.writeElemPtrImm(stream, inst), | 363 | .elem_ptr_imm => try self.writeElemPtrImm(stream, inst), |
| 361 | 364 | ||
| 362 | .@"export" => try self.writePlNodeExport(stream, inst), | 365 | .@"export" => try self.writePlNodeExport(stream, inst), |
| ... | @@ -585,6 +588,20 @@ const Writer = struct { | ... | @@ -585,6 +588,20 @@ const Writer = struct { |
| 585 | try self.writeSrc(stream, inst_data.src()); | 588 | try self.writeSrc(stream, inst_data.src()); |
| 586 | } | 589 | } |
| 587 | 590 | ||
| 591 | fn writeValidateDestructure( | ||
| 592 | self: *Writer, | ||
| 593 | stream: anytype, | ||
| 594 | inst: Zir.Inst.Index, | ||
| 595 | ) (@TypeOf(stream).Error || error{OutOfMemory})!void { | ||
| 596 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; | ||
| 597 | const extra = self.code.extraData(Zir.Inst.ValidateDestructure, inst_data.payload_index).data; | ||
| 598 | try self.writeInstRef(stream, extra.operand); | ||
| 599 | try stream.print(", {d}) (destructure=", .{extra.expect_len}); | ||
| 600 | try self.writeSrc(stream, LazySrcLoc.nodeOffset(extra.destructure_node)); | ||
| 601 | try stream.writeAll(") "); | ||
| 602 | try self.writeSrc(stream, inst_data.src()); | ||
| 603 | } | ||
| 604 | |||
| 588 | fn writeValidateArrayInitTy( | 605 | fn writeValidateArrayInitTy( |
| 589 | self: *Writer, | 606 | self: *Writer, |
| 590 | stream: anytype, | 607 | stream: anytype, |
| ... | @@ -892,6 +909,12 @@ const Writer = struct { | ... | @@ -892,6 +909,12 @@ const Writer = struct { |
| 892 | try self.writeSrc(stream, inst_data.src()); | 909 | try self.writeSrc(stream, inst_data.src()); |
| 893 | } | 910 | } |
| 894 | 911 | ||
| 912 | fn writeElemValImm(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { | ||
| 913 | const inst_data = self.code.instructions.items(.data)[inst].elem_val_imm; | ||
| 914 | try self.writeInstRef(stream, inst_data.operand); | ||
| 915 | try stream.print(", {d})", .{inst_data.idx}); | ||
| 916 | } | ||
| 917 | |||
| 895 | fn writeElemPtrImm(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { | 918 | fn writeElemPtrImm(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { |
| 896 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; | 919 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; |
| 897 | const extra = self.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data; | 920 | const extra = self.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data; |
test/behavior.zig+1| ... | @@ -157,6 +157,7 @@ test { | ... | @@ -157,6 +157,7 @@ test { |
| 157 | _ = @import("behavior/decltest.zig"); | 157 | _ = @import("behavior/decltest.zig"); |
| 158 | _ = @import("behavior/duplicated_test_names.zig"); | 158 | _ = @import("behavior/duplicated_test_names.zig"); |
| 159 | _ = @import("behavior/defer.zig"); | 159 | _ = @import("behavior/defer.zig"); |
| 160 | _ = @import("behavior/destructure.zig"); | ||
| 160 | _ = @import("behavior/empty_tuple_fields.zig"); | 161 | _ = @import("behavior/empty_tuple_fields.zig"); |
| 161 | _ = @import("behavior/empty_union.zig"); | 162 | _ = @import("behavior/empty_union.zig"); |
| 162 | _ = @import("behavior/enum.zig"); | 163 | _ = @import("behavior/enum.zig"); |
test/behavior/destructure.zig created+100| ... | @@ -0,0 +1,100 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const expect = std.testing.expect; | ||
| 4 | |||
| 5 | test "simple destructure" { | ||
| 6 | const S = struct { | ||
| 7 | fn doTheTest() !void { | ||
| 8 | var x: u32 = undefined; | ||
| 9 | x, const y, var z: u64 = .{ 1, @as(u16, 2), 3 }; | ||
| 10 | |||
| 11 | comptime assert(@TypeOf(y) == u16); | ||
| 12 | |||
| 13 | try expect(x == 1); | ||
| 14 | try expect(y == 2); | ||
| 15 | try expect(z == 3); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | |||
| 19 | try S.doTheTest(); | ||
| 20 | try comptime S.doTheTest(); | ||
| 21 | } | ||
| 22 | |||
| 23 | test "destructure with comptime syntax" { | ||
| 24 | const S = struct { | ||
| 25 | fn doTheTest() void { | ||
| 26 | comptime var x: f32 = undefined; | ||
| 27 | comptime x, const y, var z = .{ 0.5, 123, 456 }; // z is a comptime var | ||
| 28 | |||
| 29 | comptime assert(@TypeOf(y) == comptime_int); | ||
| 30 | comptime assert(@TypeOf(z) == comptime_int); | ||
| 31 | comptime assert(x == 0.5); | ||
| 32 | comptime assert(y == 123); | ||
| 33 | comptime assert(z == 456); | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | |||
| 37 | S.doTheTest(); | ||
| 38 | comptime S.doTheTest(); | ||
| 39 | } | ||
| 40 | |||
| 41 | test "destructure from labeled block" { | ||
| 42 | const S = struct { | ||
| 43 | fn doTheTest(rt_true: bool) !void { | ||
| 44 | const x: u32, const y: u8, const z: i64 = blk: { | ||
| 45 | if (rt_true) break :blk .{ 1, 2, 3 }; | ||
| 46 | break :blk .{ 4, 5, 6 }; | ||
| 47 | }; | ||
| 48 | |||
| 49 | try expect(x == 1); | ||
| 50 | try expect(y == 2); | ||
| 51 | try expect(z == 3); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | try S.doTheTest(true); | ||
| 56 | try comptime S.doTheTest(true); | ||
| 57 | } | ||
| 58 | |||
| 59 | test "destructure tuple value" { | ||
| 60 | const tup: struct { f32, u32, i64 } = .{ 10.0, 20, 30 }; | ||
| 61 | const x, const y, const z = tup; | ||
| 62 | |||
| 63 | comptime assert(@TypeOf(x) == f32); | ||
| 64 | comptime assert(@TypeOf(y) == u32); | ||
| 65 | comptime assert(@TypeOf(z) == i64); | ||
| 66 | |||
| 67 | try expect(x == 10.0); | ||
| 68 | try expect(y == 20); | ||
| 69 | try expect(z == 30); | ||
| 70 | } | ||
| 71 | |||
| 72 | test "destructure array value" { | ||
| 73 | const arr: [3]u32 = .{ 10, 20, 30 }; | ||
| 74 | const x, const y, const z = arr; | ||
| 75 | |||
| 76 | comptime assert(@TypeOf(x) == u32); | ||
| 77 | comptime assert(@TypeOf(y) == u32); | ||
| 78 | comptime assert(@TypeOf(z) == u32); | ||
| 79 | |||
| 80 | try expect(x == 10); | ||
| 81 | try expect(y == 20); | ||
| 82 | try expect(z == 30); | ||
| 83 | } | ||
| 84 | |||
| 85 | test "destructure from struct init with named tuple fields" { | ||
| 86 | const Tuple = struct { u8, u16, u32 }; | ||
| 87 | const x, const y, const z = Tuple{ | ||
| 88 | .@"0" = 100, | ||
| 89 | .@"1" = 200, | ||
| 90 | .@"2" = 300, | ||
| 91 | }; | ||
| 92 | |||
| 93 | comptime assert(@TypeOf(x) == u8); | ||
| 94 | comptime assert(@TypeOf(y) == u16); | ||
| 95 | comptime assert(@TypeOf(z) == u32); | ||
| 96 | |||
| 97 | try expect(x == 100); | ||
| 98 | try expect(y == 200); | ||
| 99 | try expect(z == 300); | ||
| 100 | } | ||
test/cases/compile_errors/cast_without_result_type.zig+7| ... | @@ -13,6 +13,10 @@ export fn d() void { | ... | @@ -13,6 +13,10 @@ export fn d() void { |
| 13 | var x: f32 = 0; | 13 | var x: f32 = 0; |
| 14 | _ = x + @floatFromInt(123); | 14 | _ = x + @floatFromInt(123); |
| 15 | } | 15 | } |
| 16 | export fn e() void { | ||
| 17 | const x: u32, const y: u64 = @intCast(123); | ||
| 18 | _ = x + y; | ||
| 19 | } | ||
| 16 | 20 | ||
| 17 | // error | 21 | // error |
| 18 | // backend=stage2 | 22 | // backend=stage2 |
| ... | @@ -26,3 +30,6 @@ export fn d() void { | ... | @@ -26,3 +30,6 @@ export fn d() void { |
| 26 | // :9:10: note: use @as to provide explicit result type | 30 | // :9:10: note: use @as to provide explicit result type |
| 27 | // :14:13: error: @floatFromInt must have a known result type | 31 | // :14:13: error: @floatFromInt must have a known result type |
| 28 | // :14:13: note: use @as to provide explicit result type | 32 | // :14:13: note: use @as to provide explicit result type |
| 33 | // :17:34: error: @intCast must have a known result type | ||
| 34 | // :17:32: note: destructure expressions do not provide a single result type | ||
| 35 | // :17:34: note: use @as to provide explicit result type |
test/cases/compile_errors/extra_comma_in_destructure.zig created+10| ... | @@ -0,0 +1,10 @@ | ||
| 1 | export fn foo() void { | ||
| 2 | const x, const y, = .{ 1, 2 }; | ||
| 3 | _ = .{ x, y }; | ||
| 4 | } | ||
| 5 | |||
| 6 | // error | ||
| 7 | // backend=stage2 | ||
| 8 | // target=native | ||
| 9 | // | ||
| 10 | // :2:23: error: expected expression or var decl, found '=' | ||
test/cases/compile_errors/invalid_destructure_astgen.zig created+22| ... | @@ -0,0 +1,22 @@ | ||
| 1 | export fn foo() void { | ||
| 2 | const x, const y = .{ 1, 2, 3 }; | ||
| 3 | _ = .{ x, y }; | ||
| 4 | } | ||
| 5 | |||
| 6 | export fn bar() void { | ||
| 7 | var x: u32 = undefined; | ||
| 8 | x, const y: u64 = blk: { | ||
| 9 | if (true) break :blk .{ 1, 2 }; | ||
| 10 | break :blk .{ .x = 123, .y = 456 }; | ||
| 11 | }; | ||
| 12 | _ = y; | ||
| 13 | } | ||
| 14 | |||
| 15 | // error | ||
| 16 | // backend=stage2 | ||
| 17 | // target=native | ||
| 18 | // | ||
| 19 | // :2:25: error: expected 2 elements for destructure, found 3 | ||
| 20 | // :2:22: note: result destructured here | ||
| 21 | // :10:21: error: struct value cannot be destructured | ||
| 22 | // :8:21: note: result destructured here | ||
test/cases/compile_errors/invalid_destructure_sema.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | export fn foo() void { | ||
| 2 | const x, const y = 123; | ||
| 3 | _ = .{ x, y }; | ||
| 4 | } | ||
| 5 | |||
| 6 | export fn bar() void { | ||
| 7 | var x: u32 = undefined; | ||
| 8 | x, const y: u64 = blk: { | ||
| 9 | if (false) break :blk .{ 1, 2 }; | ||
| 10 | const val = .{ 3, 4, 5 }; | ||
| 11 | break :blk val; | ||
| 12 | }; | ||
| 13 | _ = y; | ||
| 14 | } | ||
| 15 | |||
| 16 | // error | ||
| 17 | // backend=stage2 | ||
| 18 | // target=native | ||
| 19 | // | ||
| 20 | // :2:24: error: type 'comptime_int' cannot be destructured | ||
| 21 | // :2:22: note: result destructured here | ||
| 22 | // :11:20: error: expected 2 elements for destructure, found 3 | ||
| 23 | // :8:21: note: result destructured here | ||
test/cases/unused_vars.zig+3| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | pub fn main() void { | 1 | pub fn main() void { |
| 2 | const x = 1; | 2 | const x = 1; |
| 3 | const y, var z = .{ 2, 3 }; | ||
| 3 | } | 4 | } |
| 4 | 5 | ||
| 5 | // error | 6 | // error |
| 6 | // | 7 | // |
| 8 | // :3:18: error: unused local variable | ||
| 9 | // :3:11: error: unused local constant | ||
| 7 | // :2:11: error: unused local constant | 10 | // :2:11: error: unused local constant |