From ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 19 Oct 2020 15:28:22 -0700 Subject: [dev.typeparams] cmd/compile/internal/importer, types2: initial check-in of types2 and importer This is a copy of the importer and types2 (unreviewed) prototype version excluding the testdata directory containing tests (see below). Each file is marked with the comment // UNREVIEWED on the first line. The plan is to check in this code wholesale (it runs and passes all tests) and then review the code file-by-file via subsequent CLs and remove the "// UNREVIEWED" comments as we review the files. Since most tests are unchanged from the original go/types, the next CL will commit those tests as they don't need to be reviewed again. (Eventually we may want to factor them out and share them from a single place, e.g. the test directory.) The existing file fmtmap_test.go was updated. Change-Id: I9bd0ad1a7e7188b501423483a44d18e623c0fe71 Reviewed-on: https://go-review.googlesource.com/c/go/+/263624 Trust: Robert Griesemer Trust: Keith Randall Run-TryBot: Robert Griesemer Run-TryBot: Keith Randall TryBot-Result: Go Bot Reviewed-by: Keith Randall Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/types2/subst.go | 554 +++++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 src/cmd/compile/internal/types2/subst.go (limited to 'src/cmd/compile/internal/types2/subst.go') diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go new file mode 100644 index 0000000000..27c907de10 --- /dev/null +++ b/src/cmd/compile/internal/types2/subst.go @@ -0,0 +1,554 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" +) + +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 syntax.Pos, typ Type, targs []Type, poslist []syntax.Pos) (res Type) { + if check.conf.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(poslist == nil || 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). + if res == nil { + 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(pos, "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(nopos, iface) + if len(iface.allMethods) > 0 { + // If the type argument is a type parameter itself, its pointer designation + // must match the pointer designation of the callee's type parameter. + // 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 tparg := targ.TypeParam(); tparg != nil { + if tparg.ptr != tpar.ptr { + check.errorf(pos, "pointer designation mismatch") + break + } + } else if base, isPtr := deref(targ); isPtr && base.TypeParam() != nil { + check.errorf(pos, "%s has no methods", targ) + break + } + // If a type parameter is marked as a pointer type, the type bound applies + // to a pointer of the type argument. + actual := targ + if tpar.ptr { + actual = NewPointer(targ) + } + if m, wrong := check.missingMethod(actual, iface, true); m != nil { + // TODO(gri) needs to print updated name to avoid major confusion in error message! + // (print warning for now) + // 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(pos, "%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. + check.softErrorf(pos, + "%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s", + targ, tpar.bound, wrong, m, + ) + } else { + check.softErrorf(pos, "%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 + } + // iface.allTypes != nil + + // 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 := targ.TypeParam(); targ != nil { + targBound := targ.Bound() + if targBound.allTypes == nil { + check.softErrorf(pos, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) + break + } + for _, t := range unpack(targBound.allTypes) { + if !iface.isSatisfiedBy(t.Under()) { + // TODO(gri) match this error message with the one below (or vice versa) + check.softErrorf(pos, "%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(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ.Under(), 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 syntax.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 syntax.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, + 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(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 subst.check.conf.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 new_targs []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) + new_targ := subst.typ(targ) + 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)) + copy(new_targs, t.targs) + } + new_targs[i] = new_targ + } + } + + if new_targs == nil { + dump(">>> nothing to substitute in %s", t) + return t // nothing to substitute + } + + } else { + + // not yet instantiated + dump(">>> first instantiation of %s", t) + new_targs = subst.smap.targs + + } + + // before creating a new named type, check if we have this one already + h := instantiatedHash(t, new_targs) + 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 = new_targs + subst.check.typMap[h] = named + subst.cache[t] = named + + // do the substitution + dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs) + 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: + 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 +} -- cgit v1.2.3-54-g00ecf