go/types: LookupSelection: returns LookupFieldOrMethod as a Selection

Also, rewrite some uses of LookupFieldOrMethod in terms of it.

+ doc, relnote

Fixes #70737

Change-Id: I58a6dd78ee78560d8b6ea2d821381960a72660ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/647196
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Alan Donovan 2025-02-06 22:28:30 -05:00
parent e9242ee812
commit b3aff930cf
7 changed files with 109 additions and 25 deletions

View File

@ -10,6 +10,7 @@ pkg go/types, const RecvVar = 3 #70250
pkg go/types, const RecvVar VarKind #70250 pkg go/types, const RecvVar VarKind #70250
pkg go/types, const ResultVar = 5 #70250 pkg go/types, const ResultVar = 5 #70250
pkg go/types, const ResultVar VarKind #70250 pkg go/types, const ResultVar VarKind #70250
pkg go/types, func LookupSelection(Type, bool, *Package, string) (Selection, bool) #70737
pkg go/types, method (*Var) Kind() VarKind #70250 pkg go/types, method (*Var) Kind() VarKind #70250
pkg go/types, method (*Var) SetKind(VarKind) #70250 pkg go/types, method (*Var) SetKind(VarKind) #70250
pkg go/types, method (VarKind) String() string #70250 pkg go/types, method (VarKind) String() string #70250

View File

@ -0,0 +1,3 @@
The new [LookupSelection] function looks up the field or method of a
given name and receiver type, like the existing [LookupFieldOrMethod]
function, but returns the result in the form of a [Selection].

View File

@ -181,12 +181,11 @@ var X T[int]
src := prefix + test.decl src := prefix + test.decl
pkg := mustTypecheck(src, nil, nil) pkg := mustTypecheck(src, nil, nil)
typ := NewPointer(pkg.Scope().Lookup("X").Type()) typ := NewPointer(pkg.Scope().Lookup("X").Type())
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m") sel, ok := LookupSelection(typ, false, pkg, "m")
m, _ := obj.(*Func) if !ok {
if m == nil { t.Fatalf(`LookupSelection(%s, "m") failed, want func m`, typ)
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
} }
if got := ObjectString(m, RelativeTo(pkg)); got != test.want { if got := ObjectString(sel.Obj(), RelativeTo(pkg)); got != test.want {
t.Errorf("instantiated %q, want %q", got, test.want) t.Errorf("instantiated %q, want %q", got, test.want)
} }
} }
@ -203,15 +202,15 @@ var _ T[int]
` `
pkg := mustTypecheck(src, nil, nil) pkg := mustTypecheck(src, nil, nil)
typ := pkg.Scope().Lookup("T").Type().(*Named) typ := pkg.Scope().Lookup("T").Type().(*Named)
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m") sel, ok := LookupSelection(typ, false, pkg, "m")
if obj == nil { if !ok {
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj) t.Fatalf(`LookupSelection(%s, "m") failed, want func m`, typ)
} }
// Verify that the original method is not mutated by instantiating T (this // Verify that the original method is not mutated by instantiating T (this
// bug manifested when subst did not return a new signature). // bug manifested when subst did not return a new signature).
want := "func (T[P]).m()" want := "func (T[P]).m()"
if got := stripAnnotations(ObjectString(obj, RelativeTo(pkg))); got != want { if got := stripAnnotations(ObjectString(sel.Obj(), RelativeTo(pkg))); got != want {
t.Errorf("instantiated %q, want %q", got, want) t.Errorf("instantiated %q, want %q", got, want)
} }
} }

View File

