// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package shadow defines an Analyzer that checks for shadowed variables. package shadow import ( "go/ast" "go/token" "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) // NOTE: Experimental. Not part of the vet suite. const Doc = `check for possible unintended shadowing of variables This analyzer check for shadowed variables. A shadowed variable is a variable declared in an inner scope with the same name and type as a variable in an outer scope, and where the outer variable is mentioned after the inner one is declared. (This definition can be refined; the module generates too many false positives and is not yet enabled by default.) For example: func BadRead(f *os.File, buf []byte) error { var err error for { n, err := f.Read(buf) // shadows the function variable 'err' if err != nil { break // causes return of wrong value } foo(buf) } return err } ` var Analyzer = &analysis.Analyzer{ Name: "shadow", Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, } // flags var strict = false func init() { Analyzer.Flags.BoolVar(&strict, "strict", strict, "whether to be strict about shadowing; can be noisy") } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) spans := make(map[types.Object]span) for id, obj := range pass.TypesInfo.Defs { // Ignore identifiers that don't denote objects // (package names, symbolic variables such as t // in t := x.(type) of type switch headers). if obj != nil { growSpan(spans, obj, id.Pos(), id.End()) } } for id, obj := range pass.TypesInfo.Uses { growSpan(spans, obj, id.Pos(), id.End()) } for node, obj := range pass.TypesInfo.Implicits { // A type switch with a short variable declaration // such as t := x.(type) doesn't declare the symbolic // variable (t in the example) at the switch header; // instead a new variable t (with specific type) is // declared implicitly for each case. Such variables // are found in the types.Info.Implicits (not Defs) // map. Add them here, assuming they are declared at // the type cases' colon ":". if cc, ok := node.(*ast.CaseClause); ok { growSpan(spans, obj, cc.Colon, cc.Colon) } } nodeFilter := []ast.Node{ (*ast.AssignStmt)(nil), (*ast.GenDecl)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { switch n := n.(type) { case *ast.AssignStmt: checkShadowAssignment(pass, spans, n) case *ast.GenDecl: checkShadowDecl(pass, spans, n) } }) return nil, nil } // A span stores the minimum range of byte positions in the file in which a // given variable (types.Object) is mentioned. It is lexically defined: it spans // from the beginning of its first mention to the end of its last mention. // A variable is considered shadowed (if strict is off) only if the // shadowing variable is declared within the span of the shadowed variable. // In other words, if a variable is shadowed but not used after the shadowed // variable is declared, it is inconsequential and not worth complaining about. // This simple check dramatically reduces the nuisance rate for the shadowing // check, at least until something cleverer comes along. // // One wrinkle: A "naked return" is a silent use of a variable that the Span // will not capture, but the compilers catch naked returns of shadowed // variables so we don't need to. // // Cases this gets wrong (TODO): // - If a for loop's continuation statement mentions a variable redeclared in // the block, we should complain about it but don't. // - A variable declared inside a function literal can falsely be identified // as shadowing a variable in the outer function. // type span struct { min token.Pos max token.Pos } // contains reports whether the position is inside the span. func (s span) contains(pos token.Pos) bool { return s.min <= pos && pos < s.max } // growSpan expands the span for the object to contain the source range [pos, end). func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) { if strict { return // No need } s, ok := spans[obj] if ok { if s.min > pos { s.min = pos } if s.max < end { s.max = end } } else { s = span{pos, end} } spans[obj] = s } // checkShadowAssignment checks for shadowing in a short variable declaration. func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *ast.AssignStmt) { if a.Tok != token.DEFINE { return } if idiomaticShortRedecl(pass, a) { return } for _, expr := range a.Lhs { ident, ok := expr.(*ast.Ident) if !ok { pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier") return } checkShadowing(pass, spans, ident) } } // idiomaticShortRedecl reports whether this short declaration can be ignored for // the purposes of shadowing, that is, that any redeclarations it contains are deliberate. func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool { // Don't complain about deliberate redeclarations of the form // i := i // Such constructs are idiomatic in range loops to create a new variable // for each iteration. Another example is // switch n := n.(type) if len(a.Rhs) != len(a.Lhs) { return false } // We know it's an assignment, so the LHS must be all identifiers. (We check anyway.) for i, expr := range a.Lhs { lhs, ok := expr.(*ast.Ident) if !ok { pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier") return true // Don't do any more processing. } switch rhs := a.Rhs[i].(type) { case *ast.Ident: if lhs.Name != rhs.Name { return false } case *ast.TypeAssertExpr: if id, ok := rhs.X.(*ast.Ident); ok { if lhs.Name != id.Name { return false } } default: return false } } return true } // idiomaticRedecl reports whether this declaration spec can be ignored for // the purposes of shadowing, that is, that any redeclarations it contains are deliberate. func idiomaticRedecl(d *ast.ValueSpec) bool { // Don't complain about deliberate redeclarations of the form // var i, j = i, j // Don't ignore redeclarations of the form // var i = 3 if len(d.Names) != len(d.Values) { return false } for i, lhs := range d.Names { rhs, ok := d.Values[i].(*ast.Ident) if !ok || lhs.Name != rhs.Name { return false } } return true } // checkShadowDecl checks for shadowing in a general variable declaration. func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.GenDecl) { if d.Tok != token.VAR { return } for _, spec := range d.Specs { valueSpec, ok := spec.(*ast.ValueSpec) if !ok { pass.ReportRangef(spec, "invalid AST: var GenDecl not ValueSpec") return } // Don't complain about deliberate redeclarations of the form // var i = i if idiomaticRedecl(valueSpec) { return } for _, ident := range valueSpec.Names { checkShadowing(pass, spans, ident) } } } // checkShadowing checks whether the identifier shadows an identifier in an outer scope. func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) { if ident.Name == "_" { // Can't shadow the blank identifier. return } obj := pass.TypesInfo.Defs[ident] if obj == nil { return } // obj.Parent.Parent is the surrounding scope. If we can find another declaration // starting from there, we have a shadowed identifier. _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos()) if shadowed == nil { return } // Don't complain if it's shadowing a universe-declared identifier; that's fine. if shadowed.Parent() == types.Universe { return } if strict { // The shadowed identifier must appear before this one to be an instance of shadowing. if shadowed.Pos() > ident.Pos() { return } } else { // Don't complain if the span of validity of the shadowed identifier doesn't include // the shadowing identifier. span, ok := spans[shadowed] if !ok { pass.ReportRangef(ident, "internal error: no range for %q", ident.Name) return } if !span.contains(ident.Pos()) { return } } // Don't complain if the types differ: that implies the programmer really wants two different things. if types.Identical(obj.Type(), shadowed.Type()) { line := pass.Fset.Position(shadowed.Pos()).Line pass.ReportRangef(ident, "declaration of %q shadows declaration at line %d", obj.Name(), line) } }