gopls/internal/analysis/modernize: pass to use go1.26 new(x)

This CL adds a new analyzer to modernize declarations of
and calls to functions of this form:

    func varOf(x int) *int { return &x }

    ... = varOf(123)

so that they are transformed to:

    func varOf(x int) *int { return new(x) }

    ... = new(123)

(Such functions are widely used in serialization packages,
for instance the proto.{Int64,String,Bool} helpers used with
protobufs.)

In earlier drafts we also added

    //go:fix inline

to the varOf function, but this isn't safe until we
fix golang/go#75726.

Also:
- factor analysisinternal.EnclosingScope out of gopls' Rename.
- fix a trivial panic recently introduced to stringscutprefix.

+ test, doc

Updates golang/go#45624

Change-Id: Ic5fa36515c7da4377c01f7e155c622fca992adda
Reviewed-on: https://go-review.googlesource.com/c/tools/+/704820
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Madeline Kalil <mkalil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
diff --git a/go/analysis/passes/modernize/any.go b/go/analysis/passes/modernize/any.go
index ddb9d8e..05999f8 100644
--- a/go/analysis/passes/modernize/any.go
+++ b/go/analysis/passes/modernize/any.go
@@ -32,16 +32,12 @@
 	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
 
 	for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.18") {
-		file := curFile.Node().(*ast.File)
-
 		for curIface := range curFile.Preorder((*ast.InterfaceType)(nil)) {
 			iface := curIface.Node().(*ast.InterfaceType)
 
 			if iface.Methods.NumFields() == 0 {
 				// Check that 'any' is not shadowed.
-				// TODO(adonovan): find scope using only local Cursor operations.
-				scope := pass.TypesInfo.Scopes[file].Innermost(iface.Pos())
-				if _, obj := scope.LookupParent("any", iface.Pos()); obj == builtinAny {
+				if lookup(pass.TypesInfo, curIface, "any") == builtinAny {
 					pass.Report(analysis.Diagnostic{
 						Pos:     iface.Pos(),
 						End:     iface.End(),
diff --git a/go/analysis/passes/modernize/doc.go b/go/analysis/passes/modernize/doc.go
index 050d385..9848a59 100644
--- a/go/analysis/passes/modernize/doc.go
+++ b/go/analysis/passes/modernize/doc.go
@@ -143,6 +143,32 @@
 as the behavior of `min` and `max` with NaN values can differ from
 the original if/else statement.
 
+# Analyzer newexpr
+
+newexpr: simplify code by using go1.26's new(expr)
+
+This analyzer finds declarations of functions of this form:
+
+	func varOf(x int) *int { return &x }
+
+and suggests a fix to turn them into inlinable wrappers around
+go1.26's built-in new(expr) function:
+
+	func varOf(x int) *int { return new(x) }
+
+In addition, this analyzer suggests a fix for each call
+to one of the functions before it is transformed, so that
+
+	use(varOf(123))
+
+is replaced by:
+
+	use(new(123))
+
+(Wrapper functions such as varOf are common when working with Go
+serialization packages such as for JSON or protobuf, where pointers
+are often used to express optionality.)
+
 # Analyzer omitzero
 
 omitzero: suggest replacing omitempty with omitzero for struct fields
diff --git a/go/analysis/passes/modernize/minmax.go b/go/analysis/passes/modernize/minmax.go
index edb27fd..350a3c4 100644
--- a/go/analysis/passes/modernize/minmax.go
+++ b/go/analysis/passes/modernize/minmax.go
@@ -70,7 +70,6 @@
 			b       = compare.Y
 			lhs     = tassign.Lhs[0]
 			rhs     = tassign.Rhs[0]
-			scope   = pass.TypesInfo.Scopes[ifStmt.Body]
 			sign    = isInequality(compare.Op)
 
 			// callArg formats a call argument, preserving comments from [start-end).
@@ -104,7 +103,7 @@
 
 				sym := cond(sign < 0, "min", "max")
 
-				if _, obj := scope.LookupParent(sym, ifStmt.Pos()); !is[*types.Builtin](obj) {
+				if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
 					return // min/max function is shadowed
 				}
 
@@ -160,7 +159,7 @@
 				}
 				sym := cond(sign < 0, "min", "max")
 
-				if _, obj := scope.LookupParent(sym, ifStmt.Pos()); !is[*types.Builtin](obj) {
+				if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
 					return // min/max function is shadowed
 				}
 
diff --git a/go/analysis/passes/modernize/modernize.go b/go/analysis/passes/modernize/modernize.go
index 08cb58f..6c7fce5 100644
--- a/go/analysis/passes/modernize/modernize.go
+++ b/go/analysis/passes/modernize/modernize.go
@@ -38,6 +38,7 @@
 	ForVarAnalyzer,
 	MapsLoopAnalyzer,
 	MinMaxAnalyzer,
+	NewExprAnalyzer,
 	OmitZeroAnalyzer,
 	RangeIntAnalyzer,
 	ReflectTypeForAnalyzer,
@@ -163,6 +164,7 @@
 	builtinFalse   = types.Universe.Lookup("false")
 	builtinLen     = types.Universe.Lookup("len")
 	builtinMake    = types.Universe.Lookup("make")
+	builtinNew     = types.Universe.Lookup("new")
 	builtinNil     = types.Universe.Lookup("nil")
 	builtinString  = types.Universe.Lookup("string")
 	builtinTrue    = types.Universe.Lookup("true")
@@ -207,3 +209,10 @@
 	})
 	return noEffects
 }
+
+// lookup returns the symbol denoted by name at the position of the cursor.
+func lookup(info *types.Info, cur inspector.Cursor, name string) types.Object {
+	scope := analysisinternal.EnclosingScope(info, cur)
+	_, obj := scope.LookupParent(name, cur.Node().Pos())
+	return obj
+}
diff --git a/go/analysis/passes/modernize/modernize_test.go b/go/analysis/passes/modernize/modernize_test.go
index 754033a..e1fb76a 100644
--- a/go/analysis/passes/modernize/modernize_test.go
+++ b/go/analysis/passes/modernize/modernize_test.go
@@ -39,6 +39,10 @@
 	RunWithSuggestedFixes(t, TestData(), modernize.MinMaxAnalyzer, "minmax", "minmax/userdefined", "minmax/wrongoperators", "minmax/nonstrict", "minmax/wrongreturn")
 }
 
+func TestNewExpr(t *testing.T) {
+	RunWithSuggestedFixes(t, TestData(), modernize.NewExprAnalyzer, "newexpr")
+}
+
 func TestOmitZero(t *testing.T) {
 	RunWithSuggestedFixes(t, TestData(), modernize.OmitZeroAnalyzer, "omitzero")
 }
diff --git a/go/analysis/passes/modernize/newexpr.go b/go/analysis/passes/modernize/newexpr.go
new file mode 100644
index 0000000..08bc71d
--- /dev/null
+++ b/go/analysis/passes/modernize/newexpr.go
@@ -0,0 +1,207 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modernize
+
+import (
+	_ "embed"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"strings"
+
+	"fmt"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+	"golang.org/x/tools/go/types/typeutil"
+	"golang.org/x/tools/internal/analysisinternal"
+)
+
+var NewExprAnalyzer = &analysis.Analyzer{
+	Name:      "newexpr",
+	Doc:       analysisinternal.MustExtractDoc(doc, "newexpr"),
+	URL:       "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr",
+	Requires:  []*analysis.Analyzer{inspect.Analyzer},
+	Run:       run,
+	FactTypes: []analysis.Fact{&newLike{}},
+}
+
+func run(pass *analysis.Pass) (any, error) {
+	var (
+		inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+		info    = pass.TypesInfo
+	)
+
+	// Detect functions that are new-like, i.e. have the form:
+	//
+	//	func f(x T) *T { return &x }
+	//
+	// meaning that it is equivalent to new(x), if x has type T.
+	for curFuncDecl := range inspect.Root().Preorder((*ast.FuncDecl)(nil)) {
+		decl := curFuncDecl.Node().(*ast.FuncDecl)
+		fn := info.Defs[decl.Name].(*types.Func)
+		if decl.Body != nil && len(decl.Body.List) == 1 {
+			if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
+				if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
+					if id, ok := unary.X.(*ast.Ident); ok {
+						if v, ok := info.Uses[id].(*types.Var); ok {
+							sig := fn.Signature()
+							if sig.Results().Len() == 1 &&
+								is[*types.Pointer](sig.Results().At(0).Type()) && // => no iface conversion
+								sig.Params().Len() == 1 &&
+								sig.Params().At(0) == v {
+
+								// Export a fact for each one.
+								pass.ExportObjectFact(fn, &newLike{})
+
+								// Check file version.
+								file := enclosingFile(curFuncDecl)
+								if !fileUses(info, file, "go1.26") {
+									continue // new(expr) not available in this file
+								}
+
+								var edits []analysis.TextEdit
+
+								// If 'new' is not shadowed, replace func body: &x -> new(x).
+								// This makes it safely and cleanly inlinable.
+								curRet, _ := curFuncDecl.FindNode(ret)
+								if lookup(info, curRet, "new") == builtinNew {
+									edits = []analysis.TextEdit{
+										// return    &x
+										//        ---- -
+										// return new(x)
+										{
+											Pos:     unary.OpPos,
+											End:     unary.OpPos + token.Pos(len("&")),
+											NewText: []byte("new("),
+										},
+										{
+											Pos:     unary.X.End(),
+											End:     unary.X.End(),
+											NewText: []byte(")"),
+										},
+									}
+								}
+
+								// Disabled until we resolve https://go.dev/issue/75726
+								// (Go version skew between caller and callee in inliner.)
+								// TODO(adonovan): fix and reenable.
+								//
+								// Also, restore these lines to our section of doc.go:
+								// 	//go:fix inline
+								//	...
+								// 	(The directive comment causes the inline analyzer to suggest
+								// 	that calls to such functions are inlined.)
+								if false {
+									// Add a //go:fix inline annotation, if not already present.
+									// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
+									if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
+										edits = append(edits, analysis.TextEdit{
+											Pos:     decl.Pos(),
+											End:     decl.Pos(),
+											NewText: []byte("//go:fix inline\n"),
+										})
+									}
+								}
+
+								if len(edits) > 0 {
+									pass.Report(analysis.Diagnostic{
+										Pos:     decl.Name.Pos(),
+										End:     decl.Name.End(),
+										Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
+										SuggestedFixes: []analysis.SuggestedFix{
+											{
+												Message:   "Make %s an inlinable wrapper around new(expr)",
+												TextEdits: edits,
+											},
+										},
+									})
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Report and transform calls, when safe.
+	// In effect, this is inlining the new-like function
+	// even before we have marked the callee with //go:fix inline.
+	for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
+		call := curCall.Node().(*ast.CallExpr)
+		var fact newLike
+		if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
+			pass.ImportObjectFact(fn, &fact) {
+
+			// Check file version.
+			file := enclosingFile(curCall)
+			if !fileUses(info, file, "go1.26") {
+				continue // new(expr) not available in this file
+			}
+
+			// Check new is not shadowed.
+			if lookup(info, curCall, "new") != builtinNew {
+				continue
+			}
+
+			// The return type *T must exactly match the argument type T.
+			// (We formulate it this way--not in terms of the parameter
+			// type--to support generics.)
+			var targ types.Type
+			{
+				arg := call.Args[0]
+				tvarg := info.Types[arg]
+
+				// Constants: we must work around the type checker
+				// bug that causes info.Types to wrongly report the
+				// "typed" type for an untyped constant.
+				// (See "historical reasons" in issue go.dev/issue/70638.)
+				//
+				// We don't have a reliable way to do this but we can attempt
+				// to re-typecheck the constant expression on its own, in
+				// the original lexical environment but not as a part of some
+				// larger expression that implies a conversion to some "typed" type.
+				// (For the genesis of this idea see (*state).arguments
+				// in ../../../../internal/refactor/inline/inline.go.)
+				if tvarg.Value != nil {
+					info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
+					if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
+						continue // unexpected error
+					}
+					tvarg = info2.Types[arg]
+				}
+
+				targ = types.Default(tvarg.Type)
+			}
+			if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
+				continue
+			}
+
+			pass.Report(analysis.Diagnostic{
+				Pos:     call.Pos(),
+				End:     call.End(),
+				Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
+				SuggestedFixes: []analysis.SuggestedFix{{
+					Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
+					TextEdits: []analysis.TextEdit{{
+						Pos:     call.Fun.Pos(),
+						End:     call.Fun.End(),
+						NewText: []byte("new"),
+					}},
+				}},
+			})
+		}
+	}
+
+	return nil, nil
+}
+
+// A newLike fact records that its associated function is "new-like".
+type newLike struct{}
+
+func (*newLike) AFact()         {}
+func (*newLike) String() string { return "newlike" }
diff --git a/go/analysis/passes/modernize/stringscutprefix.go b/go/analysis/passes/modernize/stringscutprefix.go
index c5194c5..199c0b5 100644
--- a/go/analysis/passes/modernize/stringscutprefix.go
+++ b/go/analysis/passes/modernize/stringscutprefix.go
@@ -184,7 +184,8 @@
 					lhs := assign.Lhs[0]
 					obj := typeutil.Callee(info, call)
 
-					if obj != stringsTrimPrefix && obj != bytesTrimPrefix && obj != stringsTrimSuffix && obj != bytesTrimSuffix {
+					if obj == nil ||
+						obj != stringsTrimPrefix && obj != bytesTrimPrefix && obj != stringsTrimSuffix && obj != bytesTrimSuffix {
 						continue
 					}
 
diff --git a/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go
new file mode 100644
index 0000000..e36898a
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go
@@ -0,0 +1,29 @@
+//go:build go1.26
+
+package newexpr
+
+// intVar returns a new var whose value is i.
+func intVar(i int) *int { return &i } // want `intVar can be an inlinable wrapper around new\(expr\)` intVar:"newlike"
+
+func int64Var(i int64) *int64 { return &i } // want `int64Var can be an inlinable wrapper around new\(expr\)` int64Var:"newlike"
+
+func stringVar(s string) *string { return &s } // want `stringVar can be an inlinable wrapper around new\(expr\)` stringVar:"newlike"
+
+func varOf[T any](x T) *T { return &x } // want `varOf can be an inlinable wrapper around new\(expr\)` varOf:"newlike"
+
+var (
+	s struct {
+		int
+		string
+	}
+	_ = intVar(123)       // want `call of intVar\(x\) can be simplified to new\(x\)`
+	_ = int64Var(123)     // nope: implicit conversion from untyped int to int64
+	_ = stringVar("abc")  // want `call of stringVar\(x\) can be simplified to new\(x\)`
+	_ = varOf(s)          // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = varOf(123)        // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = varOf(int64(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = varOf[int](123)   // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = varOf[int64](123) // nope: implicit conversion from untyped int to int64
+	_ = varOf(            // want `call of varOf\(x\) can be simplified to new\(x\)`
+		varOf(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
+)
diff --git a/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go.golden b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go.golden
new file mode 100644
index 0000000..648618e
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go.golden
@@ -0,0 +1,29 @@
+//go:build go1.26
+
+package newexpr
+
+// intVar returns a new var whose value is i.
+func intVar(i int) *int { return new(i) } // want `intVar can be an inlinable wrapper around new\(expr\)` intVar:"newlike"
+
+func int64Var(i int64) *int64 { return new(i) } // want `int64Var can be an inlinable wrapper around new\(expr\)` int64Var:"newlike"
+
+func stringVar(s string) *string { return new(s) } // want `stringVar can be an inlinable wrapper around new\(expr\)` stringVar:"newlike"
+
+func varOf[T any](x T) *T { return new(x) } // want `varOf can be an inlinable wrapper around new\(expr\)` varOf:"newlike"
+
+var (
+	s struct {
+		int
+		string
+	}
+	_ = new(123)          // want `call of intVar\(x\) can be simplified to new\(x\)`
+	_ = int64Var(123)     // nope: implicit conversion from untyped int to int64
+	_ = new("abc")        // want `call of stringVar\(x\) can be simplified to new\(x\)`
+	_ = new(s)            // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = new(123)          // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = new(int64(123))   // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = new(123)          // want `call of varOf\(x\) can be simplified to new\(x\)`
+	_ = varOf[int64](123) // nope: implicit conversion from untyped int to int64
+	_ = new(              // want `call of varOf\(x\) can be simplified to new\(x\)`
+		new(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
+)
\ No newline at end of file
diff --git a/go/analysis/passes/modernize/testdata/src/newexpr/newexpr_go125.go b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr_go125.go
new file mode 100644
index 0000000..b4dd1b7
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/newexpr/newexpr_go125.go
@@ -0,0 +1 @@
+package newexpr
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 6e0f39a..6389b3b 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -3437,6 +3437,32 @@
 
 Package documentation: [minmax](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#minmax)
 
+<a id='newexpr'></a>
+## `newexpr`: simplify code by using go1.26's new(expr)
+
+This analyzer finds declarations of functions of this form:
+
+	func varOf(x int) *int { return &x }
+
+and suggests a fix to turn them into inlinable wrappers around go1.26's built-in new(expr) function:
+
+	func varOf(x int) *int { return new(x) }
+
+In addition, this analyzer suggests a fix for each call to one of the functions before it is transformed, so that
+
+	use(varOf(123))
+
+is replaced by:
+
+	use(new(123))
+
+(Wrapper functions such as varOf are common when working with Go serialization packages such as for JSON or protobuf, where pointers are often used to express optionality.)
+
+
+Default: on.
+
+Package documentation: [newexpr](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr)
+
 <a id='nilfunc'></a>
 ## `nilfunc`: check for useless comparisons between functions and nil
 
diff --git a/gopls/doc/release/v0.21.0.md b/gopls/doc/release/v0.21.0.md
index a4f344b..f09b4f2 100644
--- a/gopls/doc/release/v0.21.0.md
+++ b/gopls/doc/release/v0.21.0.md
@@ -11,12 +11,37 @@
 ## Editing features
 ## Analysis features
 
+### `reflecttypefor` analyzer
+
 <!-- golang/go#60088 -->
+
 The new `reflecttypefor` modernizer simplifies calls to
 `reflect.TypeOf` to use `reflect.TypeFor` when the runtime type is
 known at compile time. For example, `reflect.TypeOf(uint32(0))`
 becomes `reflect.TypeFor[uint32]()`.
 
+### `newexpr` analyzer
+
+<!-- golang/go#45624 -->
+
+The `newexpr` modernizer finds declarations of and calls to functions
+of this form:
+```go
+func varOf(x int) *int { return &x }
+
+use(varOf(123))
+```
+so that they are transformed to:
+```go
+//go:fix inline
+func varOf(x int) *int { return new(x) }
+
+use(new(123))
+```
+(Such wrapper functions are widely used in serialization packages,
+for instance the proto.{Int64,String,Bool} helpers used with
+protobufs.)
+
 ## Code transformation features
 
 <!-- golang/go#42301 -->
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index 2feffef..45e4a80 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -1539,6 +1539,12 @@
 							"Status": ""
 						},
 						{
+							"Name": "\"newexpr\"",
+							"Doc": "simplify code by using go1.26's new(expr)\n\nThis analyzer finds declarations of functions of this form:\n\n\tfunc varOf(x int) *int { return \u0026x }\n\nand suggests a fix to turn them into inlinable wrappers around\ngo1.26's built-in new(expr) function:\n\n\tfunc varOf(x int) *int { return new(x) }\n\nIn addition, this analyzer suggests a fix for each call\nto one of the functions before it is transformed, so that\n\n\tuse(varOf(123))\n\nis replaced by:\n\n\tuse(new(123))\n\n(Wrapper functions such as varOf are common when working with Go\nserialization packages such as for JSON or protobuf, where pointers\nare often used to express optionality.)",
+							"Default": "true",
+							"Status": ""
+						},
+						{
 							"Name": "\"nilfunc\"",
 							"Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
 							"Default": "true",
@@ -3393,6 +3399,12 @@
 			"Default": true
 		},
 		{
+			"Name": "newexpr",
+			"Doc": "simplify code by using go1.26's new(expr)\n\nThis analyzer finds declarations of functions of this form:\n\n\tfunc varOf(x int) *int { return \u0026x }\n\nand suggests a fix to turn them into inlinable wrappers around\ngo1.26's built-in new(expr) function:\n\n\tfunc varOf(x int) *int { return new(x) }\n\nIn addition, this analyzer suggests a fix for each call\nto one of the functions before it is transformed, so that\n\n\tuse(varOf(123))\n\nis replaced by:\n\n\tuse(new(123))\n\n(Wrapper functions such as varOf are common when working with Go\nserialization packages such as for JSON or protobuf, where pointers\nare often used to express optionality.)",
+			"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr",
+			"Default": true
+		},
+		{
 			"Name": "nilfunc",
 			"Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
 			"URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
diff --git a/gopls/internal/golang/rename_check.go b/gopls/internal/golang/rename_check.go
index 1af4619..0596909 100644
--- a/gopls/internal/golang/rename_check.go
+++ b/gopls/internal/golang/rename_check.go
@@ -47,6 +47,7 @@
 	"golang.org/x/tools/go/ast/inspector"
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/util/safetoken"
+	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
 	"golang.org/x/tools/refactor/satisfy"
@@ -354,7 +355,7 @@
 		switch n := cur.Node().(type) {
 		case *ast.Ident:
 			if pkg.TypesInfo().Uses[n] == obj {
-				block := enclosingBlock(pkg.TypesInfo(), cur)
+				block := analysisinternal.EnclosingScope(pkg.TypesInfo(), cur)
 				if !fn(n, block) {
 					ok = false
 				}
@@ -399,27 +400,6 @@
 	return ok
 }
 
-// enclosingBlock returns the innermost block logically enclosing the
-// AST node (an ast.Ident), specified as a Cursor.
-func enclosingBlock(info *types.Info, curId inspector.Cursor) *types.Scope {
-	for cur := range curId.Enclosing() {
-		n := cur.Node()
-		// For some reason, go/types always associates a
-		// function's scope with its FuncType.
-		// See comments about scope above.
-		switch f := n.(type) {
-		case *ast.FuncDecl:
-			n = f.Type
-		case *ast.FuncLit:
-			n = f.Type
-		}
-		if b := info.Scopes[n]; b != nil {
-			return b
-		}
-	}
-	panic("no Scope for *ast.File")
-}
-
 func (r *renamer) checkLabel(label *types.Label) {
 	// Check there are no identical labels in the function's label block.
 	// (Label blocks don't nest, so this is easy.)
diff --git a/gopls/internal/settings/analysis.go b/gopls/internal/settings/analysis.go
index 9b7400f..df78cab 100644
--- a/gopls/internal/settings/analysis.go
+++ b/gopls/internal/settings/analysis.go
@@ -254,6 +254,7 @@
 	{analyzer: modernize.ForVarAnalyzer, severity: protocol.SeverityHint},
 	{analyzer: modernize.MapsLoopAnalyzer, severity: protocol.SeverityHint},
 	{analyzer: modernize.MinMaxAnalyzer, severity: protocol.SeverityHint},
+	{analyzer: modernize.NewExprAnalyzer, severity: protocol.SeverityHint},
 	{analyzer: modernize.OmitZeroAnalyzer, severity: protocol.SeverityHint},
 	{analyzer: modernize.RangeIntAnalyzer, severity: protocol.SeverityHint},
 	{analyzer: modernize.ReflectTypeForAnalyzer, severity: protocol.SeverityHint},
diff --git a/internal/analysisinternal/analysis.go b/internal/analysisinternal/analysis.go
index cea89d3..57ec1a9 100644
--- a/internal/analysisinternal/analysis.go
+++ b/internal/analysisinternal/analysis.go
@@ -678,3 +678,21 @@
 	c, _ = moreiters.First(c.Enclosing((*ast.File)(nil)))
 	return c.Node().(*ast.File)
 }
+
+// EnclosingScope returns the innermost block logically enclosing the cursor.
+func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope {
+	for cur := range cur.Enclosing() {
+		n := cur.Node()
+		// A function's Scope is associated with its FuncType.
+		switch f := n.(type) {
+		case *ast.FuncDecl:
+			n = f.Type
+		case *ast.FuncLit:
+			n = f.Type
+		}
+		if b := info.Scopes[n]; b != nil {
+			return b
+		}
+	}
+	panic("no Scope for *ast.File")
+}