| 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 | 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 | 249 | .expected_fn => { |
| 245 | 250 | return stream.print("expected function, found '{s}'", .{ |
| 246 | 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 | 589 | .error_union, |
| 585 | 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 | 599 | .fn_decl, |
| 588 | 600 | .fn_proto_simple, |
| 589 | 601 | .fn_proto_multi, |
| ... | ... | @@ -816,6 +828,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { |
| 816 | 828 | .assign_add_sat, |
| 817 | 829 | .assign_sub_sat, |
| 818 | 830 | .assign, |
| 831 | .assign_destructure, | |
| 819 | 832 | .merge_error_sets, |
| 820 | 833 | .mul, |
| 821 | 834 | .div, |
| ... | ... | @@ -2846,6 +2859,7 @@ pub const Error = struct { |
| 2846 | 2859 | expected_container_members, |
| 2847 | 2860 | expected_expr, |
| 2848 | 2861 | expected_expr_or_assignment, |
| 2862 | expected_expr_or_var_decl, | |
| 2849 | 2863 | expected_fn, |
| 2850 | 2864 | expected_inlinable, |
| 2851 | 2865 | expected_labelable, |
| ... | ... | @@ -3006,6 +3020,20 @@ pub const Node = struct { |
| 3006 | 3020 | assign_sub_sat, |
| 3007 | 3021 | /// `lhs = rhs`. main_token is op. |
| 3008 | 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 | 3037 | /// `lhs || rhs`. main_token is the `||`. |
| 3010 | 3038 | merge_error_sets, |
| 3011 | 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 | 658 | } |
| 659 | 659 | |
| 660 | 660 | const thread_local_token = p.eatToken(.keyword_threadlocal); |
| 661 | const var_decl = try p.parseVarDecl(); | |
| 661 | const var_decl = try p.parseGlobalVarDecl(); | |
| 662 | 662 | if (var_decl != 0) { |
| 663 | try p.expectSemicolon(.expected_semi_after_decl, false); | |
| 664 | 663 | return var_decl; |
| 665 | 664 | } |
| 666 | 665 | if (thread_local_token != null) { |
| ... | ... | @@ -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 | |
| 796 | fn parseVarDecl(p: *Parse) !Node.Index { | |
| 794 | /// VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? | |
| 795 | /// Returns a `*_var_decl` node with its rhs (init expression) initialized to 0. | |
| 796 | fn parseVarDeclProto(p: *Parse) !Node.Index { | |
| 797 | 797 | const mut_token = p.eatToken(.keyword_const) orelse |
| 798 | 798 | p.eatToken(.keyword_var) orelse |
| 799 | 799 | return null_node; |
| ... | ... | @@ -803,18 +803,7 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 803 | 803 | const align_node = try p.parseByteAlign(); |
| 804 | 804 | const addrspace_node = try p.parseAddrSpace(); |
| 805 | 805 | const section_node = try p.parseLinkSection(); |
| 806 | const init_node: Node.Index = switch (p.token_tags[p.tok_i]) { | |
| 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 | }; | |
| 806 | ||
| 818 | 807 | if (section_node == 0 and addrspace_node == 0) { |
| 819 | 808 | if (align_node == 0) { |
| 820 | 809 | return p.addNode(.{ |
| ... | ... | @@ -822,31 +811,33 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 822 | 811 | .main_token = mut_token, |
| 823 | 812 | .data = .{ |
| 824 | 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 | 820 | return p.addNode(.{ |
| 830 | 821 | .tag = .aligned_var_decl, |
| 831 | 822 | .main_token = mut_token, |
| 832 | 823 | .data = .{ |
| 833 | 824 | .lhs = align_node, |
| 834 | .rhs = init_node, | |
| 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, | |
| 825 | .rhs = 0, | |
| 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 | 841 | } else { |
| 851 | 842 | return p.addNode(.{ |
| 852 | 843 | .tag = .global_var_decl, |
| ... | ... | @@ -858,12 +849,38 @@ fn parseVarDecl(p: *Parse) !Node.Index { |
| 858 | 849 | .addrspace_node = addrspace_node, |
| 859 | 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 | 884 | /// ContainerField |
| 868 | 885 | /// <- doc_comment? KEYWORD_comptime? IDENTIFIER (COLON TypeExpr)? ByteAlign? (EQUAL Expr)? |
| 869 | 886 | /// / doc_comment? KEYWORD_comptime? (IDENTIFIER COLON)? !KEYWORD_fn TypeExpr ByteAlign? (EQUAL Expr)? |
| ... | ... | @@ -918,8 +935,7 @@ fn expectContainerField(p: *Parse) !Node.Index { |
| 918 | 935 | } |
| 919 | 936 | |
| 920 | 937 | /// Statement |
| 921 | /// <- KEYWORD_comptime? VarDecl | |
| 922 | /// / KEYWORD_comptime BlockExprStatement | |
| 938 | /// <- KEYWORD_comptime ComptimeStatement | |
| 923 | 939 | /// / KEYWORD_nosuspend BlockExprStatement |
| 924 | 940 | /// / KEYWORD_suspend BlockExprStatement |
| 925 | 941 | /// / KEYWORD_defer BlockExprStatement |
| ... | ... | @@ -927,27 +943,28 @@ fn expectContainerField(p: *Parse) !Node.Index { |
| 927 | 943 | /// / IfStatement |
| 928 | 944 | /// / LabeledStatement |
| 929 | 945 | /// / SwitchExpr |
| 930 | /// / AssignExpr SEMICOLON | |
| 931 | fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | |
| 932 | const comptime_token = p.eatToken(.keyword_comptime); | |
| 933 | ||
| 934 | if (allow_defer_var) { | |
| 935 | const var_decl = try p.parseVarDecl(); | |
| 936 | if (var_decl != 0) { | |
| 937 | try p.expectSemicolon(.expected_semi_after_decl, true); | |
| 938 | return var_decl; | |
| 946 | /// / VarDeclExprStatement | |
| 947 | fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | |
| 948 | if (p.eatToken(.keyword_comptime)) |comptime_token| { | |
| 949 | const block_expr = try p.parseBlockExpr(); | |
| 950 | if (block_expr != 0) { | |
| 951 | return p.addNode(.{ | |
| 952 | .tag = .@"comptime", | |
| 953 | .main_token = comptime_token, | |
| 954 | .data = .{ | |
| 955 | .lhs = block_expr, | |
| 956 | .rhs = undefined, | |
| 957 | }, | |
| 958 | }); | |
| 939 | 959 | } |
| 940 | } | |
| 941 | 960 | |
| 942 | if (comptime_token) |token| { | |
| 943 | return p.addNode(.{ | |
| 944 | .tag = .@"comptime", | |
| 945 | .main_token = token, | |
| 946 | .data = .{ | |
| 947 | .lhs = try p.expectBlockExprStatement(), | |
| 948 | .rhs = undefined, | |
| 949 | }, | |
| 950 | }); | |
| 961 | if (allow_defer_var) { | |
| 962 | return p.expectVarDeclExprStatement(comptime_token); | |
| 963 | } else { | |
| 964 | const assign = try p.expectAssignExpr(); | |
| 965 | try p.expectSemicolon(.expected_semi_after_stmt, true); | |
| 966 | return assign; | |
| 967 | } | |
| 951 | 968 | } |
| 952 | 969 | |
| 953 | 970 | switch (p.token_tags[p.tok_i]) { |
| ... | ... | @@ -1011,21 +1028,145 @@ fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { |
| 1011 | 1028 | const labeled_statement = try p.parseLabeledStatement(); |
| 1012 | 1029 | if (labeled_statement != 0) return labeled_statement; |
| 1013 | 1030 | |
| 1014 | const assign_expr = try p.parseAssignExpr(); | |
| 1015 | if (assign_expr != 0) { | |
| 1031 | if (allow_defer_var) { | |
| 1032 | return p.expectVarDeclExprStatement(null); | |
| 1033 | } else { | |
| 1034 | const assign = try p.expectAssignExpr(); | |
| 1016 | 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 { | |
| 1024 | const statement = try p.parseStatement(allow_defer_var); | |
| 1025 | if (statement == 0) { | |
| 1026 | return p.fail(.expected_statement); | |
| 1055 | /// VarDeclExprStatement | |
| 1056 | /// <- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON | |
| 1057 | /// / Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON | |
| 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 | 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 | 1486 | } |
| 1346 | 1487 | } |
| 1347 | 1488 | |
| 1348 | /// AssignExpr <- Expr (AssignOp Expr)? | |
| 1489 | /// AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)? | |
| 1349 | 1490 | /// |
| 1350 | 1491 | /// AssignOp |
| 1351 | 1492 | /// <- ASTERISKEQUAL |
| ... | ... | @@ -1369,8 +1510,40 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index { |
| 1369 | 1510 | fn parseAssignExpr(p: *Parse) !Node.Index { |
| 1370 | 1511 | const expr = try p.parseExpr(); |
| 1371 | 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 | 1547 | .asterisk_equal => .assign_mul, |
| 1375 | 1548 | .slash_equal => .assign_div, |
| 1376 | 1549 | .percent_equal => .assign_mod, |
| ... | ... | @@ -1389,18 +1562,51 @@ fn parseAssignExpr(p: *Parse) !Node.Index { |
| 1389 | 1562 | .plus_pipe_equal => .assign_add_sat, |
| 1390 | 1563 | .minus_pipe_equal => .assign_sub_sat, |
| 1391 | 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 | 1592 | return p.addNode(.{ |
| 1395 | .tag = tag, | |
| 1396 | .main_token = p.nextToken(), | |
| 1593 | .tag = .assign_destructure, | |
| 1594 | .main_token = equal_token, | |
| 1397 | 1595 | .data = .{ |
| 1398 | .lhs = expr, | |
| 1399 | .rhs = try p.expectExpr(), | |
| 1596 | .lhs = @intCast(extra_start), | |
| 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 | 1610 | fn expectAssignExpr(p: *Parse) !Node.Index { |
| 1405 | 1611 | const expr = try p.parseAssignExpr(); |
| 1406 | 1612 | if (expr == 0) { |
| ... | ... | @@ -3260,7 +3466,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3260 | 3466 | .main_token = arrow_token, |
| 3261 | 3467 | .data = .{ |
| 3262 | 3468 | .lhs = 0, |
| 3263 | .rhs = try p.expectAssignExpr(), | |
| 3469 | .rhs = try p.expectSingleAssignExpr(), | |
| 3264 | 3470 | }, |
| 3265 | 3471 | }), |
| 3266 | 3472 | 1 => return p.addNode(.{ |
| ... | ... | @@ -3268,7 +3474,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3268 | 3474 | .main_token = arrow_token, |
| 3269 | 3475 | .data = .{ |
| 3270 | 3476 | .lhs = items[0], |
| 3271 | .rhs = try p.expectAssignExpr(), | |
| 3477 | .rhs = try p.expectSingleAssignExpr(), | |
| 3272 | 3478 | }, |
| 3273 | 3479 | }), |
| 3274 | 3480 | else => return p.addNode(.{ |
| ... | ... | @@ -3276,7 +3482,7 @@ fn parseSwitchProng(p: *Parse) !Node.Index { |
| 3276 | 3482 | .main_token = arrow_token, |
| 3277 | 3483 | .data = .{ |
| 3278 | 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 | 4348 | \\ for ("") |_| {} else defer {} |
| 4349 | 4349 | \\} |
| 4350 | 4350 | , &[_]Error{ |
| 4351 | .expected_statement, | |
| 4352 | .expected_statement, | |
| 4353 | .expected_statement, | |
| 4354 | .expected_statement, | |
| 4355 | .expected_statement, | |
| 4356 | .expected_statement, | |
| 4351 | .expected_expr_or_assignment, | |
| 4352 | .expected_expr_or_assignment, | |
| 4353 | .expected_expr_or_assignment, | |
| 4354 | .expected_expr_or_assignment, | |
| 4355 | .expected_expr_or_assignment, | |
| 4356 | .expected_expr_or_assignment, | |
| 4357 | 4357 | }); |
| 4358 | 4358 | } |
| 4359 | 4359 | |
| ... | ... | @@ -6078,7 +6078,7 @@ test "recovery: missing for payload" { |
| 6078 | 6078 | try testError( |
| 6079 | 6079 | \\comptime { |
| 6080 | 6080 | \\ const a = for(a) {}; |
| 6081 | \\ const a: for(a) blk: {}; | |
| 6081 | \\ const a: for(a) blk: {} = {}; | |
| 6082 | 6082 | \\ for(a) {} |
| 6083 | 6083 | \\} |
| 6084 | 6084 | , &[_]Error{ |
lib/std/zig/render.zig+82-32| ... | ... | @@ -164,7 +164,7 @@ fn renderMember( |
| 164 | 164 | .local_var_decl, |
| 165 | 165 | .simple_var_decl, |
| 166 | 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 | 169 | .test_decl => { |
| 170 | 170 | const test_token = main_tokens[decl]; |
| ... | ... | @@ -427,6 +427,42 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, |
| 427 | 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 | 466 | .bit_not, |
| 431 | 467 | .bool_not, |
| 432 | 468 | .negation, |
| ... | ... | @@ -943,7 +979,16 @@ fn renderAsmInput( |
| 943 | 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 | 992 | if (var_decl.visib_token) |visib_token| { |
| 948 | 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 | 1005 | try renderToken(ais, tree, thread_local_token, Space.space); // threadlocal |
| 961 | 1006 | } |
| 962 | 1007 | |
| 963 | if (var_decl.comptime_token) |comptime_token| { | |
| 964 | try renderToken(ais, tree, comptime_token, Space.space); // comptime | |
| 1008 | if (!ignore_comptime_token) { | |
| 1009 | if (var_decl.comptime_token) |comptime_token| { | |
| 1010 | try renderToken(ais, tree, comptime_token, Space.space); // comptime | |
| 1011 | } | |
| 965 | 1012 | } |
| 966 | 1013 | |
| 967 | 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 | |
| 970 | (var_decl.ast.align_node != 0 or | |
| 971 | var_decl.ast.addrspace_node != 0 or | |
| 972 | var_decl.ast.section_node != 0 or | |
| 973 | var_decl.ast.init_node != 0)) | |
| 974 | Space.space | |
| 975 | else | |
| 976 | Space.none; | |
| 977 | try renderIdentifier(ais, tree, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name | |
| 1016 | if (var_decl.ast.type_node != 0 or var_decl.ast.align_node != 0 or | |
| 1017 | var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or | |
| 1018 | var_decl.ast.init_node != 0) | |
| 1019 | { | |
| 1020 | const name_space = if (var_decl.ast.type_node == 0 and | |
| 1021 | (var_decl.ast.align_node != 0 or | |
| 1022 | var_decl.ast.addrspace_node != 0 or | |
| 1023 | var_decl.ast.section_node != 0 or | |
| 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 | 1034 | if (var_decl.ast.type_node != 0) { |
| 980 | 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 | 1038 | { |
| 984 | 1039 | try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .space); |
| 985 | 1040 | } else { |
| 986 | try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .none); | |
| 987 | const semicolon = tree.lastToken(var_decl.ast.type_node) + 1; | |
| 988 | return renderToken(ais, tree, semicolon, Space.newline); // ; | |
| 1041 | return renderExpression(gpa, ais, tree, var_decl.ast.type_node, space); | |
| 989 | 1042 | } |
| 990 | 1043 | } |
| 991 | 1044 | |
| ... | ... | @@ -1001,8 +1054,7 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 1001 | 1054 | { |
| 1002 | 1055 | try renderToken(ais, tree, rparen, .space); // ) |
| 1003 | 1056 | } else { |
| 1004 | try renderToken(ais, tree, rparen, .none); // ) | |
| 1005 | return renderToken(ais, tree, rparen + 1, Space.newline); // ; | |
| 1057 | return renderToken(ais, tree, rparen, space); // ) | |
| 1006 | 1058 | } |
| 1007 | 1059 | } |
| 1008 | 1060 | |
| ... | ... | @@ -1031,23 +1083,21 @@ fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDec |
| 1031 | 1083 | if (var_decl.ast.init_node != 0) { |
| 1032 | 1084 | try renderToken(ais, tree, rparen, .space); // ) |
| 1033 | 1085 | } else { |
| 1034 | try renderToken(ais, tree, rparen, .none); // ) | |
| 1035 | return renderToken(ais, tree, rparen + 1, Space.newline); // ; | |
| 1086 | return renderToken(ais, tree, rparen, space); // ) | |
| 1036 | 1087 | } |
| 1037 | 1088 | } |
| 1038 | 1089 | |
| 1039 | if (var_decl.ast.init_node != 0) { | |
| 1040 | const eq_token = tree.firstToken(var_decl.ast.init_node) - 1; | |
| 1041 | const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; | |
| 1042 | { | |
| 1043 | ais.pushIndent(); | |
| 1044 | try renderToken(ais, tree, eq_token, eq_space); // = | |
| 1045 | ais.popIndent(); | |
| 1046 | } | |
| 1047 | ais.pushIndentOneShot(); | |
| 1048 | return renderExpression(gpa, ais, tree, var_decl.ast.init_node, .semicolon); // ; | |
| 1090 | assert(var_decl.ast.init_node != 0); | |
| 1091 | ||
| 1092 | const eq_token = tree.firstToken(var_decl.ast.init_node) - 1; | |
| 1093 | const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; | |
| 1094 | { | |
| 1095 | ais.pushIndent(); | |
| 1096 | try renderToken(ais, tree, eq_token, eq_space); // = | |
| 1097 | ais.popIndent(); | |
| 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 | 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 | 1875 | .local_var_decl, |
| 1826 | 1876 | .simple_var_decl, |
| 1827 | 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 | 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 | 280 | /// The result instruction from the expression must be ignored. |
| 281 | 281 | /// Always an instruction with tag `alloc_inferred`. |
| 282 | 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 | 298 | const PtrResultLoc = struct { |
| 285 | 299 | inst: Zir.Inst.Ref, |
| ... | ... | @@ -298,6 +312,12 @@ const ResultInfo = struct { |
| 298 | 312 | const ptr_ty = try gz.addUnNode(.typeof, ptr.inst, node); |
| 299 | 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 | 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 | 419 | .asm_input => unreachable, |
| 400 | 420 | |
| 401 | 421 | .assign, |
| 422 | .assign_destructure, | |
| 402 | 423 | .assign_bit_and, |
| 403 | 424 | .assign_bit_or, |
| 404 | 425 | .assign_shl, |
| ... | ... | @@ -621,6 +642,13 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 621 | 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 | 652 | .assign_shl => { |
| 625 | 653 | try assignShift(gz, scope, node, .shl); |
| 626 | 654 | return rvalue(gz, ri, .void_value, node); |
| ... | ... | @@ -1478,6 +1506,33 @@ fn arrayInitExpr( |
| 1478 | 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 | 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 | 2040 | // TODO: Update this to do a proper load from the rl_ptr, once Sema can support it. |
| 1969 | 2041 | break :blk .none; |
| 1970 | 2042 | }, |
| 2043 | .destructure => return, // value must be a tuple or array, so never restore/pop | |
| 1971 | 2044 | else => result, |
| 1972 | 2045 | }, |
| 1973 | 2046 | else => .none, // always restore/pop |
| ... | ... | @@ -2340,6 +2413,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod |
| 2340 | 2413 | .simple_var_decl, |
| 2341 | 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 | 2418 | .@"defer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_normal), |
| 2344 | 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 | 2556 | .elem_ptr_node, |
| 2482 | 2557 | .elem_ptr_imm, |
| 2483 | 2558 | .elem_val_node, |
| 2559 | .elem_val_imm, | |
| 2484 | 2560 | .field_ptr, |
| 2485 | 2561 | .field_ptr_init, |
| 2486 | 2562 | .field_val, |
| ... | ... | @@ -2686,6 +2762,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2686 | 2762 | .validate_array_init_ty, |
| 2687 | 2763 | .validate_struct_init_ty, |
| 2688 | 2764 | .validate_deref, |
| 2765 | .validate_destructure, | |
| 2689 | 2766 | .save_err_ret_index, |
| 2690 | 2767 | .restore_err_ret_index, |
| 2691 | 2768 | => break :b true, |
| ... | ... | @@ -3227,6 +3304,301 @@ fn assign(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!voi |
| 3227 | 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 | 3602 | fn assignOp( |
| 3231 | 3603 | gz: *GenZir, |
| 3232 | 3604 | scope: *Scope, |
| ... | ... | @@ -9059,6 +9431,7 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev |
| 9059 | 9431 | .array_cat, |
| 9060 | 9432 | .array_mult, |
| 9061 | 9433 | .assign, |
| 9434 | .assign_destructure, | |
| 9062 | 9435 | .assign_bit_and, |
| 9063 | 9436 | .assign_bit_or, |
| 9064 | 9437 | .assign_shl, |
| ... | ... | @@ -9237,6 +9610,7 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In |
| 9237 | 9610 | .array_cat, |
| 9238 | 9611 | .array_mult, |
| 9239 | 9612 | .assign, |
| 9613 | .assign_destructure, | |
| 9240 | 9614 | .assign_bit_and, |
| 9241 | 9615 | .assign_bit_or, |
| 9242 | 9616 | .assign_shl, |
| ... | ... | @@ -9483,6 +9857,7 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool { |
| 9483 | 9857 | .array_cat, |
| 9484 | 9858 | .array_mult, |
| 9485 | 9859 | .assign, |
| 9860 | .assign_destructure, | |
| 9486 | 9861 | .assign_bit_and, |
| 9487 | 9862 | .assign_bit_or, |
| 9488 | 9863 | .assign_shl, |
| ... | ... | @@ -9830,6 +10205,37 @@ fn rvalue( |
| 9830 | 10205 | _ = try gz.addBin(.store_to_inferred_ptr, alloc, result); |
| 9831 | 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 | 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 | 216 | .assign => { |
| 207 | 217 | _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none); |
| 208 | 218 | _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.typed_ptr); |
src/Sema.zig+54| ... | ... | @@ -1018,6 +1018,7 @@ fn analyzeBodyInner( |
| 1018 | 1018 | .elem_ptr_imm => try sema.zirElemPtrImm(block, inst), |
| 1019 | 1019 | .elem_val => try sema.zirElemVal(block, inst), |
| 1020 | 1020 | .elem_val_node => try sema.zirElemValNode(block, inst), |
| 1021 | .elem_val_imm => try sema.zirElemValImm(block, inst), | |
| 1021 | 1022 | .elem_type_index => try sema.zirElemTypeIndex(block, inst), |
| 1022 | 1023 | .elem_type => try sema.zirElemType(block, inst), |
| 1023 | 1024 | .indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst), |
| ... | ... | @@ -1379,6 +1380,11 @@ fn analyzeBodyInner( |
| 1379 | 1380 | i += 1; |
| 1380 | 1381 | continue; |
| 1381 | 1382 | }, |
| 1383 | .validate_destructure => { | |
| 1384 | try sema.zirValidateDestructure(block, inst); | |
| 1385 | i += 1; | |
| 1386 | continue; | |
| 1387 | }, | |
| 1382 | 1388 | .@"export" => { |
| 1383 | 1389 | try sema.zirExport(block, inst); |
| 1384 | 1390 | i += 1; |
| ... | ... | @@ -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 | 5224 | fn failWithBadMemberAccess( |
| 5182 | 5225 | sema: *Sema, |
| 5183 | 5226 | block: *Block, |
| ... | ... | @@ -10304,6 +10347,17 @@ fn zirElemValNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 10304 | 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 | 10361 | fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 10308 | 10362 | const tracy = trace(@src()); |
| 10309 | 10363 | defer tracy.end(); |
src/Zir.zig+29| ... | ... | @@ -434,6 +434,10 @@ pub const Inst = struct { |
| 434 | 434 | /// Payload is `Bin`. |
| 435 | 435 | /// No OOB safety check is emitted. |
| 436 | 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 | 441 | /// Emits a compile error if the operand is not `void`. |
| 438 | 442 | /// Uses the `un_node` field. |
| 439 | 443 | ensure_result_used, |
| ... | ... | @@ -725,6 +729,9 @@ pub const Inst = struct { |
| 725 | 729 | /// Check that operand type supports the dereference operand (.*). |
| 726 | 730 | /// Uses the `un_node` field. |
| 727 | 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 | 735 | /// A struct literal with a specified type, with no fields. |
| 729 | 736 | /// Uses the `un_node` field. |
| 730 | 737 | struct_init_empty, |
| ... | ... | @@ -1069,6 +1076,7 @@ pub const Inst = struct { |
| 1069 | 1076 | .elem_ptr_node, |
| 1070 | 1077 | .elem_ptr_imm, |
| 1071 | 1078 | .elem_val_node, |
| 1079 | .elem_val_imm, | |
| 1072 | 1080 | .ensure_result_used, |
| 1073 | 1081 | .ensure_result_non_error, |
| 1074 | 1082 | .ensure_err_union_payload_void, |
| ... | ... | @@ -1145,6 +1153,7 @@ pub const Inst = struct { |
| 1145 | 1153 | .validate_struct_init, |
| 1146 | 1154 | .validate_array_init, |
| 1147 | 1155 | .validate_deref, |
| 1156 | .validate_destructure, | |
| 1148 | 1157 | .struct_init_empty, |
| 1149 | 1158 | .struct_init, |
| 1150 | 1159 | .struct_init_ref, |
| ... | ... | @@ -1295,6 +1304,7 @@ pub const Inst = struct { |
| 1295 | 1304 | .validate_struct_init, |
| 1296 | 1305 | .validate_array_init, |
| 1297 | 1306 | .validate_deref, |
| 1307 | .validate_destructure, | |
| 1298 | 1308 | .@"export", |
| 1299 | 1309 | .export_value, |
| 1300 | 1310 | .set_runtime_safety, |
| ... | ... | @@ -1369,6 +1379,7 @@ pub const Inst = struct { |
| 1369 | 1379 | .elem_ptr_node, |
| 1370 | 1380 | .elem_ptr_imm, |
| 1371 | 1381 | .elem_val_node, |
| 1382 | .elem_val_imm, | |
| 1372 | 1383 | .field_ptr, |
| 1373 | 1384 | .field_ptr_init, |
| 1374 | 1385 | .field_val, |
| ... | ... | @@ -1615,6 +1626,7 @@ pub const Inst = struct { |
| 1615 | 1626 | .elem_ptr_imm = .pl_node, |
| 1616 | 1627 | .elem_val = .pl_node, |
| 1617 | 1628 | .elem_val_node = .pl_node, |
| 1629 | .elem_val_imm = .elem_val_imm, | |
| 1618 | 1630 | .ensure_result_used = .un_node, |
| 1619 | 1631 | .ensure_result_non_error = .un_node, |
| 1620 | 1632 | .ensure_err_union_payload_void = .un_node, |
| ... | ... | @@ -1689,6 +1701,7 @@ pub const Inst = struct { |
| 1689 | 1701 | .validate_struct_init = .pl_node, |
| 1690 | 1702 | .validate_array_init = .pl_node, |
| 1691 | 1703 | .validate_deref = .un_node, |
| 1704 | .validate_destructure = .pl_node, | |
| 1692 | 1705 | .struct_init_empty = .un_node, |
| 1693 | 1706 | .field_type = .pl_node, |
| 1694 | 1707 | .field_type_ref = .pl_node, |
| ... | ... | @@ -2295,6 +2308,12 @@ pub const Inst = struct { |
| 2295 | 2308 | block: Ref, // If restored, the index is from this block's entrypoint |
| 2296 | 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 | 2318 | // Make sure we don't accidentally add a field to make this union |
| 2300 | 2319 | // bigger than expected. Note that in Debug builds, Zig is allowed |
| ... | ... | @@ -2334,6 +2353,7 @@ pub const Inst = struct { |
| 2334 | 2353 | defer_err_code, |
| 2335 | 2354 | save_err_ret_index, |
| 2336 | 2355 | restore_err_ret_index, |
| 2356 | elem_val_imm, | |
| 2337 | 2357 | }; |
| 2338 | 2358 | }; |
| 2339 | 2359 | |
| ... | ... | @@ -3233,6 +3253,15 @@ pub const Inst = struct { |
| 3233 | 3253 | index: u32, |
| 3234 | 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 | 3267 | pub const SpecialProng = enum { none, @"else", under }; |
src/print_zir.zig+23| ... | ... | @@ -242,6 +242,7 @@ const Writer = struct { |
| 242 | 242 | .bool_br_or, |
| 243 | 243 | => try self.writeBoolBr(stream, inst), |
| 244 | 244 | |
| 245 | .validate_destructure => try self.writeValidateDestructure(stream, inst), | |
| 245 | 246 | .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst), |
| 246 | 247 | .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), |
| 247 | 248 | .ptr_type => try self.writePtrType(stream, inst), |
| ... | ... | @@ -357,6 +358,8 @@ const Writer = struct { |
| 357 | 358 | |
| 358 | 359 | .for_len => try self.writePlNodeMultiOp(stream, inst), |
| 359 | 360 | |
| 361 | .elem_val_imm => try self.writeElemValImm(stream, inst), | |
| 362 | ||
| 360 | 363 | .elem_ptr_imm => try self.writeElemPtrImm(stream, inst), |
| 361 | 364 | |
| 362 | 365 | .@"export" => try self.writePlNodeExport(stream, inst), |
| ... | ... | @@ -585,6 +588,20 @@ const Writer = struct { |
| 585 | 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 | 605 | fn writeValidateArrayInitTy( |
| 589 | 606 | self: *Writer, |
| 590 | 607 | stream: anytype, |
| ... | ... | @@ -892,6 +909,12 @@ const Writer = struct { |
| 892 | 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 | 918 | fn writeElemPtrImm(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { |
| 896 | 919 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; |
| 897 | 920 | const extra = self.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data; |
test/behavior.zig+1| ... | ... | @@ -157,6 +157,7 @@ test { |
| 157 | 157 | _ = @import("behavior/decltest.zig"); |
| 158 | 158 | _ = @import("behavior/duplicated_test_names.zig"); |
| 159 | 159 | _ = @import("behavior/defer.zig"); |
| 160 | _ = @import("behavior/destructure.zig"); | |
| 160 | 161 | _ = @import("behavior/empty_tuple_fields.zig"); |
| 161 | 162 | _ = @import("behavior/empty_union.zig"); |
| 162 | 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 | 13 | var x: f32 = 0; |
| 14 | 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 | 21 | // error |
| 18 | 22 | // backend=stage2 |
| ... | ... | @@ -26,3 +30,6 @@ export fn d() void { |
| 26 | 30 | // :9:10: note: use @as to provide explicit result type |
| 27 | 31 | // :14:13: error: @floatFromInt must have a known result type |
| 28 | 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 | 1 | pub fn main() void { |
| 2 | 2 | const x = 1; |
| 3 | const y, var z = .{ 2, 3 }; | |
| 3 | 4 | } |
| 4 | 5 | |
| 5 | 6 | // error |
| 6 | 7 | // |
| 8 | // :3:18: error: unused local variable | |
| 9 | // :3:11: error: unused local constant | |
| 7 | 10 | // :2:11: error: unused local constant |