@ -8,6 +8,45 @@ package types2
import "bytes" import "bytes"
// LookupSelection selects the field or method whose ID is Id(pkg,
// name), on a value of type T. If addressable is set, T is the type
// of an addressable variable (this matters only for method lookups).
// T must not be nil.
//
// If the selection is valid:
//
// - [Selection.Obj] returns the field ([Var]) or method ([Func]);
// - [Selection.Indirect] reports whether there were any pointer
// indirections on the path to the field or method.
// - [Selection.Index] returns the index sequence, defined below.
//
// The last index entry is the field or method index in the (possibly
// embedded) type where the entry was found, either:
//
// 1. the list of declared methods of a named type; or
// 2. the list of all methods (method set) of an interface type; or
// 3. the list of fields of a struct type.
//
// The earlier index entries are the indices of the embedded struct
// fields traversed to get to the found entry, starting at depth 0.
//
// See also [LookupFieldOrMethod], which returns the components separately.
func LookupSelection(T Type, addressable bool, pkg *Package, name string) (Selection, bool) {
obj, index, indirect := LookupFieldOrMethod(T, addressable, pkg, name)
var kind SelectionKind
switch obj.(type) {
case nil:
return Selection{}, false
case *Func:
kind = MethodVal
case *Var:
kind = FieldVal
default:
panic(obj) // can't happen
}
return Selection{kind, T, obj, index, indirect}, true
}
// Internal use of LookupFieldOrMethod: If the obj result is a method // Internal use of LookupFieldOrMethod: If the obj result is a method
// associated with a concrete (non-interface) type, the method's signature // associated with a concrete (non-interface) type, the method's signature
// may not be fully set up. Call Checker.objDecl(obj, nil) before accessing // may not be fully set up. Call Checker.objDecl(obj, nil) before accessing
@ -38,6 +77,8 @@ import "bytes"
// - If indirect is set, a method with a pointer receiver type was found // - If indirect is set, a method with a pointer receiver type was found
// but there was no pointer on the path from the actual receiver type to // but there was no pointer on the path from the actual receiver type to
// the method's formal receiver base type, nor was the receiver addressable. // the method's formal receiver base type, nor was the receiver addressable.
//
// See also [LookupSelection], which returns the result as a [Selection].
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
if T == nil { if T == nil {
panic("LookupFieldOrMethod on nil type") panic("LookupFieldOrMethod on nil type")

View File

@ -632,14 +632,14 @@ func TestIssue13898(t *testing.T) {
} }
// lookup go/types.Object.Pkg method // lookup go/types.Object.Pkg method
m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") sel, ok := types.LookupSelection(typ, false, nil, "Pkg")
if m == nil { if !ok {
t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) t.Fatalf("go/types.Object.Pkg not found")
} }
// the method must belong to go/types // the method must belong to go/types
if m.Pkg().Path() != "go/types" { if sel.Obj().Pkg().Path() != "go/types" {
t.Fatalf("found %v; want go/types", m.Pkg()) t.Fatalf("found %v; want go/types", sel.Obj().Pkg())
} }
} }
@ -699,8 +699,8 @@ func TestIssue20046(t *testing.T) {
// "./issue20046".V.M must exist // "./issue20046".V.M must exist
pkg := compileAndImportPkg(t, "issue20046") pkg := compileAndImportPkg(t, "issue20046")
obj := lookupObj(t, pkg.Scope(), "V") obj := lookupObj(t, pkg.Scope(), "V")
if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { if _, ok := types.LookupSelection(obj.Type(), false, nil, "M"); !ok {
t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) t.Fatalf("V.M not found")
} }
} }
func TestIssue25301(t *testing.T) { func TestIssue25301(t *testing.T) {

View File

@ -184,12 +184,11 @@ var X T[int]
src := prefix + test.decl src := prefix + test.decl
pkg := mustTypecheck(src, nil, nil) pkg := mustTypecheck(src, nil, nil)
typ := NewPointer(pkg.Scope().Lookup("X").Type()) typ := NewPointer(pkg.Scope().Lookup("X").Type())
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m") sel, ok := LookupSelection(typ, false, pkg, "m")
m, _ := obj.(*Func) if !ok {
if m == nil { t.Fatalf(`LookupSelection(%s, "m") failed, want func m`, typ)
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
} }
if got := ObjectString(m, RelativeTo(pkg)); got != test.want { if got := ObjectString(sel.Obj(), RelativeTo(pkg)); got != test.want {
t.Errorf("instantiated %q, want %q", got, test.want) t.Errorf("instantiated %q, want %q", got, test.want)
} }
} }
@ -206,15 +205,15 @@ var _ T[int]
` `
pkg := mustTypecheck(src, nil, nil) pkg := mustTypecheck(src, nil, nil)
typ := pkg.Scope().Lookup("T").Type().(*Named) typ := pkg.Scope().Lookup("T").Type().(*Named)
obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m") sel, ok := LookupSelection(typ, false, pkg, "m")
if obj == nil { if !ok {
t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj) t.Fatalf(`LookupSelection(%s, "m") failed, want func m`, typ)
} }
// Verify that the original method is not mutated by instantiating T (this // Verify that the original method is not mutated by instantiating T (this
// bug manifested when subst did not return a new signature). // bug manifested when subst did not return a new signature).
want := "func (T[P]).m()" want := "func (T[P]).m()"
if got := stripAnnotations(ObjectString(obj, RelativeTo(pkg))); got != want { if got := stripAnnotations(ObjectString(sel.Obj(), RelativeTo(pkg))); got != want {
t.Errorf("instantiated %q, want %q", got, want) t.Errorf("instantiated %q, want %q", got, want)
} }
} }

