Skip to content

Commit 36c2d1c

Browse files
committed
Change struct definition discovery to position based instead of a type name
1 parent d53373c commit 36c2d1c

File tree

5 files changed

+55
-61
lines changed

5 files changed

+55
-61
lines changed

gopls/doc/features/diagnostics.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ There is an optional third source of diagnostics:
7272
are transitively free from errors, so optimization diagnostics
7373
will not be shown on packages that do not build.
7474

75-
7675
## Recomputation of diagnostics
7776

7877
By default, diagnostics are automatically recomputed each time the source files
@@ -278,7 +277,7 @@ func doSomething(i int) string {
278277
When you attempt to access a field on a type that does not have the field,
279278
the compiler will report an error such as "type X has no field or method Y".
280279
In this scenario, gopls now offers a quick fix to generate a stub declaration of
281-
the missing field, inferring its type from the accessing type or assigning a designated value.
280+
the missing field, inferring its type from the context in which it is used.
282281

283282
Consider the following code where `Foo` does not have a field `bar`:
284283

gopls/doc/release/v0.17.0.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ into account its signature, including input parameters and results.
206206

207207
Since this feature is implemented by the server (gopls), it is compatible with
208208
all LSP-compliant editors. VS Code users may continue to use the client-side
209-
`Go: Generate Unit Tests For file/function/package` command which utilizes the
209+
`Go: Generate Unit Tests For file/function/package` command which uses the
210210
[gotests](https://github.com/cweill/gotests) tool.
211211

212212
## Generate missing struct field from access

gopls/internal/golang/stub.go

+24-27
Original file line numberDiff line numberDiff line change
@@ -198,23 +198,23 @@ func insertStructField(ctx context.Context, snapshot *cache.Snapshot, mp *metada
198198
}
199199

200200
// find the struct type declaration
201-
var structType *ast.StructType
202-
ast.Inspect(declPGF.File, func(n ast.Node) bool {
203-
if typeSpec, ok := n.(*ast.TypeSpec); ok {
204-
if typeSpec.Name.Name == fieldInfo.Named.Obj().Name() {
205-
if st, ok := typeSpec.Type.(*ast.StructType); ok {
206-
structType = st
207-
return false
208-
}
209-
}
210-
}
211-
return true
212-
})
201+
pos := fieldInfo.Named.Obj().Pos()
202+
endPos := pos + token.Pos(len(fieldInfo.Named.Obj().Name()))
203+
curIdent, ok := declPGF.Cursor.FindPos(pos, endPos)
204+
if !ok {
205+
return nil, nil, fmt.Errorf("could not find identifier at position %v-%v", pos, endPos)
206+
}
213207

214-
if structType == nil {
215-
return nil, nil, fmt.Errorf("could not find struct definition")
208+
// Rest of the code remains the same
209+
typeNode, ok := curIdent.NextSibling()
210+
if !ok {
211+
return nil, nil, fmt.Errorf("could not find type specification")
216212
}
217213

214+
structType, ok := typeNode.Node().(*ast.StructType)
215+
if !ok {
216+
return nil, nil, fmt.Errorf("type at position %v is not a struct type", pos)
217+
}
218218
// Find metadata for the symbol's declaring package
219219
// as we'll need its import mapping.
220220
declMeta := findFileInDeps(snapshot, mp, declPGF.URI)
@@ -229,27 +229,24 @@ func insertStructField(ctx context.Context, snapshot *cache.Snapshot, mp *metada
229229
if insertPos == structType.Fields.Opening {
230230
// struct has no fields yet
231231
insertPos = structType.Fields.Closing
232+
_, err = declPGF.Mapper.PosRange(declPGF.Tok, insertPos, insertPos)
233+
if err != nil {
234+
return nil, nil, err
235+
}
232236
}
233237

234238
var buf bytes.Buffer
235239
if err := fieldInfo.Emit(&buf, qual); err != nil {
236240
return nil, nil, err
237241
}
238242

239-
_, err = declPGF.Mapper.PosRange(declPGF.Tok, insertPos, insertPos)
240-
if err != nil {
241-
return nil, nil, err
242-
}
243-
244-
textEdit := analysis.TextEdit{
245-
Pos: insertPos,
246-
End: insertPos,
247-
NewText: buf.Bytes(),
248-
}
249-
250243
return fieldInfo.Fset, &analysis.SuggestedFix{
251-
Message: fmt.Sprintf("Add field %s to struct %s", fieldInfo.Expr.Sel.Name, fieldInfo.Named.Obj().Name()),
252-
TextEdits: []analysis.TextEdit{textEdit},
244+
Message: fmt.Sprintf("Add field %s to struct %s", fieldInfo.Expr.Sel.Name, fieldInfo.Named.Obj().Name()),
245+
TextEdits: []analysis.TextEdit{{
246+
Pos: insertPos,
247+
End: insertPos,
248+
NewText: buf.Bytes(),
249+
}},
253250
}, nil
254251
}
255252

gopls/internal/golang/stubmethods/stubmethods.go

+22-23
Original file line numberDiff line numberDiff line change
@@ -458,36 +458,35 @@ func concreteType(info *types.Info, e ast.Expr) (*types.Named, bool) {
458458
return named, isPtr
459459
}
460460

461-
// GetFieldStubInfo creates a StructFieldInfo instance to generate a struct field in a given SelectorExpr
461+
// GetFieldStubInfo finds innermost enclosing selector x.f where x is a named struct type or a pointer to a struct type.
462462
func GetFieldStubInfo(fset *token.FileSet, info *types.Info, pgf *parsego.File, start, end token.Pos) *StructFieldInfo {
463463
path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
464464
for _, node := range path {
465465
s, ok := node.(*ast.SelectorExpr)
466-
if !ok {
467-
continue
468-
}
469-
// If recvExpr is a package name, compiler error would be
470-
// e.g., "undefined: http.bar", thus will not hit this code path.
471-
recvExpr := s.X
472-
recvNamed, _ := concreteType(info, recvExpr)
466+
if ok {
467+
// If recvExpr is a package name, compiler error would be
468+
// e.g., "undefined: http.bar", thus will not hit this code path.
469+
recvExpr := s.X
470+
recvNamed, _ := concreteType(info, recvExpr)
473471

474-
if recvNamed == nil || recvNamed.Obj().Pkg() == nil {
475-
return nil
476-
}
472+
if recvNamed == nil || recvNamed.Obj().Pkg() == nil {
473+
return nil
474+
}
477475

478-
structType, ok := recvNamed.Underlying().(*types.Struct)
479-
if !ok {
480-
break
481-
}
476+
structType, ok := recvNamed.Underlying().(*types.Struct)
477+
if !ok {
478+
break
479+
}
482480

483-
// Have: x.f where x has a named struct type.
484-
return &StructFieldInfo{
485-
Fset: fset,
486-
Expr: s,
487-
Named: recvNamed,
488-
info: info,
489-
path: path,
490-
structType: structType,
481+
// Have: x.f where x has a named struct type.
482+
return &StructFieldInfo{
483+
Fset: fset,
484+
Expr: s,
485+
Named: recvNamed,
486+
info: info,
487+
path: path,
488+
structType: structType,
489+
}
491490
}
492491
}
493492

gopls/internal/util/typesutil/typesutil.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
6565
switch parent := parent.(type) {
6666
case *ast.AssignStmt:
6767
right := pos > parent.TokPos
68+
expr, opposites := parent.Lhs, parent.Rhs
6869
if right {
69-
typs = append(typs, typeFromExprAssignExpr(parent.Rhs, parent.Lhs, info, pos, validType)...)
70-
} else {
71-
typs = append(typs, typeFromExprAssignExpr(parent.Lhs, parent.Rhs, info, pos, validType)...)
70+
expr, opposites = opposites, expr
7271
}
72+
typs = append(typs, typeFromExprAssignExpr(expr, opposites, info, pos, validType)...)
7373
case *ast.ValueSpec:
7474
if len(parent.Values) == 1 {
7575
for _, lhs := range parent.Names {
@@ -234,7 +234,7 @@ func EnclosingSignature(path []ast.Node, info *types.Info) *types.Signature {
234234
// f.x = v
235235
// where v - a value which type the function extracts
236236
func typeFromExprAssignExpr(exprs, opposites []ast.Expr, info *types.Info, pos token.Pos, validType func(t types.Type) types.Type) []types.Type {
237-
typs := make([]types.Type, 0)
237+
var typs []types.Type
238238
// Append all lhs's type
239239
if len(exprs) == 1 {
240240
for i := range opposites {
@@ -245,16 +245,15 @@ func typeFromExprAssignExpr(exprs, opposites []ast.Expr, info *types.Info, pos t
245245
}
246246
// Lhs and Rhs counts do not match, give up
247247
if len(opposites) != len(exprs) {
248-
return typs
248+
return nil
249249
}
250250
// Append corresponding index of lhs's type
251251
for i := range exprs {
252252
if astutil.NodeContains(exprs[i], pos) {
253253
t := info.TypeOf(opposites[i])
254-
typs = append(typs, validType(t))
255-
break
254+
return []types.Type{validType(t)}
256255
}
257256
}
258257

259-
return typs
258+
return nil
260259
}

0 commit comments

Comments
 (0)