Skip to content

Commit 58ad7eb

Browse files
committed
go/types/objectpath: add support for type parameters
This CL adds an initial draft of support for type parameters in the go/types/objectpath package. For now, introduce two new operators: - the 'T' operator (type->type), which requires an integer operand and goes from *Named and *Signature types to the type parameter at the given index. - the 'C' operator (type->type), which goes from a *TypeParam type to its constraint. Along the way, reorganize the path tests and update some errors messages to consistently format the expected type with %T. Fixes golang/go#48588 Change-Id: Ibdf03a86b7d8e24a8faa1f2fc42f2be8db20ca75 Reviewed-on: https://go-review.googlesource.com/c/tools/+/350148 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Tim King <[email protected]>
1 parent 3883e4a commit 58ad7eb

File tree

3 files changed

+311
-141
lines changed

3 files changed

+311
-141
lines changed

go/types/objectpath/objectpath.go

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
"strings"
2828

2929
"go/types"
30+
31+
"golang.org/x/tools/internal/typeparams"
3032
)
3133

3234
// A Path is an opaque name that identifies a types.Object
@@ -57,7 +59,9 @@ type Path string
5759
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
5860
// - The only OT operator is Object.Type,
5961
// which we encode as '.' because dot cannot appear in an identifier.
60-
// - The TT operators are encoded as [EKPRU].
62+
// - The TT operators are encoded as [EKPRUTC];
63+
// one of these (TypeParam) requires an integer operand,
64+
// which is encoded as a string of decimal digits.
6165
// - The TO operators are encoded as [AFMO];
6266
// three of these (At,Field,Method) require an integer operand,
6367
// which is encoded as a string of decimal digits.
@@ -89,17 +93,19 @@ const (
8993
opType = '.' // .Type() (Object)
9094

9195
// type->type operators
92-
opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
93-
opKey = 'K' // .Key() (Map)
94-
opParams = 'P' // .Params() (Signature)
95-
opResults = 'R' // .Results() (Signature)
96-
opUnderlying = 'U' // .Underlying() (Named)
96+
opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
97+
opKey = 'K' // .Key() (Map)
98+
opParams = 'P' // .Params() (Signature)
99+
opResults = 'R' // .Results() (Signature)
100+
opUnderlying = 'U' // .Underlying() (Named)
101+
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
102+
opConstraint = 'C' // .Constraint() (TypeParam)
97103

98104
// type->object operators
99-
opAt = 'A' // .At(i) (Tuple)
100-
opField = 'F' // .Field(i) (Struct)
101-
opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
102-
opObj = 'O' // .Obj() (Named)
105+
opAt = 'A' // .At(i) (Tuple)
106+
opField = 'F' // .Field(i) (Struct)
107+
opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
108+
opObj = 'O' // .Obj() (Named, TypeParam)
103109
)
104110

105111
// The For function returns the path to an object relative to its package,
@@ -190,10 +196,15 @@ func For(obj types.Object) (Path, error) {
190196
// 3. Not a package-level object.
191197
// Reject obviously non-viable cases.
192198
switch obj := obj.(type) {
199+
case *types.TypeName:
200+
if _, ok := obj.Type().(*typeparams.TypeParam); !ok {
201+
// With the exception of type parameters, only package-level type names
202+
// have a path.
203+
return "", fmt.Errorf("no path for %v", obj)
204+
}
193205
case *types.Const, // Only package-level constants have a path.
194-
*types.TypeName, // Only package-level types have a path.
195-
*types.Label, // Labels are function-local.
196-
*types.PkgName: // PkgNames are file-local.
206+
*types.Label, // Labels are function-local.
207+
*types.PkgName: // PkgNames are file-local.
197208
return "", fmt.Errorf("no path for %v", obj)
198209

199210
case *types.Var:
@@ -245,6 +256,12 @@ func For(obj types.Object) (Path, error) {
245256
return Path(r), nil
246257
}
247258
} else {
259+
if named, _ := T.(*types.Named); named != nil {
260+
if r := findTypeParam(obj, typeparams.ForNamed(named), path); r != nil {
261+
// generic named type
262+
return Path(r), nil
263+
}
264+
}
248265
// defined (named) type
249266
if r := find(obj, T.Underlying(), append(path, opUnderlying)); r != nil {
250267
return Path(r), nil
@@ -313,6 +330,9 @@ func find(obj types.Object, T types.Type, path []byte) []byte {
313330
}
314331
return find(obj, T.Elem(), append(path, opElem))
315332
case *types.Signature:
333+
if r := findTypeParam(obj, typeparams.ForSignature(T), path); r != nil {
334+
return r
335+
}
316336
if r := find(obj, T.Params(), append(path, opParams)); r != nil {
317337
return r
318338
}
@@ -353,10 +373,30 @@ func find(obj types.Object, T types.Type, path []byte) []byte {
353373
}
354374
}
355375
return nil
376+
case *typeparams.TypeParam:
377+
name := T.Obj()
378+
if name == obj {
379+
return append(path, opObj)
380+
}
381+
if r := find(obj, T.Constraint(), append(path, opConstraint)); r != nil {
382+
return r
383+
}
384+
return nil
356385
}
357386
panic(T)
358387
}
359388

