Skip to content

Commit 8b51d66

Browse files
committed
go/types/objectpath: support parameterized type aliases
This change caused objectpath to treat Alias nodes more like Named types: as first-class nodes, with type parameters, and a destructuring operation (Alias.Rhs(), opRhs, encoded 'a') access the RHS type. A number of historical bugs made this trickier than it should have been: - go1.22 prints Alias wrongly, requiring a workaround in the test. - aliases.Enabled is too expensive to call in the decoder, so we must trust that when we see an opRhs and we don't have an alias, it's because !Enabled(), not a bug. - legacy aliases still need to be handled, and order matters. - the test of parameterized aliases can't be added until the GOEXPERIMENT has gone away (soon). Updates golang/go#46477 Change-Id: Ia903f81e29fb7dbb6e17d1e6a962fad73b3e1f7b Reviewed-on: https://go-review.googlesource.com/c/tools/+/601235 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Tim King <[email protected]> Commit-Queue: Alan Donovan <[email protected]>
1 parent 12d2c34 commit 8b51d66

File tree

5 files changed

+94
-18
lines changed

5 files changed

+94
-18
lines changed

go/types/objectpath/objectpath.go

+28-12
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type Path string
5151
//
5252
// PO package->object Package.Scope.Lookup
5353
// OT object->type Object.Type
54-
// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying} [EKPRUTrC]
54+
// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying,Rhs} [EKPRUTrCa]
5555
// TO type->object Type.{At,Field,Method,Obj} [AFMO]
5656
//
5757
// All valid paths start with a package and end at an object
@@ -63,7 +63,7 @@ type Path string
6363
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
6464
// - The only OT operator is Object.Type,
6565
// which we encode as '.' because dot cannot appear in an identifier.
66-
// - The TT operators are encoded as [EKPRUTrC];
66+
// - The TT operators are encoded as [EKPRUTrCa];
6767
// two of these ({,Recv}TypeParams) require an integer operand,
6868
// which is encoded as a string of decimal digits.
6969
// - The TO operators are encoded as [AFMO];
@@ -106,6 +106,7 @@ const (
106106
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
107107
opRecvTypeParam = 'r' // .RecvTypeParams.At(i) (Signature)
108108
opConstraint = 'C' // .Constraint() (TypeParam)
109+
opRhs = 'a' // .Rhs() (Alias)
109110

110111
// type->object operators
111112
opAt = 'A' // .At(i) (Tuple)
@@ -279,21 +280,26 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
279280
path = append(path, opType)
280281

281282
T := o.Type()
283+
if alias, ok := T.(*aliases.Alias); ok {
284+
if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam, nil); r != nil {
285+
return Path(r), nil
286+
}
287+
if r := find(obj, aliases.Rhs(alias), append(path, opRhs), nil); r != nil {
288+
return Path(r), nil
289+
}
282290

283-
if tname.IsAlias() {
284-
// type alias
291+
} else if tname.IsAlias() {
292+
// legacy alias
285293
if r := find(obj, T, path, nil); r != nil {
286294
return Path(r), nil
287295
}
288-
} else {
289-
if named, _ := T.(*types.Named); named != nil {
290-
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil {
291-
// generic named type
292-
return Path(r), nil
293-
}
294-
}
296+
297+
} else if named, ok := T.(*types.Named); ok {
295298
// defined (named) type
296-
if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil {
299+
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil {
300+
return Path(r), nil
301+
}
302+
if r := find(obj, named.Underlying(), append(path, opUnderlying), nil); r != nil {
297303
return Path(r), nil
298304
}
299305
}
@@ -657,6 +663,16 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
657663
}
658664
t = named.Underlying()
659665

666+
case opRhs:
667+
if alias, ok := t.(*aliases.Alias); ok {
668+
t = aliases.Rhs(alias)
669+
} else if false && aliases.Enabled() {
670+
// The Enabled check is too expensive, so for now we
671+
// simply assume that aliases are not enabled.
672+
// TODO(adonovan): replace with "if true {" when go1.24 is assured.
673+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t)
674+
}
675+
660676
case opTypeParam:
661677
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
662678
if !ok {

go/types/objectpath/objectpath_go118_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"golang.org/x/tools/go/types/objectpath"
1414
)
1515

