go/types: add examples directory

Also: fix substitution for cyclic types

Change-Id: I2d4eca6846c1ac9a2b4d0278246228e1f61aea08
This commit is contained in:
Robert Griesemer 2019-07-23 22:49:44 -07:00
parent 025f8ad924
commit 0f7748af30
6 changed files with 133 additions and 36 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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)))){}

View File

@ -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
}

View File

@ -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

View File

@ -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))){}