mirror of
https://github.com/golang/go
synced 2024-10-06 08:00:07 +00:00
[dev.typeparams] cmd/compile: lazy import resolution for types2
This CL adds three new functions to the types2 API to support lazy import resolution: 1. A new Scope.InsertLazy method to allow recording that Objects exist in a particular Scope (in particular, package scopes) without having to yet fully construct those objects. Instead, types2 will call the provided `resolve` function if/when the object is actually needed. 2. Similarly, a new NewTypeNameLazy function to create TypeName objects without yet instantiating their underlying Named instance. 3. Finally, an InstantiateLazy method, that allows creating type instances without requiring any of the types to be expanded right away. Importantly, this requires providing a types2.Checker argument to handle recursive types correctly. The APIs as-is are a bit clumsy (esp. NewTypeNameLazy), but seem to work well for cmd/compile's needs. In particular, they simplify some of the complexities of handling recursive type definitions within the importer. Also, the current prototype is a bit fragile. It uses sync.Once to manage concurrent lazy resolution, which is frustrating to debug in the presence of reentrancy issues. It also means the importer needs to deal with concurrency as well. These aren't issues for types2 though as cmd/compile only walks the type-checked AST sequentially. Finally, it looks like some of the details of lazy type names are similar to the lazy "instance" stuff used for generics, so maybe there's opportunity for unifying them under a more general (but still internal) lazy type mechanism. I had originally intended for this CL to also update the types2 importer, but (1) it doesn't have access to the types2.Checker instance needed to call InstantiateLazy, and (2) it creates a new TypeName/TypeParam at each use rather than reusing them, which evidently works with types2.Instantiate but not types2.(*Checker).instantiate (i.e., InstantiateLazy). I spent a while trying to fix these issues, but kept running into more subtle issues. Instead, I've included my WIP "unified IR" CL as a followup CL that demonstrates these Lazy methods (see noder/reader2.go). Updates #46449. Change-Id: I4d1e8e649f6325a11790d25fd90c39fa07c8d41d Reviewed-on: https://go-review.googlesource.com/c/go/+/323569 Run-TryBot: Matthew Dempsky <mdempsky@google.com> Trust: Matthew Dempsky <mdempsky@google.com> Trust: Robert Griesemer <gri@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
4d2b528795
commit
2175e2f573
|
@ -71,7 +71,7 @@ type importKey struct {
|
|||
// A dotImportKey describes a dot-imported object in the given scope.
|
||||
type dotImportKey struct {
|
||||
scope *Scope
|
||||
obj Object
|
||||
name string
|
||||
}
|
||||
|
||||
// A Checker maintains the state of the type checker.
|
||||
|
|
|
@ -522,7 +522,7 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) {
|
|||
// is detected, the result is Typ[Invalid]. If a cycle is detected and
|
||||
// n0.check != nil, the cycle is reported.
|
||||
func (n0 *Named) under() Type {
|
||||
u := n0.underlying
|
||||
u := n0.Underlying()
|
||||
|
||||
if u == Typ[Invalid] {
|
||||
return u
|
||||
|
@ -560,7 +560,7 @@ func (n0 *Named) under() Type {
|
|||
seen := map[*Named]int{n0: 0}
|
||||
path := []Object{n0.obj}
|
||||
for {
|
||||
u = n.underlying
|
||||
u = n.Underlying()
|
||||
if u == nil {
|
||||
u = Typ[Invalid]
|
||||
break
|
||||
|
@ -764,7 +764,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
|
|||
// and field names must be distinct."
|
||||
base := asNamed(obj.typ) // shouldn't fail but be conservative
|
||||
if base != nil {
|
||||
if t, _ := base.underlying.(*Struct); t != nil {
|
||||
if t, _ := base.Underlying().(*Struct); t != nil {
|
||||
for _, fld := range t.fields {
|
||||
if fld.name != "_" {
|
||||
assert(mset.insert(fld) == nil)
|
||||
|
@ -806,6 +806,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
|
|||
}
|
||||
|
||||
if base != nil {
|
||||
base.expand() // TODO(mdempsky): Probably unnecessary.
|
||||
base.methods = append(base.methods, m)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ func Instantiate(pos syntax.Pos, typ Type, targs []Type) (res Type) {
|
|||
var tparams []*TypeName
|
||||
switch t := typ.(type) {
|
||||
case *Named:
|
||||
tparams = t.tparams
|
||||
tparams = t.TParams()
|
||||
case *Signature:
|
||||
tparams = t.tparams
|
||||
defer func() {
|
||||
|
@ -61,3 +61,19 @@ func Instantiate(pos syntax.Pos, typ Type, targs []Type) (res Type) {
|
|||
smap := makeSubstMap(tparams, targs)
|
||||
return (*Checker)(nil).subst(pos, typ, smap)
|
||||
}
|
||||
|
||||
// InstantiateLazy is like Instantiate, but avoids actually
|
||||
// instantiating the type until needed.
|
||||
func (check *Checker) InstantiateLazy(pos syntax.Pos, typ Type, targs []Type) (res Type) {
|
||||
base := asNamed(typ)
|
||||
if base == nil {
|
||||
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
|
||||
}
|
||||
|
||||
return &instance{
|
||||
check: check,
|
||||
pos: pos,
|
||||
base: base,
|
||||
targs: targs,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ func (check *Checker) labels(body *syntax.BlockStmt) {
|
|||
}
|
||||
|
||||
// spec: "It is illegal to define a label that is never used."
|
||||
for _, obj := range all.elems {
|
||||
for name, obj := range all.elems {
|
||||
obj = resolve(name, obj)
|
||||
if lbl := obj.(*Label); !lbl.used {
|
||||
check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (check *Checker) lookupFieldOrMethod(T Type, addressable bool, pkg *Package
|
|||
// pointer type but discard the result if it is a method since we would
|
||||
// not have found it for T (see also issue 8590).
|
||||
if t := asNamed(T); t != nil {
|
||||
if p, _ := t.underlying.(*Pointer); p != nil {
|
||||
if p, _ := t.Underlying().(*Pointer); p != nil {
|
||||
obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name)
|
||||
if _, ok := obj.(*Func); ok {
|
||||
return nil, nil, false
|
||||
|
@ -126,6 +126,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
|
|||
seen[named] = true
|
||||
|
||||
// look for a matching attached method
|
||||
named.expand()
|
||||
if i, m := lookupMethod(named.methods, pkg, name); m != nil {
|
||||
// potential match
|
||||
// caution: method may not have a proper signature yet
|
||||
|
@ -400,7 +401,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
|
|||
// In order to compare the signatures, substitute the receiver
|
||||
// type parameters of ftyp with V's instantiation type arguments.
|
||||
// This lazily instantiates the signature of method f.
|
||||
if Vn != nil && len(Vn.tparams) > 0 {
|
||||
if Vn != nil && len(Vn.TParams()) > 0 {
|
||||
// Be careful: The number of type arguments may not match
|
||||
// the number of receiver parameters. If so, an error was
|
||||
// reported earlier but the length discrepancy is still
|
||||
|
|
|
@ -276,6 +276,14 @@ func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName
|
|||
return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}}
|
||||
}
|
||||
|
||||
// NewTypeNameLazy returns a new defined type like NewTypeName, but it
|
||||
// lazily calls resolve to finish constructing the Named object.
|
||||
func NewTypeNameLazy(pos syntax.Pos, pkg *Package, name string, resolve func(named *Named) (tparams []*TypeName, underlying Type, methods []*Func)) *TypeName {
|
||||
obj := NewTypeName(pos, pkg, name, nil)
|
||||
NewNamed(obj, nil, nil).resolve = resolve
|
||||
return obj
|
||||
}
|
||||
|
||||
// IsAlias reports whether obj is an alias name for a type.
|
||||
func (obj *TypeName) IsAlias() bool {
|
||||
switch t := obj.typ.(type) {
|
||||
|
|
|
@ -21,7 +21,7 @@ func isNamed(typ Type) bool {
|
|||
func isGeneric(typ Type) bool {
|
||||
// A parameterized type is only instantiated if it doesn't have an instantiation already.
|
||||
named, _ := typ.(*Named)
|
||||
return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil
|
||||
return named != nil && named.obj != nil && named.TParams() != nil && named.targs == nil
|
||||
}
|
||||
|
||||
func is(typ Type, what BasicInfo) bool {
|
||||
|
|
|
@ -308,22 +308,26 @@ func (check *Checker) collectObjects() {
|
|||
check.dotImportMap = make(map[dotImportKey]*PkgName)
|
||||
}
|
||||
// merge imported scope with file scope
|
||||
for _, obj := range imp.scope.elems {
|
||||
for name, obj := range imp.scope.elems {
|
||||
// Note: Avoid eager resolve(name, obj) here, so we only
|
||||
// resolve dot-imported objects as needed.
|
||||
|
||||
// A package scope may contain non-exported objects,
|
||||
// do not import them!
|
||||
if obj.Exported() {
|
||||
if isExported(name) {
|
||||
// declare dot-imported object
|
||||
// (Do not use check.declare because it modifies the object
|
||||
// via Object.setScopePos, which leads to a race condition;
|
||||
// the object may be imported into more than one file scope
|
||||
// concurrently. See issue #32154.)
|
||||
if alt := fileScope.Insert(obj); alt != nil {
|
||||
if alt := fileScope.Lookup(name); alt != nil {
|
||||
var err error_
|
||||
err.errorf(s.LocalPkgName, "%s redeclared in this block", obj.Name())
|
||||
err.errorf(s.LocalPkgName, "%s redeclared in this block", alt.Name())
|
||||
err.recordAltDecl(alt)
|
||||
check.report(&err)
|
||||
} else {
|
||||
check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName
|
||||
fileScope.insert(name, obj)
|
||||
check.dotImportMap[dotImportKey{fileScope, name}] = pkgName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,8 +473,9 @@ func (check *Checker) collectObjects() {
|
|||
|
||||
// verify that objects in package and file scopes have different names
|
||||
for _, scope := range fileScopes {
|
||||
for _, obj := range scope.elems {
|
||||
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
|
||||
for name, obj := range scope.elems {
|
||||
if alt := pkg.scope.Lookup(name); alt != nil {
|
||||
obj = resolve(name, obj)
|
||||
var err error_
|
||||
if pkg, ok := obj.(*PkgName); ok {
|
||||
err.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported())
|
||||
|
|
|
@ -134,6 +134,7 @@ func (s sanitizer) typ(typ Type) Type {
|
|||
if debug && t.check != nil {
|
||||
panic("internal error: Named.check != nil")
|
||||
}
|
||||
t.expand()
|
||||
if orig := s.typ(t.fromRHS); orig != t.fromRHS {
|
||||
t.fromRHS = orig
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Scope maintains a set of objects and links to its containing
|
||||
|
@ -66,7 +67,7 @@ func (s *Scope) Child(i int) *Scope { return s.children[i] }
|
|||
// Lookup returns the object in scope s with the given name if such an
|
||||
// object exists; otherwise the result is nil.
|
||||
func (s *Scope) Lookup(name string) Object {
|
||||
return s.elems[name]
|
||||
return resolve(name, s.elems[name])
|
||||
}
|
||||
|
||||
// LookupParent follows the parent chain of scopes starting with s until
|
||||
|
@ -81,7 +82,7 @@ func (s *Scope) Lookup(name string) Object {
|
|||
// whose scope is the scope of the package that exported them.
|
||||
func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) {
|
||||
for ; s != nil; s = s.parent {
|
||||
if obj := s.elems[name]; obj != nil && (!pos.IsKnown() || obj.scopePos().Cmp(pos) <= 0) {
|
||||
if obj := s.Lookup(name); obj != nil && (!pos.IsKnown() || obj.scopePos().Cmp(pos) <= 0) {
|
||||
return s, obj
|
||||
}
|
||||
}
|
||||
|
@ -95,19 +96,38 @@ func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) {
|
|||
// if not already set, and returns nil.
|
||||
func (s *Scope) Insert(obj Object) Object {
|
||||
name := obj.Name()
|
||||
if alt := s.elems[name]; alt != nil {
|
||||
if alt := s.Lookup(name); alt != nil {
|
||||
return alt
|
||||
}
|
||||
if s.elems == nil {
|
||||
s.elems = make(map[string]Object)
|
||||
}
|
||||
s.elems[name] = obj
|
||||
s.insert(name, obj)
|
||||
if obj.Parent() == nil {
|
||||
obj.setParent(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertLazy is like Insert, but allows deferring construction of the
|
||||
// inserted object until it's accessed with Lookup. The Object
|
||||
// returned by resolve must have the same name as given to InsertLazy.
|
||||
// If s already contains an alternative object with the same name,
|
||||
// InsertLazy leaves s unchanged and returns false. Otherwise it
|
||||
// records the binding and returns true. The object's parent scope
|
||||
// will be set to s after resolve is called.
|
||||
func (s *Scope) InsertLazy(name string, resolve func() Object) bool {
|
||||
if s.elems[name] != nil {
|
||||
return false
|
||||
}
|
||||
s.insert(name, &lazyObject{parent: s, resolve: resolve})
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Scope) insert(name string, obj Object) {
|
||||
if s.elems == nil {
|
||||
s.elems = make(map[string]Object)
|
||||
}
|
||||
s.elems[name] = obj
|
||||
}
|
||||
|
||||
// Squash merges s with its parent scope p by adding all
|
||||
// objects of s to p, adding all children of s to the
|
||||
// children of p, and removing s from p's children.
|
||||
|
@ -117,7 +137,8 @@ func (s *Scope) Insert(obj Object) Object {
|
|||
func (s *Scope) Squash(err func(obj, alt Object)) {
|
||||
p := s.parent
|
||||
assert(p != nil)
|
||||
for _, obj := range s.elems {
|
||||
for name, obj := range s.elems {
|
||||
obj = resolve(name, obj)
|
||||
obj.setParent(nil)
|
||||
if alt := p.Insert(obj); alt != nil {
|
||||
err(obj, alt)
|
||||
|
@ -196,7 +217,7 @@ func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
|
|||
|
||||
indn1 := indn + ind
|
||||
for _, name := range s.Names() {
|
||||
fmt.Fprintf(w, "%s%s\n", indn1, s.elems[name])
|
||||
fmt.Fprintf(w, "%s%s\n", indn1, s.Lookup(name))
|
||||
}
|
||||
|
||||
if recurse {
|
||||
|
@ -214,3 +235,57 @@ func (s *Scope) String() string {
|
|||
s.WriteTo(&buf, 0, false)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// A lazyObject represents an imported Object that has not been fully
|
||||
// resolved yet by its importer.
|
||||
type lazyObject struct {
|
||||
parent *Scope
|
||||
resolve func() Object
|
||||
obj Object
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// resolve returns the Object represented by obj, resolving lazy
|
||||
// objects as appropriate.
|
||||
func resolve(name string, obj Object) Object {
|
||||
if lazy, ok := obj.(*lazyObject); ok {
|
||||
lazy.once.Do(func() {
|
||||
obj := lazy.resolve()
|
||||
|
||||
if _, ok := obj.(*lazyObject); ok {
|
||||
panic("recursive lazy object")
|
||||
}
|
||||
if obj.Name() != name {
|
||||
panic("lazy object has unexpected name")
|
||||
}
|
||||
|
||||
if obj.Parent() == nil {
|
||||
obj.setParent(lazy.parent)
|
||||
}
|
||||
lazy.obj = obj
|
||||
})
|
||||
|
||||
obj = lazy.obj
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// stub implementations so *lazyObject implements Object and we can
|
||||
// store them directly into Scope.elems.
|
||||
func (*lazyObject) Parent() *Scope { panic("unreachable") }
|
||||
func (*lazyObject) Pos() syntax.Pos { panic("unreachable") }
|
||||
func (*lazyObject) Pkg() *Package { panic("unreachable") }
|
||||
func (*lazyObject) Name() string { panic("unreachable") }
|
||||
func (*lazyObject) Type() Type { panic("unreachable") }
|
||||
func (*lazyObject) Exported() bool { panic("unreachable") }
|
||||
func (*lazyObject) Id() string { panic("unreachable") }
|
||||
func (*lazyObject) String() string { panic("unreachable") }
|
||||
func (*lazyObject) order() uint32 { panic("unreachable") }
|
||||
func (*lazyObject) color() color { panic("unreachable") }
|
||||
func (*lazyObject) setType(Type) { panic("unreachable") }
|
||||
func (*lazyObject) setOrder(uint32) { panic("unreachable") }
|
||||
func (*lazyObject) setColor(color color) { panic("unreachable") }
|
||||
func (*lazyObject) setParent(*Scope) { panic("unreachable") }
|
||||
func (*lazyObject) sameId(pkg *Package, name string) bool { panic("unreachable") }
|
||||
func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }
|
||||
func (*lazyObject) setScopePos(pos syntax.Pos) { panic("unreachable") }
|
||||
|
|
|
@ -62,7 +62,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
|
|||
// again when we type-check the signature.
|
||||
// TODO(gri) maybe the receiver should be marked as invalid instead?
|
||||
if recv := asNamed(check.genericType(rname, false)); recv != nil {
|
||||
recvTParams = recv.tparams
|
||||
recvTParams = recv.TParams()
|
||||
}
|
||||
}
|
||||
// provide type parameter bounds
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) {
|
|||
{Interface{}, 52, 104},
|
||||
{Map{}, 16, 32},
|
||||
{Chan{}, 12, 24},
|
||||
{Named{}, 68, 136},
|
||||
{Named{}, 84, 160},
|
||||
{TypeParam{}, 28, 48},
|
||||
{instance{}, 52, 96},
|
||||
{top{}, 0, 0},
|
||||
|
|
|
@ -64,7 +64,8 @@ func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body
|
|||
|
||||
func (check *Checker) usage(scope *Scope) {
|
||||
var unused []*Var
|
||||
for _, elem := range scope.elems {
|
||||
for name, elem := range scope.elems {
|
||||
elem = resolve(name, elem)
|
||||
if v, _ := elem.(*Var); v != nil && !v.used {
|
||||
unused = append(unused, v)
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, poslis
|
|||
var tparams []*TypeName
|
||||
switch t := typ.(type) {
|
||||
case *Named:
|
||||
tparams = t.tparams
|
||||
tparams = t.TParams()
|
||||
case *Signature:
|
||||
tparams = t.tparams
|
||||
defer func() {
|
||||
|
@ -347,7 +347,7 @@ func (subst *subster) typ(typ Type) Type {
|
|||
}
|
||||
}
|
||||
|
||||
if t.tparams == nil {
|
||||
if t.TParams() == nil {
|
||||
dump(">>> %s is not parameterized", t)
|
||||
return t // type is not parameterized
|
||||
}
|
||||
|
@ -357,7 +357,7 @@ func (subst *subster) typ(typ Type) Type {
|
|||
if len(t.targs) > 0 {
|
||||
// already instantiated
|
||||
dump(">>> %s already instantiated", t)
|
||||
assert(len(t.targs) == len(t.tparams))
|
||||
assert(len(t.targs) == len(t.TParams()))
|
||||
// For each (existing) type argument targ, determine if it needs
|
||||
// to be substituted; i.e., if it is or contains a type parameter
|
||||
// that has a type argument for it.
|
||||
|
@ -367,7 +367,7 @@ func (subst *subster) typ(typ Type) Type {
|
|||
if new_targ != targ {
|
||||
dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
|
||||
if new_targs == nil {
|
||||
new_targs = make([]Type, len(t.tparams))
|
||||
new_targs = make([]Type, len(t.TParams()))
|
||||
copy(new_targs, t.targs)
|
||||
}
|
||||
new_targs[i] = new_targ
|
||||
|
@ -397,7 +397,7 @@ func (subst *subster) typ(typ Type) Type {
|
|||
|
||||
// create a new named type and populate caches to avoid endless recursion
|
||||
tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil)
|
||||
named := subst.check.newNamed(tname, t, t.underlying, t.tparams, t.methods) // method signatures are updated lazily
|
||||
named := subst.check.newNamed(tname, t, t.Underlying(), t.TParams(), t.methods) // method signatures are updated lazily
|
||||
named.targs = new_targs
|
||||
if subst.check != nil {
|
||||
subst.check.typMap[h] = named
|
||||
|
@ -406,7 +406,7 @@ func (subst *subster) typ(typ Type) Type {
|
|||
|
||||
// do the substitution
|
||||
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs)
|
||||
named.underlying = subst.typOrNil(t.underlying)
|
||||
named.underlying = subst.typOrNil(t.Underlying())
|
||||
named.fromRHS = named.underlying // for cycle detection (Checker.validType)
|
||||
|
||||
return named
|
||||
|
|
|
@ -6,6 +6,7 @@ package types2
|
|||
|
||||
import (
|
||||
"cmd/compile/internal/syntax"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
@ -497,6 +498,9 @@ type Named struct {
|
|||
tparams []*TypeName // type parameters, or nil
|
||||
targs []Type // type arguments (after instantiation), or nil
|
||||
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
|
||||
|
||||
resolve func(*Named) ([]*TypeName, Type, []*Func)
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
|
||||
|
@ -509,6 +513,35 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
|
|||
return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods)
|
||||
}
|
||||
|
||||
func (t *Named) expand() *Named {
|
||||
if t.resolve == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
t.once.Do(func() {
|
||||
// TODO(mdempsky): Since we're passing t to resolve anyway
|
||||
// (necessary because types2 expects the receiver type for methods
|
||||
// on defined interface types to be the Named rather than the
|
||||
// underlying Interface), maybe it should just handle calling
|
||||
// SetTParams, SetUnderlying, and AddMethod instead? Those
|
||||
// methods would need to support reentrant calls though. It would
|
||||
// also make the API more future-proof towards further extensions
|
||||
// (like SetTParams).
|
||||
|
||||
tparams, underlying, methods := t.resolve(t)
|
||||
|
||||
switch underlying.(type) {
|
||||
case nil, *Named:
|
||||
panic("invalid underlying type")
|
||||
}
|
||||
|
||||
t.tparams = tparams
|
||||
t.underlying = underlying
|
||||
t.methods = methods
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
// newNamed is like NewNamed but with a *Checker receiver and additional orig argument.
|
||||
func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams []*TypeName, methods []*Func) *Named {
|
||||
typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods}
|
||||
|
@ -550,10 +583,10 @@ func (t *Named) Orig() *Named { return t.orig }
|
|||
|
||||
// TParams returns the type parameters of the named type t, or nil.
|
||||
// The result is non-nil for an (originally) parameterized type even if it is instantiated.
|
||||
func (t *Named) TParams() []*TypeName { return t.tparams }
|
||||
func (t *Named) TParams() []*TypeName { return t.expand().tparams }
|
||||
|
||||
// SetTParams sets the type parameters of the named type t.
|
||||
func (t *Named) SetTParams(tparams []*TypeName) { t.tparams = tparams }
|
||||
func (t *Named) SetTParams(tparams []*TypeName) { t.expand().tparams = tparams }
|
||||
|
||||
// TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated.
|
||||
func (t *Named) TArgs() []Type { return t.targs }
|
||||
|
@ -562,10 +595,10 @@ func (t *Named) TArgs() []Type { return t.targs }
|
|||
func (t *Named) SetTArgs(args []Type) { t.targs = args }
|
||||
|
||||
// NumMethods returns the number of explicit methods whose receiver is named type t.
|
||||
func (t *Named) NumMethods() int { return len(t.methods) }
|
||||
func (t *Named) NumMethods() int { return len(t.expand().methods) }
|
||||
|
||||
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
|
||||
func (t *Named) Method(i int) *Func { return t.methods[i] }
|
||||
func (t *Named) Method(i int) *Func { return t.expand().methods[i] }
|
||||
|
||||
// SetUnderlying sets the underlying type and marks t as complete.
|
||||
func (t *Named) SetUnderlying(underlying Type) {
|
||||
|
@ -575,11 +608,12 @@ func (t *Named) SetUnderlying(underlying Type) {
|
|||
if _, ok := underlying.(*Named); ok {
|
||||
panic("types2.Named.SetUnderlying: underlying type must not be *Named")
|
||||
}
|
||||
t.underlying = underlying
|
||||
t.expand().underlying = underlying
|
||||
}
|
||||
|
||||
// AddMethod adds method m unless it is already in the method list.
|
||||
func (t *Named) AddMethod(m *Func) {
|
||||
t.expand()
|
||||
if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 {
|
||||
t.methods = append(t.methods, m)
|
||||
}
|
||||
|
@ -743,7 +777,7 @@ func (t *Signature) Underlying() Type { return t }
|
|||
func (t *Interface) Underlying() Type { return t }
|
||||
func (t *Map) Underlying() Type { return t }
|
||||
func (t *Chan) Underlying() Type { return t }
|
||||
func (t *Named) Underlying() Type { return t.underlying }
|
||||
func (t *Named) Underlying() Type { return t.expand().underlying }
|
||||
func (t *TypeParam) Underlying() Type { return t }
|
||||
func (t *instance) Underlying() Type { return t }
|
||||
func (t *top) Underlying() Type { return t }
|
||||
|
|
|
@ -272,9 +272,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
|
|||
buf.WriteByte('[')
|
||||
writeTypeList(buf, t.targs, qf, visited)
|
||||
buf.WriteByte(']')
|
||||
} else if t.tparams != nil {
|
||||
} else if t.TParams() != nil {
|
||||
// parameterized type
|
||||
writeTParamList(buf, t.tparams, qf, visited)
|
||||
writeTParamList(buf, t.TParams(), qf, visited)
|
||||
}
|
||||
|
||||
case *TypeParam:
|
||||
|
|
|
@ -58,7 +58,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo
|
|||
// If so, mark the respective package as used.
|
||||
// (This code is only needed for dot-imports. Without them,
|
||||
// we only have to mark variables, see *Var case below).
|
||||
if pkgName := check.dotImportMap[dotImportKey{scope, obj}]; pkgName != nil {
|
||||
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
|
||||
pkgName.used = true
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue