Skip to content

Commit

Permalink
unused: handle embedded aliases
Browse files Browse the repository at this point in the history
Closes: gh-1361
Closes: gh-1365
(cherry picked from commit d717045)
  • Loading branch information
dominikh committed Mar 15, 2023
1 parent 2eef176 commit 3cd466f
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 10 deletions.
62 changes: 62 additions & 0 deletions unused/testdata/src/example.com/embedding-alias/embedding-alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package pkg

type s1 struct{} //@ used("s1", true)

// Make sure the alias is used, and not just the type it points to.
type a1 = s1 //@ used("a1", true)

type E1 struct { //@ used("E1", true)
a1 //@ used("a1", true)
}

func F1(e E1) { //@ used("F1", true), used("e", true)
_ = e.a1
}

// Make sure fields get used correctly when embedded multiple times
type s2 struct { //@ used("s2", true)
a int //@ used("a", true)
b int //@ used("b", true)
c int //@ used("c", false)
}

type a2 = s2 //@ used("a2", true)

type E2 struct { //@ used("E2", true)
a2 //@ used("a2", true)
}

type E3 struct { //@ used("E3", true)
a2 //@ used("a2", true)
}

func F2(e E2) { //@ used("F2", true), used("e", true)
_ = e.a
}

func F3(e E3) { //@ used("F3", true), used("e", true)
_ = e.b
}

// Make sure embedding aliases to unnamed types works
type a4 = struct { //@ used("a4", true)
a int //@ used("a", true)
b int //@ used("b", true)
c int //@ used("c", false)
}

type E4 struct { //@ used("E4", true)
a4 //@ used("a4", true)
}

type E5 struct { //@ used("E5", true)
a4 //@ used("a4", true)
}

func F4(e E4) { //@ used("F4", true), used("e", true)
_ = e.a
}

func F5(e E5) { //@ used("F5", true), used("e", true)
_ = e.b
}
33 changes: 23 additions & 10 deletions unused/unused.go
Original file line number Diff line number Diff line change
Expand Up @@ -1394,28 +1394,38 @@ func (g *graph) stmt(stmt ast.Stmt, by types.Object) {
// embeddedField sees the field declared by the embedded field node, and marks the type as used by the field.
//
// Embedded fields are special in two ways: they don't have names, so we don't have immediate access to an ast.Ident to
// resolve to the field's types.Var, and we cannot use g.read on the type because eventually we do get to an ast.Ident,
// and ObjectOf resolves embedded fields to the field they declare, not the type. That's why we have code specially for
// handling embedded fields.
// resolve to the field's types.Var and need to instead walk the AST, and we cannot use g.read on the type because
// eventually we do get to an ast.Ident, and ObjectOf resolves embedded fields to the field they declare, not the type.
// That's why we have code specially for handling embedded fields.
func (g *graph) embeddedField(node ast.Node, by types.Object) *types.Var {
// We need to traverse the tree to find the ast.Ident, but all the nodes we traverse should be used by the object we
// get once we resolve the ident. Collect the nodes and process them once we've found the ident.
nodes := make([]ast.Node, 0, 4)
for {
switch node_ := node.(type) {
case *ast.Ident:
// obj is the field
obj := g.info.ObjectOf(node_).(*types.Var)
// the field is declared by the enclosing type
g.see(obj, by)
for _, n := range nodes {
g.read(n, obj)
}
switch typ := typeutil.Dereference(g.info.TypeOf(node_)).(type) {
case *types.Named:
g.use(typ.Obj(), obj)
case *types.Basic:
// Nothing to do
default:
lint.ExhaustiveTypeSwitch(typ)

if tname, ok := g.info.Uses[node_].(*types.TypeName); ok && tname.IsAlias() {
// When embedding an alias we want to use the alias, not what the alias points to.
g.use(tname, obj)
} else {
switch typ := typeutil.Dereference(g.info.TypeOf(node_)).(type) {
case *types.Named:
// (7.2) fields use their types
g.use(typ.Obj(), obj)
case *types.Basic:
// Nothing to do
default:
// Other types are only possible for aliases, which we've already handled
lint.ExhaustiveTypeSwitch(typ)
}
}
return obj
case *ast.StarExpr:
Expand Down Expand Up @@ -1518,6 +1528,9 @@ func (g *graph) namedType(typ *types.TypeName, spec ast.Expr) {
obj := g.info.ObjectOf(name)
g.see(obj, typ)
// (7.2) fields use their types
//
// This handles aliases correctly because ObjectOf(alias) returns the TypeName of the alias, not
// what the alias points to.
g.read(field.Type, obj)
if name.Name == "_" {
// (9.9) objects named the blank identifier are used
Expand Down

0 comments on commit 3cd466f

Please sign in to comment.