View File

@ -11,6 +11,45 @@ package types
import "bytes" import "bytes"
// LookupSelection selects the field or method whose ID is Id(pkg,
// name), on a value of type T. If addressable is set, T is the type
// of an addressable variable (this matters only for method lookups).
// T must not be nil.
//
// If the selection is valid:
//
// - [Selection.Obj] returns the field ([Var]) or method ([Func]);
// - [Selection.Indirect] reports whether there were any pointer
// indirections on the path to the field or method.
// - [Selection.Index] returns the index sequence, defined below.
//
// The last index entry is the field or method index in the (possibly
// embedded) type where the entry was found, either:
//
// 1. the list of declared methods of a named type; or
// 2. the list of all methods (method set) of an interface type; or
// 3. the list of fields of a struct type.
//
// The earlier index entries are the indices of the embedded struct
// fields traversed to get to the found entry, starting at depth 0.
//
// See also [LookupFieldOrMethod], which returns the components separately.
func LookupSelection(T Type, addressable bool, pkg *Package, name string) (Selection, bool) {
obj, index, indirect := LookupFieldOrMethod(T, addressable, pkg, name)
var kind SelectionKind
switch obj.(type) {
case nil:
return Selection{}, false
case *Func:
kind = MethodVal
case *Var:
kind = FieldVal
default:
panic(obj) // can't happen
}
return Selection{kind, T, obj, index, indirect}, true
}
// Internal use of LookupFieldOrMethod: If the obj result is a method // Internal use of LookupFieldOrMethod: If the obj result is a method
// associated with a concrete (non-interface) type, the method's signature // associated with a concrete (non-interface) type, the method's signature
// may not be fully set up. Call Checker.objDecl(obj, nil) before accessing // may not be fully set up. Call Checker.objDecl(obj, nil) before accessing
@ -41,6 +80,8 @@ import "bytes"
// - If indirect is set, a method with a pointer receiver type was found // - If indirect is set, a method with a pointer receiver type was found
// but there was no pointer on the path from the actual receiver type to // but there was no pointer on the path from the actual receiver type to
// the method's formal receiver base type, nor was the receiver addressable. // the method's formal receiver base type, nor was the receiver addressable.
//
// See also [LookupSelection], which returns the result as a [Selection].
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
if T == nil { if T == nil {
panic("LookupFieldOrMethod on nil type") panic("LookupFieldOrMethod on nil type")