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
|
fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc
|
||||||
ct_cond_stack []ast.Expr
|
ct_cond_stack []ast.Expr
|
||||||
mut:
|
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
|
files []ast.File
|
||||||
expr_level int // to avoid infinite recursion segfaults due to compiler bugs
|
expr_level int // to avoid infinite recursion segfaults due to compiler bugs
|
||||||
inside_sql bool // to handle sql table fields pseudo variables
|
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) {
|
pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
||||||
mut ast_file := ast_file_
|
mut ast_file := ast_file_
|
||||||
|
c.reset_checker_state_at_start_of_new_file()
|
||||||
c.change_current_file(ast_file)
|
c.change_current_file(ast_file)
|
||||||
for i, ast_import in ast_file.imports {
|
for i, ast_import in ast_file.imports {
|
||||||
for sym in ast_import.syms {
|
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 {
|
for mut stmt in ast_file.stmts {
|
||||||
if stmt in [ast.ConstDecl, ast.ExprStmt] {
|
if stmt in [ast.ConstDecl, ast.ExprStmt] {
|
||||||
c.expr_level = 0
|
c.expr_level = 0
|
||||||
|
@ -148,6 +187,8 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
c.stmt_level = 0
|
||||||
for mut stmt in ast_file.stmts {
|
for mut stmt in ast_file.stmts {
|
||||||
if stmt is ast.GlobalDecl {
|
if stmt is ast.GlobalDecl {
|
||||||
c.expr_level = 0
|
c.expr_level = 0
|
||||||
|
@ -157,6 +198,8 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
c.stmt_level = 0
|
||||||
for mut stmt in ast_file.stmts {
|
for mut stmt in ast_file.stmts {
|
||||||
if stmt !is ast.ConstDecl && stmt !is ast.GlobalDecl && stmt !is ast.ExprStmt {
|
if stmt !is ast.ConstDecl && stmt !is ast.GlobalDecl && stmt !is ast.ExprStmt {
|
||||||
c.expr_level = 0
|
c.expr_level = 0
|
||||||
|
@ -166,6 +209,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
c.check_scope_vars(c.file.scope)
|
c.check_scope_vars(c.file.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4654,7 +4698,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.inside_defer = true
|
c.inside_defer = true
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.inside_defer = false
|
c.inside_defer = false
|
||||||
}
|
}
|
||||||
ast.EnumDecl {
|
ast.EnumDecl {
|
||||||
|
@ -4679,6 +4723,16 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
||||||
}
|
}
|
||||||
else {}
|
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)
|
c.check_expr_opt_call(node.expr, or_typ)
|
||||||
// TODO This should work, even if it's prolly useless .-.
|
// TODO This should work, even if it's prolly useless .-.
|
||||||
// node.typ = c.check_expr_opt_call(node.expr, ast.void_type)
|
// 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) {
|
fn (mut c Checker) block(node ast.Block) {
|
||||||
if node.is_unsafe {
|
if node.is_unsafe {
|
||||||
c.inside_unsafe = true
|
c.inside_unsafe = true
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.inside_unsafe = false
|
c.inside_unsafe = false
|
||||||
} else {
|
} 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.stmt(node.inc)
|
||||||
}
|
}
|
||||||
c.check_loop_label(node.label, node.pos)
|
c.check_loop_label(node.label, node.pos)
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.loop_label = prev_loop_label
|
c.loop_label = prev_loop_label
|
||||||
c.in_for_count--
|
c.in_for_count--
|
||||||
}
|
}
|
||||||
|
@ -4803,7 +4857,7 @@ fn (mut c Checker) comptime_for(node ast.ComptimeFor) {
|
||||||
if node.kind == .fields {
|
if node.kind == .fields {
|
||||||
c.comptime_fields_type[node.val_var] = node.typ
|
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) {
|
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.check_loop_label(node.label, node.pos)
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.loop_label = prev_loop_label
|
c.loop_label = prev_loop_label
|
||||||
c.in_for_count--
|
c.in_for_count--
|
||||||
}
|
}
|
||||||
|
@ -4949,7 +5003,7 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
|
||||||
// TODO: update loop var type
|
// TODO: update loop var type
|
||||||
// how does this work currenly?
|
// how does this work currenly?
|
||||||
c.check_loop_label(node.label, node.pos)
|
c.check_loop_label(node.label, node.pos)
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.loop_label = prev_loop_label
|
c.loop_label = prev_loop_label
|
||||||
c.in_for_count--
|
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) {
|
fn (mut c Checker) stmts(stmts []ast.Stmt) {
|
||||||
mut unreachable := token.Position{
|
mut unreachable := token.Position{
|
||||||
line_nr: -1
|
line_nr: -1
|
||||||
}
|
}
|
||||||
c.expected_type = ast.void_type
|
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 c.scope_returns {
|
||||||
if unreachable.line_nr == -1 {
|
if unreachable.line_nr == -1 {
|
||||||
unreachable = stmt.pos
|
unreachable = stmt.pos
|
||||||
|
@ -5271,6 +5337,7 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
|
||||||
c.scope_returns = false
|
c.scope_returns = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.stmt_level--
|
||||||
if unreachable.line_nr >= 0 {
|
if unreachable.line_nr >= 0 {
|
||||||
c.error('unreachable code', unreachable)
|
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_with_return := 0
|
||||||
mut nbranches_without_return := 0
|
mut nbranches_without_return := 0
|
||||||
for branch in node.branches {
|
for branch in node.branches {
|
||||||
c.stmts(branch.stmts)
|
if node.is_expr {
|
||||||
|
c.stmts(branch.stmts)
|
||||||
|
} else {
|
||||||
|
c.stmts_list(branch.stmts)
|
||||||
|
}
|
||||||
if node.is_expr {
|
if node.is_expr {
|
||||||
if branch.stmts.len > 0 {
|
if branch.stmts.len > 0 {
|
||||||
// ignore last statement - workaround
|
// 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
|
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.locked_names << id_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
c.rlocked_names = []
|
c.rlocked_names = []
|
||||||
c.locked_names = []
|
c.locked_names = []
|
||||||
// handle `x := rlock a { a.getval() }`
|
// 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
|
c.ct_cond_stack << branch.cond
|
||||||
}
|
}
|
||||||
if !c.skip_flags {
|
if !c.skip_flags {
|
||||||
c.stmts(branch.stmts)
|
if node_is_expr {
|
||||||
|
c.stmts(branch.stmts)
|
||||||
|
} else {
|
||||||
|
c.stmts_list(branch.stmts)
|
||||||
|
}
|
||||||
} else if c.pref.output_cross_c {
|
} else if c.pref.output_cross_c {
|
||||||
mut is_freestanding_block := false
|
mut is_freestanding_block := false
|
||||||
if branch.cond is ast.Ident {
|
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 = []
|
branch.stmts = []
|
||||||
node.branches[i].stmts = []
|
node.branches[i].stmts = []
|
||||||
}
|
}
|
||||||
c.stmts(branch.stmts)
|
if node_is_expr {
|
||||||
|
c.stmts(branch.stmts)
|
||||||
|
} else {
|
||||||
|
c.stmts_list(branch.stmts)
|
||||||
|
}
|
||||||
} else if !is_comptime_type_is_expr {
|
} else if !is_comptime_type_is_expr {
|
||||||
node.branches[i].stmts = []
|
node.branches[i].stmts = []
|
||||||
}
|
}
|
||||||
|
@ -6843,7 +6922,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
||||||
} else {
|
} else {
|
||||||
// smartcast sumtypes and interfaces when using `is`
|
// smartcast sumtypes and interfaces when using `is`
|
||||||
c.smartcast_if_conds(branch.cond, mut branch.scope)
|
c.smartcast_if_conds(branch.cond, mut branch.scope)
|
||||||
c.stmts(branch.stmts)
|
if node_is_expr {
|
||||||
|
c.stmts(branch.stmts)
|
||||||
|
} else {
|
||||||
|
c.stmts_list(branch.stmts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if expr_required {
|
if expr_required {
|
||||||
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
|
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_unsafe := c.inside_unsafe
|
||||||
prev_inside_anon_fn := c.inside_anon_fn
|
prev_inside_anon_fn := c.inside_anon_fn
|
||||||
prev_returns := c.returns
|
prev_returns := c.returns
|
||||||
|
prev_stmt_level := c.stmt_level
|
||||||
c.fn_level++
|
c.fn_level++
|
||||||
c.in_for_count = 0
|
c.in_for_count = 0
|
||||||
c.inside_defer = false
|
c.inside_defer = false
|
||||||
c.inside_unsafe = false
|
c.inside_unsafe = false
|
||||||
c.returns = false
|
c.returns = false
|
||||||
defer {
|
defer {
|
||||||
|
c.stmt_level = prev_stmt_level
|
||||||
c.fn_level--
|
c.fn_level--
|
||||||
c.returns = prev_returns
|
c.returns = prev_returns
|
||||||
c.inside_anon_fn = prev_inside_anon_fn
|
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.fn_scope = node.scope
|
||||||
c.stmts(node.stmts)
|
c.stmts_list(node.stmts)
|
||||||
node_has_top_return := has_top_return(node.stmts)
|
node_has_top_return := has_top_return(node.stmts)
|
||||||
node.has_return = c.returns || node_has_top_return
|
node.has_return = c.returns || node_has_top_return
|
||||||
c.check_noreturn_fn_decl(mut node)
|
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
|
var.typ = parent_var.typ
|
||||||
}
|
}
|
||||||
c.stmts(node.decl.stmts)
|
c.stmts_list(node.decl.stmts)
|
||||||
c.fn_decl(mut node.decl)
|
c.fn_decl(mut node.decl)
|
||||||
return node.typ
|
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