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  
    58  								// Export a fact for each one.
    59  								pass.ExportObjectFact(fn, &newLike{})
    60  
    61  								// Check file version.
    62  								file := astutil.EnclosingFile(curFuncDecl)
    63  								if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
    64  									continue // new(expr) not available in this file
    65  								}
    66  
    67  								var edits []analysis.TextEdit
    68  
    69  								// If 'new' is not shadowed, replace func body: &x -> new(x).
    70  								// This makes it safely and cleanly inlinable.
    71  								curRet, _ := curFuncDecl.FindNode(ret)
    72  								if lookup(info, curRet, "new") == builtinNew {
    73  									edits = []analysis.TextEdit{
    74  										// return    &x
    75  										//        ---- -
    76  										// return new(x)
    77  										{
    78  											Pos:     unary.OpPos,
    79  											End:     unary.OpPos + token.Pos(len("&")),
    80  											NewText: []byte("new("),
    81  										},
    82  										{
    83  											Pos:     unary.X.End(),
    84  											End:     unary.X.End(),
    85  											NewText: []byte(")"),
    86  										},
    87  									}
    88  								}
    89  
    90  								// Add a //go:fix inline annotation, if not already present.
    91  								//
    92  								// The inliner will not inline a newer callee body into an
    93  								// older Go file; see https://go.dev/issue/75726.
    94  								//
    95  								// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
    96  								if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
    97  									return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
    98  								}) {
    99  									edits = append(edits, analysis.TextEdit{
   100  										Pos:     decl.Pos(),
   101  										End:     decl.Pos(),
   102  										NewText: []byte("//go:fix inline\n"),
   103  									})
   104  								}
   105  
   106  								if len(edits) > 0 {
   107  									pass.Report(analysis.Diagnostic{
   108  										Pos:     decl.Name.Pos(),
   109  										End:     decl.Name.End(),
   110  										Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
   111  										SuggestedFixes: []analysis.SuggestedFix{
   112  											{
   113  												Message:   "Make %s an inlinable wrapper around new(expr)",
   114  												TextEdits: edits,
   115  											},
   116  										},
   117  									})
   118  								}
   119  							}
   120  						}
   121  					}
   122  				}
   123  			}
   124  		}
   125  	}
   126  
   127  	// Report and transform calls, when safe.
   128  	// In effect, this is inlining the new-like function
   129  	// even before we have marked the callee with //go:fix inline.
   130  	for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
   131  		call := curCall.Node().(*ast.CallExpr)
   132  		var fact newLike
   133  		if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
   134  			pass.ImportObjectFact(fn, &fact) {
   135  
   136  			// Check file version.
   137  			file := astutil.EnclosingFile(curCall)
   138  			if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
   139  				continue // new(expr) not available in this file
   140  			}
   141  
   142  			// Check new is not shadowed.
   143  			if lookup(info, curCall, "new") != builtinNew {
   144  				continue
   145  			}
   146  
   147  			// The return type *T must exactly match the argument type T.
   148  			// (We formulate it this way--not in terms of the parameter
   149  			// type--to support generics.)
   150  			var targ types.Type
   151  			{
   152  				arg := call.Args[0]
   153  				tvarg := info.Types[arg]
   154  
   155  				// Constants: we must work around the type checker
   156  				// bug that causes info.Types to wrongly report the
   157  				// "typed" type for an untyped constant.
   158  				// (See "historical reasons" in issue go.dev/issue/70638.)
   159  				//
   160  				// We don't have a reliable way to do this but we can attempt
   161  				// to re-typecheck the constant expression on its own, in
   162  				// the original lexical environment but not as a part of some
   163  				// larger expression that implies a conversion to some "typed" type.
   164  				// (For the genesis of this idea see (*state).arguments
   165  				// in ../../../../internal/refactor/inline/inline.go.)
   166  				if tvarg.Value != nil {
   167  					info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
   168  					if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
   169  						continue // unexpected error
   170  					}
   171  					tvarg = info2.Types[arg]
   172  				}
   173  
   174  				targ = types.Default(tvarg.Type)
   175  			}
   176  			if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
   177  				continue
   178  			}
   179  
   180  			pass.Report(analysis.Diagnostic{
   181  				Pos:     call.Pos(),
   182  				End:     call.End(),
   183  				Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
   184  				SuggestedFixes: []analysis.SuggestedFix{{
   185  					Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
   186  					TextEdits: []analysis.TextEdit{{
   187  						Pos:     call.Fun.Pos(),
   188  						End:     call.Fun.End(),
   189  						NewText: []byte("new"),
   190  					}},
   191  				}},
   192  			})
   193  		}
   194  	}
   195  
   196  	return nil, nil
   197  }
   198  
   199  // A newLike fact records that its associated function is "new-like".
   200  type newLike struct{}
   201  
   202  func (*newLike) AFact()         {}
   203  func (*newLike) String() string { return "newlike" }
   204  

View as plain text