mirror of https://github.com/golang/go.git
go/types: add examples directory
Also: fix substitution for cyclic types Change-Id: I2d4eca6846c1ac9a2b4d0278246228e1f61aea08
This commit is contained in:
parent
025f8ad924
commit
0f7748af30
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)))){}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))){}
|
||||
Loading…
Reference in New Issue