checker: add a check for unused `x << y` expressions (where x != array) (#12586)
parent
12585e88e1
commit
deaeffc4db
|
@ -85,6 +85,12 @@ pub mut:
|
|||
fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc
|
||||
ct_cond_stack []ast.Expr
|
||||
mut:
|
||||
stmt_level int // the nesting level inside each stmts list;
|
||||
// .stmt_level is used to check for `evaluated but not used` ExprStmts like `1 << 1`
|
||||
// 1 for statements directly at each inner scope level;
|
||||
// increases for `x := if cond { statement_list1} else {statement_list2}`;
|
||||
// increases for `x := optfn() or { statement_list3 }`;
|
||||
is_last_stmt bool
|
||||
files []ast.File
|
||||
expr_level int // to avoid infinite recursion segfaults due to compiler bugs
|
||||
inside_sql bool // to handle sql table fields pseudo variables
|
||||
|
@ -121,8 +127,40 @@ pub fn new_checker(table &ast.Table, pref &pref.Preferences) &Checker {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) reset_checker_state_at_start_of_new_file() {
|
||||
c.expected_type = ast.void_type
|
||||
c.expected_or_type = ast.void_type
|
||||
c.const_decl = ''
|
||||
c.in_for_count = 0
|
||||
c.returns = false
|
||||
c.scope_returns = false
|
||||
c.mod = ''
|
||||
c.is_builtin_mod = false
|
||||
c.inside_unsafe = false
|
||||
c.inside_const = false
|
||||
c.inside_anon_fn = false
|
||||
c.inside_ref_lit = false
|
||||
c.inside_defer = false
|
||||
c.inside_fn_arg = false
|
||||
c.inside_ct_attr = false
|
||||
c.skip_flags = false
|
||||
c.fn_level = 0
|
||||
c.expr_level = 0
|
||||
c.stmt_level = 0
|
||||
c.inside_sql = false
|
||||
c.cur_orm_ts = ast.TypeSymbol{}
|
||||
c.prevent_sum_type_unwrapping_once = false
|
||||
c.loop_label = ''
|
||||
c.using_new_err_struct = false
|
||||
c.inside_selector_expr = false
|
||||
c.inside_println_arg = false
|
||||
c.inside_decl_rhs = false
|
||||
c.inside_if_guard = false
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
||||
mut ast_file := ast_file_
|
||||
c.reset_checker_state_at_start_of_new_file()
|
||||
c.change_current_file(ast_file)
|
||||
for i, ast_import in ast_file.imports {
|
||||
for sym in ast_import.syms {
|
||||
|
@ -139,6 +177,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
|||
}
|
||||
}
|
||||
}
|
||||
c.stmt_level = 0
|
||||
for mut stmt in ast_file.stmts {
|
||||
if stmt in [ast.ConstDecl, ast.ExprStmt] {
|
||||
c.expr_level = 0
|
||||
|
@ -148,6 +187,8 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
|||
return
|
||||
}
|
||||
}
|
||||
//
|
||||
c.stmt_level = 0
|
||||
for mut stmt in ast_file.stmts {
|
||||
if stmt is ast.GlobalDecl {
|
||||
c.expr_level = 0
|
||||
|
@ -157,6 +198,8 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
|||
return
|
||||
}
|
||||
}
|
||||
//
|
||||
c.stmt_level = 0
|
||||
for mut stmt in ast_file.stmts {
|
||||
if stmt !is ast.ConstDecl && stmt !is ast.GlobalDecl && stmt !is ast.ExprStmt {
|
||||
c.expr_level = 0
|
||||
|
@ -166,6 +209,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
|||
return
|
||||
}
|
||||
}
|
||||
//
|
||||
c.check_scope_vars(c.file.scope)
|
||||
}
|
||||
|
||||
|
@ -4654,7 +4698,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
|||
}
|
||||
}
|
||||
c.inside_defer = true
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.inside_defer = false
|
||||
}
|
||||
ast.EnumDecl {
|
||||
|
@ -4679,6 +4723,16 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
|||
}
|
||||
else {}
|
||||
}
|
||||
if !c.pref.is_repl && (c.stmt_level == 1 || (c.stmt_level > 1 && !c.is_last_stmt)) {
|
||||
if node.expr is ast.InfixExpr {
|
||||
if node.expr.op == .left_shift {
|
||||
left_sym := c.table.get_final_type_symbol(node.expr.left_type)
|
||||
if left_sym.kind != .array {
|
||||
c.error('unused expression', node.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.check_expr_opt_call(node.expr, or_typ)
|
||||
// TODO This should work, even if it's prolly useless .-.
|
||||
// node.typ = c.check_expr_opt_call(node.expr, ast.void_type)
|
||||
|
@ -4757,10 +4811,10 @@ fn (mut c Checker) assert_stmt(node ast.AssertStmt) {
|
|||
fn (mut c Checker) block(node ast.Block) {
|
||||
if node.is_unsafe {
|
||||
c.inside_unsafe = true
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.inside_unsafe = false
|
||||
} else {
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4789,7 +4843,7 @@ fn (mut c Checker) for_c_stmt(node ast.ForCStmt) {
|
|||
c.stmt(node.inc)
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
@ -4803,7 +4857,7 @@ fn (mut c Checker) comptime_for(node ast.ComptimeFor) {
|
|||
if node.kind == .fields {
|
||||
c.comptime_fields_type[node.val_var] = node.typ
|
||||
}
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
|
||||
|
@ -4913,7 +4967,7 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
|
|||
}
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
@ -4949,7 +5003,7 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
|
|||
// TODO: update loop var type
|
||||
// how does this work currenly?
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
@ -5252,12 +5306,24 @@ fn (mut c Checker) import_stmt(node ast.Import) {
|
|||
}
|
||||
}
|
||||
|
||||
// stmts_list is the same as .stmts(), but it should be called for top level statements in the inner scope
|
||||
fn (mut c Checker) stmts_list(stmts []ast.Stmt) {
|
||||
old_stmt_level := c.stmt_level
|
||||
c.stmt_level = 0
|
||||
c.stmts(stmts)
|
||||
c.stmt_level = old_stmt_level
|
||||
}
|
||||
|
||||
// stmts processes a list of statements. It can be called even for a list of statements that end with an expression,
|
||||
// For example for the or block in `x := opt() or { stmt1 stmt2 ExprStmt }`
|
||||
fn (mut c Checker) stmts(stmts []ast.Stmt) {
|
||||
mut unreachable := token.Position{
|
||||
line_nr: -1
|
||||
}
|
||||
c.expected_type = ast.void_type
|
||||
for stmt in stmts {
|
||||
c.stmt_level++
|
||||
for i, stmt in stmts {
|
||||
c.is_last_stmt = i == stmts.len - 1
|
||||
if c.scope_returns {
|
||||
if unreachable.line_nr == -1 {
|
||||
unreachable = stmt.pos
|
||||
|
@ -5271,6 +5337,7 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
|
|||
c.scope_returns = false
|
||||
}
|
||||
}
|
||||
c.stmt_level--
|
||||
if unreachable.line_nr >= 0 {
|
||||
c.error('unreachable code', unreachable)
|
||||
}
|
||||
|
@ -6171,7 +6238,11 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
|
|||
mut nbranches_with_return := 0
|
||||
mut nbranches_without_return := 0
|
||||
for branch in node.branches {
|
||||
if node.is_expr {
|
||||
c.stmts(branch.stmts)
|
||||
} else {
|
||||
c.stmts_list(branch.stmts)
|
||||
}
|
||||
if node.is_expr {
|
||||
if branch.stmts.len > 0 {
|
||||
// ignore last statement - workaround
|
||||
|
@ -6616,7 +6687,7 @@ pub fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type {
|
|||
}
|
||||
}
|
||||
}
|
||||
c.stmts(branch.stmts)
|
||||
c.stmts_list(branch.stmts)
|
||||
}
|
||||
return ast.bool_type
|
||||
}
|
||||
|
@ -6644,7 +6715,7 @@ pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type {
|
|||
c.locked_names << id_name
|
||||
}
|
||||
}
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
c.rlocked_names = []
|
||||
c.locked_names = []
|
||||
// handle `x := rlock a { a.getval() }`
|
||||
|
@ -6817,7 +6888,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
|||
c.ct_cond_stack << branch.cond
|
||||
}
|
||||
if !c.skip_flags {
|
||||
if node_is_expr {
|
||||
c.stmts(branch.stmts)
|
||||
} else {
|
||||
c.stmts_list(branch.stmts)
|
||||
}
|
||||
} else if c.pref.output_cross_c {
|
||||
mut is_freestanding_block := false
|
||||
if branch.cond is ast.Ident {
|
||||
|
@ -6829,7 +6904,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
|||
branch.stmts = []
|
||||
node.branches[i].stmts = []
|
||||
}
|
||||
if node_is_expr {
|
||||
c.stmts(branch.stmts)
|
||||
} else {
|
||||
c.stmts_list(branch.stmts)
|
||||
}
|
||||
} else if !is_comptime_type_is_expr {
|
||||
node.branches[i].stmts = []
|
||||
}
|
||||
|
@ -6843,7 +6922,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
|||
} else {
|
||||
// smartcast sumtypes and interfaces when using `is`
|
||||
c.smartcast_if_conds(branch.cond, mut branch.scope)
|
||||
if node_is_expr {
|
||||
c.stmts(branch.stmts)
|
||||
} else {
|
||||
c.stmts_list(branch.stmts)
|
||||
}
|
||||
}
|
||||
if expr_required {
|
||||
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
|
||||
|
@ -8107,12 +8190,14 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
|||
prev_inside_unsafe := c.inside_unsafe
|
||||
prev_inside_anon_fn := c.inside_anon_fn
|
||||
prev_returns := c.returns
|
||||
prev_stmt_level := c.stmt_level
|
||||
c.fn_level++
|
||||
c.in_for_count = 0
|
||||
c.inside_defer = false
|
||||
c.inside_unsafe = false
|
||||
c.returns = false
|
||||
defer {
|
||||
c.stmt_level = prev_stmt_level
|
||||
c.fn_level--
|
||||
c.returns = prev_returns
|
||||
c.inside_anon_fn = prev_inside_anon_fn
|
||||
|
@ -8372,7 +8457,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
|||
}
|
||||
}
|
||||
c.fn_scope = node.scope
|
||||
c.stmts(node.stmts)
|
||||
c.stmts_list(node.stmts)
|
||||
node_has_top_return := has_top_return(node.stmts)
|
||||
node.has_return = c.returns || node_has_top_return
|
||||
c.check_noreturn_fn_decl(mut node)
|
||||
|
@ -8406,7 +8491,7 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type {
|
|||
}
|
||||
var.typ = parent_var.typ
|
||||
}
|
||||
c.stmts(node.decl.stmts)
|
||||
c.stmts_list(node.decl.stmts)
|
||||
c.fn_decl(mut node.decl)
|
||||
return node.typ
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:4:2: error: unused expression
|
||||
2 | mut a := 12
|
||||
3 | mut arr := []int{}
|
||||
4 | a << 1
|
||||
| ~~~~~~
|
||||
5 | if true {
|
||||
6 | a << 2
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:6:3: error: unused expression
|
||||
4 | a << 1
|
||||
5 | if true {
|
||||
6 | a << 2
|
||||
| ~~~~~~
|
||||
7 | }
|
||||
8 | c := if true { a << 111 } else { a << 333 }
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:10:2: error: unused expression
|
||||
8 | c := if true { a << 111 } else { a << 333 }
|
||||
9 | println(c)
|
||||
10 | a << 1
|
||||
| ~~~~~~
|
||||
11 | println(a)
|
||||
12 | 5 << 9
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:12:2: error: unused expression
|
||||
10 | a << 1
|
||||
11 | println(a)
|
||||
12 | 5 << 9
|
||||
| ~~~~~~
|
||||
13 | for i in 0 .. 10 {
|
||||
14 | z := i << 5
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:15:3: error: unused expression
|
||||
13 | for i in 0 .. 10 {
|
||||
14 | z := i << 5
|
||||
15 | i << 5
|
||||
| ~~~~~~
|
||||
16 | println(z)
|
||||
17 | }
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:33:3: error: unused expression
|
||||
31 | //
|
||||
32 | x := if true {
|
||||
33 | a << 1
|
||||
| ~~~~~~
|
||||
34 | 999
|
||||
35 | } else {
|
||||
vlib/v/checker/tests/left_shift_op_expr_not_used.vv:37:3: error: unused expression
|
||||
35 | } else {
|
||||
36 | println('---')
|
||||
37 | a << 9999
|
||||
| ~~~~~~~~~
|
||||
38 | println('---')
|
||||
39 | 555
|
|
@ -0,0 +1,42 @@
|
|||
fn main() {
|
||||
mut a := 12
|
||||
mut arr := []int{}
|
||||
a << 1
|
||||
if true {
|
||||
a << 2
|
||||
}
|
||||
c := if true { a << 111 } else { a << 333 }
|
||||
println(c)
|
||||
a << 1
|
||||
println(a)
|
||||
5 << 9
|
||||
for i in 0 .. 10 {
|
||||
z := i << 5
|
||||
i << 5
|
||||
println(z)
|
||||
}
|
||||
//
|
||||
arr << 1
|
||||
if true {
|
||||
arr << 2
|
||||
}
|
||||
d := if true {
|
||||
arr << 111
|
||||
777
|
||||
} else {
|
||||
arr << 333
|
||||
999
|
||||
}
|
||||
println(d)
|
||||
//
|
||||
x := if true {
|
||||
a << 1
|
||||
999
|
||||
} else {
|
||||
println('---')
|
||||
a << 9999
|
||||
println('---')
|
||||
555
|
||||
}
|
||||
println(x)
|
||||
}
|
Loading…
Reference in New Issue