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

View as plain text