[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:
Matthew Dempsky 2021-05-29 15:35:18 -07:00
parent 4d2b528795
commit 2175e2f573
17 changed files with 186 additions and 43 deletions

View file

@ -71,7 +71,7 @@ type importKey struct {
// A dotImportKey describes a dot-imported object in the given scope. // A dotImportKey describes a dot-imported object in the given scope.
type dotImportKey struct { type dotImportKey struct {
scope *Scope scope *Scope
obj Object name string
} }
// A Checker maintains the state of the type checker. // A Checker maintains the state of the type checker.

View file

@ -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 // is detected, the result is Typ[Invalid]. If a cycle is detected and
// n0.check != nil, the cycle is reported. // n0.check != nil, the cycle is reported.
func (n0 *Named) under() Type { func (n0 *Named) under() Type {
u := n0.underlying u := n0.Underlying()
if u == Typ[Invalid] { if u == Typ[Invalid] {
return u return u
@ -560,7 +560,7 @@ func (n0 *Named) under() Type {
seen := map[*Named]int{n0: 0} seen := map[*Named]int{n0: 0}
path := []Object{n0.obj} path := []Object{n0.obj}
for { for {
u = n.underlying u = n.Underlying()
if u == nil { if u == nil {
u = Typ[Invalid] u = Typ[Invalid]
break break
@ -764,7 +764,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
// and field names must be distinct." // and field names must be distinct."
base := asNamed(obj.typ) // shouldn't fail but be conservative base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil { if base != nil {
if t, _ := base.underlying.(*Struct); t != nil { if t, _ := base.Underlying().(*Struct); t != nil {
for _, fld := range t.fields { for _, fld := range t.fields {
if fld.name != "_" { if fld.name != "_" {
assert(mset.insert(fld) == nil) assert(mset.insert(fld) == nil)
@ -806,6 +806,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
} }
if base != nil { if base != nil {
base.expand() // TODO(mdempsky): Probably unnecessary.
base.methods = append(base.methods, m) base.methods = append(base.methods, m)
} }
} }

View file

@ -23,7 +23,7 @@ func Instantiate(pos syntax.Pos, typ Type, targs []Type) (res Type) {
var tparams []*TypeName var tparams []*TypeName
switch t := typ.(type) { switch t := typ.(type) {
case *Named: case *Named:
tparams = t.tparams tparams = t.TParams()
case *Signature: case *Signature:
tparams = t.tparams tparams = t.tparams
defer func() { defer func() {
@ -61,3 +61,19 @@ func Instantiate(pos syntax.Pos, typ Type, targs []Type) (res Type) {
smap := makeSubstMap(tparams, targs) smap := makeSubstMap(tparams, targs)
return (*Checker)(nil).subst(pos, typ, smap) 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,
}
}

View file

@ -32,7 +32,8 @@ func (check *Checker) labels(body *syntax.BlockStmt) {
} }
// spec: "It is illegal to define a label that is never used." // 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 { if lbl := obj.(*Label); !lbl.used {
check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name) check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name)
} }

View file

@ -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 // pointer type but discard the result if it is a method since we would
// not have found it for T (see also issue 8590). // not have found it for T (see also issue 8590).
if t := asNamed(T); t != nil { 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) obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name)
if _, ok := obj.(*Func); ok { if _, ok := obj.(*Func); ok {
return nil, nil, false return nil, nil, false
@ -126,6 +126,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
seen[named] = true seen[named] = true
// look for a matching attached method // look for a matching attached method
named.expand()
if i, m := lookupMethod(named.methods, pkg, name); m != nil { if i, m := lookupMethod(named.methods, pkg, name); m != nil {
// potential match // potential match
// caution: method may not have a proper signature yet // 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 // In order to compare the signatures, substitute the receiver
// type parameters of ftyp with V's instantiation type arguments. // type parameters of ftyp with V's instantiation type arguments.
// This lazily instantiates the signature of method f. // 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 // Be careful: The number of type arguments may not match
// the number of receiver parameters. If so, an error was // the number of receiver parameters. If so, an error was
// reported earlier but the length discrepancy is still // reported earlier but the length discrepancy is still

View file

@ -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}} 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. // IsAlias reports whether obj is an alias name for a type.
func (obj *TypeName) IsAlias() bool { func (obj *TypeName) IsAlias() bool {
switch t := obj.typ.(type) { switch t := obj.typ.(type) {

View file

@ -21,7 +21,7 @@ func isNamed(typ Type) bool {
func isGeneric(typ Type) bool { func isGeneric(typ Type) bool {
// A parameterized type is only instantiated if it doesn't have an instantiation already. // A parameterized type is only instantiated if it doesn't have an instantiation already.
named, _ := typ.(*Named) 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 { func is(typ Type, what BasicInfo) bool {

View file

@ -308,22 +308,26 @@ func (check *Checker) collectObjects() {
check.dotImportMap = make(map[dotImportKey]*PkgName) check.dotImportMap = make(map[dotImportKey]*PkgName)
} }
// merge imported scope with file scope // 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, // A package scope may contain non-exported objects,
// do not import them! // do not import them!
if obj.Exported() { if isExported(name) {
// declare dot-imported object // declare dot-imported object
// (Do not use check.declare because it modifies the object // (Do not use check.declare because it modifies the object
// via Object.setScopePos, which leads to a race condition; // via Object.setScopePos, which leads to a race condition;
// the object may be imported into more than one file scope // the object may be imported into more than one file scope
// concurrently. See issue #32154.) // concurrently. See issue #32154.)
if alt := fileScope.Insert(obj); alt != nil { if alt := fileScope.Lookup(name); alt != nil {
var err error_ 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) err.recordAltDecl(alt)
check.report(&err) check.report(&err)
} else { } 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 // verify that objects in package and file scopes have different names
for _, scope := range fileScopes { for _, scope := range fileScopes {
for _, obj := range scope.elems { for name, obj := range scope.elems {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil { if alt := pkg.scope.Lookup(name); alt != nil {
obj = resolve(name, obj)
var err error_ var err error_
if pkg, ok := obj.(*PkgName); ok { if pkg, ok := obj.(*PkgName); ok {
err.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported()) err.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported())

View file

@ -134,6 +134,7 @@ func (s sanitizer) typ(typ Type) Type {
if debug && t.check != nil { if debug && t.check != nil {
panic("internal error: Named.check != nil") panic("internal error: Named.check != nil")
} }
t.expand()
if orig := s.typ(t.fromRHS); orig != t.fromRHS { if orig := s.typ(t.fromRHS); orig != t.fromRHS {
t.fromRHS = orig t.fromRHS = orig
} }

View file

@ -13,6 +13,7 @@ import (
"io" "io"
"sort" "sort"
"strings" "strings"
"sync"
) )
// A Scope maintains a set of objects and links to its containing // 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 // Lookup returns the object in scope s with the given name if such an
// object exists; otherwise the result is nil. // object exists; otherwise the result is nil.
func (s *Scope) Lookup(name string) Object { 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 // 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. // whose scope is the scope of the package that exported them.
func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) { func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) {
for ; s != nil; s = s.parent { 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 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. // if not already set, and returns nil.
func (s *Scope) Insert(obj Object) Object { func (s *Scope) Insert(obj Object) Object {
name := obj.Name() name := obj.Name()
if alt := s.elems[name]; alt != nil { if alt := s.Lookup(name); alt != nil {
return alt return alt
} }
if s.elems == nil { s.insert(name, obj)
s.elems = make(map[string]Object)
}
s.elems[name] = obj
if obj.Parent() == nil { if obj.Parent() == nil {
obj.setParent(s) obj.setParent(s)
} }
return nil 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 // Squash merges s with its parent scope p by adding all
// objects of s to p, adding all children of s to the // objects of s to p, adding all children of s to the
// children of p, and removing s from p's children. // 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)) { func (s *Scope) Squash(err func(obj, alt Object)) {
p := s.parent p := s.parent
assert(p != nil) assert(p != nil)
for _, obj := range s.elems { for name, obj := range s.elems {
obj = resolve(name, obj)
obj.setParent(nil) obj.setParent(nil)
if alt := p.Insert(obj); alt != nil { if alt := p.Insert(obj); alt != nil {
err(obj, alt) err(obj, alt)
@ -196,7 +217,7 @@ func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
indn1 := indn + ind indn1 := indn + ind
for _, name := range s.Names() { 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 { if recurse {
@ -214,3 +235,57 @@ func (s *Scope) String() string {
s.WriteTo(&buf, 0, false) s.WriteTo(&buf, 0, false)
return buf.String() 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") }

View file

@ -62,7 +62,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
// again when we type-check the signature. // again when we type-check the signature.
// TODO(gri) maybe the receiver should be marked as invalid instead? // TODO(gri) maybe the receiver should be marked as invalid instead?
if recv := asNamed(check.genericType(rname, false)); recv != nil { if recv := asNamed(check.genericType(rname, false)); recv != nil {
recvTParams = recv.tparams recvTParams = recv.TParams()
} }
} }
// provide type parameter bounds // provide type parameter bounds

View file

@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) {
{Interface{}, 52, 104}, {Interface{}, 52, 104},
{Map{}, 16, 32}, {Map{}, 16, 32},
{Chan{}, 12, 24}, {Chan{}, 12, 24},
{Named{}, 68, 136}, {Named{}, 84, 160},
{TypeParam{}, 28, 48}, {TypeParam{}, 28, 48},
{instance{}, 52, 96}, {instance{}, 52, 96},
{top{}, 0, 0}, {top{}, 0, 0},

View file

@ -64,7 +64,8 @@ func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body
func (check *Checker) usage(scope *Scope) { func (check *Checker) usage(scope *Scope) {
var unused []*Var 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 { if v, _ := elem.(*Var); v != nil && !v.used {
unused = append(unused, v) unused = append(unused, v)
} }

View file

@ -76,7 +76,7 @@ func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, poslis
var tparams []*TypeName var tparams []*TypeName
switch t := typ.(type) { switch t := typ.(type) {
case *Named: case *Named:
tparams = t.tparams tparams = t.TParams()
case *Signature: case *Signature:
tparams = t.tparams tparams = t.tparams
defer func() { 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) dump(">>> %s is not parameterized", t)
return t // type is not parameterized return t // type is not parameterized
} }
@ -357,7 +357,7 @@ func (subst *subster) typ(typ Type) Type {
if len(t.targs) > 0 { if len(t.targs) > 0 {
// already instantiated // already instantiated
dump(">>> %s already instantiated", t) 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 // For each (existing) type argument targ, determine if it needs
// to be substituted; i.e., if it is or contains a type parameter // to be substituted; i.e., if it is or contains a type parameter
// that has a type argument for it. // that has a type argument for it.
@ -367,7 +367,7 @@ func (subst *subster) typ(typ Type) Type {
if new_targ != targ { if new_targ != targ {
dump(">>> substituted %d targ %s => %s", i, targ, new_targ) dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
if new_targs == nil { if new_targs == nil {
new_targs = make([]Type, len(t.tparams)) new_targs = make([]Type, len(t.TParams()))
copy(new_targs, t.targs) copy(new_targs, t.targs)
} }
new_targs[i] = new_targ 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 // create a new named type and populate caches to avoid endless recursion
tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil) 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 named.targs = new_targs
if subst.check != nil { if subst.check != nil {
subst.check.typMap[h] = named subst.check.typMap[h] = named
@ -406,7 +406,7 @@ func (subst *subster) typ(typ Type) Type {
// do the substitution // do the substitution
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs) 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) named.fromRHS = named.underlying // for cycle detection (Checker.validType)
return named return named

View file

@ -6,6 +6,7 @@ package types2
import ( import (
"cmd/compile/internal/syntax" "cmd/compile/internal/syntax"
"sync"
"sync/atomic" "sync/atomic"
) )
@ -497,6 +498,9 @@ type Named struct {
tparams []*TypeName // type parameters, or nil tparams []*TypeName // type parameters, or nil
targs []Type // type arguments (after instantiation), 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 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. // 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) 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. // 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 { 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} 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. // 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. // 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. // 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. // 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 } 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 } func (t *Named) SetTArgs(args []Type) { t.targs = args }
// NumMethods returns the number of explicit methods whose receiver is named type t. // 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(). // 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. // SetUnderlying sets the underlying type and marks t as complete.
func (t *Named) SetUnderlying(underlying Type) { func (t *Named) SetUnderlying(underlying Type) {
@ -575,11 +608,12 @@ func (t *Named) SetUnderlying(underlying Type) {
if _, ok := underlying.(*Named); ok { if _, ok := underlying.(*Named); ok {
panic("types2.Named.SetUnderlying: underlying type must not be *Named") 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. // AddMethod adds method m unless it is already in the method list.
func (t *Named) AddMethod(m *Func) { func (t *Named) AddMethod(m *Func) {
t.expand()
if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 {
t.methods = append(t.methods, m) 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 *Interface) Underlying() Type { return t }
func (t *Map) Underlying() Type { return t } func (t *Map) Underlying() Type { return t }
func (t *Chan) 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 *TypeParam) Underlying() Type { return t }
func (t *instance) Underlying() Type { return t } func (t *instance) Underlying() Type { return t }
func (t *top) Underlying() Type { return t } func (t *top) Underlying() Type { return t }

View file

@ -272,9 +272,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
buf.WriteByte('[') buf.WriteByte('[')
writeTypeList(buf, t.targs, qf, visited) writeTypeList(buf, t.targs, qf, visited)
buf.WriteByte(']') buf.WriteByte(']')
} else if t.tparams != nil { } else if t.TParams() != nil {
// parameterized type // parameterized type
writeTParamList(buf, t.tparams, qf, visited) writeTParamList(buf, t.TParams(), qf, visited)
} }
case *TypeParam: case *TypeParam:

View file

@ -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. // If so, mark the respective package as used.
// (This code is only needed for dot-imports. Without them, // (This code is only needed for dot-imports. Without them,
// we only have to mark variables, see *Var case below). // 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 pkgName.used = true
} }