authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-08-02 20:44:14+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-08-03 16:45:33+03:00
logd1d24b426dd8f12e6d643f45fcb6bb11dddaa8ef
tree11db2665f9af9c29081145c0c74d9728731ffe7c
parentaa78ebaf95af5a3587194d8dbcb101a49c0bb898

AstGen: check loop bodies and else branches for unused result


5 files changed, 133 insertions(+), 52 deletions(-)

src/AstGen.zig+55-12
...@@ -768,12 +768,12 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr...@@ -768,12 +768,12 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
768 .if_simple => return ifExpr(gz, scope, rl.br(), node, tree.ifSimple(node)),768 .if_simple => return ifExpr(gz, scope, rl.br(), node, tree.ifSimple(node)),
769 .@"if" => return ifExpr(gz, scope, rl.br(), node, tree.ifFull(node)),769 .@"if" => return ifExpr(gz, scope, rl.br(), node, tree.ifFull(node)),
770770
771 .while_simple => return whileExpr(gz, scope, rl.br(), node, tree.whileSimple(node)),771 .while_simple => return whileExpr(gz, scope, rl.br(), node, tree.whileSimple(node), false),
772 .while_cont => return whileExpr(gz, scope, rl.br(), node, tree.whileCont(node)),772 .while_cont => return whileExpr(gz, scope, rl.br(), node, tree.whileCont(node), false),
773 .@"while" => return whileExpr(gz, scope, rl.br(), node, tree.whileFull(node)),773 .@"while" => return whileExpr(gz, scope, rl.br(), node, tree.whileFull(node), false),
774774
775 .for_simple => return forExpr(gz, scope, rl.br(), node, tree.forSimple(node)),775 .for_simple => return forExpr(gz, scope, rl.br(), node, tree.forSimple(node), false),
776 .@"for" => return forExpr(gz, scope, rl.br(), node, tree.forFull(node)),776 .@"for" => return forExpr(gz, scope, rl.br(), node, tree.forFull(node), false),
777777
778 .slice_open => {778 .slice_open => {
779 const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);779 const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);
...@@ -2152,6 +2152,7 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod...@@ -2152,6 +2152,7 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
2152 const astgen = gz.astgen;2152 const astgen = gz.astgen;
2153 const tree = astgen.tree;2153 const tree = astgen.tree;
2154 const node_tags = tree.nodes.items(.tag);2154 const node_tags = tree.nodes.items(.tag);
2155 const node_data = tree.nodes.items(.data);
21552156
2156 if (statements.len == 0) return;2157 if (statements.len == 0) return;
21572158
...@@ -2178,8 +2179,10 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod...@@ -2178,8 +2179,10 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
2178 },2179 },
2179 );2180 );
2180 }2181 }
2181 switch (node_tags[statement]) {2182 var inner_node = statement;
2182 // zig fmt: off2183 while (true) {
2184 switch (node_tags[inner_node]) {
2185 // zig fmt: off
2183 .global_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.globalVarDecl(statement)),2186 .global_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.globalVarDecl(statement)),
2184 .local_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.localVarDecl(statement)),2187 .local_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.localVarDecl(statement)),
2185 .simple_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.simpleVarDecl(statement)),2188 .simple_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.simpleVarDecl(statement)),
...@@ -2204,9 +2207,23 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod...@@ -2204,9 +2207,23 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
2204 .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap),2207 .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap),
2205 .assign_mul => try assignOp(gz, scope, statement, .mul),2208 .assign_mul => try assignOp(gz, scope, statement, .mul),
2206 .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap),2209 .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap),
2210
2211 .grouped_expression => {
2212 inner_node = node_data[statement].lhs;
2213 continue;
2214 },
22072215
2208 else => noreturn_src_node = try unusedResultExpr(gz, scope, statement),2216 .while_simple => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileSimple(inner_node), true),
2217 .while_cont => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileCont(inner_node), true),
2218 .@"while" => _ = try whileExpr(gz, scope, .discard, inner_node, tree.whileFull(inner_node), true),
2219
2220 .for_simple => _ = try forExpr(gz, scope, .discard, inner_node, tree.forSimple(inner_node), true),
2221 .@"for" => _ = try forExpr(gz, scope, .discard, inner_node, tree.forFull(inner_node), true),
2222
2223 else => noreturn_src_node = try unusedResultExpr(gz, scope, inner_node),
2209 // zig fmt: on2224 // zig fmt: on
2225 }
2226 break;
2210 }2227 }
2211 }2228 }
22122229
...@@ -2245,6 +2262,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2245,6 +2262,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2245 // We need to emit an error if the result is not `noreturn` or `void`, but2262 // We need to emit an error if the result is not `noreturn` or `void`, but
2246 // we want to avoid adding the ZIR instruction if possible for performance.2263 // we want to avoid adding the ZIR instruction if possible for performance.
2247 const maybe_unused_result = try expr(gz, scope, .none, statement);2264 const maybe_unused_result = try expr(gz, scope, .none, statement);
2265 return addEnsureResult(gz, maybe_unused_result, statement);
2266}
2267
2268fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: Ast.Node.Index) InnerError!Ast.Node.Index {
2248 var noreturn_src_node: Ast.Node.Index = 0;2269 var noreturn_src_node: Ast.Node.Index = 0;
2249 const elide_check = if (refToIndex(maybe_unused_result)) |inst| b: {2270 const elide_check = if (refToIndex(maybe_unused_result)) |inst| b: {
2250 // Note that this array becomes invalid after appending more items to it2271 // Note that this array becomes invalid after appending more items to it
...@@ -5648,6 +5669,7 @@ fn whileExpr(...@@ -5648,6 +5669,7 @@ fn whileExpr(
5648 rl: ResultLoc,5669 rl: ResultLoc,
5649 node: Ast.Node.Index,5670 node: Ast.Node.Index,
5650 while_full: Ast.full.While,5671 while_full: Ast.full.While,
5672 is_statement: bool,
5651) InnerError!Zir.Inst.Ref {5673) InnerError!Zir.Inst.Ref {
5652 const astgen = parent_gz.astgen;5674 const astgen = parent_gz.astgen;
5653 const tree = astgen.tree;5675 const tree = astgen.tree;
...@@ -5818,6 +5840,8 @@ fn whileExpr(...@@ -5818,6 +5840,8 @@ fn whileExpr(
5818 try then_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);5840 try then_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);
5819 }5841 }
5820 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);5842 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
5843 _ = try addEnsureResult(&then_scope, then_result, while_full.ast.then_expr);
5844
5821 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);5845 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
5822 try then_scope.addDbgBlockEnd();5846 try then_scope.addDbgBlockEnd();
58235847
...@@ -5860,7 +5884,11 @@ fn whileExpr(...@@ -5860,7 +5884,11 @@ fn whileExpr(
5860 // control flow apply to outer loops; not this one.5884 // control flow apply to outer loops; not this one.
5861 loop_scope.continue_block = 0;5885 loop_scope.continue_block = 0;
5862 loop_scope.break_block = 0;5886 loop_scope.break_block = 0;
5863 const e = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);5887 const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
5888 if (is_statement) {
5889 _ = try addEnsureResult(&else_scope, else_result, else_node);
5890 }
5891
5864 if (!else_scope.endsWithNoReturn()) {5892 if (!else_scope.endsWithNoReturn()) {
5865 loop_scope.break_count += 1;5893 loop_scope.break_count += 1;
5866 }5894 }
...@@ -5868,7 +5896,7 @@ fn whileExpr(...@@ -5868,7 +5896,7 @@ fn whileExpr(
5868 try else_scope.addDbgBlockEnd();5896 try else_scope.addDbgBlockEnd();
5869 break :blk .{5897 break :blk .{
5870 .src = else_node,5898 .src = else_node,
5871 .result = e,5899 .result = else_result,
5872 };5900 };
5873 } else .{5901 } else .{
5874 .src = while_full.ast.then_expr,5902 .src = while_full.ast.then_expr,
...@@ -5881,7 +5909,7 @@ fn whileExpr(...@@ -5881,7 +5909,7 @@ fn whileExpr(
5881 }5909 }
5882 }5910 }
5883 const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";5911 const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
5884 return finishThenElseBlock(5912 const result = try finishThenElseBlock(
5885 parent_gz,5913 parent_gz,
5886 rl,5914 rl,
5887 node,5915 node,
...@@ -5896,6 +5924,10 @@ fn whileExpr(...@@ -5896,6 +5924,10 @@ fn whileExpr(
5896 cond_block,5924 cond_block,
5897 break_tag,5925 break_tag,
5898 );5926 );
5927 if (is_statement) {
5928 _ = try parent_gz.addUnNode(.ensure_result_used, result, node);
5929 }
5930 return result;
5899}5931}
59005932
5901fn forExpr(5933fn forExpr(
...@@ -5904,6 +5936,7 @@ fn forExpr(...@@ -5904,6 +5936,7 @@ fn forExpr(
5904 rl: ResultLoc,5936 rl: ResultLoc,
5905 node: Ast.Node.Index,5937 node: Ast.Node.Index,
5906 for_full: Ast.full.While,5938 for_full: Ast.full.While,
5939 is_statement: bool,
5907) InnerError!Zir.Inst.Ref {5940) InnerError!Zir.Inst.Ref {
5908 const astgen = parent_gz.astgen;5941 const astgen = parent_gz.astgen;
59095942
...@@ -6047,6 +6080,8 @@ fn forExpr(...@@ -6047,6 +6080,8 @@ fn forExpr(
6047 };6080 };
60486081
6049 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);6082 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
6083 _ = try addEnsureResult(&then_scope, then_result, for_full.ast.then_expr);
6084
6050 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);6085 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
6051 try then_scope.addDbgBlockEnd();6086 try then_scope.addDbgBlockEnd();
60526087
...@@ -6064,6 +6099,10 @@ fn forExpr(...@@ -6064,6 +6099,10 @@ fn forExpr(
6064 loop_scope.continue_block = 0;6099 loop_scope.continue_block = 0;
6065 loop_scope.break_block = 0;6100 loop_scope.break_block = 0;
6066 const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);6101 const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
6102 if (is_statement) {
6103 _ = try addEnsureResult(&else_scope, else_result, else_node);
6104 }
6105
6067 if (!else_scope.endsWithNoReturn()) {6106 if (!else_scope.endsWithNoReturn()) {
6068 loop_scope.break_count += 1;6107 loop_scope.break_count += 1;
6069 }6108 }
...@@ -6082,7 +6121,7 @@ fn forExpr(...@@ -6082,7 +6121,7 @@ fn forExpr(
6082 }6121 }
6083 }6122 }
6084 const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";6123 const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
6085 return finishThenElseBlock(6124 const result = try finishThenElseBlock(
6086 parent_gz,6125 parent_gz,
6087 rl,6126 rl,
6088 node,6127 node,
...@@ -6097,6 +6136,10 @@ fn forExpr(...@@ -6097,6 +6136,10 @@ fn forExpr(
6097 cond_block,6136 cond_block,
6098 break_tag,6137 break_tag,
6099 );6138 );
6139 if (is_statement) {
6140 _ = try parent_gz.addUnNode(.ensure_result_used, result, node);
6141 }
6142 return result;
6100}6143}
61016144
6102fn switchExpr(6145fn switchExpr(
test/cases/compile_errors/for_loop_body_expression_ignored.zig created+35
...@@ -0,0 +1,35 @@
1fn returns() usize {
2 return 2;
3}
4export fn f1() void {
5 for ("hello") |_| returns();
6}
7export fn f2() void {
8 var x: anyerror!i32 = error.Bad;
9 for ("hello") |_| returns() else unreachable;
10 _ = x;
11}
12export fn f3() void {
13 for ("hello") |_| {} else true;
14}
15export fn f4() void {
16 const foo = for ("hello") |_| returns() else true;
17 _ = foo;
18}
19
20// error
21// backend=stage2
22// target=native
23//
24// :5:30: error: value of type 'usize' ignored
25// :5:30: note: all non-void values must be used
26// :5:30: note: this error can be suppressed by assigning the value to '_'
27// :9:30: error: value of type 'usize' ignored
28// :9:30: note: all non-void values must be used
29// :9:30: note: this error can be suppressed by assigning the value to '_'
30// :13:31: error: value of type 'bool' ignored
31// :13:31: note: all non-void values must be used
32// :13:31: note: this error can be suppressed by assigning the value to '_'
33// :16:42: error: value of type 'usize' ignored
34// :16:42: note: all non-void values must be used
35// :16:42: note: this error can be suppressed by assigning the value to '_'
test/cases/compile_errors/stage1/obj/for_loop_body_expression_ignored.zig deleted-18
...@@ -1,18 +0,0 @@
1fn returns() usize {
2 return 2;
3}
4export fn f1() void {
5 for ("hello") |_| returns();
6}
7export fn f2() void {
8 var x: anyerror!i32 = error.Bad;
9 for ("hello") |_| returns() else unreachable;
10 _ = x;
11}
12
13// error
14// backend=stage1
15// target=native
16//
17// tmp.zig:5:30: error: expression value is ignored
18// tmp.zig:9:30: error: expression value is ignored
test/cases/compile_errors/stage1/obj/while_loop_body_expression_ignored.zig deleted-22
...@@ -1,22 +0,0 @@
1fn returns() usize {
2 return 2;
3}
4export fn f1() void {
5 while (true) returns();
6}
7export fn f2() void {
8 var x: ?i32 = null;
9 while (x) |_| returns();
10}
11export fn f3() void {
12 var x: anyerror!i32 = error.Bad;
13 while (x) |_| returns() else |_| unreachable;
14}
15
16// error
17// backend=stage1
18// target=native
19//
20// tmp.zig:5:25: error: expression value is ignored
21// tmp.zig:9:26: error: expression value is ignored
22// tmp.zig:13:26: error: expression value is ignored
test/cases/compile_errors/while_loop_body_expression_ignored.zig created+43
...@@ -0,0 +1,43 @@
1fn returns() usize {
2 return 2;
3}
4export fn f1() void {
5 while (true) returns();
6}
7export fn f2() void {
8 var x: ?i32 = null;
9 while (x) |_| returns();
10}
11export fn f3() void {
12 var x: anyerror!i32 = error.Bad;
13 while (x) |_| returns() else |_| unreachable;
14}
15export fn f4() void {
16 var a = true;
17 while (a) {} else true;
18}
19export fn f5() void {
20 var a = true;
21 const foo = while (a) returns() else true;
22 _ = foo;
23}
24
25// error
26// backend=stage2
27// target=native
28//
29// :5:25: error: value of type 'usize' ignored
30// :5:25: note: all non-void values must be used
31// :5:25: note: this error can be suppressed by assigning the value to '_'
32// :9:26: error: value of type 'usize' ignored
33// :9:26: note: all non-void values must be used
34// :9:26: note: this error can be suppressed by assigning the value to '_'
35// :13:26: error: value of type 'usize' ignored
36// :13:26: note: all non-void values must be used
37// :13:26: note: this error can be suppressed by assigning the value to '_'
38// :17:23: error: value of type 'bool' ignored
39// :17:23: note: all non-void values must be used
40// :17:23: note: this error can be suppressed by assigning the value to '_'
41// :21:34: error: value of type 'usize' ignored
42// :21:34: note: all non-void values must be used
43// :21:34: note: this error can be suppressed by assigning the value to '_'