1
0
mirror of https://github.com/golang/go synced 2024-11-25 19:07:57 -07:00

go/types, types2: set an origin object for vars and funcs

Historically, Objects in go/types were canonical, meaning each entity
was represented by exactly one variable and could thus be identified by
its address. With object instantiation this is no longer the case: Var
and Func objects must be copied to hold substituted type information,
and there may be more than one Var or Func variable representing the
same source-level entity.

This CL adds Origin methods to *Var and *Func, so users can efficiently
navigate to the corresponding canonical object on the generic type.

Fixes #51682

Change-Id: Ia49e15bd6515e1db1eb3b09b88ba666659601316
Reviewed-on: https://go-review.googlesource.com/c/go/+/395535
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Findley 2022-03-24 12:41:25 -04:00
parent 89533024b0
commit 1170771074
11 changed files with 187 additions and 39 deletions

2
api/next/51682.txt Normal file
View File

@ -0,0 +1,2 @@
pkg go/types, method (*Func) Origin() *Func #51682
pkg go/types, method (*Var) Origin() *Var #51682

View File

@ -2349,22 +2349,31 @@ type T[P any] struct {
field P
}
func (recv *T[Q]) concreteMethod() {}
func (recv *T[Q]) concreteMethod(mParam Q) (mResult Q) { return }
type FT[P any] func(ftp P) (ftrp P)
type FT[P any] func(ftParam P) (ftResult P)
func F[P any](fp P) (frp P){ return }
func F[P any](fParam P) (fResult P){ return }
type I[P any] interface {
interfaceMethod(P)
}
type R[P any] T[P]
func (R[P]) m() {} // having a method triggers expansion of R
var (
t T[int]
ft FT[int]
f = F[int]
i I[int]
)
func fn() {
var r R[int]
_ = r
}
`
info := &Info{
Defs: make(map[*syntax.Name]Object),
@ -2380,18 +2389,32 @@ var (
}
lookup := func(name string) Type { return pkg.Scope().Lookup(name).Type() }
fnScope := pkg.Scope().Lookup("fn").(*Func).Scope()
tests := []struct {
ident string
obj Object
name string
obj Object
}{
// Struct fields
{"field", lookup("t").Underlying().(*Struct).Field(0)},
{"field", fnScope.Lookup("r").Type().Underlying().(*Struct).Field(0)},
// Methods and method fields
{"concreteMethod", lookup("t").(*Named).Method(0)},
{"recv", lookup("t").(*Named).Method(0).Type().(*Signature).Recv()},
{"ftp", lookup("ft").Underlying().(*Signature).Params().At(0)},
{"ftrp", lookup("ft").Underlying().(*Signature).Results().At(0)},
{"fp", lookup("f").(*Signature).Params().At(0)},
{"frp", lookup("f").(*Signature).Results().At(0)},
{"mParam", lookup("t").(*Named).Method(0).Type().(*Signature).Params().At(0)},
{"mResult", lookup("t").(*Named).Method(0).Type().(*Signature).Results().At(0)},
// Interface methods
{"interfaceMethod", lookup("i").Underlying().(*Interface).Method(0)},
// Function type fields
{"ftParam", lookup("ft").Underlying().(*Signature).Params().At(0)},
{"ftResult", lookup("ft").Underlying().(*Signature).Results().At(0)},
// Function fields
{"fParam", lookup("f").(*Signature).Params().At(0)},
{"fResult", lookup("f").(*Signature).Results().At(0)},
}
// Collect all identifiers by name.
@ -2405,14 +2428,17 @@ var (
for _, test := range tests {
test := test
t.Run(test.ident, func(t *testing.T) {
if got := len(idents[test.ident]); got != 1 {
t.Fatalf("found %d identifiers named %s, want 1", got, test.ident)
t.Run(test.name, func(t *testing.T) {
if got := len(idents[test.name]); got != 1 {
t.Fatalf("found %d identifiers named %s, want 1", got, test.name)
}
ident := idents[test.ident][0]
ident := idents[test.name][0]
def := info.Defs[ident]
if def == test.obj {
t.Fatalf("info.Defs[%s] contains the test object", test.ident)
t.Fatalf("info.Defs[%s] contains the test object", test.name)
}
if orig := originObject(test.obj); def != orig {
t.Errorf("info.Defs[%s] does not match obj.Origin()", test.name)
}
if def.Pkg() != test.obj.Pkg() {
t.Errorf("Pkg() = %v, want %v", def.Pkg(), test.obj.Pkg())
@ -2437,6 +2463,16 @@ var (
}
}
func originObject(obj Object) Object {
switch obj := obj.(type) {
case *Var:
return obj.Origin()
case *Func:
return obj.Origin()
}
return obj
}
func TestImplements(t *testing.T) {
const src = `
package p

