cmd/compile/internal/types2: avoid "declared but not used" errors for invalid code

Agressively mark all LHS variables in assignments as used if there
is any error in the (entire) assignment. This reduces the number of
spurious "declared but not used" errors in programs that are invalid
in the first place. This behavior is closer to the behavior of the
compiler's original type checker (types1) and lets us remove lines
of the form "_ = variable" just to satisfy test cases. It also makes
more important errors visible by not crowding them out.

Remove the Checker.useLHS function and use Checker.use instead:
useLHS didn't evaluate top-level variables, but we actually want
them to be evaluated in an error scenario so that they are getting
used (and thus we don't get the "declared but not used" error).

Fixes #42937.

Change-Id: Idda460f6b81c66735bf9fd597c54188949bf12b8
Reviewed-on: https://go-review.googlesource.com/c/go/+/351730
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2021-09-22 21:57:19 -07:00
parent c0766d2cd0
commit ddb5a42b25
9 changed files with 57 additions and 71 deletions

View file

@ -156,6 +156,7 @@ func (check *Checker) initVar(lhs *Var, x *operand, context string) Type {
check.assignment(x, lhs.typ, context) check.assignment(x, lhs.typ, context)
if x.mode == invalid { if x.mode == invalid {
lhs.used = true // avoid follow-on "declared but not used" errors
return nil return nil
} }
@ -164,7 +165,7 @@ func (check *Checker) initVar(lhs *Var, x *operand, context string) Type {
func (check *Checker) assignVar(lhs syntax.Expr, x *operand) Type { func (check *Checker) assignVar(lhs syntax.Expr, x *operand) Type {
if x.mode == invalid || x.typ == Typ[Invalid] { if x.mode == invalid || x.typ == Typ[Invalid] {
check.useLHS(lhs) check.use(lhs)
return nil return nil
} }
@ -306,8 +307,18 @@ func (check *Checker) initVars(lhs []*Var, orig_rhs []syntax.Expr, returnPos syn
return return
} }
ok := true
for i, lhs := range lhs { for i, lhs := range lhs {
check.initVar(lhs, rhs[i], context) if check.initVar(lhs, rhs[i], context) == nil {
ok = false
}
}
// avoid follow-on "declared but not used" errors if any initialization failed
if !ok {
for _, lhs := range lhs {
lhs.used = true
}
} }
} }
@ -315,7 +326,7 @@ func (check *Checker) assignVars(lhs, orig_rhs []syntax.Expr) {
rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2) rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2)
if len(lhs) != len(rhs) { if len(lhs) != len(rhs) {
check.useLHS(lhs...) check.use(lhs...)
// don't report an error if we already reported one // don't report an error if we already reported one
for _, x := range rhs { for _, x := range rhs {
if x.mode == invalid { if x.mode == invalid {
@ -339,8 +350,26 @@ func (check *Checker) assignVars(lhs, orig_rhs []syntax.Expr) {
return return
} }
ok := true
for i, lhs := range lhs { for i, lhs := range lhs {
check.assignVar(lhs, rhs[i]) if check.assignVar(lhs, rhs[i]) == nil {
ok = false
}
}
// avoid follow-on "declared but not used" errors if any assignment failed
if !ok {
// don't call check.use to avoid re-evaluation of the lhs expressions
for _, lhs := range lhs {
if name, _ := unparen(lhs).(*syntax.Name); name != nil {
if obj := check.lookup(name.Value); obj != nil {
// see comment in assignVar
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg {
v.used = true
}
}
}
}
} }
} }
@ -371,7 +400,7 @@ func (check *Checker) shortVarDecl(pos syntax.Pos, lhs, rhs []syntax.Expr) {
for i, lhs := range lhs { for i, lhs := range lhs {
ident, _ := lhs.(*syntax.Name) ident, _ := lhs.(*syntax.Name)
if ident == nil { if ident == nil {
check.useLHS(lhs) check.use(lhs)
check.errorf(lhs, "non-name %s on left side of :=", lhs) check.errorf(lhs, "non-name %s on left side of :=", lhs)
hasErr = true hasErr = true
continue continue

View file

@ -627,48 +627,20 @@ Error:
func (check *Checker) use(arg ...syntax.Expr) { func (check *Checker) use(arg ...syntax.Expr) {
var x operand var x operand
for _, e := range arg { for _, e := range arg {
// Certain AST fields may legally be nil (e.g., the ast.SliceExpr.High field). switch n := e.(type) {
if e == nil { case nil:
// some AST fields may be nil (e.g., elements of syntax.SliceExpr.Index)
// TODO(gri) can those fields really make it here?
continue
case *syntax.Name:
// don't report an error evaluating blank
if n.Value == "_" {
continue continue
} }
if l, _ := e.(*syntax.ListExpr); l != nil { case *syntax.ListExpr:
check.use(l.ElemList...) check.use(n.ElemList...)
continue continue
} }
check.rawExpr(&x, e, nil, false) check.rawExpr(&x, e, nil, false)
} }
} }
// useLHS is like use, but doesn't "use" top-level identifiers.
// It should be called instead of use if the arguments are
// expressions on the lhs of an assignment.
// The arguments must not be nil.
func (check *Checker) useLHS(arg ...syntax.Expr) {
var x operand
for _, e := range arg {
// If the lhs is an identifier denoting a variable v, this assignment
// is not a 'use' of v. Remember current value of v.used and restore
// after evaluating the lhs via check.rawExpr.
var v *Var
var v_used bool
if ident, _ := unparen(e).(*syntax.Name); ident != nil {
// never type-check the blank name on the lhs
if ident.Value == "_" {
continue
}
if _, obj := check.scope.LookupParent(ident.Value, nopos); obj != nil {
// It's ok to mark non-local variables, but ignore variables
// from other packages to avoid potential race conditions with
// dot-imported variables.
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
v = w
v_used = v.used
}
}
}
check.rawExpr(&x, e, nil, false)
if v != nil {
v.used = v_used // restore v.used
}
}
}

View file

@ -654,9 +654,6 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) {
// declaration, but the post statement must not." // declaration, but the post statement must not."
if s, _ := s.Post.(*syntax.AssignStmt); s != nil && s.Op == syntax.Def { if s, _ := s.Post.(*syntax.AssignStmt); s != nil && s.Op == syntax.Def {
// The parser already reported an error. // The parser already reported an error.
// Don't call useLHS here because we want to use the lhs in
// this erroneous statement so that we don't get errors about
// these lhs variables being declared but not used.
check.use(s.Lhs) // avoid follow-up errors check.use(s.Lhs) // avoid follow-up errors
} }
check.stmt(inner, s.Body) check.stmt(inner, s.Body)

View file

@ -7,6 +7,5 @@
package main package main
func main() { func main() {
var s string = nil; // ERROR "illegal|invalid|incompatible|cannot" var s string = nil // ERROR "illegal|invalid|incompatible|cannot"
_ = s
} }

View file

@ -7,7 +7,6 @@
package main package main
func main() { func main() {
const a uint64 = 10; const a uint64 = 10
var b int64 = a; // ERROR "convert|cannot|incompatible" var b int64 = a // ERROR "convert|cannot|incompatible"
_ = b
} }

View file

@ -9,6 +9,5 @@ package main
func f() (int, bool) { return 0, true } func f() (int, bool) { return 0, true }
func main() { func main() {
x, y := f(), 2; // ERROR "multi|2-valued" x, y := f(), 2 // ERROR "multi|2-valued"
_, _ = x, y
} }

View file

@ -10,17 +10,13 @@ package main
func f1() { func f1() {
a, b := f() // ERROR "assignment mismatch|does not match|cannot initialize" a, b := f() // ERROR "assignment mismatch|does not match|cannot initialize"
_ = a
_ = b
} }
func f2() { func f2() {
var a, b int var a, b int
a, b = f() // ERROR "assignment mismatch|does not match|cannot assign" a, b = f() // ERROR "assignment mismatch|does not match|cannot assign"
_ = a
_ = b
} }
func f() int { func f() int {
return 1; return 1
} }

View file

@ -13,12 +13,10 @@ const zero = 0
func main() { func main() {
var x int var x int
_ = x
x = make(map[int]int) // ERROR "cannot use make\(map\[int\]int\)|incompatible" x = make(map[int]int) // ERROR "cannot use make\(map\[int\]int\)|incompatible"
x = make(map[int]int, 0) // ERROR "cannot use make\(map\[int\]int, 0\)|incompatible" x = make(map[int]int, 0) // ERROR "cannot use make\(map\[int\]int, 0\)|incompatible"
x = make(map[int]int, zero) // ERROR "cannot use make\(map\[int\]int, zero\)|incompatible" x = make(map[int]int, zero) // ERROR "cannot use make\(map\[int\]int, zero\)|incompatible"
x = make(chan int) // ERROR "cannot use make\(chan int\)|incompatible" x = make(chan int) // ERROR "cannot use make\(chan int\)|incompatible"
x = make(chan int, 0) // ERROR "cannot use make\(chan int, 0\)|incompatible" x = make(chan int, 0) // ERROR "cannot use make\(chan int, 0\)|incompatible"
x = make(chan int, zero) // ERROR "cannot use make\(chan int, zero\)|incompatible" x = make(chan int, zero) // ERROR "cannot use make\(chan int, zero\)|incompatible"
_ = x
} }

View file

@ -24,7 +24,6 @@ type Start struct {
func (start *Start) Next() *Inst { return nil } func (start *Start) Next() *Inst { return nil }
func AddInst(Inst) *Inst { func AddInst(Inst) *Inst {
print("ok in addinst\n") print("ok in addinst\n")
return nil return nil
@ -33,8 +32,6 @@ func AddInst(Inst) *Inst {
func main() { func main() {
print("call addinst\n") print("call addinst\n")
var x Inst = AddInst(new(Start)) // ERROR "pointer to interface|incompatible type" var x Inst = AddInst(new(Start)) // ERROR "pointer to interface|incompatible type"
_ = x
print("return from addinst\n") print("return from addinst\n")
var y *Inst = new(Start) // ERROR "pointer to interface|incompatible type" var y *Inst = new(Start) // ERROR "pointer to interface|incompatible type"
_ = y
} }