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
 }