[dev.regabi] cmd/compile: report unused variables during typecheck

Unused variables are a type-checking error, so they should be reported
during typecheck rather than walk.

One catch is that we only want to report unused-variable errors for
functions that type check successfully, but some errors are reported
during noding, so we don't have an easy way to detect that
currently. As an approximate solution, we simply check if we've
reported any errors yet.

Passes toolstash -cmp.

Change-Id: I9400bfc94312c71d0c908a491e85c16d62224c9c
Reviewed-on: https://go-review.googlesource.com/c/go/+/280973
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2020-12-31 21:32:52 -08:00
parent fd22df9905
commit b8fd3440cd
2 changed files with 34 additions and 30 deletions

View File

@ -171,6 +171,7 @@ func FuncBody(n *ir.Func) {
decldepth = 1
errorsBefore := base.Errors()
Stmts(n.Body)
CheckUnused(n)
CheckReturn(n)
if base.Errors() > errorsBefore {
n.Body.Set(nil) // type errors; do not compile
@ -2203,6 +2204,39 @@ func isTermNode(n ir.Node) bool {
return false
}
// CheckUnused checks for any declared variables that weren't used.
func CheckUnused(fn *ir.Func) {
// Only report unused variables if we haven't seen any type-checking
// errors yet.
if base.Errors() != 0 {
return
}
// Propagate the used flag for typeswitch variables up to the NONAME in its definition.
for _, ln := range fn.Dcl {
if ln.Op() == ir.ONAME && ln.Class_ == ir.PAUTO && ln.Used() {
if guard, ok := ln.Defn.(*ir.TypeSwitchGuard); ok {
guard.Used = true
}
}
}
for _, ln := range fn.Dcl {
if ln.Op() != ir.ONAME || ln.Class_ != ir.PAUTO || ln.Used() {
continue
}
if defn, ok := ln.Defn.(*ir.TypeSwitchGuard); ok {
if defn.Used {
continue
}
base.ErrorfAt(defn.Tag.Pos(), "%v declared but not used", ln.Sym())
defn.Used = true // suppress repeats
} else {
base.ErrorfAt(ln.Pos(), "%v declared but not used", ln.Sym())
}
}
}
// CheckReturn makes sure that fn terminates appropriately.
func CheckReturn(fn *ir.Func) {
if fn.Type().NumResults() != 0 && len(fn.Body) != 0 {

View File

@ -37,36 +37,6 @@ func Walk(fn *ir.Func) {
lno := base.Pos
// Final typecheck for any unused variables.
for i, ln := range fn.Dcl {
if ln.Op() == ir.ONAME && (ln.Class_ == ir.PAUTO || ln.Class_ == ir.PAUTOHEAP) {
ln = typecheck.AssignExpr(ln).(*ir.Name)
fn.Dcl[i] = ln
}
}
// Propagate the used flag for typeswitch variables up to the NONAME in its definition.
for _, ln := range fn.Dcl {
if ln.Op() == ir.ONAME && (ln.Class_ == ir.PAUTO || ln.Class_ == ir.PAUTOHEAP) && ln.Defn != nil && ln.Defn.Op() == ir.OTYPESW && ln.Used() {
ln.Defn.(*ir.TypeSwitchGuard).Used = true
}
}
for _, ln := range fn.Dcl {
if ln.Op() != ir.ONAME || (ln.Class_ != ir.PAUTO && ln.Class_ != ir.PAUTOHEAP) || ln.Sym().Name[0] == '&' || ln.Used() {
continue
}
if defn, ok := ln.Defn.(*ir.TypeSwitchGuard); ok {
if defn.Used {
continue
}
base.ErrorfAt(defn.Tag.Pos(), "%v declared but not used", ln.Sym())
defn.Used = true // suppress repeats
} else {
base.ErrorfAt(ln.Pos(), "%v declared but not used", ln.Sym())
}
}
base.Pos = lno
if base.Errors() > errorsBefore {
return