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  		if !typesinternal.NoEffects(info, expr) {
    51  			continue // don't eliminate operand: may have effects
    52  		}
    53  
    54  		t := info.TypeOf(expr)
    55  		var edits []analysis.TextEdit
    56  
    57  		// Special case for TypeOf((*T)(nil)).Elem(),
    58  		// needed when T is an interface type.
    59  		if astutil.IsChildOf(curCall, edge.SelectorExpr_X) {
    60  			curSel := unparenEnclosing(curCall).Parent()
    61  			if astutil.IsChildOf(curSel, edge.CallExpr_Fun) {
    62  				call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr)
    63  				obj := typeutil.Callee(info, call2)
    64  				if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
    65  					if ptr, ok := t.(*types.Pointer); ok {
    66  						// Have: TypeOf(expr).Elem() where expr : *T
    67  						t = ptr.Elem()
    68  						// reflect.TypeOf(expr).Elem()
    69  						//                     -------
    70  						// reflect.TypeOf(expr)
    71  						edits = []analysis.TextEdit{{
    72  							Pos: call.End(),
    73  							End: call2.End(),
    74  						}}
    75  					}
    76  				}
    77  			}
    78  		}
    79  
    80  		// TypeOf(x) where x has an interface type is a
    81  		// dynamic operation; don't transform it to TypeFor.
    82  		// (edits == nil means "not the Elem() special case".)
    83  		if types.IsInterface(t) && edits == nil {
    84  			continue
    85  		}
    86  
    87  		file := astutil.EnclosingFile(curCall)
    88  		if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_22) {
    89  			continue // TypeFor requires go1.22
    90  		}
    91  		tokFile := pass.Fset.File(file.Pos())
    92  
    93  		// Format the type as valid Go syntax.
    94  		// TODO(adonovan): FileQualifier needs to respect
    95  		// visibility at the current point, and either fail
    96  		// or edit the imports as needed.
    97  		qual := typesinternal.FileQualifier(file, pass.Pkg)
    98  		tstr := types.TypeString(t, qual)
    99  
   100  		sel, ok := call.Fun.(*ast.SelectorExpr)
   101  		if !ok {
   102  			continue // e.g. reflect was dot-imported
   103  		}
   104  
   105  		// If the call argument contains the last use
   106  		// of a variable, as in:
   107  		//	var zero T
   108  		//	reflect.TypeOf(zero)
   109  		// remove the declaration of that variable.
   110  		curArg0 := curCall.ChildAt(edge.CallExpr_Args, 0)
   111  		edits = append(edits, refactor.DeleteUnusedVars(index, info, tokFile, curArg0)...)
   112  
   113  		pass.Report(analysis.Diagnostic{
   114  			Pos:     call.Fun.Pos(),
   115  			End:     call.Fun.End(),
   116  			Message: "reflect.TypeOf call can be simplified using TypeFor",
   117  			SuggestedFixes: []analysis.SuggestedFix{{
   118  				// reflect.TypeOf    (...T value...)
   119  				//         ------     -------------
   120  				// reflect.TypeFor[T](             )
   121  				Message: "Replace TypeOf by TypeFor",
   122  				TextEdits: append([]analysis.TextEdit{
   123  					{
   124  						Pos:     sel.Sel.Pos(),
   125  						End:     sel.Sel.End(),
   126  						NewText: []byte("TypeFor[" + tstr + "]"),
   127  					},
   128  					// delete (pure) argument
   129  					{
   130  						Pos: call.Lparen + 1,
   131  						End: call.Rparen,
   132  					},
   133  				}, edits...),
   134  			}},
   135  		})
   136  	}
   137  
   138  	return nil, nil
   139  }
   140  

View as plain text