| // 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 types2 |
| |
| import ( |
| "cmd/compile/internal/syntax" |
| "errors" |
| "fmt" |
| ) |
| |
| // An Environment is an opaque type checking environment. It may be used to |
| // share identical type instances across type checked packages or calls to |
| // Instantiate. |
| type Environment struct { |
| // For now, Environment just hides a Checker. |
| // Eventually, we strive to remove the need for a checker. |
| check *Checker |
| } |
| |
| // NewEnvironment returns a new Environment, initialized with the given |
| // Checker, or nil. |
| func NewEnvironment(check *Checker) *Environment { |
| return &Environment{check} |
| } |
| |
| // Instantiate instantiates the type typ with the given type arguments targs. |
| // typ must be a *Named or a *Signature type, and its number of type parameters |
| // must match the number of provided type arguments. The result is a new, |
| // instantiated (not parameterized) type of the same kind (either a *Named or a |
| // *Signature). Any methods attached to a *Named are simply copied; they are |
| // not instantiated. |
| // |
| // If env is non-nil, it may be used to de-dupe the instance against previous |
| // instances with the same identity. This functionality is implemented for |
| // environments with non-nil Checkers. |
| // |
| // If verify is set and constraint satisfaction fails, the returned error may |
| // be of dynamic type ArgumentError indicating which type argument did not |
| // satisfy its corresponding type parameter constraint, and why. |
| // |
| // TODO(rfindley): change this function to also return an error if lengths of |
| // tparams and targs do not match. |
| func Instantiate(env *Environment, typ Type, targs []Type, validate bool) (Type, error) { |
| var check *Checker |
| if env != nil { |
| check = env.check |
| } |
| inst := check.instance(nopos, typ, targs) |
| |
| var err error |
| if validate { |
| var tparams []*TypeParam |
| switch t := typ.(type) { |
| case *Named: |
| tparams = t.TParams().list() |
| case *Signature: |
| tparams = t.TParams().list() |
| } |
| if i, err := check.verify(nopos, tparams, targs); err != nil { |
| return inst, ArgumentError{i, err} |
| } |
| } |
| |
| return inst, err |
| } |
| |
| // instantiate creates an instance and defers verification of constraints to |
| // later in the type checking pass. For Named types the resulting instance will |
| // be unexpanded. |
| func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, posList []syntax.Pos) (res Type) { |
| assert(check != nil) |
| if check.conf.Trace { |
| check.trace(pos, "-- instantiating %s with %s", typ, NewTypeList(targs)) |
| check.indent++ |
| defer func() { |
| check.indent-- |
| var under Type |
| if res != nil { |
| // Calling under() here may lead to endless instantiations. |
| // Test case: type T[P any] T[P] |
| // TODO(gri) investigate if that's a bug or to be expected. |
| under = safeUnderlying(res) |
| } |
| check.trace(pos, "=> %s (under = %s)", res, under) |
| }() |
| } |
| |
| inst := check.instance(pos, typ, targs) |
| |
| assert(len(posList) <= len(targs)) |
| check.later(func() { |
| // Collect tparams again because lazily loaded *Named types may not have |
| // had tparams set up above. |
| var tparams []*TypeParam |
| switch t := typ.(type) { |
| case *Named: |
| tparams = t.TParams().list() |
| case *Signature: |
| tparams = t.TParams().list() |
| } |
| // Avoid duplicate errors; instantiate will have complained if tparams |
| // and targs do not have the same length. |
| if len(tparams) == len(targs) { |
| if i, err := check.verify(pos, tparams, targs); err != nil { |
| // best position for error reporting |
| pos := pos |
| if i < len(posList) { |
| pos = posList[i] |
| } |
| check.softErrorf(pos, err.Error()) |
| } |
| } |
| }) |
| return inst |
| } |
| |
| // instance creates a type or function instance using the given original type |
| // typ and arguments targs. For Named types the resulting instance will be |
| // unexpanded. |
| func (check *Checker) instance(pos syntax.Pos, typ Type, targs []Type) Type { |
| switch t := typ.(type) { |
| case *Named: |
| h := instantiatedHash(t, targs) |
| if check != nil { |
| // typ may already have been instantiated with identical type arguments. |
| // In that case, re-use the existing instance. |
| if named := check.typMap[h]; named != nil { |
| return named |
| } |
| } |
| tname := NewTypeName(pos, t.obj.pkg, t.obj.name, nil) |
| named := check.newNamed(tname, t, nil, nil, nil) // methods and tparams are set when named is loaded |
| named.targs = NewTypeList(targs) |
| named.instPos = &pos |
| if check != nil { |
| check.typMap[h] = named |
| } |
| return named |
| |
| case *Signature: |
| tparams := t.TParams() |
| if !check.validateTArgLen(pos, tparams.Len(), len(targs)) { |
| return Typ[Invalid] |
| } |
| if tparams.Len() == 0 { |
| return typ // nothing to do (minor optimization) |
| } |
| sig := check.subst(pos, typ, makeSubstMap(tparams.list(), targs), nil).(*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 == t { |
| copy := *sig |
| sig = © |
| } |
| // After instantiating a generic signature, it is not generic |
| // anymore; we need to set tparams to nil. |
| sig.tparams = nil |
| return sig |
| } |
| |
| // only types and functions can be generic |
| panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ)) |
| } |
| |
| // 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 syntax.Pos, ntparams, ntargs int) bool { |
| if ntargs != ntparams { |
| // TODO(gri) provide better error message |
| if check != nil { |
| check.errorf(pos, "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 syntax.Pos, tparams []*TypeParam, targs []Type) (int, error) { |
| smap := makeSubstMap(tparams, targs) |
| for i, tpar := range tparams { |
| // stop checking bounds after the first failure |
| if err := check.satisfies(pos, targs[i], tpar, smap); err != nil { |
| return i, err |
| } |
| } |
| return -1, nil |
| } |
| |
| // satisfies reports whether the type argument targ satisfies the constraint of type parameter |
| // parameter tpar (after any of its type parameters have been substituted through smap). |
| // A suitable error is reported if the result is false. |
| // TODO(gri) This should be a method of interfaces or type sets. |
| func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap substMap) error { |
| iface := tpar.iface() |
| if iface.Empty() { |
| return nil // no type bound |
| } |
| |
| // TODO(rfindley): it would be great if users could pass in a qualifier here, |
| // rather than falling back to verbose qualification. Maybe this can be part |
| // of a the shared environment. |
| var qf Qualifier |
| if check != nil { |
| qf = check.qualifier |
| } |
| errorf := func(format string, args ...interface{}) error { |
| return errors.New(sprintf(qf, format, args...)) |
| } |
| |
| // 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 instantiate |
| // the parameterized type. |
| iface = check.subst(pos, iface, smap, nil).(*Interface) |
| |
| // if iface is comparable, targ must be comparable |
| // TODO(gri) the error messages needs to be better, here |
| if iface.IsComparable() && !Comparable(targ) { |
| if tpar := asTypeParam(targ); tpar != nil && tpar.iface().typeSet().IsAll() { |
| return errorf("%s has no constraints", targ) |
| } |
| return errorf("%s does not satisfy comparable", targ) |
| } |
| |
| // targ must implement iface (methods) |
| // - check only if we have methods |
| if iface.NumMethods() > 0 { |
| // If the type argument is a pointer to a type parameter, the type argument's |
| // method set is empty. |
| // TODO(gri) is this what we want? (spec question) |
| if base, isPtr := deref(targ); isPtr && asTypeParam(base) != nil { |
| return errorf("%s has no methods", targ) |
| } |
| if m, wrong := check.missingMethod(targ, iface, true); m != nil { |
| // TODO(gri) needs to print updated name to avoid major confusion in error message! |
| // (print warning for now) |
| // Old warning: |
| // check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m) |
| if wrong != nil { |
| // TODO(gri) This can still report uninstantiated types which makes the error message |
| // more difficult to read then necessary. |
| return errorf("%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s", |
| targ, tpar.bound, wrong, m, |
| ) |
| } |
| return errorf("%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name) |
| } |
| } |
| |
| // targ's underlying type must also be one of the interface types listed, if any |
| if !iface.typeSet().hasTerms() { |
| return nil // nothing to do |
| } |
| |
| // If targ is itself a type parameter, each of its possible types, but at least one, must be in the |
| // list of iface types (i.e., the targ type list must be a non-empty subset of the iface types). |
| if targ := asTypeParam(targ); targ != nil { |
| targBound := targ.iface() |
| if !targBound.typeSet().hasTerms() { |
| return errorf("%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) |
| } |
| if !targBound.typeSet().subsetOf(iface.typeSet()) { |
| // TODO(gri) need better error message |
| return errorf("%s does not satisfy %s", targ, tpar.bound) |
| } |
| return nil |
| } |
| |
| // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any. |
| if !iface.typeSet().includes(targ) { |
| // TODO(gri) better error message |
| return errorf("%s does not satisfy %s", targ, tpar.bound) |
| } |
| |
| return nil |
| } |