authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-08-31 14:30:58+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-15 11:33:53-07:00
log88f5315ddfc6eaf3e28433504ec046fb3252db7c
tree5cd6e8e16b285d136a1fbaa98d12739aeab34feb
parent50ef10eb4963167225f7153dc5165292dbac0046

compiler: implement destructuring syntax

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: #498

16 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,
586591
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 }
659659
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}
794793
795/// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? (EQUAL Expr)? SEMICOLON794/// VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
796fn parseVarDecl(p: *Parse) !Node.Index {795/// Returns a `*_var_decl` node with its rhs (init expression) initialized to 0.
796fn parseVarDeclProto(p: *Parse) !Node.Index {
797 const mut_token = p.eatToken(.keyword_const) orelse797 const mut_token = p.eatToken(.keyword_const) orelse
798 p.eatToken(.keyword_var) orelse798 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}
866857
858/// GlobalVarDecl <- VarDeclProto (EQUAL Expr?) SEMICOLON
859fn 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/// ContainerField884/// 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}
919936
920/// Statement937/// Statement
921/// <- KEYWORD_comptime? VarDecl938/// <- KEYWORD_comptime ComptimeStatement
922/// / KEYWORD_comptime BlockExprStatement
923/// / KEYWORD_nosuspend BlockExprStatement939/// / KEYWORD_nosuspend BlockExprStatement
924/// / KEYWORD_suspend BlockExprStatement940/// / KEYWORD_suspend BlockExprStatement
925/// / KEYWORD_defer BlockExprStatement941/// / KEYWORD_defer BlockExprStatement
...@@ -927,27 +943,28 @@ fn expectContainerField(p: *Parse) !Node.Index {...@@ -927,27 +943,28 @@ fn expectContainerField(p: *Parse) !Node.Index {
927/// / IfStatement943/// / IfStatement
928/// / LabeledStatement944/// / LabeledStatement
929/// / SwitchExpr945/// / SwitchExpr
930/// / AssignExpr SEMICOLON946/// / VarDeclExprStatement
931fn parseStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {947fn 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| {
933949 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 }
941960
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 }
952969
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;
10131030
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}
10191039
1020 return null_node;1040/// ComptimeStatement
1041/// <- BlockExpr
1042/// / VarDeclExprStatement
1043fn 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}
10221054
1023fn 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);1058fn 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}
10301171
1031/// If a parse error occurs, reports an error, but then finds the next statement1172/// 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}
13471488
1348/// AssignExpr <- Expr (AssignOp Expr)?1489/// AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)?
1349///1490///
1350/// AssignOp1491/// AssignOp
1351/// <- ASTERISKEQUAL1492/// <- ASTERISKEQUAL
...@@ -1369,8 +1510,40 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index {...@@ -1369,8 +1510,40 @@ fn parseBlockExpr(p: *Parse) Error!Node.Index {
1369fn parseAssignExpr(p: *Parse) !Node.Index {1510fn 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}
13721515
1373 const tag: Node.Tag = switch (p.token_tags[p.tok_i]) {1516/// SingleAssignExpr <- Expr (AssignOp Expr)?
1517fn 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
1531fn 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
1545fn 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
1569fn 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}
14031601
1602fn 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
1404fn expectAssignExpr(p: *Parse) !Node.Index {1610fn 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}
43594359
...@@ -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),
168168
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 },
429429
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); // rparen979 return renderToken(ais, tree, datas[asm_input].rhs, space); // rparen
944}980}
945981
946fn renderVarDecl(gpa: Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDecl) Error!void {982fn 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); // pub993 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); // threadlocal1005 try renderToken(ais, tree, thread_local_token, Space.space); // threadlocal
961 }1006 }
9621007
963 if (var_decl.comptime_token) |comptime_token| {1008 if (!ignore_comptime_token) {
964 try renderToken(ais, tree, comptime_token, Space.space); // comptime1009 if (var_decl.comptime_token) |comptime_token| {
1010 try renderToken(ais, tree, comptime_token, Space.space); // comptime
1011 }
965 }1012 }
9661013
967 try renderToken(ais, tree, var_decl.ast.mut_token, .space); // var1014 try renderToken(ais, tree, var_decl.ast.mut_token, .space); // var
9681015
969 const name_space = if (var_decl.ast.type_node == 0 and1016 if (var_decl.ast.type_node != 0 or var_decl.ast.align_node != 0 or
970 (var_decl.ast.align_node != 0 or1017 var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or
971 var_decl.ast.addrspace_node != 0 or1018 var_decl.ast.init_node != 0)
972 var_decl.ast.section_node != 0 or1019 {
973 var_decl.ast.init_node != 0))1020 const name_space = if (var_decl.ast.type_node == 0 and
974 Space.space1021 (var_decl.ast.align_node != 0 or
975 else1022 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); // name1024 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 }
9781033
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 }
9911044
...@@ -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 }
10081060
...@@ -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 }
10381089
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}
10521102
1053fn renderIf(gpa: Allocator, ais: *Ais, tree: Ast, if_node: Ast.full.If, space: Space) Error!void {1103fn 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 };
283297
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 }
302322
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,
400420
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 },
623644
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}
14831538
...@@ -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}
17121784
...@@ -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/pop2046 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).?),
23422415
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),
23452420
...@@ -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}
32293306
3307/// Handles destructure assignments where no LHS is a `const` or `var` decl.
3308fn 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.
3370fn 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
3230fn assignOp(3602fn 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}
983510241
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}
51805186
5187fn 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
5181fn failWithBadMemberAccess(5224fn 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}
1030610349
10350fn 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
10307fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {10361fn 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 entrypoint2308 block: Ref, // If restored, the index is from this block's entrypoint
2296 operand: Ref, // If non-error (or .none), then restore the index2309 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 },
22982317
2299 // Make sure we don't accidentally add a field to make this union2318 // Make sure we don't accidentally add a field to make this union
2300 // bigger than expected. Note that in Debug builds, Zig is allowed2319 // 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 };
23392359
...@@ -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};
32373266
3238pub const SpecialProng = enum { none, @"else", under };3267pub 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),
244244
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 {
357358
358 .for_len => try self.writePlNodeMultiOp(stream, inst),359 .for_len => try self.writePlNodeMultiOp(stream, inst),
359360
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),
361364
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 }
587590
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 }
894911
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 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const expect = std.testing.expect;
4
5test "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
23test "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
41test "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
59test "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
72test "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
85test "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}
16export fn e() void {
17 const x: u32, const y: u64 = @intCast(123);
18 _ = x + y;
19}
1620
17// error21// error
18// backend=stage222// 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 type30// :9:10: note: use @as to provide explicit result type
27// :14:13: error: @floatFromInt must have a known result type31// :14:13: error: @floatFromInt must have a known result type
28// :14:13: note: use @as to provide explicit result type32// :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 @@
1export 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 @@
1export fn foo() void {
2 const x, const y = .{ 1, 2, 3 };
3 _ = .{ x, y };
4}
5
6export 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 @@
1export fn foo() void {
2 const x, const y = 123;
3 _ = .{ x, y };
4}
5
6export 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 @@
1pub fn main() void {1pub fn main() void {
2 const x = 1;2 const x = 1;
3 const y, var z = .{ 2, 3 };
3}4}
45
5// error6// error
6//7//
8// :3:18: error: unused local variable
9// :3:11: error: unused local constant
7// :2:11: error: unused local constant10// :2:11: error: unused local constant