aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/types2/subst.go
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2020-10-19 15:28:22 -0700
committerRobert Griesemer <gri@golang.org>2020-10-21 00:51:12 +0000
commitca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b (patch)
treec061b8a1ffa5064e361e9ba58ea6693fab6ab0e0 /src/cmd/compile/internal/types2/subst.go
parent6ff16fe3ee46f8e35c18226d04bd38a396eb4175 (diff)
downloadgo-ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b.tar.gz
go-ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b.zip
[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 <gri@golang.org> Trust: Keith Randall <khr@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> Run-TryBot: Keith Randall <khr@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Diffstat (limited to 'src/cmd/compile/internal/types2/subst.go')
-rw-r--r--src/cmd/compile/internal/types2/subst.go554
1 files changed, 554 insertions, 0 deletions
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 = &copy
+ }
+ // 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 &copy
+ }
+ }
+ 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 &copy
+ }
+ }
+ 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
+}