View File

@ -192,7 +192,7 @@ func (t *Named) instantiateMethod(i int) *Func {
}
sig.recv = substVar(origSig.recv, rtyp)
return NewFunc(origm.pos, origm.pkg, origm.name, sig)
return substFunc(origm, sig)
}
// SetUnderlying sets the underlying type and marks t as complete.

View File

@ -327,6 +327,7 @@ type Var struct {
embedded bool // if set, the variable is an embedded struct field, and name is the type name
isField bool // var is struct field
used bool // set if the variable was used
origin *Var // if non-nil, the Var from which this one was instantiated
}
// NewVar returns a new variable.
@ -357,6 +358,20 @@ func (obj *Var) Embedded() bool { return obj.embedded }
// IsField reports whether the variable is a struct field.
func (obj *Var) IsField() bool { return obj.isField }
// Origin returns the canonical Var for its receiver, i.e. the Var object
// recorded in Info.Defs.
//
// For synthetic Vars created during instantiation (such as struct fields or
// function parameters that depend on type arguments), this will be the
// corresponding Var on the generic (uninstantiated) type. For all other Vars
// Origin returns the receiver.
func (obj *Var) Origin() *Var {
if obj.origin != nil {
return obj.origin
}
return obj
}
func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression
// A Func represents a declared function, concrete method, or abstract
@ -364,7 +379,8 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
// An abstract method may belong to many interfaces due to embedding.
type Func struct {
object
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
origin *Func // if non-nil, the Func from which this one was instantiated
}
// NewFunc returns a new function with the given signature, representing
@ -375,7 +391,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
if sig != nil {
typ = sig
}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil}
}
// FullName returns the package- or receiver-type-qualified name of
@ -391,6 +407,20 @@ func (obj *Func) FullName() string {
// (but there is also no mechanism to get to an instantiated function).
func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope }
// Origin returns the canonical Func for its receiver, i.e. the Func object
// recorded in Info.Defs.
//
// For synthetic functions created during instantiation (such as methods on an
// instantiated Named type or interface methods that depend on type arguments),
// this will be the corresponding Func on the generic (uninstantiated) type.
// For all other Funcs Origin returns the receiver.
func (obj *Func) Origin() *Func {
if obj.origin != nil {
return obj.origin
}
return obj
}
// hasPtrRecv reports whether the receiver is of the form *T for the given method obj.
func (obj *Func) hasPtrRecv() bool {
// If a method's receiver type is set, use that as the source of truth for the receiver.

View File

@ -39,8 +39,8 @@ func TestSizeof(t *testing.T) {
{PkgName{}, 64, 104},
{Const{}, 64, 104},
{TypeName{}, 56, 88},
{Var{}, 60, 96},
{Func{}, 60, 96},
{Var{}, 64, 104},
{Func{}, 64, 104},
{Label{}, 60, 96},
{Builtin{}, 60, 96},
{Nil{}, 56, 88},

View File

@ -299,6 +299,7 @@ func (subst *subster) var_(v *Var) *Var {
func substVar(v *Var, typ Type) *Var {
copy := *v
copy.typ = typ
copy.origin = v.Origin()
return &copy
}
@ -332,14 +333,19 @@ func (subst *subster) varList(in []*Var) (out []*Var, copied bool) {
func (subst *subster) func_(f *Func) *Func {
if f != nil {
if typ := subst.typ(f.typ); typ != f.typ {
copy := *f
copy.typ = typ
return &copy
return substFunc(f, typ)
}
}
return f
}
func substFunc(f *Func, typ Type) *Func {
copy := *f
copy.typ = typ
copy.origin = f.Origin()
return &copy
}
func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) {
out = in
for i, f := range in {
@ -415,7 +421,7 @@ func replaceRecvType(in []*Func, old, new Type) (out []*Func, copied bool) {
}
newsig := *sig
newsig.recv = substVar(sig.recv, new)
out[i] = NewFunc(method.pos, method.pkg, method.name, &newsig)
out[i] = substFunc(method, &newsig)
}
}
return

View File

@ -731,6 +731,8 @@ func TestUsesInfo(t *testing.T) {
`m`,
`func (generic_m10.E[int]).m()`,
},
{`package generic_m11; type T[A any] interface{ m(); n() }; func _(t1 T[int], t2 T[string]) { t1.m(); t2.n() }`, `m`, `func (generic_m11.T[int]).m()`},
{`package generic_m12; type T[A any] interface{ m(); n() }; func _(t1 T[int], t2 T[string]) { t1.m(); t2.n() }`, `n`, `func (generic_m12.T[string]).n()`},
}
for _, test := range tests {
@ -2368,22 +2370,31 @@ type T[P any] struct {
field P
}
func (recv *T[Q]) concreteMethod() {}
func (recv *T[Q]) concreteMethod(mParam Q) (mResult Q) { return }
type FT[P any] func(ftp P) (ftrp P)
type FT[P any] func(ftParam P) (ftResult P)
func F[P any](fp P) (frp P){ return }
func F[P any](fParam P) (fResult P){ return }
type I[P any] interface {
interfaceMethod(P)
}
type R[P any] T[P]
func (R[P]) m() {} // having a method triggers expansion of R
var (
t T[int]
ft FT[int]
f = F[int]
i I[int]
)
func fn() {
var r R[int]
_ = r
}
`
info := &Info{
Defs: make(map[*ast.Ident]Object),
@ -2400,18 +2411,32 @@ var (
}
lookup := func(name string) Type { return pkg.Scope().Lookup(name).Type() }
fnScope := pkg.Scope().Lookup("fn").(*Func).Scope()
tests := []struct {
name string
obj Object
}{
// Struct fields
{"field", lookup("t").Underlying().(*Struct).Field(0)},
{"field", fnScope.Lookup("r").Type().Underlying().(*Struct).Field(0)},
// Methods and method fields
{"concreteMethod", lookup("t").(*Named).Method(0)},
{"recv", lookup("t").(*Named).Method(0).Type().(*Signature).Recv()},
{"ftp", lookup("ft").Underlying().(*Signature).Params().At(0)},
{"ftrp", lookup("ft").Underlying().(*Signature).Results().At(0)},
{"fp", lookup("f").(*Signature).Params().At(0)},
{"frp", lookup("f").(*Signature).Results().At(0)},
{"mParam", lookup("t").(*Named).Method(0).Type().(*Signature).Params().At(0)},
{"mResult", lookup("t").(*Named).Method(0).Type().(*Signature).Results().At(0)},
// Interface methods
{"interfaceMethod", lookup("i").Underlying().(*Interface).Method(0)},
// Function type fields
{"ftParam", lookup("ft").Underlying().(*Signature).Params().At(0)},
{"ftResult", lookup("ft").Underlying().(*Signature).Results().At(0)},
// Function fields
{"fParam", lookup("f").(*Signature).Params().At(0)},
{"fResult", lookup("f").(*Signature).Results().At(0)},
}
// Collect all identifiers by name.
@ -2434,6 +2459,9 @@ var (
if def == test.obj {
t.Fatalf("info.Defs[%s] contains the test object", test.name)
}
if orig := originObject(test.obj); def != orig {
t.Errorf("info.Defs[%s] does not match obj.Origin()", test.name)
}
if def.Pkg() != test.obj.Pkg() {
t.Errorf("Pkg() = %v, want %v", def.Pkg(), test.obj.Pkg())
}
@ -2457,6 +2485,16 @@ var (
}
}
func originObject(obj Object) Object {
switch obj := obj.(type) {
case *Var:
return obj.Origin()
case *Func:
return obj.Origin()
}
return obj
}
func TestImplements(t *testing.T) {
const src = `
package p

View File

@ -194,7 +194,7 @@ func (t *Named) instantiateMethod(i int) *Func {
}
sig.recv = substVar(origSig.recv, rtyp)
return NewFunc(origm.pos, origm.pkg, origm.name, sig)
return substFunc(origm, sig)
}
// SetUnderlying sets the underlying type and marks t as complete.

View File

@ -281,6 +281,7 @@ type Var struct {
embedded bool // if set, the variable is an embedded struct field, and name is the type name
isField bool // var is struct field
used bool // set if the variable was used
origin *Var // if non-nil, the Var from which this one was instantiated
}
// NewVar returns a new variable.
@ -311,6 +312,20 @@ func (obj *Var) Embedded() bool { return obj.embedded }
// IsField reports whether the variable is a struct field.
func (obj *Var) IsField() bool { return obj.isField }
// Origin returns the canonical Var for its receiver, i.e. the Var object
// recorded in Info.Defs.
//
// For synthetic Vars created during instantiation (such as struct fields or
// function parameters that depend on type arguments), this will be the
// corresponding Var on the generic (uninstantiated) type. For all other Vars
// Origin returns the receiver.
func (obj *Var) Origin() *Var {
if obj.origin != nil {
return obj.origin
}
return obj
}
func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression
// A Func represents a declared function, concrete method, or abstract
@ -318,7 +333,8 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
// An abstract method may belong to many interfaces due to embedding.
type Func struct {
object
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
origin *Func // if non-nil, the Func from which this one was instantiated
}
// NewFunc returns a new function with the given signature, representing
@ -329,7 +345,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
if sig != nil {
typ = sig
}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false, nil}
}
// FullName returns the package- or receiver-type-qualified name of
@ -345,6 +361,20 @@ func (obj *Func) FullName() string {
// (but there is also no mechanism to get to an instantiated function).
func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope }
// Origin returns the canonical Func for its receiver, i.e. the Func object
// recorded in Info.Defs.
//
// For synthetic functions created during instantiation (such as methods on an
// instantiated Named type or interface methods that depend on type arguments),
// this will be the corresponding Func on the generic (uninstantiated) type.
// For all other Funcs Origin returns the receiver.
func (obj *Func) Origin() *Func {
if obj.origin != nil {
return obj.origin
}
return obj
}
// hasPtrRecv reports whether the receiver is of the form *T for the given method obj.
func (obj *Func) hasPtrRecv() bool {
// If a method's receiver type is set, use that as the source of truth for the receiver.

View File

@ -38,8 +38,8 @@ func TestSizeof(t *testing.T) {
{PkgName{}, 48, 88},
{Const{}, 48, 88},
{TypeName{}, 40, 72},
{Var{}, 44, 80},
{Func{}, 44, 80},
{Var{}, 48, 88},
{Func{}, 48, 88},
{Label{}, 44, 80},
{Builtin{}, 44, 80},
{Nil{}, 40, 72},

View File

@ -299,6 +299,7 @@ func (subst *subster) var_(v *Var) *Var {
func substVar(v *Var, typ Type) *Var {
copy := *v
copy.typ = typ
copy.origin = v.Origin()
return &copy
}
@ -332,14 +333,19 @@ func (subst *subster) varList(in []*Var) (out []*Var, copied bool) {
func (subst *subster) func_(f *Func) *Func {
if f != nil {
if typ := subst.typ(f.typ); typ != f.typ {
copy := *f
copy.typ = typ
return &copy
return substFunc(f, typ)
}
}
return f
}
func substFunc(f *Func, typ Type) *Func {
copy := *f
copy.typ = typ
copy.origin = f.Origin()
return &copy
}
func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) {
out = in
for i, f := range in {
@ -415,7 +421,7 @@ func replaceRecvType(in []*Func, old, new Type) (out []*Func, copied bool) {
}
newsig := *sig
newsig.recv = substVar(sig.recv, new)
out[i] = NewFunc(method.pos, method.pkg, method.name, &newsig)
out[i] = substFunc(method, &newsig)
}
}
return