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  		// Don't offer a fix if the type contains an unnamed struct or unnamed
   106  		// interface because the replacement would be significantly more verbose.
   107  		// (See golang/go#76698)
   108  		if isComplicatedType(t) {
   109  			continue
   110  		}
   111  
   112  		// Don't offer the fix if the type string is too long. We define "too
   113  		// long" as more than three times the length of the original expression
   114  		// and at least 16 characters (a 3x length increase of a very
   115  		// short expression should not be cause for skipping the fix).
   116  		oldLen := int(expr.End() - expr.Pos())
   117  		newLen := len(tstr)
   118  		if newLen >= 16 && newLen > 3*oldLen {
   119  			continue
   120  		}
   121  
   122  		// If the call argument contains the last use
   123  		// of a variable, as in:
   124  		//	var zero T
   125  		//	reflect.TypeOf(zero)
   126  		// remove the declaration of that variable.
   127  		curArg0 := curCall.ChildAt(edge.CallExpr_Args, 0)
   128  		edits = append(edits, refactor.DeleteUnusedVars(index, info, tokFile, curArg0)...)
   129  
   130  		pass.Report(analysis.Diagnostic{
   131  			Pos:     call.Fun.Pos(),
   132  			End:     call.Fun.End(),
   133  			Message: "reflect.TypeOf call can be simplified using TypeFor",
   134  			SuggestedFixes: []analysis.SuggestedFix{{
   135  				// reflect.TypeOf    (...T value...)
   136  				//         ------     -------------
   137  				// reflect.TypeFor[T](             )
   138  				Message: "Replace TypeOf by TypeFor",
   139  				TextEdits: append([]analysis.TextEdit{
   140  					{
   141  						Pos:     sel.Sel.Pos(),
   142  						End:     sel.Sel.End(),
   143  						NewText: []byte("TypeFor[" + tstr + "]"),
   144  					},
   145  					// delete (pure) argument
   146  					{
   147  						Pos: call.Lparen + 1,
   148  						End: call.Rparen,
   149  					},
   150  				}, edits...),
   151  			}},
   152  		})
   153  	}
   154  
   155  	return nil, nil
   156  }
   157  
   158  // isComplicatedType reports whether type t is complicated, e.g. it is or contains an
   159  // unnamed struct, interface, or function signature.
   160  func isComplicatedType(t types.Type) bool {
   161  	var check func(typ types.Type) bool
   162  	check = func(typ types.Type) bool {
   163  		switch t := typ.(type) {
   164  		case typesinternal.NamedOrAlias:
   165  			for ta := range t.TypeArgs().Types() {
   166  				if check(ta) {
   167  					return true
   168  				}
   169  			}
   170  			return false
   171  		case *types.Struct, *types.Interface, *types.Signature:
   172  			// These are complex types with potentially many elements
   173  			// so we should avoid duplicating their definition.
   174  			return true
   175  		case *types.Pointer:
   176  			return check(t.Elem())
   177  		case *types.Slice:
   178  			return check(t.Elem())
   179  		case *types.Array:
   180  			return check(t.Elem())
   181  		case *types.Chan:
   182  			return check(t.Elem())
   183  		case *types.Map:
   184  			return check(t.Key()) || check(t.Elem())
   185  		case *types.Basic:
   186  			return false
   187  		case *types.TypeParam:
   188  			return false
   189  		default:
   190  			// Includes types.Union
   191  			return true
   192  		}
   193  	}
   194  
   195  	return check(t)
   196  }
   197  

View as plain text