From 52934f1b1e57963877bada22fa413dc36a3cf493 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 12 Jul 2019 17:51:31 -0700 Subject: [PATCH] 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 --- src/go/types/builtins.go | 4 +- src/go/types/call.go | 12 ++- src/go/types/check_test.go | 1 + src/go/types/decl.go | 6 +- src/go/types/expr.go | 10 +- src/go/types/object.go | 11 +++ src/go/types/predicates.go | 3 + src/go/types/subst.go | 13 ++- src/go/types/testdata/typeinst2.go2 | 36 +++++++ src/go/types/type.go | 64 +++++++------ src/go/types/typestring.go | 45 +++++++-- src/go/types/typexpr.go | 144 +++++++++++++++++++++------- 12 files changed, 266 insertions(+), 83 deletions(-) create mode 100644 src/go/types/testdata/typeinst2.go2 diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index 3086acbbe9..65d760f3b7 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -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 } diff --git a/src/go/types/call.go b/src/go/types/call.go index b36ada7949..4a4a292c4a 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -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) diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index e721c7a705..5b6c52fbbb 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -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"}, } diff --git a/src/go/types/decl.go b/src/go/types/decl.go index aabba91279..039ded089d 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -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() } diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 82198e715f..5cfab6ad42 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -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 diff --git a/src/go/types/object.go b/src/go/types/object.go index 04a4cfb85b..a123df8d90 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -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 diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 0c7bfa2369..9ba0ddcfa4 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -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? diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 955585c713..8bb7be9e83 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -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() } diff --git a/src/go/types/testdata/typeinst2.go2 b/src/go/types/testdata/typeinst2.go2 new file mode 100644 index 0000000000..2753b7d7aa --- /dev/null +++ b/src/go/types/testdata/typeinst2.go2 @@ -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 +} \ No newline at end of file diff --git a/src/go/types/type.go b/src/go/types/type.go index 373918ed5a..096aa9f69b 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -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) } diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index ac12a3a4c2..9e291922ca 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -235,17 +235,30 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { } case *Named: - s := "" - 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 := "" + 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 { diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 9006ec4a98..1ad88d60ee 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -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)