16+
// TODO(adonovan): merge this back into objectpath_test.go.
1617
func TestGenericPaths(t *testing.T) {
1718
pkgs := map[string]map[string]string{
1819
"b": {"b.go": `

go/types/objectpath/objectpath_test.go

+52-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"fmt"
1010
"go/ast"
11+
"go/build"
1112
"go/importer"
1213
"go/parser"
1314
"go/token"
@@ -19,9 +20,21 @@ import (
1920
"golang.org/x/tools/go/gcexportdata"
2021
"golang.org/x/tools/go/loader"
2122
"golang.org/x/tools/go/types/objectpath"
23+
"golang.org/x/tools/internal/aliases"
2224
)
2325

2426
func TestPaths(t *testing.T) {
27+
for _, aliases := range []int{0, 1} {
28+
t.Run(fmt.Sprint(aliases), func(t *testing.T) {
29+
testPaths(t, aliases)
30+
})
31+
}
32+
}
33+
34+
func testPaths(t *testing.T, gotypesalias int) {
35+
// override default set by go1.19 in go.mod
36+
t.Setenv("GODEBUG", fmt.Sprintf("gotypesalias=%d", gotypesalias))
37+
2538
pkgs := map[string]map[string]string{
2639
"b": {"b.go": `
2740
package b
@@ -40,6 +53,11 @@ type U T
4053
4154
type A = struct{ x int }
4255
56+
type unexported2 struct { z int }
57+
type AN = unexported2 // alias of named
58+
59+
// type GA[T any] = T // see below
60+
4361
var V []*a.T
4462
4563
type M map[struct{x int}]struct{y int}
@@ -75,9 +93,17 @@ type T struct{x, y int}
7593
{"b", "T.UF0", "field A int", ""},
7694
{"b", "T.UF1", "field b int", ""},
7795
{"b", "T.UF2", "field T a.T", ""},
78-
{"b", "U.UF2", "field T a.T", ""}, // U.U... are aliases for T.U...
79-
{"b", "A", "type b.A = struct{x int}", ""},
96+
{"b", "U.UF2", "field T a.T", ""}, // U.U... are aliases for T.U...
97+
{"b", "A", "type b.A = struct{x int}", ""}, // go1.22/alias=1: "type b.A = b.A"
98+
{"b", "A.aF0", "field x int", ""},
8099
{"b", "A.F0", "field x int", ""},
100+
{"b", "AN", "type b.AN = b.unexported2", ""}, // go1.22/alias=1: "type b.AN = b.AN"
101+
{"b", "AN.UF0", "field z int", ""},
102+
{"b", "AN.aO", "type b.unexported2 struct{z int}", ""},
103+
{"b", "AN.O", "type b.unexported2 struct{z int}", ""},
104+
{"b", "AN.aUF0", "field z int", ""},
105+
{"b", "AN.UF0", "field z int", ""},
106+
// {"b", "GA", "type parameter b.GA = T", ""}, // TODO(adonovan): enable once GOEXPERIMENT=aliastypeparams has gone, and only when gotypesalias=1
81107
{"b", "V", "var b.V []*a.T", ""},
82108
{"b", "M", "type b.M map[struct{x int}]struct{y int}", ""},
83109
{"b", "M.UKF0", "field x int", ""},
@@ -126,6 +152,20 @@ type T struct{x, y int}
126152
}
127153

128154
for _, test := range paths {
155+
// go1.22 gotypesalias=1 prints aliases wrong: "type A = A".
156+
// (Fixed by https://go.dev/cl/574716.)
157+
// Work around it here by updating the expectation.
158+
if slicesContains(build.Default.ReleaseTags, "go1.22") &&
159+
!slicesContains(build.Default.ReleaseTags, "go1.23") &&
160+
aliases.Enabled() {
161+
if test.pkg == "b" && test.path == "A" {
162+
test.wantobj = "type b.A = b.A"
163+
}
164+
if test.pkg == "b" && test.path == "AN" {
165+
test.wantobj = "type b.AN = b.AN"
166+
}
167+
}
168+
129169
if err := testPath(prog, test); err != nil {
130170
t.Error(err)
131171
}
@@ -387,3 +427,13 @@ func (T) X() { }
387427
}
388428
}
389429
}
430+
431+
// TODO(adonovan): use go1.21 slices.Contains.
432+
func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
433+
for _, elem := range slice {
434+
if elem == x {
435+
return true
436+
}
437+
}
438+
return false
439+
}

internal/aliases/aliases_go121.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import (
1515
// It will never be created by go/types.
1616
type Alias struct{}
1717

18-
func (*Alias) String() string { panic("unreachable") }
19-
func (*Alias) Underlying() types.Type { panic("unreachable") }
20-
func (*Alias) Obj() *types.TypeName { panic("unreachable") }
21-
func Rhs(alias *Alias) types.Type { panic("unreachable") }
18+
func (*Alias) String() string { panic("unreachable") }
19+
func (*Alias) Underlying() types.Type { panic("unreachable") }
20+
func (*Alias) Obj() *types.TypeName { panic("unreachable") }
21+
func Rhs(alias *Alias) types.Type { panic("unreachable") }
22+
func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") }
2223

2324
// Unalias returns the type t for go <=1.21.
2425
func Unalias(t types.Type) types.Type { return t }

internal/aliases/aliases_go122.go

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ func Rhs(alias *Alias) types.Type {
2828
return Unalias(alias)
2929
}
3030

31+
// TypeParams returns the type parameter list of the alias.
32+
func TypeParams(alias *Alias) *types.TypeParamList {
33+
if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok {
34+
return alias.TypeParams() // go1.23+
35+
}
36+
return nil
37+
}
38+
3139
// Unalias is a wrapper of types.Unalias.
3240
func Unalias(t types.Type) types.Type { return types.Unalias(t) }
3341

0 commit comments

Comments
 (0)