// Copyright 2018 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 actual // types. package types import ( "bytes" "fmt" "go/token" ) // TODO(rFindley) decide error codes for the errors in this file, and check // if error spans can be improved type substMap struct { // The targs field is currently needed for *Named type substitution. // TODO(gri) rewrite that code, get rid of this field, and make this // struct just the map (proj) targs []Type proj map[*_TypeParam]Type } // makeSubstMap creates a new substitution map mapping tpars[i] to targs[i]. // If targs[i] is nil, tpars[i] is not substituted. func makeSubstMap(tpars []*TypeName, targs []Type) *substMap { assert(len(tpars) == len(targs)) proj := make(map[*_TypeParam]Type, len(tpars)) for i, tpar := range tpars { // We must expand type arguments otherwise *instance // types end up as components in composite types. // TODO(gri) explain why this causes problems, if it does targ := expand(targs[i]) // possibly nil targs[i] = targ proj[tpar.typ.(*_TypeParam)] = targ } return &substMap{targs, proj} } func (m *substMap) String() string { return fmt.Sprintf("%s", m.proj) } func (m *substMap) empty() bool { return len(m.proj) == 0 } func (m *substMap) lookup(tpar *_TypeParam) Type { if t := m.proj[tpar]; t != nil { return t } return tpar } func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist []token.Pos) (res Type) { if trace { check.trace(pos, "-- instantiating %s with %s", typ, typeListString(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 = res.Underlying() } check.trace(pos, "=> %s (under = %s)", res, under) }() } assert(len(poslist) <= len(targs)) // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? var tparams []*TypeName switch t := typ.(type) { case *Named: tparams = t.tparams case *Signature: tparams = t.tparams defer func() { // If we had an unexpected failure somewhere don't panic below when // asserting res.(*Signature). Check for *Signature in case Typ[Invalid] // is returned. if _, ok := res.(*Signature); !ok { return } // 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 t == res { copy := *t res = © } // After instantiating a generic signature, it is not generic // anymore; we need to set tparams to nil. res.(*Signature).tparams = nil }() default: check.dump("%v: cannot instantiate %v", pos, typ) unreachable() // only defined types and (defined) functions can be generic } // the number of supplied types must match the number of type parameters if len(targs) != len(tparams) { // TODO(gri) provide better error message check.errorf(atPos(pos), _Todo, "got %d arguments but %d type parameters", len(targs), len(tparams)) return Typ[Invalid] } if len(tparams) == 0 { return typ // nothing to do (minor optimization) } smap := makeSubstMap(tparams, targs) // check bounds for i, tname := range tparams { tpar := tname.typ.(*_TypeParam) iface := tpar.Bound() if iface.Empty() { continue // no type bound } targ := targs[i] // best position for error reporting pos := pos if i < len(poslist) { pos = poslist[i] } // 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).(*Interface) // targ must implement iface (methods) // - check only if we have methods check.completeInterface(token.NoPos, iface) if len(iface.allMethods) > 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 { check.errorf(atPos(pos), 0, "%s has no methods", targ) break } 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 m.name == "==" { // We don't want to report "missing method ==". check.softErrorf(atPos(pos), 0, "%s does not satisfy comparable", targ) } else if wrong != nil { // TODO(gri) This can still report uninstantiated types which makes the error message // more difficult to read then necessary. // TODO(rFindley) should this use parentheses rather than ':' for qualification? check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s", targ, tpar.bound, wrong, m, ) } else { check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name) } break } } // targ's underlying type must also be one of the interface types listed, if any if iface.allTypes == nil { continue // 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.Bound() if targBound.allTypes == nil { check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) break } for _, t := range unpackType(targBound.allTypes) { if !iface.isSatisfiedBy(t) { // TODO(gri) match this error message with the one below (or vice versa) check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, t, iface.allTypes) break } } break } // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any. if !iface.isSatisfiedBy(targ) { check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s or %s not found in %s)", targ, tpar.bound, targ, under(targ), iface.allTypes) break } } return check.subst(pos, typ, smap) } // subst returns the type typ with its type parameters tpars replaced by // the corresponding type arguments targs, recursively. // subst is functional in the sense that it doesn't modify the incoming // type. If a substitution took place, the result type is different from // from the incoming type. func (check *Checker) subst(pos token.Pos, typ Type, smap *substMap) Type { if smap.empty() { return typ } // common cases switch t := typ.(type) { case *Basic: return typ // nothing to do case *_TypeParam: return smap.lookup(t) } // general case subst := subster{check, pos, make(map[Type]Type), smap} return subst.typ(typ) } type subster struct { check *Checker pos token.Pos cache map[Type]Type smap *substMap } func (subst *subster) typ(typ Type) Type { switch t := typ.(type) { case nil: // Call typOrNil if it's possible that typ is nil. panic("nil typ") case *Basic, *bottom, *top: // nothing to do case *Array: elem := subst.typOrNil(t.elem) if elem != t.elem { return &Array{len: t.len, elem: elem} } case *Slice: elem := subst.typOrNil(t.elem) if elem != t.elem { return &Slice{elem: elem} } case *Struct: if fields, copied := subst.varList(t.fields); copied { return &Struct{fields: fields, tags: t.tags} } case *Pointer: base := subst.typ(t.base) if base != t.base { return &Pointer{base: base} } case *Tuple: return subst.tuple(t) case *Signature: // TODO(gri) rethink the recv situation with respect to methods on parameterized types // recv := subst.var_(t.recv) // TODO(gri) this causes a stack overflow - explain recv := t.recv params := subst.tuple(t.params) results := subst.tuple(t.results) if recv != t.recv || params != t.params || results != t.results { return &Signature{ rparams: t.rparams, // TODO(rFindley) why can't we nil out tparams here, rather than in // instantiate above? tparams: t.tparams, scope: t.scope, recv: recv, params: params, results: results, variadic: t.variadic, } } case *_Sum: types, copied := subst.typeList(t.types) if copied { // Don't do it manually, with a Sum literal: the new // types list may not be unique and NewSum may remove // duplicates. return _NewSum(types) } case *Interface: methods, mcopied := subst.funcList(t.methods) types := t.types if t.types != nil { types = subst.typ(t.types) } embeddeds, ecopied := subst.typeList(t.embeddeds) if mcopied || types != t.types || ecopied { iface := &Interface{methods: methods, types: types, embeddeds: embeddeds} subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement subst.check.completeInterface(token.NoPos, iface) return iface } case *Map: key := subst.typ(t.key) elem := subst.typ(t.elem) if key != t.key || elem != t.elem { return &Map{key: key, elem: elem} } case *Chan: elem := subst.typ(t.elem) if elem != t.elem { return &Chan{dir: t.dir, elem: elem} } case *Named: subst.check.indent++ defer func() { subst.check.indent-- }() dump := func(format string, args ...interface{}) { if trace { subst.check.trace(subst.pos, format, args...) } } if t.tparams == nil { dump(">>> %s is not parameterized", t) return t // type is not parameterized } var newTargs []Type if len(t.targs) > 0 { // already instantiated dump(">>> %s already instantiated", t) 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. for i, targ := range t.targs { dump(">>> %d targ = %s", i, targ) newTarg := subst.typ(targ) if newTarg != targ { dump(">>> substituted %d targ %s => %s", i, targ, newTarg) if newTargs == nil { newTargs = make([]Type, len(t.tparams)) copy(newTargs, t.targs) } newTargs[i] = newTarg } } if newTargs == nil { dump(">>> nothing to substitute in %s", t) return t // nothing to substitute } } else { // not yet instantiated dump(">>> first instantiation of %s", t) // TODO(rFindley) can we instead subst the tparam types here? newTargs = subst.smap.targs } // before creating a new named type, check if we have this one already h := instantiatedHash(t, newTargs) dump(">>> new type hash: %s", h) if named, found := subst.check.typMap[h]; found { dump(">>> found %s", named) subst.cache[t] = named return named } // 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.underlying, t.methods) // method signatures are updated lazily named.tparams = t.tparams // new type is still parameterized named.targs = newTargs subst.check.typMap[h] = named subst.cache[t] = named // do the substitution dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, newTargs) named.underlying = subst.typOrNil(t.underlying) named.orig = named.underlying // for cycle detection (Checker.validType) return named case *_TypeParam: return subst.smap.lookup(t) case *instance: // TODO(gri) can we avoid the expansion here and just substitute the type parameters? return subst.typ(t.expand()) default: panic("unimplemented") } return typ } // TODO(gri) Eventually, this should be more sophisticated. // It won't work correctly for locally declared types. func instantiatedHash(typ *Named, targs []Type) string { var buf bytes.Buffer writeTypeName(&buf, typ.obj, nil) buf.WriteByte('[') writeTypeList(&buf, targs, nil, nil) buf.WriteByte(']') // With respect to the represented type, whether a // type is fully expanded or stored as instance // does not matter - they are the same types. // Remove the instanceMarkers printed for instances. res := buf.Bytes() i := 0 for _, b := range res { if b != instanceMarker { res[i] = b i++ } } return string(res[:i]) } func typeListString(list []Type) string { var buf bytes.Buffer writeTypeList(&buf, list, nil, nil) return buf.String() } // typOrNil is like typ but if the argument is nil it is replaced with Typ[Invalid]. // A nil type may appear in pathological cases such as type T[P any] []func(_ T([]_)) // where an array/slice element is accessed before it is set up. func (subst *subster) typOrNil(typ Type) Type { if typ == nil { return Typ[Invalid] } return subst.typ(typ) } func (subst *subster) var_(v *Var) *Var { if v != nil { if typ := subst.typ(v.typ); typ != v.typ { copy := *v copy.typ = typ return © } } return v } func (subst *subster) tuple(t *Tuple) *Tuple { if t != nil { if vars, copied := subst.varList(t.vars); copied { return &Tuple{vars: vars} } } return t } func (subst *subster) varList(in []*Var) (out []*Var, copied bool) { out = in for i, v := range in { if w := subst.var_(v); w != v { if !copied { // first variable that got substituted => allocate new out slice // and copy all variables new := make([]*Var, len(in)) copy(new, out) out = new copied = true } out[i] = w } } return } func (subst *subster) func_(f *Func) *Func { if f != nil { if typ := subst.typ(f.typ); typ != f.typ { copy := *f copy.typ = typ return © } } return f } func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) { out = in for i, f := range in { if g := subst.func_(f); g != f { if !copied { // first function that got substituted => allocate new out slice // and copy all functions new := make([]*Func, len(in)) copy(new, out) out = new copied = true } out[i] = g } } return } func (subst *subster) typeList(in []Type) (out []Type, copied bool) { out = in for i, t := range in { if u := subst.typ(t); u != t { if !copied { // first function that got substituted => allocate new out slice // and copy all functions new := make([]Type, len(in)) copy(new, out) out = new copied = true } out[i] = u } } return }