Skip to content

Commit 6a1c766

Browse files
committed
x/tools/gopls: implement struct field generation quickfix
1 parent b4a7b92 commit 6a1c766

File tree

2 files changed

+52
-92
lines changed

2 files changed

+52
-92
lines changed

gopls/doc/release/v0.17.0.md

+8
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,11 @@ from the context of the access.
9292
The new `yield` analyzer detects mistakes using the `yield` function
9393
in a Go 1.23 iterator, such as failure to check its boolean result and
9494
break out of a loop.
95+
96+
## Generate missing struct field from access
97+
When you attempt to access a field on a type that does not have the field,
98+
the compiler will report an error like “type X has no field or method Y”.
99+
Gopls now offers a new code action, “Declare missing field of T.f”,
100+
where T is the concrete type and f is the undefined field.
101+
The stub field's signature is inferred
102+
from the context of the access.

gopls/internal/golang/stubmethods/stubcalledfunc.go

+44-92
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
308308
arg := callExpr.Args[0]
309309
composite, ok := arg.(*ast.CompositeLit)
310310
if ok {
311-
t := typeFromCompositeLit(info, path, composite)
311+
t := typeFromExpr(info, path, composite)
312312
typs = append(typs, t)
313313
break
314314
}
@@ -325,7 +325,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
325325
arg := callExpr.Args[0]
326326
composite, ok := arg.(*ast.CompositeLit)
327327
if ok {
328-
t := typeFromCompositeLit(info, path, composite)
328+
t := typeFromExpr(info, path, composite)
329329
t = types.NewPointer(t)
330330
typs = append(typs, t)
331331
break
@@ -345,23 +345,23 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
345345
// a variable
346346
ident, ok := rh.(*ast.Ident)
347347
if ok {
348-
if t := typeFromIdent(info, path, ident); t != nil {
348+
if t := typeFromExpr(info, path, ident); t != nil {
349349
typs = append(typs, t)
350350
}
351351
break
352352
}
353353

354354
selectorExpr, ok := rh.(*ast.SelectorExpr)
355355
if ok {
356-
if t := typeFromIdent(info, path, selectorExpr.Sel); t != nil {
356+
if t := typeFromExpr(info, path, selectorExpr.Sel); t != nil {
357357
typs = append(typs, t)
358358
}
359359
break
360360
}
361361
// composite
362362
composite, ok := rh.(*ast.CompositeLit)
363363
if ok {
364-
t := typeFromCompositeLit(info, path, composite)
364+
t := typeFromExpr(info, path, composite)
365365
typs = append(typs, t)
366366
break
367367
}
@@ -386,7 +386,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
386386
if ok {
387387
ident, ok := starExpr.X.(*ast.Ident)
388388
if ok {
389-
if t := typeFromIdent(info, path, ident); t != nil {
389+
if t := typeFromExpr(info, path, ident); t != nil {
390390
if pointer, ok := t.(*types.Pointer); ok {
391391
t = pointer.Elem()
392392
}
@@ -487,102 +487,54 @@ func lastSection(identName string) string {
487487
}
488488
}
489489

490-
func typeFromCompositeLit(info *types.Info, path []ast.Node, composite *ast.CompositeLit) types.Type {
491-
if t := info.TypeOf(composite); t != nil {
492-
if !containsInvalid(t) {
493-
t = types.Default(t)
494-
if named, ok := t.(*types.Named); ok {
495-
if pkg := named.Obj().Pkg(); pkg != nil {
496-
// Find the file in the path that contains this assignment
497-
var file *ast.File
498-
for _, n := range path {
499-
if f, ok := n.(*ast.File); ok {
500-
file = f
501-
break
502-
}
503-
}
504-
505-
if file != nil {
506-
// Look for any import spec that imports this package
507-
var pkgName string
508-
for _, imp := range file.Imports {
509-
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
510-
// Use the alias if specified, otherwise use package name
511-
if imp.Name != nil {
512-
pkgName = imp.Name.Name
513-
} else {
514-
pkgName = pkg.Name()
515-
}
516-
break
517-
}
518-
}
519-
if pkgName == "" {
520-
pkgName = pkg.Name() // fallback to package name if no import found
521-
}
490+
func typeFromExpr(info *types.Info, path []ast.Node, expr ast.Expr) types.Type {
491+
t := info.TypeOf(expr)
492+
if t == nil {
493+
return nil
494+
}
522495

523-
// Create new package with the correct name (either alias or original)
524-
newPkg := types.NewPackage(pkgName, pkgName)
525-
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
526-
t = types.NewNamed(newName, named.Underlying(), nil)
496+
if !containsInvalid(t) {
497+
t = types.Default(t)
498+
if named, ok := t.(*types.Named); ok {
499+
if pkg := named.Obj().Pkg(); pkg != nil {
500+
// find the file in the path that contains this assignment
501+
var file *ast.File
502+
for _, n := range path {
503+
if f, ok := n.(*ast.File); ok {
504+
file = f
505+
break
527506
}
528507
}
529-
return t
530-
}
531-
} else {
532-
t = anyType
533-
}
534-
return t
535-
}
536-
return nil
537-
}
538508

539-
func typeFromIdent(info *types.Info, path []ast.Node, ident *ast.Ident) types.Type {
540-
if t := info.TypeOf(ident); t != nil {
541-
if !containsInvalid(t) {
542-
t = types.Default(t)
543-
if named, ok := t.(*types.Named); ok {
544-
if pkg := named.Obj().Pkg(); pkg != nil {
545-
// find the file in the path that contains this assignment
546-
var file *ast.File
547-
for _, n := range path {
548-
if f, ok := n.(*ast.File); ok {
549-
file = f
509+
if file != nil {
510+
// look for any import spec that imports this package
511+
var pkgName string
512+
for _, imp := range file.Imports {
513+
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
514+
// use the alias if specified, otherwise use package name
515+
if imp.Name != nil {
516+
pkgName = imp.Name.Name
517+
} else {
518+
pkgName = pkg.Name()
519+
}
550520
break
551521
}
552522
}
553-
554-
if file != nil {
555-
// look for any import spec that imports this package
556-
var pkgName string
557-
for _, imp := range file.Imports {
558-
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
559-
// use the alias if specified, otherwise use package name
560-
if imp.Name != nil {
561-
pkgName = imp.Name.Name
562-
} else {
563-
pkgName = pkg.Name()
564-
}
565-
break
566-
}
567-
}
568-
// fallback to package name if no import found
569-
if pkgName == "" {
570-
pkgName = pkg.Name()
571-
}
572-
573-
// create new package with the correct name (either alias or original)
574-
newPkg := types.NewPackage(pkgName, pkgName)
575-
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
576-
t = types.NewNamed(newName, named.Underlying(), nil)
523+
// fallback to package name if no import found
524+
if pkgName == "" {
525+
pkgName = pkg.Name()
577526
}
527+
528+
// create new package with the correct name (either alias or original)
529+
newPkg := types.NewPackage(pkgName, pkgName)
530+
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
531+
t = types.NewNamed(newName, named.Underlying(), nil)
578532
}
579-
return t
580533
}
581-
} else {
582-
t = anyType
534+
return t
583535
}
584-
return t
536+
} else {
537+
t = anyType
585538
}
586-
587-
return nil
539+
return t
588540
}

0 commit comments

Comments
 (0)