Robert Findley submitted this change.
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 <rfin...@google.com>
Run-TryBot: Robert Findley <rfin...@google.com>
TryBot-Result: Go Bot <go...@golang.org>
gopls-CI: kokoro <noreply...@google.com>
Reviewed-by: Tim King <tak...@google.com>
---
M go/types/objectpath/objectpath_test.go
M go/types/objectpath/objectpath.go
A go/types/objectpath/objectpath_go118_test.go
3 files changed, 339 insertions(+), 141 deletions(-)
diff --git a/go/types/objectpath/objectpath.go b/go/types/objectpath/objectpath.go
index 81e8fdc..aa374c5 100644
--- a/go/types/objectpath/objectpath.go
+++ b/go/types/objectpath/objectpath.go
@@ -27,6 +27,8 @@
"strings"
"go/types"
+
+ "golang.org/x/tools/internal/typeparams"
)
// A Path is an opaque name that identifies a types.Object
@@ -57,7 +59,9 @@
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
// - The only OT operator is Object.Type,
// which we encode as '.' because dot cannot appear in an identifier.
-// - The TT operators are encoded as [EKPRU].
+// - The TT operators are encoded as [EKPRUTC];
+// one of these (TypeParam) requires an integer operand,
+// which is encoded as a string of decimal digits.
// - The TO operators are encoded as [AFMO];
// three of these (At,Field,Method) require an integer operand,
// which is encoded as a string of decimal digits.
@@ -89,17 +93,19 @@
opType = '.' // .Type() (Object)
// type->type operators
- opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
- opKey = 'K' // .Key() (Map)
- opParams = 'P' // .Params() (Signature)
- opResults = 'R' // .Results() (Signature)
- opUnderlying = 'U' // .Underlying() (Named)
+ opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
+ opKey = 'K' // .Key() (Map)
+ opParams = 'P' // .Params() (Signature)
+ opResults = 'R' // .Results() (Signature)
+ opUnderlying = 'U' // .Underlying() (Named)
+ opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
+ opConstraint = 'C' // .Constraint() (TypeParam)
// type->object operators
- opAt = 'A' // .At(i) (Tuple)
- opField = 'F' // .Field(i) (Struct)
- opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
- opObj = 'O' // .Obj() (Named)
+ opAt = 'A' // .At(i) (Tuple)
+ opField = 'F' // .Field(i) (Struct)
+ opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
+ opObj = 'O' // .Obj() (Named, TypeParam)
)
// The For function returns the path to an object relative to its package,
@@ -190,10 +196,15 @@
// 3. Not a package-level object.
// Reject obviously non-viable cases.
switch obj := obj.(type) {
+ case *types.TypeName:
+ if _, ok := obj.Type().(*typeparams.TypeParam); !ok {
+ // With the exception of type parameters, only package-level type names
+ // have a path.
+ return "", fmt.Errorf("no path for %v", obj)
+ }
case *types.Const, // Only package-level constants have a path.
- *types.TypeName, // Only package-level types have a path.
- *types.Label, // Labels are function-local.
- *types.PkgName: // PkgNames are file-local.
+ *types.Label, // Labels are function-local.
+ *types.PkgName: // PkgNames are file-local.
return "", fmt.Errorf("no path for %v", obj)
case *types.Var:
@@ -245,6 +256,12 @@
return Path(r), nil
}
} else {
+ if named, _ := T.(*types.Named); named != nil {
+ if r := findTypeParam(obj, typeparams.ForNamed(named), path); r != nil {
+ // generic named type
+ return Path(r), nil
+ }
+ }
// defined (named) type
if r := find(obj, T.Underlying(), append(path, opUnderlying)); r != nil {
return Path(r), nil
@@ -313,6 +330,9 @@
}
return find(obj, T.Elem(), append(path, opElem))
case *types.Signature:
+ if r := findTypeParam(obj, typeparams.ForSignature(T), path); r != nil {
+ return r
+ }
if r := find(obj, T.Params(), append(path, opParams)); r != nil {
return r
}
@@ -353,10 +373,30 @@
}
}
return nil
+ case *typeparams.TypeParam:
+ name := T.Obj()
+ if name == obj {
+ return append(path, opObj)
+ }
+ if r := find(obj, T.Constraint(), append(path, opConstraint)); r != nil {
+ return r
+ }
+ return nil
}
panic(T)
}
+func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte) []byte {
+ for i := 0; i < list.Len(); i++ {
+ tparam := list.At(i)
+ path2 := appendOpArg(path, opTypeParam, i)
+ if r := find(obj, tparam, path2); r != nil {
+ return r
+ }
+ }
+ return nil
+}
+
// Object returns the object denoted by path p within the package pkg.
func Object(pkg *types.Package, p Path) (types.Object, error) {
if p == "" {
@@ -386,6 +426,14 @@
Method(int) *types.Func
NumMethods() int
}
+ // abstraction of *types.{Named,Signature}
+ type hasTypeParams interface {
+ TypeParams() *typeparams.TypeParamList
+ }
+ // abstraction of *types.{Named,TypeParam}
+ type hasObj interface {
+ Obj() *types.TypeName
+ }
// The loop state is the pair (t, obj),
// exactly one of which is non-nil, initially obj.
@@ -401,7 +449,7 @@
// Codes [AFM] have an integer operand.
var index int
switch code {
- case opAt, opField, opMethod:
+ case opAt, opField, opMethod, opTypeParam:
rest := strings.TrimLeft(suffix, "0123456789")
numerals := suffix[:len(suffix)-len(rest)]
suffix = rest
@@ -466,14 +514,32 @@
case opUnderlying:
named, ok := t.(*types.Named)
if !ok {
- return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t)
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t)
}
t = named.Underlying()
+ case opTypeParam:
+ hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
+ if !ok {
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t)
+ }
+ tparams := hasTypeParams.TypeParams()
+ if n := tparams.Len(); index >= n {
+ return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
+ }
+ t = tparams.At(index)
+
+ case opConstraint:
+ tparam, ok := t.(*typeparams.TypeParam)
+ if !ok {
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
+ }
+ t = tparam.Constraint()
+
case opAt:
tuple, ok := t.(*types.Tuple)
if !ok {
- return nil, fmt.Errorf("cannot apply %q to %s (got %s, want tuple)", code, t, t)
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t)
}
if n := tuple.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
@@ -495,7 +561,7 @@
case opMethod:
hasMethods, ok := t.(hasMethods) // Interface or Named
if !ok {
- return nil, fmt.Errorf("cannot apply %q to %s (got %s, want interface or named)", code, t, t)
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
}
if n := hasMethods.NumMethods(); index >= n {
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, n)
@@ -504,11 +570,11 @@
t = nil
case opObj:
- named, ok := t.(*types.Named)
+ hasObj, ok := t.(hasObj)
if !ok {
- return nil, fmt.Errorf("cannot apply %q to %s (got %s, want named)", code, t, t)
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t)
}
- obj = named.Obj()
+ obj = hasObj.Obj()
t = nil
default:
diff --git a/go/types/objectpath/objectpath_go118_test.go b/go/types/objectpath/objectpath_go118_test.go
new file mode 100644
index 0000000..253b062
--- /dev/null
+++ b/go/types/objectpath/objectpath_go118_test.go
@@ -0,0 +1,93 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+// +build go1.18
+
+package objectpath_test
+
+import (
+ "go/types"
+ "testing"
+
+ "golang.org/x/tools/go/buildutil"
+ "golang.org/x/tools/go/loader"
+ "golang.org/x/tools/go/types/objectpath"
+)
+
+func TestGenericPaths(t *testing.T) {
+ pkgs := map[string]map[string]string{
+ "b": {"b.go": `
+package b
+
+const C int = 1
+
+type T[TP0 any, TP1 interface{ M0(); M1() }] struct{}
+
+func (T[RP0, RP1]) M() {}
+
+type N int
+
+func (N) M0()
+func (N) M1()
+
+type A = T[int, N]
+
+func F[FP0, FP1 any](FP0, FP1) {}
+`},
+ }
+ paths := []pathTest{
+ // Good paths
+ {"b", "T", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
+ {"b", "T.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
+ {"b", "T.M0", "func (b.T[b.RP0, b.RP1]).M()", ""},
+ {"b", "T.T0O", "type TP0 = b.TP0", ""},
+ {"b", "T.T1O", "type TP1 = b.TP1", ""},
+ {"b", "T.T1CM0", "func (interface).M0()", ""},
+ // Obj of an instance is the generic declaration.
+ {"b", "A.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
+ {"b", "A.M0", "func (b.T[int, b.N]).M()", ""},
+
+ // Bad paths
+ {"b", "N.C", "", "invalid path: ends with 'C', want [AFMO]"},
+ {"b", "N.CO", "", "cannot apply 'C' to b.N (got *types.Named, want type parameter)"},
+ {"b", "N.T", "", `invalid path: bad numeric operand "" for code 'T'`},
+ {"b", "N.T0", "", "tuple index 0 out of range [0-0)"},
+ {"b", "T.T2O", "", "tuple index 2 out of range [0-2)"},
+ {"b", "T.T1M0", "", "cannot apply 'M' to b.TP1 (got *types.TypeParam, want interface or named)"},
+ {"b", "C.T0", "", "cannot apply 'T' to int (got *types.Basic, want named or signature)"},
+ }
+
+ conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
+ conf.Import("b")
+ prog, err := conf.Load()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, test := range paths {
+ if err := testPath(prog, test); err != nil {
+ t.Error(err)
+ }
+ }
+
+ // bad objects
+ for _, test := range []struct {
+ obj types.Object
+ wantErr string
+ }{
+ {types.Universe.Lookup("any"), "predeclared type any = interface{} has no path"},
+ {types.Universe.Lookup("comparable"), "predeclared type comparable interface{} has no path"},
+ } {
+ path, err := objectpath.For(test.obj)
+ if err == nil {
+ t.Errorf("Object(%s) = %q, want error", test.obj, path)
+ continue
+ }
+ if err.Error() != test.wantErr {
+ t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
+ continue
+ }
+ }
+}
diff --git a/go/types/objectpath/objectpath_test.go b/go/types/objectpath/objectpath_test.go
index 16b6123..1e335ef 100644
--- a/go/types/objectpath/objectpath_test.go
+++ b/go/types/objectpath/objectpath_test.go
@@ -6,6 +6,7 @@
import (
"bytes"
+ "fmt"
"go/ast"
"go/importer"
"go/parser"
@@ -55,6 +56,50 @@
`},
}
+ paths := []pathTest{
+ // Good paths
+ {"b", "C", "const b.C a.Int", ""},
+ {"b", "F", "func b.F(a int, b int, c int, d a.T)", ""},
+ {"b", "F.PA0", "var a int", ""},
+ {"b", "F.PA1", "var b int", ""},
+ {"b", "F.PA2", "var c int", ""},
+ {"b", "F.PA3", "var d a.T", ""},
+ {"b", "T", "type b.T struct{A int; b int; a.T}", ""},
+ {"b", "T.O", "type b.T struct{A int; b int; a.T}", ""},
+ {"b", "T.UF0", "field A int", ""},
+ {"b", "T.UF1", "field b int", ""},
+ {"b", "T.UF2", "field T a.T", ""},
+ {"b", "U.UF2", "field T a.T", ""}, // U.U... are aliases for T.U...
+ {"b", "A", "type b.A = struct{x int}", ""},
+ {"b", "A.F0", "field x int", ""},
+ {"b", "V", "var b.V []*a.T", ""},
+ {"b", "M", "type b.M map[struct{x int}]struct{y int}", ""},
+ {"b", "M.UKF0", "field x int", ""},
+ {"b", "M.UEF0", "field y int", ""},
+ {"b", "T.M0", "func (b.T).M() *interface{f()}", ""}, // concrete method
+ {"b", "T.M0.RA0", "var *interface{f()}", ""}, // parameter
+ {"b", "T.M0.RA0.EM0", "func (interface).f()", ""}, // interface method
+ {"b", "unexportedType", "type b.unexportedType struct{}", ""},
+ {"a", "T", "type a.T struct{x int; y int}", ""},
+ {"a", "T.UF0", "field x int", ""},
+
+ // Bad paths
+ {"b", "", "", "empty path"},
+ {"b", "missing", "", `package b does not contain "missing"`},
+ {"b", "F.U", "", "invalid path: ends with 'U', want [AFMO]"},
+ {"b", "F.PA3.O", "", "path denotes type a.T struct{x int; y int}, which belongs to a different package"},
+ {"b", "F.PA!", "", `invalid path: bad numeric operand "" for code 'A'`},
+ {"b", "F.PA3.UF0", "", "path denotes field x int, which belongs to a different package"},
+ {"b", "F.PA3.UF5", "", "field index 5 out of range [0-2)"},
+ {"b", "V.EE", "", "invalid path: ends with 'E', want [AFMO]"},
+ {"b", "F..O", "", "invalid path: unexpected '.' in type context"},
+ {"b", "T.OO", "", "invalid path: code 'O' in object context"},
+ {"b", "T.EO", "", "cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
+ {"b", "A.O", "", "cannot apply 'O' to struct{x int} (got *types.Struct, want named or type param)"},
+ {"b", "A.UF0", "", "cannot apply 'U' to struct{x int} (got *types.Struct, want named)"},
+ {"b", "M.UPO", "", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
+ {"b", "C.O", "", "path denotes type a.Int int, which belongs to a different package"},
+ }
conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
conf.Import("a")
conf.Import("b")
@@ -62,9 +107,45 @@
if err != nil {
t.Fatal(err)
}
- a := prog.Imported["a"].Pkg
- b := prog.Imported["b"].Pkg
+ for _, test := range paths {
+ if err := testPath(prog, test); err != nil {
+ t.Error(err)
+ }
+ }
+
+ // bad objects
+ bInfo := prog.Imported["b"]
+ for _, test := range []struct {
+ obj types.Object
+ wantErr string
+ }{
+ {types.Universe.Lookup("nil"), "predeclared nil has no path"},
+ {types.Universe.Lookup("len"), "predeclared builtin len has no path"},
+ {types.Universe.Lookup("int"), "predeclared type int has no path"},
+ {bInfo.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
+ {bInfo.Pkg.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
+ } {
+ path, err := objectpath.For(test.obj)
+ if err == nil {
+ t.Errorf("Object(%s) = %q, want error", test.obj, path)
+ continue
+ }
+ if err.Error() != test.wantErr {
+ t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
+ continue
+ }
+ }
+}
+
+type pathTest struct {
+ pkg string
+ path objectpath.Path
+ wantobj string
+ wantErr string
+}
+
+func testPath(prog *loader.Program, test pathTest) error {
// We test objectpath by enumerating a set of paths
// and ensuring that Path(pkg, Object(pkg, path)) == path.
//
@@ -80,133 +161,63 @@
// The downside is that the test depends on the path encoding.
// The upside is that the test exercises the encoding.
- // good paths
- for _, test := range []struct {
- pkg *types.Package
- path objectpath.Path
- wantobj string
- }{
- {b, "C", "const b.C a.Int"},
- {b, "F", "func b.F(a int, b int, c int, d a.T)"},
- {b, "F.PA0", "var a int"},
- {b, "F.PA1", "var b int"},
- {b, "F.PA2", "var c int"},
- {b, "F.PA3", "var d a.T"},
- {b, "T", "type b.T struct{A int; b int; a.T}"},
- {b, "T.O", "type b.T struct{A int; b int; a.T}"},
- {b, "T.UF0", "field A int"},
- {b, "T.UF1", "field b int"},
- {b, "T.UF2", "field T a.T"},
- {b, "U.UF2", "field T a.T"}, // U.U... are aliases for T.U...
- {b, "A", "type b.A = struct{x int}"},
- {b, "A.F0", "field x int"},
- {b, "V", "var b.V []*a.T"},
- {b, "M", "type b.M map[struct{x int}]struct{y int}"},
- {b, "M.UKF0", "field x int"},
- {b, "M.UEF0", "field y int"},
- {b, "T.M0", "func (b.T).M() *interface{f()}"}, // concrete method
- {b, "T.M0.RA0", "var *interface{f()}"}, // parameter
- {b, "T.M0.RA0.EM0", "func (interface).f()"}, // interface method
- {b, "unexportedType", "type b.unexportedType struct{}"},
- {a, "T", "type a.T struct{x int; y int}"},
- {a, "T.UF0", "field x int"},
- } {
- // check path -> object
- obj, err := objectpath.Object(test.pkg, test.path)
- if err != nil {
- t.Errorf("Object(%s, %q) failed: %v",
- test.pkg.Path(), test.path, err)
- continue
+ pkg := prog.Imported[test.pkg].Pkg
+ // check path -> object
+ obj, err := objectpath.Object(pkg, test.path)
+ if (test.wantErr != "") != (err != nil) {
+ return fmt.Errorf("Object(%s, %q) returned error %q, want %q", pkg.Path(), test.path, err, test.wantErr)
+ }
+ if test.wantErr != "" {
+ if got := stripSubscripts(err.Error()); got != test.wantErr {
+ return fmt.Errorf("Object(%s, %q) error was %q, want %q",
+ pkg.Path(), test.path, got, test.wantErr)
}
- if obj.String() != test.wantobj {
- t.Errorf("Object(%s, %q) = %v, want %s",
- test.pkg.Path(), test.path, obj, test.wantobj)
- continue
- }
- if obj.Pkg() != test.pkg {
- t.Errorf("Object(%s, %q) = %v, which belongs to package %s",
- test.pkg.Path(), test.path, obj, obj.Pkg().Path())
- continue
- }
+ return nil
+ }
+ // Inv: err == nil
- // check object -> path
- path2, err := objectpath.For(obj)
- if err != nil {
- t.Errorf("For(%v) failed: %v, want %q", obj, err, test.path)
- continue
- }
- // We do not require that test.path == path2. Aliases are legal.
- // But we do require that Object(path2) finds the same object.
- obj2, err := objectpath.Object(test.pkg, path2)
- if err != nil {
- t.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)",
- test.pkg.Path(), path2, err, test.path)
- continue
- }
- if obj2 != obj {
- t.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)",
- test.pkg.Path(), obj2, obj, test.path, path2)
- continue
- }
+ if objString := stripSubscripts(obj.String()); objString != test.wantobj {
+ return fmt.Errorf("Object(%s, %q) = %s, want %s", pkg.Path(), test.path, objString, test.wantobj)
+ }
+ if obj.Pkg() != pkg {
+ return fmt.Errorf("Object(%s, %q) = %v, which belongs to package %s",
+ pkg.Path(), test.path, obj, obj.Pkg().Path())
}
- // bad paths (all relative to package b)
- for _, test := range []struct {
- pkg *types.Package
- path objectpath.Path
- wantErr string
- }{
- {b, "", "empty path"},
- {b, "missing", `package b does not contain "missing"`},
- {b, "F.U", "invalid path: ends with 'U', want [AFMO]"},
- {b, "F.PA3.O", "path denotes type a.T struct{x int; y int}, which belongs to a different package"},
- {b, "F.PA!", `invalid path: bad numeric operand "" for code 'A'`},
- {b, "F.PA3.UF0", "path denotes field x int, which belongs to a different package"},
- {b, "F.PA3.UF5", "field index 5 out of range [0-2)"},
- {b, "V.EE", "invalid path: ends with 'E', want [AFMO]"},
- {b, "F..O", "invalid path: unexpected '.' in type context"},
- {b, "T.OO", "invalid path: code 'O' in object context"},
- {b, "T.EO", "cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
- {b, "A.O", "cannot apply 'O' to struct{x int} (got struct{x int}, want named)"},
- {b, "A.UF0", "cannot apply 'U' to struct{x int} (got struct{x int}, want named)"},
- {b, "M.UPO", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
- {b, "C.O", "path denotes type a.Int int, which belongs to a different package"},
- } {
- obj, err := objectpath.Object(test.pkg, test.path)
- if err == nil {
- t.Errorf("Object(%s, %q) = %s, want error",
- test.pkg.Path(), test.path, obj)
- continue
- }
- if err.Error() != test.wantErr {
- t.Errorf("Object(%s, %q) error was %q, want %q",
- test.pkg.Path(), test.path, err, test.wantErr)
- continue
- }
+ // check object -> path
+ path2, err := objectpath.For(obj)
+ if err != nil {
+ return fmt.Errorf("For(%v) failed: %v, want %q", obj, err, test.path)
}
+ // We do not require that test.path == path2. Aliases are legal.
+ // But we do require that Object(path2) finds the same object.
+ obj2, err := objectpath.Object(pkg, path2)
+ if err != nil {
+ return fmt.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)", pkg.Path(), path2, err, test.path)
+ }
+ if obj2 != obj {
+ return fmt.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)", pkg.Path(), obj2, obj, test.path, path2)
+ }
+ return nil
+}
- // bad objects
- bInfo := prog.Imported["b"]
- for _, test := range []struct {
- obj types.Object
- wantErr string
- }{
- {types.Universe.Lookup("nil"), "predeclared nil has no path"},
- {types.Universe.Lookup("len"), "predeclared builtin len has no path"},
- {types.Universe.Lookup("int"), "predeclared type int has no path"},
- {bInfo.Info.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
- {b.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
- } {
- path, err := objectpath.For(test.obj)
- if err == nil {
- t.Errorf("Object(%s) = %q, want error", test.obj, path)
- continue
+// stripSubscripts removes type parameter id subscripts.
+//
+// TODO(rfindley): remove this function once subscripts are removed from the
+// type parameter type string.
+func stripSubscripts(s string) string {
+ var runes []rune
+ for _, r := range s {
+ // For debugging/uniqueness purposes, TypeString on a type parameter adds a
+ // subscript corresponding to the type parameter's unique id. This is going
+ // to be removed, but in the meantime we skip the subscript runes to get a
+ // deterministic output.
+ if '₀' <= r && r < '₀'+10 {
+ continue // trim type parameter subscripts
}
- if err.Error() != test.wantErr {
- t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
- continue
- }
+ runes = append(runes, r)
}
+ return string(runes)
}
// TestSourceAndExportData uses objectpath to compute a correspondence
To view, visit change 350148. To unsubscribe, or for help writing mail filters, visit settings.