golang.org/x/tools@v0.21.0/go/analysis/passes/shadow/shadow.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package shadow
     6  
     7  import (
     8  	_ "embed"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    16  	"golang.org/x/tools/go/ast/inspector"
    17  )
    18  
    19  // NOTE: Experimental. Not part of the vet suite.
    20  
    21  //go:embed doc.go
    22  var doc string
    23  
    24  var Analyzer = &analysis.Analyzer{
    25  	Name:     "shadow",
    26  	Doc:      analysisutil.MustExtractDoc(doc, "shadow"),
    27  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow",
    28  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    29  	Run:      run,
    30  }
    31  
    32  // flags
    33  var strict = false
    34  
    35  func init() {
    36  	Analyzer.Flags.BoolVar(&strict, "strict", strict, "whether to be strict about shadowing; can be noisy")
    37  }
    38  
    39  func run(pass *analysis.Pass) (interface{}, error) {
    40  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  
    42  	spans := make(map[types.Object]span)
    43  	for id, obj := range pass.TypesInfo.Defs {
    44  		// Ignore identifiers that don't denote objects
    45  		// (package names, symbolic variables such as t
    46  		// in t := x.(type) of type switch headers).
    47  		if obj != nil {
    48  			growSpan(spans, obj, id.Pos(), id.End())
    49  		}
    50  	}
    51  	for id, obj := range pass.TypesInfo.Uses {
    52  		growSpan(spans, obj, id.Pos(), id.End())
    53  	}
    54  	for node, obj := range pass.TypesInfo.Implicits {
    55  		// A type switch with a short variable declaration
    56  		// such as t := x.(type) doesn't declare the symbolic
    57  		// variable (t in the example) at the switch header;
    58  		// instead a new variable t (with specific type) is
    59  		// declared implicitly for each case. Such variables
    60  		// are found in the types.Info.Implicits (not Defs)
    61  		// map. Add them here, assuming they are declared at
    62  		// the type cases' colon ":".
    63  		if cc, ok := node.(*ast.CaseClause); ok {
    64  			growSpan(spans, obj, cc.Colon, cc.Colon)
    65  		}
    66  	}
    67  
    68  	nodeFilter := []ast.Node{
    69  		(*ast.AssignStmt)(nil),
    70  		(*ast.GenDecl)(nil),
    71  	}
    72  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    73  		switch n := n.(type) {
    74  		case *ast.AssignStmt:
    75  			checkShadowAssignment(pass, spans, n)
    76  		case *ast.GenDecl:
    77  			checkShadowDecl(pass, spans, n)
    78  		}
    79  	})
    80  	return nil, nil
    81  }
    82  
    83  // A span stores the minimum range of byte positions in the file in which a
    84  // given variable (types.Object) is mentioned. It is lexically defined: it spans
    85  // from the beginning of its first mention to the end of its last mention.
    86  // A variable is considered shadowed (if strict is off) only if the
    87  // shadowing variable is declared within the span of the shadowed variable.
    88  // In other words, if a variable is shadowed but not used after the shadowed
    89  // variable is declared, it is inconsequential and not worth complaining about.
    90  // This simple check dramatically reduces the nuisance rate for the shadowing
    91  // check, at least until something cleverer comes along.
    92  //
    93  // One wrinkle: A "naked return" is a silent use of a variable that the Span
    94  // will not capture, but the compilers catch naked returns of shadowed
    95  // variables so we don't need to.
    96  //
    97  // Cases this gets wrong (TODO):
    98  // - If a for loop's continuation statement mentions a variable redeclared in
    99  // the block, we should complain about it but don't.
   100  // - A variable declared inside a function literal can falsely be identified
   101  // as shadowing a variable in the outer function.
   102  type span struct {
   103  	min token.Pos
   104  	max token.Pos
   105  }
   106  
   107  // contains reports whether the position is inside the span.
   108  func (s span) contains(pos token.Pos) bool {
   109  	return s.min <= pos && pos < s.max
   110  }
   111  
   112  // growSpan expands the span for the object to contain the source range [pos, end).
   113  func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) {
   114  	if strict {
   115  		return // No need
   116  	}
   117  	s, ok := spans[obj]
   118  	if ok {
   119  		if s.min > pos {
   120  			s.min = pos
   121  		}
   122  		if s.max < end {
   123  			s.max = end
   124  		}
   125  	} else {
   126  		s = span{pos, end}
   127  	}
   128  	spans[obj] = s
   129  }
   130  
   131  // checkShadowAssignment checks for shadowing in a short variable declaration.
   132  func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *ast.AssignStmt) {
   133  	if a.Tok != token.DEFINE {
   134  		return
   135  	}
   136  	if idiomaticShortRedecl(pass, a) {
   137  		return
   138  	}
   139  	for _, expr := range a.Lhs {
   140  		ident, ok := expr.(*ast.Ident)
   141  		if !ok {
   142  			pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
   143  			return
   144  		}
   145  		checkShadowing(pass, spans, ident)
   146  	}
   147  }
   148  
   149  // idiomaticShortRedecl reports whether this short declaration can be ignored for
   150  // the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
   151  func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool {
   152  	// Don't complain about deliberate redeclarations of the form
   153  	//	i := i
   154  	// Such constructs are idiomatic in range loops to create a new variable
   155  	// for each iteration. Another example is
   156  	//	switch n := n.(type)
   157  	if len(a.Rhs) != len(a.Lhs) {
   158  		return false
   159  	}
   160  	// We know it's an assignment, so the LHS must be all identifiers. (We check anyway.)
   161  	for i, expr := range a.Lhs {
   162  		lhs, ok := expr.(*ast.Ident)
   163  		if !ok {
   164  			pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
   165  			return true // Don't do any more processing.
   166  		}
   167  		switch rhs := a.Rhs[i].(type) {
   168  		case *ast.Ident:
   169  			if lhs.Name != rhs.Name {
   170  				return false
   171  			}
   172  		case *ast.TypeAssertExpr:
   173  			if id, ok := rhs.X.(*ast.Ident); ok {
   174  				if lhs.Name != id.Name {
   175  					return false
   176  				}
   177  			}
   178  		default:
   179  			return false
   180  		}
   181  	}
   182  	return true
   183  }
   184  
   185  // idiomaticRedecl reports whether this declaration spec can be ignored for
   186  // the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
   187  func idiomaticRedecl(d *ast.ValueSpec) bool {
   188  	// Don't complain about deliberate redeclarations of the form
   189  	//	var i, j = i, j
   190  	// Don't ignore redeclarations of the form
   191  	//	var i = 3
   192  	if len(d.Names) != len(d.Values) {
   193  		return false
   194  	}
   195  	for i, lhs := range d.Names {
   196  		rhs, ok := d.Values[i].(*ast.Ident)
   197  		if !ok || lhs.Name != rhs.Name {
   198  			return false
   199  		}
   200  	}
   201  	return true
   202  }
   203  
   204  // checkShadowDecl checks for shadowing in a general variable declaration.
   205  func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.GenDecl) {
   206  	if d.Tok != token.VAR {
   207  		return
   208  	}
   209  	for _, spec := range d.Specs {
   210  		valueSpec, ok := spec.(*ast.ValueSpec)
   211  		if !ok {
   212  			pass.ReportRangef(spec, "invalid AST: var GenDecl not ValueSpec")
   213  			return
   214  		}
   215  		// Don't complain about deliberate redeclarations of the form
   216  		//	var i = i
   217  		if idiomaticRedecl(valueSpec) {
   218  			return
   219  		}
   220  		for _, ident := range valueSpec.Names {
   221  			checkShadowing(pass, spans, ident)
   222  		}
   223  	}
   224  }
   225  
   226  // checkShadowing checks whether the identifier shadows an identifier in an outer scope.
   227  func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) {
   228  	if ident.Name == "_" {
   229  		// Can't shadow the blank identifier.
   230  		return
   231  	}
   232  	obj := pass.TypesInfo.Defs[ident]
   233  	if obj == nil {
   234  		return
   235  	}
   236  	// obj.Parent.Parent is the surrounding scope. If we can find another declaration
   237  	// starting from there, we have a shadowed identifier.
   238  	_, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos())
   239  	if shadowed == nil {
   240  		return
   241  	}
   242  	// Don't complain if it's shadowing a universe-declared identifier; that's fine.
   243  	if shadowed.Parent() == types.Universe {
   244  		return
   245  	}
   246  	if strict {
   247  		// The shadowed identifier must appear before this one to be an instance of shadowing.
   248  		if shadowed.Pos() > ident.Pos() {
   249  			return
   250  		}
   251  	} else {
   252  		// Don't complain if the span of validity of the shadowed identifier doesn't include
   253  		// the shadowing identifier.
   254  		span, ok := spans[shadowed]
   255  		if !ok {
   256  			pass.ReportRangef(ident, "internal error: no range for %q", ident.Name)
   257  			return
   258  		}
   259  		if !span.contains(ident.Pos()) {
   260  			return
   261  		}
   262  	}
   263  	// Don't complain if the types differ: that implies the programmer really wants two different things.
   264  	if types.Identical(obj.Type(), shadowed.Type()) {
   265  		line := pass.Fset.Position(shadowed.Pos()).Line
   266  		pass.ReportRangef(ident, "declaration of %q shadows declaration at line %d", obj.Name(), line)
   267  	}
   268  }