Skip to content

Commit

Permalink
internal/facts: use alias type parameters and arguments during imports
Browse files Browse the repository at this point in the history
Derived from https://go.dev/cl/603935.

Change-Id: I409347a5bdf2218450d06f688b4c13fd302b2a16
Reviewed-on: https://go-review.googlesource.com/c/tools/+/619416
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Commit-Queue: Tim King <taking@google.com>
  • Loading branch information
timothy-king authored and Go LUCI committed Nov 18, 2024
1 parent 9b9871d commit 39cb6f0
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 28 deletions.
74 changes: 58 additions & 16 deletions internal/facts/facts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:debug gotypesalias=1

package facts_test

import (
Expand All @@ -18,8 +20,10 @@ import (

"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/facts"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/typesinternal"
)

type myFact struct {
Expand All @@ -35,10 +39,9 @@ func init() {

func TestEncodeDecode(t *testing.T) {
tests := []struct {
name string
typeparams bool // requires typeparams to be enabled
files map[string]string
plookups []pkgLookups // see testEncodeDecode for details
name string
files map[string]string
plookups []pkgLookups // see testEncodeDecode for details
}{
{
name: "loading-order",
Expand Down Expand Up @@ -184,8 +187,7 @@ func TestEncodeDecode(t *testing.T) {
},
},
{
name: "typeparams",
typeparams: true,
name: "typeparams",
files: map[string]string{
"a/a.go": `package a
type T1 int
Expand All @@ -202,9 +204,9 @@ func TestEncodeDecode(t *testing.T) {
type N3[T a.T3] func() T
type N4[T a.T4|int8] func() T
type N5[T interface{Bar() a.T5} ] func() T
type t5 struct{}; func (t5) Bar() a.T5 { return 0 }
var G1 N1[a.T1]
var G2 func() N2[a.T2]
var G3 N3[a.T3]
Expand All @@ -222,7 +224,7 @@ func TestEncodeDecode(t *testing.T) {
v5 = b.G5
v6 = b.F6[t6]
)
type t6 struct{}; func (t6) Foo() {}
`,
},
Expand All @@ -244,19 +246,44 @@ func TestEncodeDecode(t *testing.T) {
},
},
}

for i := range tests {
test := tests[i]
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testEncodeDecode(t, test.files, test.plookups)
})
}
}

func TestEncodeDecodeAliases(t *testing.T) {
testenv.NeedsGo1Point(t, 24)

files := map[string]string{
"a/a.go": `package a
type A = int
`,
"b/b.go": `package b
import "a"
type B = a.A
`,
"c/c.go": `package c
import "b";
type N1[T int|~string] = struct{}
var V1 = N1[b.B]{}
`,
}
plookups := []pkgLookups{
{"a", []lookup{}},
{"b", []lookup{}},
// fake objexpr for RHS of V1's type arg (see customFind hack)
{"c", []lookup{{"c.V1->c.N1->b.B->a.A", "myFact(a.A)"}}},
}
testEncodeDecode(t, files, plookups)
}

type lookup struct {
objexpr string
want string
objexpr string // expression whose type is a named type
want string // printed form of fact associated with that type (or "no fact")
}

type pkgLookups struct {
Expand Down Expand Up @@ -345,22 +372,37 @@ func testEncodeDecode(t *testing.T, files map[string]string, tests []pkgLookups)
}
}

// customFind allows for overriding how an object is looked up
// by find. This is necessary for objects that are accessible through
// the API but are not the type of any expression we can pass to types.CheckExpr.
var customFind = map[string]func(p *types.Package) types.Object{
"c.V1->c.N1->b.B->a.A": func(p *types.Package) types.Object {
cV1 := p.Scope().Lookup("V1")
cN1 := cV1.Type().(*types.Alias)
aT1 := aliases.TypeArgs(cN1).At(0).(*types.Alias)
zZ1 := aliases.Rhs(aT1).(*types.Alias)
return zZ1.Obj()
},
}

func find(p *types.Package, expr string) types.Object {
// types.Eval only allows us to compute a TypeName object for an expression.
// TODO(adonovan): support other expressions that denote an object:
// - an identifier (or qualified ident) for a func, const, or var
// - new(T).f for a field or method
// I've added CheckExpr in https://go-review.googlesource.com/c/go/+/144677.
// If that becomes available, use it.

if f := customFind[expr]; f != nil {
return f(p)
}
// Choose an arbitrary position within the (single-file) package
// so that we are within the scope of its import declarations.
somepos := p.Scope().Lookup(p.Scope().Names()[0]).Pos()
tv, err := types.Eval(token.NewFileSet(), p, somepos, expr)
if err != nil {
return nil
}
if n, ok := types.Unalias(tv.Type).(*types.Named); ok {
if n, ok := tv.Type.(typesinternal.NamedOrAlias); ok {
return n.Obj()
}
return nil
Expand Down
36 changes: 24 additions & 12 deletions internal/facts/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package facts

import (
"go/types"

"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typesinternal"
)

// importMap computes the import map for a package by traversing the
Expand Down Expand Up @@ -45,32 +48,41 @@ func importMap(imports []*types.Package) map[string]*types.Package {

addType = func(T types.Type) {
switch T := T.(type) {
case *types.Alias:
addType(types.Unalias(T))
case *types.Basic:
// nop
case *types.Named:
case typesinternal.NamedOrAlias: // *types.{Named,Alias}
// Add the type arguments if this is an instance.
if targs := typesinternal.TypeArgs(T); targs.Len() > 0 {
for i := 0; i < targs.Len(); i++ {
addType(targs.At(i))
}
}

// Remove infinite expansions of *types.Named by always looking at the origin.
// Some named types with type parameters [that will not type check] have
// infinite expansions:
// type N[T any] struct { F *N[N[T]] }
// importMap() is called on such types when Analyzer.RunDespiteErrors is true.
T = T.Origin()
T = typesinternal.Origin(T)
if !typs[T] {
typs[T] = true

// common aspects
addObj(T.Obj())
addType(T.Underlying())
for i := 0; i < T.NumMethods(); i++ {
addObj(T.Method(i))
}
if tparams := T.TypeParams(); tparams != nil {
if tparams := typesinternal.TypeParams(T); tparams.Len() > 0 {
for i := 0; i < tparams.Len(); i++ {
addType(tparams.At(i))
}
}
if targs := T.TypeArgs(); targs != nil {
for i := 0; i < targs.Len(); i++ {
addType(targs.At(i))

// variant aspects
switch T := T.(type) {
case *types.Alias:
addType(aliases.Rhs(T))
case *types.Named:
addType(T.Underlying())
for i := 0; i < T.NumMethods(); i++ {
addObj(T.Method(i))
}
}
}
Expand Down

0 comments on commit 39cb6f0

Please sign in to comment.