go/types: significant steps towards complete parametrized type instantiation

testdata/typeinst2.go has some complex examples that pass now.
But code elsewhere seems broken. go test has some issues at the
moment. Committing anyway to not lose the snapshot.

Change-Id: Id8f753a7b098405e2580a45ca1707ef6476c198e
This commit is contained in:
Robert Griesemer 2019-07-12 17:51:31 -07:00
parent a76283d6e4
commit 52934f1b1e
12 changed files with 266 additions and 83 deletions

View File

@ -424,7 +424,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// make(T, n, m)
// (no argument evaluated yet)
arg0 := call.Args[0]
T := check.typ(arg0)
T := check.instantiatedType(arg0)
if T == Typ[Invalid] {
return
}
@ -465,7 +465,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _New:
// new(T)
// (no argument evaluated yet)
T := check.typ(call.Args[0])
T := check.instantiatedType(call.Args[0])
if T == Typ[Invalid] {
return
}

View File

@ -23,9 +23,19 @@ func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind {
return statement
case typexpr:
// conversion
// conversion or type instantiation
T := x.typ
x.mode = invalid
if named, _ := T.(*Named); named != nil && named.obj != nil && named.obj.IsParametrized() {
// type instantiation
x.typ = check.instantiatedType(e)
if x.typ != Typ[Invalid] {
x.mode = typexpr
}
return expression
}
// conversion
switch n := len(e.Args); n {
case 0:
check.errorf(e.Rparen, "missing argument in conversion to %s", T)

View File

@ -102,6 +102,7 @@ var tests = [][]string{
// Go 2 tests (type parameters and contracts)
{"testdata/typeparams.go2"},
{"testdata/typeinst.go2"},
{"testdata/typeinst2.go2"},
{"testdata/contracts.go2"},
}

View File

@ -393,7 +393,7 @@ func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
// determine type, if any
if typ != nil {
t := check.typ(typ)
t := check.instantiatedType(typ)
if !isConstType(t) {
// don't report an error if the type is an invalid C (defined) type
// (issue #22090)
@ -419,7 +419,7 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
// determine type, if any
if typ != nil {
obj.typ = check.typ(typ)
obj.typ = check.instantiatedType(typ)
// We cannot spread the type to all lhs variables if there
// are more than one since that would mark them as checked
// (see Checker.objDecl) and the assignment of init exprs,
@ -584,6 +584,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
}
// this must happen before addMethodDecls - cannot use defer
// TODO(gri) consider refactoring this
if obj.IsParametrized() {
check.closeScope()
}

View File

@ -1044,7 +1044,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
}
case *ast.FuncLit:
if sig, ok := check.typ(e.Type).(*Signature); ok {
if sig, ok := check.instantiatedType(e.Type).(*Signature); ok {
// Anonymous functions are considered part of the
// init expression/func declaration which contains
// them: use existing package-level declaration info.
@ -1077,12 +1077,12 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
// We have an "open" [...]T array type.
// Create a new ArrayType with unknown length (-1)
// and finish setting it up after analyzing the literal.
typ = &Array{len: -1, elem: check.typ(atyp.Elt)}
typ = &Array{len: -1, elem: check.instantiatedType(atyp.Elt)}
base = typ
break
}
}
typ = check.typ(e.Type)
typ = check.instantiatedType(e.Type)
base = typ
case hint != nil:
@ -1459,7 +1459,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
check.invalidAST(e.Pos(), "use of .(type) outside type switch")
goto Error
}
T := check.typ(e.Type)
T := check.instantiatedType(e.Type)
if T == Typ[Invalid] {
goto Error
}
@ -1515,7 +1515,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *ast.ArrayType, *ast.StructType, *ast.FuncType,
*ast.InterfaceType, *ast.MapType, *ast.ChanType, *ast.ContractType:
x.mode = typexpr
x.typ = check.typ(e)
x.typ = check.typ(e) // TODO(gri) should this be check.instantiatedType?
// Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue
// even though check.typ has already called it. This is fine as both
// times the same expression and type are recorded. It is also not a

View File

@ -257,6 +257,17 @@ func (obj *TypeName) IsAlias() bool {
}
}
// TypeParams returns the list if *TypeParam types for the type parameters of obj; or nil.
func (obj *TypeName) TypeParams() (tparams []*TypeParam) {
if n := len(obj.tparams); n > 0 {
tparams = make([]*TypeParam, n)
for i, tpar := range obj.tparams {
tparams[i] = tpar.typ.(*TypeParam)
}
}
return
}
// A Variable represents a declared variable (including function parameters and results, and struct fields).
type Var struct {
object

View File

@ -307,6 +307,9 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair, tparams
return x.obj == y.obj
}
case *Parameterized:
panic("internal error: cannot compare uninstantiated parametrized types for identity")
case *TypeParam:
if y, ok := y.(*TypeParam); ok {
// TODO(gri) do we need to look at type names here?

View File

@ -113,6 +113,15 @@ func (s *subster) typ(typ Type) (res Type) {
return tname.typ
}
case *Parameterized:
// first, instantiate any arguments if necessary
targs := make([]Type, len(t.targs))
for i, a := range t.targs {
targs[i] = s.typ(a) // TODO(gri) fix this
}
// then instantiate t
return s.check.subst(t.tname.typ, targs)
case *TypeParam:
// TODO(gri) do we need to check that we're using the correct targs list/index?
if targ := s.targs[t.index]; targ != nil {
@ -169,13 +178,13 @@ func (s *subster) varList(in []*Var) (out []*Var, copied bool) {
func typesString(targs []Type) string {
var buf bytes.Buffer
buf.WriteByte('(')
buf.WriteByte('<')
for i, arg := range targs {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(TypeString(arg, nil))
}
buf.WriteByte(')')
buf.WriteByte('>')
return buf.String()
}

36
src/go/types/testdata/typeinst2.go2 vendored Normal file
View File

@ -0,0 +1,36 @@
// 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.
package p
type List(type E) []E
var _ List(List(List(int)))
// var _ List(List(List(int))) = [](List(List(int))){}
type (
T1(type P1) struct {
f1 T2(P1, float32)
}
T2(type P2, P3) struct {
f2 P2
f3 P3
}
)
func _() {
var x1 T1(int)
var x2 T2(int, float32)
x1.f1.f2 = 0
x1.f1 = x2
}
// type T3(type P) T1(T2(P, P))
func _() {
//var x1 T3(int)
//var x2 T2
//x1.f1.f2.f2 = 0
}

View File

@ -447,7 +447,7 @@ func (c *Chan) Dir() ChanDir { return c.dir }
// Elem returns the element type of channel c.
func (c *Chan) Elem() Type { return c.elem }
// A Named represents a named type.
// A Named represents a named (defined) type.
type Named struct {
info typeInfo // for cycle detection
obj *TypeName // corresponding declared object
@ -497,6 +497,14 @@ func (t *Named) AddMethod(m *Func) {
}
}
// A Parameterized represents a type name instantiated with type arguments;
// e.g., myType(P, int) where P might be a (yet to be instantiated) type parameter.
// A Parameterized is similar to an *ast.CallExpr, but for types.
type Parameterized struct {
tname *TypeName // instantiated type
targs []Type // len(targs) == len(tname.tparams)
}
// A Contract represents a contract.
type Contract struct {
TParams []*TypeName
@ -537,30 +545,32 @@ func NewTypeParam(obj *TypeName, index int) *TypeParam {
// Implementations for Type methods.
func (b *Basic) Underlying() Type { return b }
func (a *Array) Underlying() Type { return a }
func (s *Slice) Underlying() Type { return s }
func (s *Struct) Underlying() Type { return s }
func (p *Pointer) Underlying() Type { return p }
func (t *Tuple) Underlying() Type { return t }
func (s *Signature) Underlying() Type { return s }
func (t *Interface) Underlying() Type { return t }
func (m *Map) Underlying() Type { return m }
func (c *Chan) Underlying() Type { return c }
func (t *Named) Underlying() Type { return t.underlying }
func (c *Contract) Underlying() Type { return c }
func (c *TypeParam) Underlying() Type { return c }
func (b *Basic) Underlying() Type { return b }
func (a *Array) Underlying() Type { return a }
func (s *Slice) Underlying() Type { return s }
func (s *Struct) Underlying() Type { return s }
func (p *Pointer) Underlying() Type { return p }
func (t *Tuple) Underlying() Type { return t }
func (s *Signature) Underlying() Type { return s }
func (t *Interface) Underlying() Type { return t }
func (m *Map) Underlying() Type { return m }
func (c *Chan) Underlying() Type { return c }
func (t *Named) Underlying() Type { return t.underlying }
func (p *Parameterized) Underlying() Type { return p } // TODO(gri) is this correct?
func (c *Contract) Underlying() Type { return c }
func (c *TypeParam) Underlying() Type { return c }
func (b *Basic) String() string { return TypeString(b, nil) }
func (a *Array) String() string { return TypeString(a, nil) }
func (s *Slice) String() string { return TypeString(s, nil) }
func (s *Struct) String() string { return TypeString(s, nil) }
func (p *Pointer) String() string { return TypeString(p, nil) }
func (t *Tuple) String() string { return TypeString(t, nil) }
func (s *Signature) String() string { return TypeString(s, nil) }
func (t *Interface) String() string { return TypeString(t, nil) }
func (m *Map) String() string { return TypeString(m, nil) }
func (c *Chan) String() string { return TypeString(c, nil) }
func (t *Named) String() string { return TypeString(t, nil) }
func (c *Contract) String() string { return TypeString(c, nil) }
func (c *TypeParam) String() string { return TypeString(c, nil) }
func (b *Basic) String() string { return TypeString(b, nil) }
func (a *Array) String() string { return TypeString(a, nil) }
func (s *Slice) String() string { return TypeString(s, nil) }
func (s *Struct) String() string { return TypeString(s, nil) }
func (p *Pointer) String() string { return TypeString(p, nil) }
func (t *Tuple) String() string { return TypeString(t, nil) }
func (s *Signature) String() string { return TypeString(s, nil) }
func (t *Interface) String() string { return TypeString(t, nil) }
func (m *Map) String() string { return TypeString(m, nil) }
func (c *Chan) String() string { return TypeString(c, nil) }
func (t *Named) String() string { return TypeString(t, nil) }
func (p *Parameterized) String() string { return TypeString(p, nil) }
func (c *Contract) String() string { return TypeString(c, nil) }
func (c *TypeParam) String() string { return TypeString(c, nil) }

View File

@ -235,17 +235,30 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
}
case *Named:
s := "<Named w/o object>"
if obj := t.obj; obj != nil {
if obj.pkg != nil {
writePackage(buf, obj.pkg, qf)
writeTypeName(buf, t.obj, qf)
if t.obj != nil && len(t.obj.tparams) > 0 {
buf.WriteByte('(')
for i, tpar := range t.obj.tparams {
if i > 0 {
buf.WriteString(", ")
}
writeTypeName(buf, tpar, qf)
}
// TODO(gri): function-local named types should be displayed
// differently from named types at package level to avoid
// ambiguity.
s = obj.name
buf.WriteByte(')')
}
case *Parameterized:
writeTypeName(buf, t.tname, qf)
if len(t.targs) > 0 {
buf.WriteByte('(')
for i, targ := range t.targs {
if i > 0 {
buf.WriteString(", ")
}
writeType(buf, targ, qf, visited)
}
buf.WriteByte(')')
}
buf.WriteString(s)
case *Contract:
buf.WriteString("contract(")
@ -273,6 +286,20 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
}
}
func writeTypeName(buf *bytes.Buffer, obj *TypeName, qf Qualifier) {
s := "<Named w/o object>"
if obj != nil {
if obj.pkg != nil {
writePackage(buf, obj.pkg, qf)
}
// TODO(gri): function-local named types should be displayed
// differently from named types at package level to avoid
// ambiguity.
s = obj.name
}
buf.WriteString(s)
}
func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visited []Type) {
buf.WriteByte('(')
if tup != nil {

View File

@ -142,6 +142,24 @@ func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) {
return
}
// instantiatedType is like typ but it ensures that a Parametrized type is
// fully instantiated.
func (check *Checker) instantiatedType(e ast.Expr) Type {
typ := check.typ(e)
if ptyp, _ := typ.(*Parameterized); ptyp != nil {
tname := ptyp.tname
typ = check.subst(tname.typ, ptyp.targs)
if typ == nil {
return Typ[Invalid] // error was reported by check.instatiate
}
if trace {
check.trace(e.Pos(), "instantiated %s -> %s", tname, typ)
}
}
return typ
}
// funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, scope *Scope, tparams []*TypeName, ftyp *ast.FuncType) {
// type parameters are in a scope enclosing the function scope
@ -259,46 +277,33 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
}
case *ast.CallExpr:
// Type instantiation requires a type name, handle everything
// here so we don't need to introduce type parameters into
// operands: parametrized types can only appear in type
// instantiation expressions.
typ := new(Parameterized)
def.setUnderlying(typ)
// e.Fun must be a type name
var tname *TypeName
if ident, ok := e.Fun.(*ast.Ident); ok {
obj := check.lookup(ident.Name)
if obj == nil {
if ident.Name == "_" {
check.errorf(ident.Pos(), "cannot use _ as type")
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
break
}
check.recordUse(ident, obj)
tname, _ = obj.(*TypeName)
if tname == nil {
check.errorf(ident.Pos(), "%s is not a type", ident.Name)
break
}
// TODO(gri) This code cannot handle type aliases at the moment.
// Probably need to do the name lookup here.
t := check.typ(e.Fun)
if t == Typ[Invalid] {
break // error already reported
}
named, _ := t.(*Named)
if named == nil || named.obj == nil {
check.errorf(e.Pos(), "cannot instantiate type without a name")
break
}
tname := named.obj
if !tname.IsParametrized() {
check.errorf(e.Pos(), "%s is not a parametrized type", tname.name)
break
}
// typecheck tname (see check.ident for details)
check.objDecl(tname, def)
assert(tname.typ != nil)
// the number of supplied types must match the number of type parameters
// TODO(gri) fold into code below - we want to eval args always
if len(e.Args) != len(tname.tparams) {
// TODO(gri) provide better error message
check.errorf(e.Fun.Pos(), "got %d arguments but %d type parameters", len(e.Args), len(tname.tparams))
check.errorf(e.Pos(), "got %d arguments but %d type parameters", len(e.Args), len(tname.tparams))
break
}
@ -309,23 +314,92 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
}
// arguments must be types
// (If there was no error before, either all arguments are types
// or all are values, thus it suffices to check the first one.)
assert(len(args) > 0)
if x := args[0]; x.mode != typexpr {
check.errorf(x.pos(), "%s is not a type", x)
break
}
// instantiate typ
typ := check.instantiate(tname.typ, tname.tparams, args)
if typ == nil {
break // error was reported by check.instatiate
// complete parameterized type
typ.tname = tname
typ.targs = make([]Type, len(args))
for i, x := range args {
typ.targs[i] = x.typ
}
if trace {
check.trace(args[0].pos(), "instantiated %s -> %s", tname, typ)
}
return typ
/*
// Type instantiation requires a type name, handle everything
// here so we don't need to introduce type parameters into
// operands: parametrized types can only appear in type
// instantiation expressions.
// e.Fun must be a type name
var tname *TypeName
if ident, ok := e.Fun.(*ast.Ident); ok {
obj := check.lookup(ident.Name)
if obj == nil {
if ident.Name == "_" {
check.errorf(ident.Pos(), "cannot use _ as type")
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
break
}
check.recordUse(ident, obj)
tname, _ = obj.(*TypeName)
if tname == nil {
check.errorf(ident.Pos(), "%s is not a type", ident.Name)
break
}
}
if !tname.IsParametrized() {
check.errorf(e.Pos(), "%s is not a parametrized type", tname.name)
break
}
// typecheck tname (see check.ident for details)
check.objDecl(tname, def)
assert(tname.typ != nil)
// the number of supplied types must match the number of type parameters
// TODO(gri) fold into code below - we want to eval args always
if len(e.Args) != len(tname.tparams) {
// TODO(gri) provide better error message
check.errorf(e.Fun.Pos(), "got %d arguments but %d type parameters", len(e.Args), len(tname.tparams))
break
}
// evaluate arguments
args, ok := check.exprOrTypeList(e.Args) // reports error if types and expressions are mixed
if !ok {
break
}
// arguments must be types
assert(len(args) > 0)
if x := args[0]; x.mode != typexpr {
check.errorf(x.pos(), "%s is not a type", x)
break
}
// instantiate typ
typ := check.instantiate(tname.typ, tname.tparams, args)
if typ == nil {
break // error was reported by check.instatiate
}
if trace {
check.trace(args[0].pos(), "instantiated %s -> %s", tname, typ)
}
return typ
*/
case *ast.ParenExpr:
return check.definedType(e.X, def)