diff --git a/src/go/types/README b/src/go/types/README index 75a12a8058..92a3646b4f 100644 --- a/src/go/types/README +++ b/src/go/types/README @@ -4,6 +4,9 @@ presented by Ian Taylor at GopherCon 2019. NOTE: THIS IS A PROTOTYPE. NOT EVERYTHING IS IMPLEMENTED. THERE ARE BUGS. +The code is not very well tested, some parts were hacked up quickly, +and no code review has happened. Read and use the code at your own risk. + Specifically, the following pieces are missing from type-checking or lead to unexpected behavior: @@ -19,9 +22,6 @@ The following is "working" (as in passes simple tests): - Declaration and use (calls) of parameterized functions without contracts, including type inference from function arguments. -The changes/CLs of this protoype should not be considered exemplary or -final code. This is a prototype after all. See the disclaimer above. - To play with this prototype: - Cherry-pick this CL on top of tip: diff --git a/src/go/types/examples/functions.go2 b/src/go/types/examples/functions.go2 new file mode 100644 index 0000000000..11b4deee97 --- /dev/null +++ b/src/go/types/examples/functions.go2 @@ -0,0 +1,64 @@ +// Copyright 2019 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 shows some examples of type-parameterized functions. + +package p + +// Reverse is a function that takes a []T argument and +// reverses that slice in place. +func Reverse (type T) (list []T) { + i := 0 + j := len(list)-1 + for i < j { + list[i], list[j] = list[j], list[i] + } +} + +func _() { + // Reverse can be called with an explicit type argument + Reverse(int)(nil) + Reverse(string)([]string{"foo", "bar"}) + Reverse(struct{x, y int})([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // Since the type parameter is used for an incoming argument, + // it can be inferred from the provided argument's type. + Reverse([]string{"foo", "bar"}) + Reverse([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // But the incoming argument must have a type, even if it's a + // default type. An untyped nil won't work. + // Reverse(nil) // this won't type-check + + // A typed nil will work, though. + Reverse([]int(nil)) +} + +// Certain functions, such as the built-in `new` could be written using +// type parameters. +func new (type T) () *T { + var x T + return &x +} + +// When calling our own new, we need to pass the type parameter +// explicitly since there is no (value) argument from which the +// result type could be inferred. We doen't try to infer the +// result type from the assignment to keep things simple and +// easy to understand. +var _ = new(int)() +var _ *float64 = new(float64)() // the result type is indeed *float64 + +// A function may have multiple type parameters, of course. +func foo (type A, B, C) (a A, b []B, c *C) B { + // do something here + return b[0] +} + +// As before, we can pass type parameters explicitly. +var s = foo(int, string, float64)(1, []string{"first"}, new(float64)()) + +// Or we can use type inference. +var _ float64 = foo(42, []float64{1.0}, &s) + diff --git a/src/go/types/examples/types.go2 b/src/go/types/examples/types.go2 new file mode 100644 index 0000000000..cc00e10def --- /dev/null +++ b/src/go/types/examples/types.go2 @@ -0,0 +1,37 @@ +// Copyright 2019 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 shows some examples of type-parameterized types. + +package p + +// List is just what it says - a slice of E elements. +type List(type E) []E + +// A parameterized (generic) type must always be instantiated +// before it can be used to designate the type of a variable +// (including a struct field, or function parameter); though +// for the latter cases, the provided type may be another type +// parameter. So: +var _ List(byte) = []byte{} + +// A generic binary tree might be declared as follows. +type Tree(type E) struct { + left, right *Tree + payload E +} + +// A simple instantiation of Tree: +var root1 Tree(int) + +// The actual type parameter provided may be a parameterized +// type itself: +var root2 Tree(List(int)) + +// A couple of more complex examples. +// Here, we need extra parentheses around the element type of the slices on the right +// to resolve the parsing ambiguity between the conversion []List(int) and the slice +// type with a parameterized elements type [](List(int)). +var _ List(List(int)) = [](List(int)){} +var _ List(List(List(Tree(int)))) = [](List(List(Tree(int)))){} \ No newline at end of file diff --git a/src/go/types/operand.go b/src/go/types/operand.go index 80d11e2f21..05ac8ad838 100644 --- a/src/go/types/operand.go +++ b/src/go/types/operand.go @@ -257,6 +257,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) bool { // x's type V and T have identical underlying types // and at least one of V or T is not a named type + //check.dump("Vu = %s, Tu = %s, identical = %v", Vu, Tu, Identical(Vu, Tu)) if check.identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { return true } diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 3ac1ad3a30..a066edf9af 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -15,7 +15,7 @@ import ( // inst returns the instantiated type of tname. func (check *Checker) inst(tname *TypeName, targs []Type) (res Type) { if check.conf.Trace { - check.trace(tname.pos, "-- instantiating %s", tname) + check.trace(tname.pos, "-- instantiating %s with %s", tname, typeListString(targs)) check.indent++ defer func() { check.indent-- @@ -52,7 +52,7 @@ type subster struct { func (s *subster) typ(typ Type) (res Type) { // avoid repeating the same substitution for a given type // TODO(gri) is this correct in the presence of cycles? - if typ, hit := s.cache[typ]; hit { + if typ, found := s.cache[typ]; found { return typ } defer func() { @@ -121,33 +121,31 @@ func (s *subster) typ(typ Type) (res Type) { } case *Named: - underlying := s.typ(t.underlying).Underlying() - if underlying != t.underlying { - // create a new named type - for now use printed type in name - // TODO(gri) consider type map to map types to indices (on the other hand, a type string seems just as good) - // TODO(gri) review name creation and factor out - name := TypeString(t, nil) + "<" + typeListString(s.targs) + ">" - //s.check.dump("NAME = %s", name) - tname, found := s.check.typMap[name] - if !found { - // instantiate custom methods as necessary - var methods []*Func - for _, m := range t.methods { - //sig := s.typ(m.typ).(*Signature) - sig := s.check.subst(m.typ, m.tparams, s.targs).(*Signature) - m1 := NewFunc(m.pos, m.pkg, m.name, sig) - //s.check.dump("%s: method %s => %s", name, m, m1) - methods = append(methods, m1) - } - // TODO(gri) what is the correct position to use here? - tname = NewTypeName(t.obj.pos, s.check.pkg, name, nil) - //s.check.dump("name = %s", name) - NewNamed(tname, underlying, methods) - s.check.typMap[name] = tname - // TODO(gri) update the method receivers? - } + // TODO(gri) revisit name creation (function local types, etc.) and factor out + name := TypeString(t, nil) + "<" + typeListString(s.targs) + ">" + //s.check.dump("- %s => %s", t, name) + if tname, found := s.check.typMap[name]; found { + //s.check.dump("- found %s", tname) return tname.typ } + // create a new named type and populate caches to avoid endless recursion + // TODO(gri) should use actual instantiation position + tname := NewTypeName(t.obj.pos, s.check.pkg, name, nil) + s.check.typMap[name] = tname + named := NewNamed(tname, nil, nil) + s.cache[t] = named + //s.check.dump("- installed %s", tname) + named.underlying = s.typ(t.underlying).Underlying() + //s.check.dump("- finished %s", tname) + // instantiate custom methods as necessary + for _, m := range t.methods { + sig := s.check.subst(m.typ, m.tparams, s.targs).(*Signature) + m1 := NewFunc(m.pos, m.pkg, m.name, sig) + //s.check.dump("%s: method %s => %s", name, m, m1) + named.methods = append(named.methods, m1) + } + // TODO(gri) update the method receivers? + return named case *Parameterized: // first, instantiate any arguments if necessary diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index afd7803259..fc89172134 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -6,9 +6,6 @@ package p type List(type E) []E -func (l List(E)) First() E { - if len(l) == 0 { - panic("no first") - } - return l[0] -} +var _ List(List(int)) = [](List(int)){} + +//var _ List(List(List(int))) = [](List(List(int))){} \ No newline at end of file