389+
func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte) []byte {
390+
for i := 0; i < list.Len(); i++ {
391+
tparam := list.At(i)
392+
path2 := appendOpArg(path, opTypeParam, i)
393+
if r := find(obj, tparam, path2); r != nil {
394+
return r
395+
}
396+
}
397+
return nil
398+
}
399+
360400
// Object returns the object denoted by path p within the package pkg.
361401
func Object(pkg *types.Package, p Path) (types.Object, error) {
362402
if p == "" {
@@ -386,6 +426,14 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
386426
Method(int) *types.Func
387427
NumMethods() int
388428
}
429+
// abstraction of *types.{Named,Signature}
430+
type hasTypeParams interface {
431+
TypeParams() *typeparams.TypeParamList
432+
}
433+
// abstraction of *types.{Named,TypeParam}
434+
type hasObj interface {
435+
Obj() *types.TypeName
436+
}
389437

390438
// The loop state is the pair (t, obj),
391439
// exactly one of which is non-nil, initially obj.
@@ -401,7 +449,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
401449
// Codes [AFM] have an integer operand.
402450
var index int
403451
switch code {
404-
case opAt, opField, opMethod:
452+
case opAt, opField, opMethod, opTypeParam:
405453
rest := strings.TrimLeft(suffix, "0123456789")
406454
numerals := suffix[:len(suffix)-len(rest)]
407455
suffix = rest
@@ -466,14 +514,32 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
466514
case opUnderlying:
467515
named, ok := t.(*types.Named)
468516
if !ok {
469-
return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t)
517+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t)
470518
}
471519
t = named.Underlying()
472520

521+
case opTypeParam:
522+
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
523+
if !ok {
524+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t)
525+
}
526+
tparams := hasTypeParams.TypeParams()
527+
if n := tparams.Len(); index >= n {
528+
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
529+
}
530+
t = tparams.At(index)
531+
532+
case opConstraint:
533+
tparam, ok := t.(*typeparams.TypeParam)
534+
if !ok {
535+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
536+
}
537+
t = tparam.Constraint()
538+
473539
case opAt:
474540
tuple, ok := t.(*types.Tuple)
475541
if !ok {
476-
return nil, fmt.Errorf("cannot apply %q to %s (got %s, want tuple)", code, t, t)
542+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t)
477543
}
478544
if n := tuple.Len(); index >= n {
479545
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
@@ -495,7 +561,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
495561
case opMethod:
496562
hasMethods, ok := t.(hasMethods) // Interface or Named
497563
if !ok {
498-
return nil, fmt.Errorf("cannot apply %q to %s (got %s, want interface or named)", code, t, t)
564+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
499565
}
500566
if n := hasMethods.NumMethods(); index >= n {
501567
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, n)
@@ -504,11 +570,11 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
504570
t = nil
505571

506572
case opObj:
507-
named, ok := t.(*types.Named)
573+
hasObj, ok := t.(hasObj)
508574
if !ok {
509-
return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t)
575+
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t)
510576
}
511-
obj = named.Obj()
577+
obj = hasObj.Obj()
512578
t = nil
513579

514580
default:
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.18
6+
// +build go1.18
7+
8+
package objectpath_test
9+
10+
import (
11+
"go/types"
12+
"testing"
13+
14+
"golang.org/x/tools/go/buildutil"
15+
"golang.org/x/tools/go/loader"
16+
"golang.org/x/tools/go/types/objectpath"
17+
)
18+
19+
func TestGenericPaths(t *testing.T) {
20+
pkgs := map[string]map[string]string{
21+
"b": {"b.go": `
22+
package b
23+
24+
const C int = 1
25+
26+
type T[TP0 any, TP1 interface{ M0(); M1() }] struct{}
27+
28+
func (T[RP0, RP1]) M() {}
29+
30+
type N int
31+
32+
func (N) M0()
33+
func (N) M1()
34+
35+
type A = T[int, N]
36+
37+
func F[FP0, FP1 any](FP0, FP1) {}
38+
`},
39+
}
40+
paths := []pathTest{
41+
// Good paths
42+
{"b", "T", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
43+
{"b", "T.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
44+
{"b", "T.M0", "func (b.T[b.RP0, b.RP1]).M()", ""},
45+
{"b", "T.T0O", "type TP0 = b.TP0", ""},
46+
{"b", "T.T1O", "type TP1 = b.TP1", ""},
47+
{"b", "T.T1CM0", "func (interface).M0()", ""},
48+
// Obj of an instance is the generic declaration.
49+
{"b", "A.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
50+
{"b", "A.M0", "func (b.T[int, b.N]).M()", ""},
51+
52+
// Bad paths
53+
{"b", "N.C", "", "invalid path: ends with 'C', want [AFMO]"},
54+
{"b", "N.CO", "", "cannot apply 'C' to b.N (got *types.Named, want type parameter)"},
55+
{"b", "N.T", "", `invalid path: bad numeric operand "" for code 'T'`},
56+
{"b", "N.T0", "", "tuple index 0 out of range [0-0)"},
57+
{"b", "T.T2O", "", "tuple index 2 out of range [0-2)"},
58+
{"b", "T.T1M0", "", "cannot apply 'M' to b.TP1 (got *types.TypeParam, want interface or named)"},
59+
{"b", "C.T0", "", "cannot apply 'T' to int (got *types.Basic, want named or signature)"},
60+
}
61+
62+
conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
63+
conf.Import("b")
64+
prog, err := conf.Load()
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
for _, test := range paths {
70+
if err := testPath(prog, test); err != nil {
71+
t.Error(err)
72+
}
73+
}
74+
75+
// bad objects
76+
for _, test := range []struct {
77+
obj types.Object
78+
wantErr string
79+
}{
80+
{types.Universe.Lookup("any"), "predeclared type any = interface{} has no path"},
81+
{types.Universe.Lookup("comparable"), "predeclared type comparable interface{} has no path"},
82+
} {
83+
path, err := objectpath.For(test.obj)
84+
if err == nil {
85+
t.Errorf("Object(%s) = %q, want error", test.obj, path)
86+
continue
87+
}
88+
if err.Error() != test.wantErr {
89+
t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
90+
continue
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)