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")
+}