Skip to content

Commit

Permalink
x/tools/gopls: implement struct field generation quickfix
Browse files Browse the repository at this point in the history
  • Loading branch information
dennypenta committed Dec 3, 2024
1 parent b4a7b92 commit 6a1c766
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 92 deletions.
8 changes: 8 additions & 0 deletions gopls/doc/release/v0.17.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,11 @@ from the context of the access.
The new `yield` analyzer detects mistakes using the `yield` function
in a Go 1.23 iterator, such as failure to check its boolean result and
break out of a loop.

## Generate missing struct field from access
When you attempt to access a field on a type that does not have the field,
the compiler will report an error like “type X has no field or method Y”.
Gopls now offers a new code action, “Declare missing field of T.f”,
where T is the concrete type and f is the undefined field.
The stub field's signature is inferred
from the context of the access.
136 changes: 44 additions & 92 deletions gopls/internal/golang/stubmethods/stubcalledfunc.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
arg := callExpr.Args[0]
composite, ok := arg.(*ast.CompositeLit)
if ok {
t := typeFromCompositeLit(info, path, composite)
t := typeFromExpr(info, path, composite)
typs = append(typs, t)
break
}
Expand All @@ -325,7 +325,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
arg := callExpr.Args[0]
composite, ok := arg.(*ast.CompositeLit)
if ok {
t := typeFromCompositeLit(info, path, composite)
t := typeFromExpr(info, path, composite)
t = types.NewPointer(t)
typs = append(typs, t)
break
Expand All @@ -345,23 +345,23 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
// a variable
ident, ok := rh.(*ast.Ident)
if ok {
if t := typeFromIdent(info, path, ident); t != nil {
if t := typeFromExpr(info, path, ident); t != nil {
typs = append(typs, t)
}
break
}

selectorExpr, ok := rh.(*ast.SelectorExpr)
if ok {
if t := typeFromIdent(info, path, selectorExpr.Sel); t != nil {
if t := typeFromExpr(info, path, selectorExpr.Sel); t != nil {
typs = append(typs, t)
}
break
}
// composite
composite, ok := rh.(*ast.CompositeLit)
if ok {
t := typeFromCompositeLit(info, path, composite)
t := typeFromExpr(info, path, composite)
typs = append(typs, t)
break
}
Expand All @@ -386,7 +386,7 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types.
if ok {
ident, ok := starExpr.X.(*ast.Ident)
if ok {
if t := typeFromIdent(info, path, ident); t != nil {
if t := typeFromExpr(info, path, ident); t != nil {
if pointer, ok := t.(*types.Pointer); ok {
t = pointer.Elem()
}
Expand Down Expand Up @@ -487,102 +487,54 @@ func lastSection(identName string) string {
}
}

func typeFromCompositeLit(info *types.Info, path []ast.Node, composite *ast.CompositeLit) types.Type {
if t := info.TypeOf(composite); t != nil {
if !containsInvalid(t) {
t = types.Default(t)
if named, ok := t.(*types.Named); ok {
if pkg := named.Obj().Pkg(); pkg != nil {
// Find the file in the path that contains this assignment
var file *ast.File
for _, n := range path {
if f, ok := n.(*ast.File); ok {
file = f
break
}
}

if file != nil {
// Look for any import spec that imports this package
var pkgName string
for _, imp := range file.Imports {
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
// Use the alias if specified, otherwise use package name
if imp.Name != nil {
pkgName = imp.Name.Name
} else {
pkgName = pkg.Name()
}
break
}
}
if pkgName == "" {
pkgName = pkg.Name() // fallback to package name if no import found
}
func typeFromExpr(info *types.Info, path []ast.Node, expr ast.Expr) types.Type {
t := info.TypeOf(expr)
if t == nil {
return nil
}

// Create new package with the correct name (either alias or original)
newPkg := types.NewPackage(pkgName, pkgName)
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
t = types.NewNamed(newName, named.Underlying(), nil)
if !containsInvalid(t) {
t = types.Default(t)
if named, ok := t.(*types.Named); ok {
if pkg := named.Obj().Pkg(); pkg != nil {
// find the file in the path that contains this assignment
var file *ast.File
for _, n := range path {
if f, ok := n.(*ast.File); ok {
file = f
break
}
}
return t
}
} else {
t = anyType
}
return t
}
return nil
}

func typeFromIdent(info *types.Info, path []ast.Node, ident *ast.Ident) types.Type {
if t := info.TypeOf(ident); t != nil {
if !containsInvalid(t) {
t = types.Default(t)
if named, ok := t.(*types.Named); ok {
if pkg := named.Obj().Pkg(); pkg != nil {
// find the file in the path that contains this assignment
var file *ast.File
for _, n := range path {
if f, ok := n.(*ast.File); ok {
file = f
if file != nil {
// look for any import spec that imports this package
var pkgName string
for _, imp := range file.Imports {
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
// use the alias if specified, otherwise use package name
if imp.Name != nil {
pkgName = imp.Name.Name
} else {
pkgName = pkg.Name()
}
break
}
}

if file != nil {
// look for any import spec that imports this package
var pkgName string
for _, imp := range file.Imports {
if path, _ := strconv.Unquote(imp.Path.Value); path == pkg.Path() {
// use the alias if specified, otherwise use package name
if imp.Name != nil {
pkgName = imp.Name.Name
} else {
pkgName = pkg.Name()
}
break
}
}
// fallback to package name if no import found
if pkgName == "" {
pkgName = pkg.Name()
}

// create new package with the correct name (either alias or original)
newPkg := types.NewPackage(pkgName, pkgName)
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
t = types.NewNamed(newName, named.Underlying(), nil)
// fallback to package name if no import found
if pkgName == "" {
pkgName = pkg.Name()
}

// create new package with the correct name (either alias or original)
newPkg := types.NewPackage(pkgName, pkgName)
newName := types.NewTypeName(named.Obj().Pos(), newPkg, named.Obj().Name(), nil)
t = types.NewNamed(newName, named.Underlying(), nil)
}
return t
}
} else {
t = anyType
return t
}
return t
} else {
t = anyType
}

return nil
return t
}

0 comments on commit 6a1c766

Please sign in to comment.