Skip to content

Commit

Permalink
go/types, types2: set an origin object for vars and funcs
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
findleyr committed May 17, 2022
1 parent 8953302 commit 1170771
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 39 deletions.
2 changes: 2 additions & 0 deletions api/next/51682.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pkg go/types, method (*Func) Origin() *Func #51682
pkg go/types, method (*Var) Origin() *Var #51682
64 changes: 50 additions & 14 deletions src/cmd/compile/internal/types2/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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.
Expand All @@ -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())
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/named.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 32 additions & 2 deletions src/cmd/compile/internal/types2/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -357,14 +358,29 @@ 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
// (interface) method. Its Type() is always a *Signature.
// 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
Expand All @@ -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
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/sizeof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
14 changes: 10 additions & 4 deletions src/cmd/compile/internal/types2/subst.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
52 changes: 45 additions & 7 deletions src/go/types/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand All @@ -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.
Expand All @@ -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())
}
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/go/types/named.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 1170771

Please sign in to comment.