Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go

     1  // Copyright 2025 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 modernize
     6  
     7  // This file defines modernizers that use the "reflect" package.
     8  
     9  import (
    10  	"go/ast"
    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/ast/edge"
    16  	"golang.org/x/tools/go/types/typeutil"
    17  	"golang.org/x/tools/internal/analysis/analyzerutil"
    18  	typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
    19  	"golang.org/x/tools/internal/astutil"
    20  	"golang.org/x/tools/internal/refactor"
    21  	"golang.org/x/tools/internal/typesinternal"
    22  	"golang.org/x/tools/internal/typesinternal/typeindex"
    23  	"golang.org/x/tools/internal/versions"
    24  )
    25  
    26  var ReflectTypeForAnalyzer = &analysis.Analyzer{
    27  	Name: "reflecttypefor",
    28  	Doc:  analyzerutil.MustExtractDoc(doc, "reflecttypefor"),
    29  	Requires: []*analysis.Analyzer{
    30  		inspect.Analyzer,
    31  		typeindexanalyzer.Analyzer,
    32  	},
    33  	Run: reflecttypefor,
    34  	URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#reflecttypefor",
    35  }
    36  
    37  func reflecttypefor(pass *analysis.Pass) (any, error) {
    38  	var (
    39  		index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
    40  		info  = pass.TypesInfo
    41  
    42  		reflectTypeOf = index.Object("reflect", "TypeOf")
    43  	)
    44  
    45  	for curCall := range index.Calls(reflectTypeOf) {
    46  		call := curCall.Node().(*ast.CallExpr)
    47  		// Have: reflect.TypeOf(expr)
    48  
    49  		expr := call.Args[0]
    50  
    51  		// reflect.TypeFor cannot be instantiated with an untyped nil.
    52  		// We use type information rather than checking the identifier name
    53  		// to correctly handle edge cases where "nil" is shadowed (e.g. nil := "nil").
    54  		if info.Types[expr].IsNil() {
    55  			continue
    56  		}
    57  
    58  		if !typesinternal.NoEffects(info, expr) {
    59  			continue // don't eliminate operand: may have effects
    60  		}
    61  
    62  		t := info.TypeOf(expr)
    63  		var edits []analysis.TextEdit
    64  
    65  		// Special case for TypeOf((*T)(nil)).Elem(),
    66  		// needed when T is an interface type.
    67  		if astutil.IsChildOf(curCall, edge.SelectorExpr_X) {
    68  			curSel := unparenEnclosing(curCall).Parent()
    69  			if astutil.IsChildOf(curSel, edge.CallExpr_Fun) {
    70  				call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr)
    71  				obj := typeutil.Callee(info, call2)
    72  				if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
    73  					if ptr, ok := t.(*types.Pointer); ok {
    74  						// Have: TypeOf(expr).Elem() where expr : *T
    75  						t = ptr.Elem()
    76  						// reflect.TypeOf(expr).Elem()
    77  						//                     -------
    78  						// reflect.TypeOf(expr)
    79  						edits = []analysis.TextEdit{{
    80  							Pos: call.End(),
    81  							End: call2.End(),
    82  						}}
    83  					}
    84  				}
    85  			}
    86  		}
    87  
    88  		// TypeOf(x) where x has an interface type is a
    89  		// dynamic operation; don't transform it to TypeFor.
    90  		// (edits == nil means "not the Elem() special case".)
    91  		if types.IsInterface(t) && edits == nil {
    92  			continue
    93  		}
    94  
    95  		file := astutil.EnclosingFile(curCall)
    96  		if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_22) {
    97  			continue // TypeFor requires go1.22
    98  		}
    99  		tokFile := pass.Fset.File(file.Pos())
   100  
   101  		// Format the type as valid Go syntax.
   102  		// TODO(adonovan): FileQualifier needs to respect
   103  		// visibility at the current point, and either fail
   104  		// or edit the imports as needed.
   105  		qual := typesinternal.FileQualifier(file, pass.Pkg)
   106  		tstr := types.TypeString(t, qual)
   107  
   108  		sel, ok := call.Fun.(*ast.SelectorExpr)
   109  		if !ok {
   110  			continue // e.g. reflect was dot-imported
   111  		}
   112  
   113  		// If the call argument contains the last use
   114  		// of a variable, as in:
   115  		//	var zero T
   116  		//	reflect.TypeOf(zero)
   117  		// remove the declaration of that variable.
   118  		curArg0 := curCall.ChildAt(edge.CallExpr_Args, 0)
   119  		edits = append(edits, refactor.DeleteUnusedVars(index, info, tokFile, curArg0)...)
   120  
   121  		pass.Report(analysis.Diagnostic{
   122  			Pos:     call.Fun.Pos(),
   123  			End:     call.Fun.End(),
   124  			Message: "reflect.TypeOf call can be simplified using TypeFor",
   125  			SuggestedFixes: []analysis.SuggestedFix{{
   126  				// reflect.TypeOf    (...T value...)
   127  				//         ------     -------------
   128  				// reflect.TypeFor[T](             )
   129  				Message: "Replace TypeOf by TypeFor",
   130  				TextEdits: append([]analysis.TextEdit{
   131  					{
   132  						Pos:     sel.Sel.Pos(),
   133  						End:     sel.Sel.End(),
   134  						NewText: []byte("TypeFor[" + tstr + "]"),
   135  					},
   136  					// delete (pure) argument
   137  					{
   138  						Pos: call.Lparen + 1,
   139  						End: call.Rparen,
   140  					},
   141  				}, edits...),
   142  			}},
   143  		})
   144  	}
   145  
   146  	return nil, nil
   147  }
   148  

View as plain text