| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file implements instantiation of generic types |
| // through substitution of type parameters by type arguments. |
| |
| package types |
| |
| import ( |
| "errors" |
| "fmt" |
| "go/token" |
| . "internal/types/errors" |
| ) |
| |
| // Instantiate instantiates the type orig with the given type arguments targs. |
| // orig must be a *Named or a *Signature type. If there is no error, the |
| // resulting Type is an instantiated type of the same kind (either a *Named or |
| // a *Signature). Methods attached to a *Named type are also instantiated, and |
| // associated with a new *Func that has the same position as the original |
| // method, but nil function scope. |
| // |
| // If ctxt is non-nil, it may be used to de-duplicate the instance against |
| // previous instances with the same identity. As a special case, generic |
| // *Signature origin types are only considered identical if they are pointer |
| // equivalent, so that instantiating distinct (but possibly identical) |
| // signatures will yield different instances. The use of a shared context does |
| // not guarantee that identical instances are deduplicated in all cases. |
| // |
| // If validate is set, Instantiate verifies that the number of type arguments |
| // and parameters match, and that the type arguments satisfy their |
| // corresponding type constraints. If verification fails, the resulting error |
| // may wrap an *ArgumentError indicating which type argument did not satisfy |
| // its corresponding type parameter constraint, and why. |
| // |
| // If validate is not set, Instantiate does not verify the type argument count |
| // or whether the type arguments satisfy their constraints. Instantiate is |
| // guaranteed to not return an error, but may panic. Specifically, for |
| // *Signature types, Instantiate will panic immediately if the type argument |
| // count is incorrect; for *Named types, a panic may occur later inside the |
| // *Named API. |
| func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) { |
| if ctxt == nil { |
| ctxt = NewContext() |
| } |
| if validate { |
| var tparams []*TypeParam |
| switch t := orig.(type) { |
| case *Named: |
| tparams = t.TypeParams().list() |
| case *Signature: |
| tparams = t.TypeParams().list() |
| } |
| if len(targs) != len(tparams) { |
| return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams)) |
| } |
| if i, err := (*Checker)(nil).verify(token.NoPos, tparams, targs, ctxt); err != nil { |
| return nil, &ArgumentError{i, err} |
| } |
| } |
| |
| inst := (*Checker)(nil).instance(token.NoPos, orig, targs, nil, ctxt) |
| return inst, nil |
| } |
| |
| // instance instantiates the given original (generic) function or type with the |
| // provided type arguments and returns the resulting instance. If an identical |
| // instance exists already in the given contexts, it returns that instance, |
| // otherwise it creates a new one. |
| // |
| // If expanding is non-nil, it is the Named instance type currently being |
| // expanded. If ctxt is non-nil, it is the context associated with the current |
| // type-checking pass or call to Instantiate. At least one of expanding or ctxt |
| // must be non-nil. |
| // |
| // For Named types the resulting instance may be unexpanded. |
| func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, expanding *Named, ctxt *Context) (res Type) { |
| // The order of the contexts below matters: we always prefer instances in the |
| // expanding instance context in order to preserve reference cycles. |
| // |
| // Invariant: if expanding != nil, the returned instance will be the instance |
| // recorded in expanding.inst.ctxt. |
| var ctxts []*Context |
| if expanding != nil { |
| ctxts = append(ctxts, expanding.inst.ctxt) |
| } |
| if ctxt != nil { |
| ctxts = append(ctxts, ctxt) |
| } |
| assert(len(ctxts) > 0) |
| |
| // Compute all hashes; hashes may differ across contexts due to different |
| // unique IDs for Named types within the hasher. |
| hashes := make([]string, len(ctxts)) |
| for i, ctxt := range ctxts { |
| hashes[i] = ctxt.instanceHash(orig, targs) |
| } |
| |
| // If local is non-nil, updateContexts return the type recorded in |
| // local. |
| updateContexts := func(res Type) Type { |
| for i := len(ctxts) - 1; i >= 0; i-- { |
| res = ctxts[i].update(hashes[i], orig, targs, res) |
| } |
| return res |
| } |
| |
| // typ may already have been instantiated with identical type arguments. In |
| // that case, re-use the existing instance. |
| for i, ctxt := range ctxts { |
| if inst := ctxt.lookup(hashes[i], orig, targs); inst != nil { |
| return updateContexts(inst) |
| } |
| } |
| |
| switch orig := orig.(type) { |
| case *Named: |
| res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily |
| |
| case *Signature: |
| assert(expanding == nil) // function instances cannot be reached from Named types |
| |
| tparams := orig.TypeParams() |
| if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { |
| return Typ[Invalid] |
| } |
| if tparams.Len() == 0 { |
| return orig // nothing to do (minor optimization) |
| } |
| sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, ctxt).(*Signature) |
| // If the signature doesn't use its type parameters, subst |
| // will not make a copy. In that case, make a copy now (so |
| // we can set tparams to nil w/o causing side-effects). |
| if sig == orig { |
| copy := *sig |
| sig = © |
| } |
| // After instantiating a generic signature, it is not generic |
| // anymore; we need to set tparams to nil. |
| sig.tparams = nil |
| res = sig |
| |
| default: |
| // only types and functions can be generic |
| panic(fmt.Sprintf("%v: cannot instantiate %v", pos, orig)) |
| } |
| |
| // Update all contexts; it's possible that we've lost a race. |
| return updateContexts(res) |
| } |
| |
| // validateTArgLen verifies that the length of targs and tparams matches, |
| // reporting an error if not. If validation fails and check is nil, |
| // validateTArgLen panics. |
| func (check *Checker) validateTArgLen(pos token.Pos, ntparams, ntargs int) bool { |
| if ntargs != ntparams { |
| // TODO(gri) provide better error message |
| if check != nil { |
| check.errorf(atPos(pos), WrongTypeArgCount, "got %d arguments but %d type parameters", ntargs, ntparams) |
| return false |
| } |
| panic(fmt.Sprintf("%v: got %d arguments but %d type parameters", pos, ntargs, ntparams)) |
| } |
| return true |
| } |
| |
| func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) { |
| smap := makeSubstMap(tparams, targs) |
| for i, tpar := range tparams { |
| // Ensure that we have a (possibly implicit) interface as type bound (issue #51048). |
| tpar.iface() |
| // The type parameter bound is parameterized with the same type parameters |
| // as the instantiated type; before we can use it for bounds checking we |
| // need to instantiate it with the type arguments with which we instantiated |
| // the parameterized type. |
| bound := check.subst(pos, tpar.bound, smap, nil, ctxt) |
| var cause string |
| if !check.implements(targs[i], bound, true, &cause) { |
| return i, errors.New(cause) |
| } |
| } |
| return -1, nil |
| } |
| |
| // implements checks if V implements T. The receiver may be nil if implements |
| // is called through an exported API call such as AssignableTo. If constraint |
| // is set, T is a type constraint. |
| // |
| // If the provided cause is non-nil, it may be set to an error string |
| // explaining why V does not implement (or satisfy, for constraints) T. |
| func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool { |
| Vu := under(V) |
| Tu := under(T) |
| if Vu == Typ[Invalid] || Tu == Typ[Invalid] { |
| return true // avoid follow-on errors |
| } |
| if p, _ := Vu.(*Pointer); p != nil && under(p.base) == Typ[Invalid] { |
| return true // avoid follow-on errors (see issue #49541 for an example) |
| } |
| |
| verb := "implement" |
| if constraint { |
| verb = "satisfy" |
| } |
| |
| Ti, _ := Tu.(*Interface) |
| if Ti == nil { |
| if cause != nil { |
| var detail string |
| if isInterfacePtr(Tu) { |
| detail = check.sprintf("type %s is pointer to interface, not interface", T) |
| } else { |
| detail = check.sprintf("%s is not an interface", T) |
| } |
| *cause = check.sprintf("%s does not %s %s (%s)", V, verb, T, detail) |
| } |
| return false |
| } |
| |
| // Every type satisfies the empty interface. |
| if Ti.Empty() { |
| return true |
| } |
| // T is not the empty interface (i.e., the type set of T is restricted) |
| |
| // An interface V with an empty type set satisfies any interface. |
| // (The empty set is a subset of any set.) |
| Vi, _ := Vu.(*Interface) |
| if Vi != nil && Vi.typeSet().IsEmpty() { |
| return true |
| } |
| // type set of V is not empty |
| |
| // No type with non-empty type set satisfies the empty type set. |
| if Ti.typeSet().IsEmpty() { |
| if cause != nil { |
| *cause = check.sprintf("cannot %s %s (empty type set)", verb, T) |
| } |
| return false |
| } |
| |
| // V must implement T's methods, if any. |
| if m, wrong := check.missingMethod(V, Ti, true); m != nil /* !Implements(V, Ti) */ { |
| if cause != nil { |
| *cause = check.sprintf("%s does not %s %s %s", V, verb, T, check.missingMethodCause(V, T, m, wrong)) |
| } |
| return false |
| } |
| |
| // Only check comparability if we don't have a more specific error. |
| checkComparability := func() bool { |
| if !Ti.IsComparable() { |
| return true |
| } |
| // If T is comparable, V must be comparable. |
| // If V is strictly comparable, we're done. |
| if comparable(V, false /* strict comparability */, nil, nil) { |
| return true |
| } |
| // If check.conf.OldComparableSemantics is set (by the compiler or |
| // a test), we only consider strict comparability and we're done. |
| // TODO(gri) remove this check for Go 1.21 |
| if check != nil && check.conf.oldComparableSemantics { |
| if cause != nil { |
| *cause = check.sprintf("%s does not %s comparable", V, verb) |
| } |
| return false |
| } |
| // For constraint satisfaction, use dynamic (spec) comparability |
| // so that ordinary, non-type parameter interfaces implement comparable. |
| if constraint && comparable(V, true /* spec comparability */, nil, nil) { |
| // V is comparable if we are at Go 1.20 or higher. |
| if check == nil || check.allowVersion(check.pkg, 1, 20) { |
| return true |
| } |
| if cause != nil { |
| *cause = check.sprintf("%s to %s comparable requires go1.20 or later", V, verb) |
| } |
| return false |
| } |
| if cause != nil { |
| *cause = check.sprintf("%s does not %s comparable", V, verb) |
| } |
| return false |
| } |
| |
| // V must also be in the set of types of T, if any. |
| // Constraints with empty type sets were already excluded above. |
| if !Ti.typeSet().hasTerms() { |
| return checkComparability() // nothing to do |
| } |
| |
| // If V is itself an interface, each of its possible types must be in the set |
| // of T types (i.e., the V type set must be a subset of the T type set). |
| // Interfaces V with empty type sets were already excluded above. |
| if Vi != nil { |
| if !Vi.typeSet().subsetOf(Ti.typeSet()) { |
| // TODO(gri) report which type is missing |
| if cause != nil { |
| *cause = check.sprintf("%s does not %s %s", V, verb, T) |
| } |
| return false |
| } |
| return checkComparability() |
| } |
| |
| // Otherwise, V's type must be included in the iface type set. |
| var alt Type |
| if Ti.typeSet().is(func(t *term) bool { |
| if !t.includes(V) { |
| // If V ∉ t.typ but V ∈ ~t.typ then remember this type |
| // so we can suggest it as an alternative in the error |
| // message. |
| if alt == nil && !t.tilde && Identical(t.typ, under(t.typ)) { |
| tt := *t |
| tt.tilde = true |
| if tt.includes(V) { |
| alt = t.typ |
| } |
| } |
| return true |
| } |
| return false |
| }) { |
| if cause != nil { |
| var detail string |
| switch { |
| case alt != nil: |
| detail = check.sprintf("possibly missing ~ for %s in %s", alt, T) |
| case mentions(Ti, V): |
| detail = check.sprintf("%s mentions %s, but %s is not in the type set of %s", T, V, V, T) |
| default: |
| detail = check.sprintf("%s missing in %s", V, Ti.typeSet().terms) |
| } |
| *cause = check.sprintf("%s does not %s %s (%s)", V, verb, T, detail) |
| } |
| return false |
| } |
| |
| return checkComparability() |
| } |
| |
| // mentions reports whether type T "mentions" typ in an (embedded) element or term |
| // of T (whether typ is in the type set of T or not). For better error messages. |
| func mentions(T, typ Type) bool { |
| switch T := T.(type) { |
| case *Interface: |
| for _, e := range T.embeddeds { |
| if mentions(e, typ) { |
| return true |
| } |
| } |
| case *Union: |
| for _, t := range T.terms { |
| if mentions(t.typ, typ) { |
| return true |
| } |
| } |
| default: |
| if Identical(T, typ) { |
| return true |
| } |
| } |
| return false |
| } |