gopls/.../fillstruct: support generic types
The code that generates a name for a struct type,
and a default value for a struct field, now
supports the case where either contains a type
parameter.
Also:
- use type-checker's name for the struct type unless
it is a bare struct.
- populateValue: return *new(T) for a type parameter.
- various minor cleanups.
- various TODO comments for clarifications.
- fuzzy.FindBestMatch: use strings not identifiers.
Remove Find prefix (also FindMatchingIdentifiers).
Fixes golang/go#54836
Change-Id: I4f6132598b4ac7e72ea1405e4a14d6a23c1eeeaa
Reviewed-on: https://go-review.googlesource.com/c/tools/+/436777
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/analysis/fillreturns/fillreturns.go b/gopls/internal/lsp/analysis/fillreturns/fillreturns.go
index d71defb..4415ddd 100644
--- a/gopls/internal/lsp/analysis/fillreturns/fillreturns.go
+++ b/gopls/internal/lsp/analysis/fillreturns/fillreturns.go
@@ -169,8 +169,7 @@
}
retTyps = append(retTyps, retTyp)
}
- matches :=
- analysisinternal.FindMatchingIdents(retTyps, file, ret.Pos(), info, pass.Pkg)
+ matches := analysisinternal.MatchingIdents(retTyps, file, ret.Pos(), info, pass.Pkg)
for i, retTyp := range retTyps {
var match ast.Expr
var idx int
@@ -192,21 +191,19 @@
fixed[i] = match
remaining = append(remaining[:idx], remaining[idx+1:]...)
} else {
- idents, ok := matches[retTyp]
+ names, ok := matches[retTyp]
if !ok {
return nil, fmt.Errorf("invalid return type: %v", retTyp)
}
- // Find the identifier whose name is most similar to the return type.
- // If we do not find any identifier that matches the pattern,
- // generate a zero value.
- value := fuzzy.FindBestMatch(retTyp.String(), idents)
- if value == nil {
- value = analysisinternal.ZeroValue(file, pass.Pkg, retTyp)
- }
- if value == nil {
+ // Find the identifier most similar to the return type.
+ // If no identifier matches the pattern, generate a zero value.
+ if best := fuzzy.BestMatch(retTyp.String(), names); best != "" {
+ fixed[i] = ast.NewIdent(best)
+ } else if zero := analysisinternal.ZeroValue(file, pass.Pkg, retTyp); zero != nil {
+ fixed[i] = zero
+ } else {
return nil, nil
}
- fixed[i] = value
}
}
diff --git a/gopls/internal/lsp/analysis/fillstruct/fillstruct.go b/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
index aaa3075..931b219 100644
--- a/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
+++ b/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
@@ -4,6 +4,12 @@
// Package fillstruct defines an Analyzer that automatically
// fills in a struct declaration with zero value elements for each field.
+//
+// The analyzer's diagnostic is merely a prompt.
+// The actual fix is created by a separate direct call from gopls to
+// the SuggestedFixes function.
+// Tests of Analyzer.Run can be found in ./testdata/src.
+// Tests of the SuggestedFixes logic live in ../../testdata/fillstruct.
package fillstruct
import (
@@ -46,12 +52,10 @@
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
inspect.Preorder(nodeFilter, func(n ast.Node) {
- info := pass.TypesInfo
- if info == nil {
- return
- }
expr := n.(*ast.CompositeLit)
+ // Find enclosing file.
+ // TODO(adonovan): use inspect.WithStack?
var file *ast.File
for _, f := range pass.Files {
if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
@@ -63,65 +67,49 @@
return
}
- typ := info.TypeOf(expr)
+ typ := pass.TypesInfo.TypeOf(expr)
if typ == nil {
return
}
// Find reference to the type declaration of the struct being initialized.
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- break
- }
- typ = p.Elem()
- }
- typ = typ.Underlying()
-
- obj, ok := typ.(*types.Struct)
+ typ = deref(typ)
+ tStruct, ok := typ.Underlying().(*types.Struct)
if !ok {
return
}
- fieldCount := obj.NumFields()
+ // Inv: typ is the possibly-named struct type.
+
+ fieldCount := tStruct.NumFields()
// Skip any struct that is already populated or that has no fields.
if fieldCount == 0 || fieldCount == len(expr.Elts) {
return
}
- var fillable bool
+ // Are any fields in need of filling?
var fillableFields []string
for i := 0; i < fieldCount; i++ {
- field := obj.Field(i)
+ field := tStruct.Field(i)
// Ignore fields that are not accessible in the current package.
if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
continue
}
- // Ignore structs containing fields that have type parameters for now.
- // TODO: support type params.
- if typ, ok := field.Type().(*types.Named); ok {
- if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 {
- return
- }
- }
- if _, ok := field.Type().(*typeparams.TypeParam); ok {
- return
- }
- fillable = true
fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
}
- if !fillable {
+ if len(fillableFields) == 0 {
return
}
+
+ // Derive a name for the struct type.
var name string
- switch typ := expr.Type.(type) {
- case *ast.Ident:
- name = typ.Name
- case *ast.SelectorExpr:
- name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name)
- default:
+ if typ != tStruct {
+ // named struct type (e.g. pkg.S[T])
+ name = types.TypeString(typ, types.RelativeTo(pass.Pkg))
+ } else {
+ // anonymous struct type
totalFields := len(fillableFields)
- maxLen := 20
+ const maxLen = 20
// Find the index to cut off printing of fields.
var i, fieldLen int
for i = range fillableFields {
@@ -145,7 +133,13 @@
return nil, nil
}
+// SuggestedFix computes the suggested fix for the kinds of
+// diagnostics produced by the Analyzer above.
func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+ if info == nil {
+ return nil, fmt.Errorf("nil types.Info")
+ }
+
pos := rng.Start // don't use the end
// TODO(rstambler): Using ast.Inspect would probably be more efficient than
@@ -162,37 +156,29 @@
}
}
- if info == nil {
- return nil, fmt.Errorf("nil types.Info")
- }
typ := info.TypeOf(expr)
if typ == nil {
return nil, fmt.Errorf("no composite literal")
}
// Find reference to the type declaration of the struct being initialized.
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- break
- }
- typ = p.Elem()
- }
- typ = typ.Underlying()
-
- obj, ok := typ.(*types.Struct)
+ typ = deref(typ)
+ tStruct, ok := typ.Underlying().(*types.Struct)
if !ok {
- return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ)
+ return nil, fmt.Errorf("%s is not a (pointer to) struct type",
+ types.TypeString(typ, types.RelativeTo(pkg)))
}
- fieldCount := obj.NumFields()
+ // Inv: typ is the the possibly-named struct type.
+
+ fieldCount := tStruct.NumFields()
// Check which types have already been filled in. (we only want to fill in
// the unfilled types, or else we'll blat user-supplied details)
- prefilledTypes := map[string]ast.Expr{}
+ prefilledFields := map[string]ast.Expr{}
for _, e := range expr.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
if key, ok := kv.Key.(*ast.Ident); ok {
- prefilledTypes[key.Name] = kv.Value
+ prefilledFields[key.Name] = kv.Value
}
}
}
@@ -202,14 +188,16 @@
// each field we're going to set. format.Node only cares about line
// numbers, so we don't need to set columns, and each line can be
// 1 byte long.
+ // TODO(adonovan): why is this necessary? The position information
+ // is going to be wrong for the existing trees in prefilledFields.
+ // Can't the formatter just do its best with an empty fileset?
fakeFset := token.NewFileSet()
tok := fakeFset.AddFile("", -1, fieldCount+2)
line := 2 // account for 1-based lines and the left brace
- var elts []ast.Expr
var fieldTyps []types.Type
for i := 0; i < fieldCount; i++ {
- field := obj.Field(i)
+ field := tStruct.Field(i)
// Ignore fields that are not accessible in the current package.
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
fieldTyps = append(fieldTyps, nil)
@@ -217,11 +205,13 @@
}
fieldTyps = append(fieldTyps, field.Type())
}
- matches := analysisinternal.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg)
+ matches := analysisinternal.MatchingIdents(fieldTyps, file, rng.Start, info, pkg)
+ var elts []ast.Expr
for i, fieldTyp := range fieldTyps {
if fieldTyp == nil {
- continue
+ continue // TODO(adonovan): is this reachable?
}
+ fieldName := tStruct.Field(i).Name()
tok.AddLine(line - 1) // add 1 byte per line
if line > tok.LineCount() {
@@ -232,30 +222,28 @@
kv := &ast.KeyValueExpr{
Key: &ast.Ident{
NamePos: pos,
- Name: obj.Field(i).Name(),
+ Name: fieldName,
},
Colon: pos,
}
- if expr, ok := prefilledTypes[obj.Field(i).Name()]; ok {
+ if expr, ok := prefilledFields[fieldName]; ok {
kv.Value = expr
} else {
- idents, ok := matches[fieldTyp]
+ names, ok := matches[fieldTyp]
if !ok {
return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
}
- // Find the identifier whose name is most similar to the name of the field's key.
- // If we do not find any identifier that matches the pattern, generate a new value.
+ // Find the name most similar to the field name.
+ // If no name matches the pattern, generate a zero value.
// NOTE: We currently match on the name of the field key rather than the field type.
- value := fuzzy.FindBestMatch(obj.Field(i).Name(), idents)
- if value == nil {
- value = populateValue(file, pkg, fieldTyp)
- }
- if value == nil {
+ if best := fuzzy.BestMatch(fieldName, names); best != "" {
+ kv.Value = ast.NewIdent(best)
+ } else if v := populateValue(file, pkg, fieldTyp); v != nil {
+ kv.Value = v
+ } else {
return nil, nil
}
-
- kv.Value = value
}
elts = append(elts, kv)
line++
@@ -299,7 +287,7 @@
}
sug := indent(formatBuf.Bytes(), whitespace)
- if len(prefilledTypes) > 0 {
+ if len(prefilledFields) > 0 {
// Attempt a second pass through the formatter to line up columns.
sourced, err := format.Source(sug)
if err == nil {
@@ -343,16 +331,12 @@
//
// When the type of a struct field is a basic literal or interface, we return
// default values. For other types, such as maps, slices, and channels, we create
-// expressions rather than using default values.
+// empty expressions such as []T{} or make(chan T) rather than using default values.
//
// The reasoning here is that users will call fillstruct with the intention of
// initializing the struct, in which case setting these fields to nil has no effect.
func populateValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- under := typ
- if n, ok := typ.(*types.Named); ok {
- under = n.Underlying()
- }
- switch u := under.(type) {
+ switch u := typ.Underlying().(type) {
case *types.Basic:
switch {
case u.Info()&types.IsNumeric != 0:
@@ -366,6 +350,7 @@
default:
panic("unknown basic type")
}
+
case *types.Map:
k := analysisinternal.TypeExpr(f, pkg, u.Key())
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
@@ -388,6 +373,7 @@
Elt: s,
},
}
+
case *types.Array:
a := analysisinternal.TypeExpr(f, pkg, u.Elem())
if a == nil {
@@ -401,6 +387,7 @@
},
},
}
+
case *types.Chan:
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
if v == nil {
@@ -419,6 +406,7 @@
},
},
}
+
case *types.Struct:
s := analysisinternal.TypeExpr(f, pkg, typ)
if s == nil {
@@ -427,6 +415,7 @@
return &ast.CompositeLit{
Type: s,
}
+
case *types.Signature:
var params []*ast.Field
for i := 0; i < u.Params().Len(); i++ {
@@ -464,6 +453,7 @@
},
Body: &ast.BlockStmt{},
}
+
case *types.Pointer:
switch u.Elem().(type) {
case *types.Basic:
@@ -483,8 +473,34 @@
X: populateValue(f, pkg, u.Elem()),
}
}
+
case *types.Interface:
+ if param, ok := typ.(*typeparams.TypeParam); ok {
+ // *new(T) is the zero value of a type parameter T.
+ // TODO(adonovan): one could give a more specific zero
+ // value if the type has a core type that is, say,
+ // always a number or a pointer. See go/ssa for details.
+ return &ast.StarExpr{
+ X: &ast.CallExpr{
+ Fun: ast.NewIdent("new"),
+ Args: []ast.Expr{
+ ast.NewIdent(param.Obj().Name()),
+ },
+ },
+ }
+ }
+
return ast.NewIdent("nil")
}
return nil
}
+
+func deref(t types.Type) types.Type {
+ for {
+ ptr, ok := t.Underlying().(*types.Pointer)
+ if !ok {
+ return t
+ }
+ t = ptr.Elem()
+ }
+}
diff --git a/gopls/internal/lsp/analysis/fillstruct/testdata/src/a/a.go b/gopls/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
index 6856009..9ee3860 100644
--- a/gopls/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
+++ b/gopls/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
@@ -19,16 +19,16 @@
foo int
}
-var _ = basicStruct{} // want ""
+var _ = basicStruct{} // want `Fill basicStruct`
type twoArgStruct struct {
foo int
bar string
}
-var _ = twoArgStruct{} // want ""
+var _ = twoArgStruct{} // want `Fill twoArgStruct`
-var _ = twoArgStruct{ // want ""
+var _ = twoArgStruct{ // want `Fill twoArgStruct`
bar: "bar",
}
@@ -37,9 +37,9 @@
basic basicStruct
}
-var _ = nestedStruct{} // want ""
+var _ = nestedStruct{} // want `Fill nestedStruct`
-var _ = data.B{} // want ""
+var _ = data.B{} // want `Fill b.B`
type typedStruct struct {
m map[string]int
@@ -49,25 +49,25 @@
a [2]string
}
-var _ = typedStruct{} // want ""
+var _ = typedStruct{} // want `Fill typedStruct`
type funStruct struct {
fn func(i int) int
}
-var _ = funStruct{} // want ""
+var _ = funStruct{} // want `Fill funStruct`
-type funStructCompex struct {
+type funStructComplex struct {
fn func(i int, s string) (string, int)
}
-var _ = funStructCompex{} // want ""
+var _ = funStructComplex{} // want `Fill funStructComplex`
type funStructEmpty struct {
fn func()
}
-var _ = funStructEmpty{} // want ""
+var _ = funStructEmpty{} // want `Fill funStructEmpty`
type Foo struct {
A int
@@ -78,7 +78,7 @@
Y *Foo
}
-var _ = Bar{} // want ""
+var _ = Bar{} // want `Fill Bar`
type importedStruct struct {
m map[*ast.CompositeLit]ast.Field
@@ -89,7 +89,7 @@
st ast.CompositeLit
}
-var _ = importedStruct{} // want ""
+var _ = importedStruct{} // want `Fill importedStruct`
type pointerBuiltinStruct struct {
b *bool
@@ -97,17 +97,17 @@
i *int
}
-var _ = pointerBuiltinStruct{} // want ""
+var _ = pointerBuiltinStruct{} // want `Fill pointerBuiltinStruct`
var _ = []ast.BasicLit{
- {}, // want ""
+ {}, // want `Fill go/ast.BasicLit`
}
-var _ = []ast.BasicLit{{}, // want ""
+var _ = []ast.BasicLit{{}, // want "go/ast.BasicLit"
}
type unsafeStruct struct {
foo unsafe.Pointer
}
-var _ = unsafeStruct{} // want ""
+var _ = unsafeStruct{} // want `Fill unsafeStruct`
diff --git a/gopls/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go b/gopls/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
index 7972bd3..46bb8ae 100644
--- a/gopls/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
+++ b/gopls/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
@@ -12,16 +12,16 @@
foo T
}
-var _ = basicStruct[int]{} // want ""
+var _ = basicStruct[int]{} // want `Fill basicStruct\[int\]`
type twoArgStruct[F, B any] struct {
foo F
bar B
}
-var _ = twoArgStruct[string, int]{} // want ""
+var _ = twoArgStruct[string, int]{} // want `Fill twoArgStruct\[string, int\]`
-var _ = twoArgStruct[int, string]{ // want ""
+var _ = twoArgStruct[int, string]{ // want `Fill twoArgStruct\[int, string\]`
bar: "bar",
}
@@ -30,10 +30,21 @@
basic basicStruct[int]
}
-var _ = nestedStruct{}
+var _ = nestedStruct{} // want "Fill nestedStruct"
func _[T any]() {
type S struct{ t T }
- x := S{}
+ x := S{} // want "Fill S"
_ = x
}
+
+func Test() {
+ var tests = []struct {
+ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p string
+ }{
+ {}, // want "Fill anonymous struct { a: string, b: string, c: string, ... }"
+ }
+ for _, test := range tests {
+ _ = test
+ }
+}
diff --git a/gopls/internal/lsp/testdata/fillstruct/typeparams.go b/gopls/internal/lsp/testdata/fillstruct/typeparams.go
index c60cd68..c0b702f 100644
--- a/gopls/internal/lsp/testdata/fillstruct/typeparams.go
+++ b/gopls/internal/lsp/testdata/fillstruct/typeparams.go
@@ -5,7 +5,7 @@
type emptyStructWithTypeParams[A any] struct{}
-var _ = emptyStructWithTypeParams[int]{}
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
type basicStructWithTypeParams[T any] struct {
foo T
@@ -29,10 +29,9 @@
basic basicStructWithTypeParams[int]
}
-var _ = nestedStructWithTypeParams{}
+var _ = nestedStructWithTypeParams{} //@suggestedfix("}", "refactor.rewrite", "Fill")
func _[T any]() {
type S struct{ t T }
- x := S{}
- _ = x
+ _ = S{} //@suggestedfix("}", "refactor.rewrite", "Fill")
}
diff --git a/gopls/internal/lsp/testdata/fillstruct/typeparams.go.golden b/gopls/internal/lsp/testdata/fillstruct/typeparams.go.golden
index 12e2f84..625df75 100644
--- a/gopls/internal/lsp/testdata/fillstruct/typeparams.go.golden
+++ b/gopls/internal/lsp/testdata/fillstruct/typeparams.go.golden
@@ -6,7 +6,7 @@
type emptyStructWithTypeParams[A any] struct{}
-var _ = emptyStructWithTypeParams[int]{}
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
type basicStructWithTypeParams[T any] struct {
foo T
@@ -32,12 +32,11 @@
basic basicStructWithTypeParams[int]
}
-var _ = nestedStructWithTypeParams{}
+var _ = nestedStructWithTypeParams{} //@suggestedfix("}", "refactor.rewrite", "Fill")
func _[T any]() {
type S struct{ t T }
- x := S{}
- _ = x
+ _ = S{} //@suggestedfix("}", "refactor.rewrite", "Fill")
}
-- suggestedfix_typeparams_21_49 --
@@ -48,7 +47,7 @@
type emptyStructWithTypeParams[A any] struct{}
-var _ = emptyStructWithTypeParams[int]{}
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
type basicStructWithTypeParams[T any] struct {
foo T
@@ -75,12 +74,11 @@
basic basicStructWithTypeParams[int]
}
-var _ = nestedStructWithTypeParams{}
+var _ = nestedStructWithTypeParams{} //@suggestedfix("}", "refactor.rewrite", "Fill")
func _[T any]() {
type S struct{ t T }
- x := S{}
- _ = x
+ _ = S{} //@suggestedfix("}", "refactor.rewrite", "Fill")
}
-- suggestedfix_typeparams_25_1 --
@@ -91,7 +89,7 @@
type emptyStructWithTypeParams[A any] struct{}
-var _ = emptyStructWithTypeParams[int]{}
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
type basicStructWithTypeParams[T any] struct {
foo T
@@ -116,11 +114,93 @@
basic basicStructWithTypeParams[int]
}
-var _ = nestedStructWithTypeParams{}
+var _ = nestedStructWithTypeParams{} //@suggestedfix("}", "refactor.rewrite", "Fill")
func _[T any]() {
type S struct{ t T }
- x := S{}
- _ = x
+ _ = S{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+}
+
+-- suggestedfix_typeparams_32_36 --
+//go:build go1.18
+// +build go1.18
+
+package fillstruct
+
+type emptyStructWithTypeParams[A any] struct{}
+
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
+
+type basicStructWithTypeParams[T any] struct {
+ foo T
+}
+
+var _ = basicStructWithTypeParams[int]{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+type twoArgStructWithTypeParams[F, B any] struct {
+ foo F
+ bar B
+}
+
+var _ = twoArgStructWithTypeParams[string, int]{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+var _ = twoArgStructWithTypeParams[int, string]{
+ bar: "bar",
+} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+type nestedStructWithTypeParams struct {
+ bar string
+ basic basicStructWithTypeParams[int]
+}
+
+var _ = nestedStructWithTypeParams{
+ bar: "",
+ basic: basicStructWithTypeParams{},
+} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+func _[T any]() {
+ type S struct{ t T }
+ _ = S{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+}
+
+-- suggestedfix_typeparams_36_8 --
+//go:build go1.18
+// +build go1.18
+
+package fillstruct
+
+type emptyStructWithTypeParams[A any] struct{}
+
+var _ = emptyStructWithTypeParams[int]{} // no suggested fix
+
+type basicStructWithTypeParams[T any] struct {
+ foo T
+}
+
+var _ = basicStructWithTypeParams[int]{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+type twoArgStructWithTypeParams[F, B any] struct {
+ foo F
+ bar B
+}
+
+var _ = twoArgStructWithTypeParams[string, int]{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+var _ = twoArgStructWithTypeParams[int, string]{
+ bar: "bar",
+} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+type nestedStructWithTypeParams struct {
+ bar string
+ basic basicStructWithTypeParams[int]
+}
+
+var _ = nestedStructWithTypeParams{} //@suggestedfix("}", "refactor.rewrite", "Fill")
+
+func _[T any]() {
+ type S struct{ t T }
+ _ = S{
+ t: *new(T),
+ } //@suggestedfix("}", "refactor.rewrite", "Fill")
}
diff --git a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
index 93d9c63..1e1c876 100644
--- a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -13,7 +13,7 @@
FormatCount = 6
ImportCount = 8
SemanticTokenCount = 3
-SuggestedFixCount = 67
+SuggestedFixCount = 69
FunctionExtractionCount = 27
MethodExtractionCount = 6
DefinitionsCount = 110
diff --git a/internal/analysisinternal/analysis.go b/internal/analysisinternal/analysis.go
index d538e07..3b983cc 100644
--- a/internal/analysisinternal/analysis.go
+++ b/internal/analysisinternal/analysis.go
@@ -83,6 +83,9 @@
}
}
+// TypeExpr returns syntax for the specified type. References to
+// named types from packages other than pkg are qualified by an appropriate
+// package name, as defined by the import environment of file.
func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
switch t := typ.(type) {
case *types.Basic:
@@ -312,19 +315,21 @@
})
}
-// FindMatchingIdents finds all identifiers in 'node' that match any of the given types.
+// MatchingIdents finds the names of all identifiers in 'node' that match any of the given types.
// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that
// is unrecognized.
-func FindMatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]*ast.Ident {
- matches := map[types.Type][]*ast.Ident{}
+func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string {
+
// Initialize matches to contain the variable types we are searching for.
+ matches := make(map[types.Type][]string)
for _, typ := range typs {
if typ == nil {
- continue
+ continue // TODO(adonovan): is this reachable?
}
- matches[typ] = []*ast.Ident{}
+ matches[typ] = nil // create entry
}
+
seen := map[types.Object]struct{}{}
ast.Inspect(node, func(n ast.Node) bool {
if n == nil {
@@ -336,8 +341,7 @@
//
// x := fakeStruct{f0: x}
//
- assignment, ok := n.(*ast.AssignStmt)
- if ok && pos > assignment.Pos() && pos <= assignment.End() {
+ if assign, ok := n.(*ast.AssignStmt); ok && pos > assign.Pos() && pos <= assign.End() {
return false
}
if n.End() > pos {
@@ -370,17 +374,17 @@
return true
}
// The object must match one of the types that we are searching for.
- if idents, ok := matches[obj.Type()]; ok {
- matches[obj.Type()] = append(idents, ast.NewIdent(ident.Name))
- }
- // If the object type does not exactly match any of the target types, greedily
- // find the first target type that the object type can satisfy.
- for typ := range matches {
- if obj.Type() == typ {
- continue
- }
- if equivalentTypes(obj.Type(), typ) {
- matches[typ] = append(matches[typ], ast.NewIdent(ident.Name))
+ // TODO(adonovan): opt: use typeutil.Map?
+ if names, ok := matches[obj.Type()]; ok {
+ matches[obj.Type()] = append(names, ident.Name)
+ } else {
+ // If the object type does not exactly match
+ // any of the target types, greedily find the first
+ // target type that the object type can satisfy.
+ for typ := range matches {
+ if equivalentTypes(obj.Type(), typ) {
+ matches[typ] = append(matches[typ], ident.Name)
+ }
}
}
return true
@@ -389,7 +393,7 @@
}
func equivalentTypes(want, got types.Type) bool {
- if want == got || types.Identical(want, got) {
+ if types.Identical(want, got) {
return true
}
// Code segment to help check for untyped equality from (golang/go#32146).
diff --git a/internal/fuzzy/matcher.go b/internal/fuzzy/matcher.go
index 92e1001..c0efd30 100644
--- a/internal/fuzzy/matcher.go
+++ b/internal/fuzzy/matcher.go
@@ -8,7 +8,6 @@
import (
"bytes"
"fmt"
- "go/ast"
)
const (
@@ -407,29 +406,29 @@
return false
}
-// FindBestMatch employs fuzzy matching to evaluate the similarity of each given identifier to the
-// given pattern. We return the identifier whose name is most similar to the pattern.
-func FindBestMatch(pattern string, idents []*ast.Ident) ast.Expr {
+// BestMatch returns the name most similar to the
+// pattern, using fuzzy matching, or the empty string.
+func BestMatch(pattern string, names []string) string {
fuzz := NewMatcher(pattern)
- var bestFuzz ast.Expr
+ best := ""
highScore := float32(0) // minimum score is 0 (no match)
- for _, ident := range idents {
+ for _, name := range names {
// TODO: Improve scoring algorithm.
- score := fuzz.Score(ident.Name)
+ score := fuzz.Score(name)
if score > highScore {
highScore = score
- bestFuzz = ident
+ best = name
} else if score == 0 {
// Order matters in the fuzzy matching algorithm. If we find no match
// when matching the target to the identifier, try matching the identifier
// to the target.
- revFuzz := NewMatcher(ident.Name)
+ revFuzz := NewMatcher(name)
revScore := revFuzz.Score(pattern)
if revScore > highScore {
highScore = revScore
- bestFuzz = ident
+ best = name
}
}
}
- return bestFuzz
+ return best
}