]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile: lazy import resolution for types2
authorMatthew Dempsky <mdempsky@google.com>
Sat, 29 May 2021 22:35:18 +0000 (15:35 -0700)
committerMatthew Dempsky <mdempsky@google.com>
Fri, 4 Jun 2021 04:28:10 +0000 (04:28 +0000)
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>
17 files changed:
src/cmd/compile/internal/types2/check.go
src/cmd/compile/internal/types2/decl.go
src/cmd/compile/internal/types2/instantiate.go
src/cmd/compile/internal/types2/labels.go
src/cmd/compile/internal/types2/lookup.go
src/cmd/compile/internal/types2/object.go
src/cmd/compile/internal/types2/predicates.go
src/cmd/compile/internal/types2/resolver.go
src/cmd/compile/internal/types2/sanitize.go
src/cmd/compile/internal/types2/scope.go
src/cmd/compile/internal/types2/signature.go
src/cmd/compile/internal/types2/sizeof_test.go
src/cmd/compile/internal/types2/stmt.go
src/cmd/compile/internal/types2/subst.go
src/cmd/compile/internal/types2/type.go
src/cmd/compile/internal/types2/typestring.go
src/cmd/compile/internal/types2/typexpr.go

index f80a918467e6cf81c8312c07f841e8346053b081..5d3c2c8ad253855ce74f644aa8a009b67ef487b9 100644 (file)
@@ -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.
index aa70f3880b8d3ad37c4329f91e0f781460e388f4..00b4ef70107063205f9d4d188f13a52b4f2d5108 100644 (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
 // 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)
                }
        }
index 0df52e851c9de4b6f02726325ad1698fb73c060e..85c897a9097e3db9f081a11741effa6c4a33ce98 100644 (file)
@@ -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,
+       }
+}
index d3206988b54b69d41fd525e6ba9beec487b3e8ba..6f02e2fc969b084f4c86496ff8e9cc0999be2f6c 100644 (file)
@@ -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)
                }
index eb2b17dd4debd1f7043cf385a1044f131e88691a..93ed620449e187c388d88928cccd698be11a9795 100644 (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
        // 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
index 8ed55f1dbf9c28d12ecfa3bfea2534e8cbafa61d..82297ff17f11ea035da3b572cade6c44a79c4081 100644 (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}}
 }
 
+// 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) {
index 74436836cd86c2823207b48b26d47bb4f84d2234..66de249044067e9bad5c28170a6c384380da7486 100644 (file)
@@ -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 {
index 9b1482b14e1b075471e65765f943093e4af8bfd4..018a20cfb2dc69722ce32b9e15aa3985d6538205 100644 (file)
@@ -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())
index 03aef90fe132358cb7cd6b5d5c7a42d7f839e31e..4e654e074fcceb6aa0caaed7159f3299b37d90ef 100644 (file)
@@ -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
                }
index ade0a79b31d11daf4fab28c7094d15b8175f22b7..2f1814a6319773d9dc345fa1f365c3e62106c784 100644 (file)
@@ -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") }
index c8c4cca0a781536697229bde023735098ae0b0d0..a7edc5ac03d78aff4a7f2211df7685948e842788 100644 (file)
@@ -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
index daa039bf92a0b2cbe37d3f36c4a0c7d76785d1b3..3cb162764c815e6610bbfab6b7d7ecea67955ff2 100644 (file)
@@ -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},
index e9ffd4f5ca9c681691883d21b2ed9ff56fcaf46a..ab6643212641ef7fbe0fa686e446a43cacda8990 100644 (file)
@@ -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)
                }
index 35ca197d64cdbdcf5e54c8a68656a3abc5a0c928..dd8dd74161e43e715a34943ed56378d8bb30f1e5 100644 (file)
@@ -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
index 92f35f1279c7f164c707bad8bfbf65a6cea77714..604520d27f80253eaf93e107d56e07984fc6df2b 100644 (file)
@@ -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 }
index 28583b62d9a8cb479caf32f18abf390fd765fa1b..07ed510d1188ad09f1d83a9b57d9e06882d5ecd3 100644 (file)
@@ -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:
index b27b2a00df024c3636e7e8fb6c98bbf8e4bfacd4..583bb464b2036d443aa9e0676065fdb13a390a85 100644 (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.
        // (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
        }