// 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. /* This file contains the code to 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 } */ package main import ( "flag" "go/ast" "go/token" "go/types" ) var strictShadowing = flag.Bool("shadowstrict", false, "whether to be strict about shadowing; can be noisy") func init() { register("shadow", "check for shadowed variables (experimental; must be set explicitly)", checkShadow, assignStmt, genDecl) experimental["shadow"] = true } func checkShadow(f *File, node ast.Node) { switch n := node.(type) { case *ast.AssignStmt: checkShadowAssignment(f, n) case *ast.GenDecl: checkShadowDecl(f, n) } } // 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 *strictShadowing 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 instance represented // by the identifier. func (pkg *Package) growSpan(ident *ast.Ident, obj types.Object) { if *strictShadowing { return // No need } pos := ident.Pos() end := ident.End() span, ok := pkg.spans[obj] if ok { if span.min > pos { span.min = pos } if span.max < end { span.max = end } } else { span = Span{pos, end} } pkg.spans[obj] = span } // checkShadowAssignment checks for shadowing in a short variable declaration. func checkShadowAssignment(f *File, a *ast.AssignStmt) { if a.Tok != token.DEFINE { return } if f.idiomaticShortRedecl(a) { return } for _, expr := range a.Lhs { ident, ok := expr.(*ast.Ident) if !ok { f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier") return } checkShadowing(f, 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 (f *File) idiomaticShortRedecl(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 { f.Badf(expr.Pos(), "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 (f *File) idiomaticRedecl(d *ast.ValueSpec) bool { // Don't complain about deliberate redeclarations of the form // var i, j = i, j if len(d.Names) != len(d.Values) { return false } for i, lhs := range d.Names { if rhs, ok := d.Values[i].(*ast.Ident); ok { if lhs.Name != rhs.Name { return false } } } return true } // checkShadowDecl checks for shadowing in a general variable declaration. func checkShadowDecl(f *File, d *ast.GenDecl) { if d.Tok != token.VAR { return } for _, spec := range d.Specs { valueSpec, ok := spec.(*ast.ValueSpec) if !ok { f.Badf(spec.Pos(), "invalid AST: var GenDecl not ValueSpec") return } // Don't complain about deliberate redeclarations of the form // var i = i if f.idiomaticRedecl(valueSpec) { return } for _, ident := range valueSpec.Names { checkShadowing(f, ident) } } } // checkShadowing checks whether the identifier shadows an identifier in an outer scope. func checkShadowing(f *File, ident *ast.Ident) { if ident.Name == "_" { // Can't shadow the blank identifier. return } obj := f.pkg.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 *strictShadowing { // 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 := f.pkg.spans[shadowed] if !ok { f.Badf(ident.Pos(), "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()) { f.Badf(ident.Pos(), "declaration of %q shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) } }