Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.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  import (
     8  	_ "embed"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  	"slices"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  	"golang.org/x/tools/go/ast/inspector"
    18  	"golang.org/x/tools/go/types/typeutil"
    19  	"golang.org/x/tools/internal/analysis/analyzerutil"
    20  	"golang.org/x/tools/internal/astutil"
    21  	"golang.org/x/tools/internal/versions"
    22  )
    23  
    24  var NewExprAnalyzer = &analysis.Analyzer{
    25  	Name:      "newexpr",
    26  	Doc:       analyzerutil.MustExtractDoc(doc, "newexpr"),
    27  	URL:       "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#newexpr",
    28  	Requires:  []*analysis.Analyzer{inspect.Analyzer},
    29  	Run:       run,
    30  	FactTypes: []analysis.Fact{&newLike{}},
    31  }
    32  
    33  func run(pass *analysis.Pass) (any, error) {
    34  	var (
    35  		inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    36  		info    = pass.TypesInfo
    37  	)
    38  
    39  	// Detect functions that are new-like, i.e. have the form:
    40  	//
    41  	//	func f(x T) *T { return &x }
    42  	//
    43  	// meaning that it is equivalent to new(x), if x has type T.
    44  	for curFuncDecl := range inspect.Root().Preorder((*ast.FuncDecl)(nil)) {
    45  		decl := curFuncDecl.Node().(*ast.FuncDecl)
    46  		fn := info.Defs[decl.Name].(*types.Func)
    47  		if decl.Body != nil && len(decl.Body.List) == 1 {
    48  			if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
    49  				if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
    50  					if id, ok := unary.X.(*ast.Ident); ok {
    51  						if v, ok := info.Uses[id].(*types.Var); ok {
    52  							sig := fn.Signature()
    53  							if sig.Results().Len() == 1 &&
    54  								is[*types.Pointer](sig.Results().At(0).Type()) && // => no iface conversion
    55  								sig.Params().Len() == 1 &&
    56  								sig.Params().At(0) == v &&
    57  								!sig.Variadic() { // we can't safely transform a variadic function call, so skip them
    58  
    59  								// Export a fact for each one.
    60  								pass.ExportObjectFact(fn, &newLike{})
    61  
    62  								// Check file version.
    63  								file := astutil.EnclosingFile(curFuncDecl)
    64  								if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
    65  									continue // new(expr) not available in this file
    66  								}
    67  
    68  								var edits []analysis.TextEdit
    69  
    70  								// If 'new' is not shadowed, replace func body: &x -> new(x).
    71  								// This makes it safely and cleanly inlinable.
    72  								curRet, _ := curFuncDecl.FindNode(ret)
    73  								if lookup(info, curRet, "new") == builtinNew {
    74  									edits = []analysis.TextEdit{
    75  										// return    &x
    76  										//        ---- -
    77  										// return new(x)
    78  										{
    79  											Pos:     unary.OpPos,
    80  											End:     unary.OpPos + token.Pos(len("&")),
    81  											NewText: []byte("new("),
    82  										},
    83  										{
    84  											Pos:     unary.X.End(),
    85  											End:     unary.X.End(),
    86  											NewText: []byte(")"),
    87  										},
    88  									}
    89  								}
    90  
    91  								// Add a //go:fix inline annotation, if not already present.
    92  								//
    93  								// The inliner will not inline a newer callee body into an
    94  								// older Go file; see https://go.dev/issue/75726.
    95  								//
    96  								// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
    97  								if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
    98  									return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
    99  								}) {
   100  									edits = append(edits, analysis.TextEdit{
   101  										Pos:     decl.Pos(),
   102  										End:     decl.Pos(),
   103  										NewText: []byte("//go:fix inline\n"),
   104  									})
   105  								}
   106  
   107  								if len(edits) > 0 {
   108  									pass.Report(analysis.Diagnostic{
   109  										Pos:     decl.Name.Pos(),
   110  										End:     decl.Name.End(),
   111  										Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
   112  										SuggestedFixes: []analysis.SuggestedFix{
   113  											{
   114  												Message:   "Make %s an inlinable wrapper around new(expr)",
   115  												TextEdits: edits,
   116  											},
   117  										},
   118  									})
   119  								}
   120  							}
   121  						}
   122  					}
   123  				}
   124  			}
   125  		}
   126  	}
   127  
   128  	// Report and transform calls, when safe.
   129  	// In effect, this is inlining the new-like function
   130  	// even before we have marked the callee with //go:fix inline.
   131  	for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
   132  		call := curCall.Node().(*ast.CallExpr)
   133  		var fact newLike
   134  		if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
   135  			pass.ImportObjectFact(fn, &fact) {
   136  
   137  			// Check file version.
   138  			file := astutil.EnclosingFile(curCall)
   139  			if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
   140  				continue // new(expr) not available in this file
   141  			}
   142  
   143  			// Check new is not shadowed.
   144  			if lookup(info, curCall, "new") != builtinNew {
   145  				continue
   146  			}
   147  
   148  			// The return type *T must exactly match the argument type T.
   149  			// (We formulate it this way--not in terms of the parameter
   150  			// type--to support generics.)
   151  			var targ types.Type
   152  			{
   153  				arg := call.Args[0]
   154  				tvarg := info.Types[arg]
   155  
   156  				// Constants: we must work around the type checker
   157  				// bug that causes info.Types to wrongly report the
   158  				// "typed" type for an untyped constant.
   159  				// (See "historical reasons" in issue go.dev/issue/70638.)
   160  				//
   161  				// We don't have a reliable way to do this but we can attempt
   162  				// to re-typecheck the constant expression on its own, in
   163  				// the original lexical environment but not as a part of some
   164  				// larger expression that implies a conversion to some "typed" type.
   165  				// (For the genesis of this idea see (*state).arguments
   166  				// in ../../../../internal/refactor/inline/inline.go.)
   167  				if tvarg.Value != nil {
   168  					info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
   169  					if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
   170  						continue // unexpected error
   171  					}
   172  					tvarg = info2.Types[arg]
   173  				}
   174  
   175  				targ = types.Default(tvarg.Type)
   176  			}
   177  			if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
   178  				continue
   179  			}
   180  
   181  			pass.Report(analysis.Diagnostic{
   182  				Pos:     call.Pos(),
   183  				End:     call.End(),
   184  				Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
   185  				SuggestedFixes: []analysis.SuggestedFix{{
   186  					Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
   187  					TextEdits: []analysis.TextEdit{{
   188  						Pos:     call.Fun.Pos(),
   189  						End:     call.Fun.End(),
   190  						NewText: []byte("new"),
   191  					}},
   192  				}},
   193  			})
   194  		}
   195  	}
   196  
   197  	return nil, nil
   198  }
   199  
   200  // A newLike fact records that its associated function is "new-like".
   201  type newLike struct{}
   202  
   203  func (*newLike) AFact()         {}
   204  func (*newLike) String() string { return "newlike" }
   205  

View as plain text