go/types: check that generic types are instantiated before use

This (temporarily) disables the use of recursively defined
generic types w/o repeating the type arguments, but such
type definitions need some more (type-checking) love anyway
before all conditions are checked.

Change-Id: I173f5ce2296b1c484ac95a3a887812c0b1f88245
This commit is contained in:
Robert Griesemer 2019-12-03 22:21:39 -08:00
parent eacaaec439
commit fefe08cbea
9 changed files with 67 additions and 41 deletions

View File

@ -5,3 +5,6 @@ TODO
- implement contract embedding
- interface embedding doesn't take care of literal type constraints yet
(need an allTypes list, like we have an allMethods list?)
OPEN ISSUES
- do we allow parenthesized generic uninstantiated types?

View File

@ -50,9 +50,9 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
}
// x.typ is typed
// A generic (non-instantiated) value cannot be assigned to a variable.
if isGeneric(x.typ) {
check.errorf(x.pos(), "cannot use generic %s in %s", x, context)
// A generic (non-instantiated) function value cannot be assigned to a variable.
if sig, _ := x.typ.Underlying().(*Signature); sig != nil && len(sig.tparams) > 0 {
check.errorf(x.pos(), "cannot use generic function %s without instantiation in %s", x, context)
}
// spec: "If a left-hand side is the blank identifier, any typed or

View File

@ -18,7 +18,8 @@ var _ List(byte) = []byte{}
// A generic binary tree might be declared as follows.
type Tree(type E) struct {
left, right *Tree
// TODO(gri) should be able to use Tree w/o repeating type args
left, right *Tree(E)
payload E
}
@ -98,4 +99,10 @@ var xbool T(bool)
// consider such types identical. Consequently:
func _() {
xint = xbool // ERROR assignment
}
}
// Parameterized types cannot be used without instantiation.
var _ T // ERROR cannot use generic type T
// TODO(gri) Should we allow parentheses around generic, uninstantiated types?
// var _ (T)(int)

View File

@ -158,7 +158,11 @@ func operandString(x *operand, qf Qualifier) string {
// <typ>
if hasType {
if x.typ != Typ[Invalid] {
buf.WriteString(" of type ")
intro := " of type "
if isGeneric(x.typ) {
intro = " of generic type "
}
buf.WriteString(intro)
WriteType(&buf, x.typ, qf)
} else {
buf.WriteString(" with invalid type")

View File

@ -19,6 +19,13 @@ func isNamed(typ Type) bool {
return ok
}
// isGeneric reports whether a type is a generic, uninstantiated type
// (generic signatures are not included).
func isGeneric(typ Type) bool {
named, _ := typ.(*Named)
return named != nil && named.obj != nil && named.obj.IsParameterized() && named.targs == nil
}
func is(typ Type, what BasicInfo) bool {
switch t := typ.Underlying().(type) {
case *Basic:
@ -60,12 +67,6 @@ func IsInterface(typ Type) bool {
return ok
}
func isGeneric(typ Type) bool {
// TODO(gri) can a defined type ever be generic?
t, ok := typ.Underlying().(*Signature)
return ok && len(t.tparams) > 0
}
// Comparable reports whether values of type T are comparable.
func Comparable(T Type) bool {
switch t := T.Underlying().(type) {

View File

@ -182,6 +182,4 @@ func adderSum(type T Adder(T))(data []T) T {
return s
}
// TODO(gri) Report an error if we use parameterized interface type bound
// and we forget to pass the type argument.
func buggySig(type T Adder)(data []T) T
func _(type T Adder /* ERROR cannot use generic type Adder */)(data []T) T

View File

@ -25,8 +25,8 @@ type A1( /* ERROR cannot be parameterized */ type P) = P /* ERROR undeclared */
var x int
type _ x /* ERROR not a type */ (int)
type _ int /* ERROR not a parametrized type */ ()
type _ myInt /* ERROR not a parametrized type */ ()
type _ int /* ERROR not a generic type */ ()
type _ myInt /* ERROR not a generic type */ ()
// TODO(gri) better error messages
type _ T1 /* ERROR got 0 arguments but 1 type parameters */ ()

View File

@ -21,7 +21,7 @@ func reverse(type T)(list []T) []T {
return rlist
}
var _ = reverse /* ERROR cannot use generic reverse */
var _ = reverse /* ERROR cannot use generic function reverse */
var _ = reverse(int, float32 /* ERROR got 2 type arguments */ ) ([]int{1, 2, 3})
var _ = reverse(int)([ /* ERROR cannot use */ ]float32{1, 2, 3})
var f = reverse(chan int)
@ -52,7 +52,7 @@ func new(type T)() *T {
return &x
}
var _ = new /* ERROR cannot use generic new */
var _ = new /* ERROR cannot use generic function new */
var _ *int = new(int)()
func _(type T)(map[T /* ERROR invalid map key type */]int) // w/o contract we don't know if T is comparable

View File

@ -116,6 +116,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool)
}
// typ type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type.
func (check *Checker) typ(e ast.Expr) Type {
return check.definedType(e, nil)
}
@ -125,21 +126,28 @@ func (check *Checker) typ(e ast.Expr) Type {
// in a type declaration, and def.underlying will be set to the type of e before
// any components of e are type-checked.
//
func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) {
if check.conf.Trace {
check.trace(e.Pos(), "%s", e)
check.indent++
defer func() {
check.indent--
check.trace(e.Pos(), "=> %s", T)
}()
func (check *Checker) definedType(e ast.Expr, def *Named) Type {
typ := check.typInternal(e, def)
assert(isTyped(typ))
if isGeneric(typ) {
check.errorf(e.Pos(), "cannot use generic type %s without instantiation", typ)
typ = Typ[Invalid]
}
check.recordTypeAndValue(e, typexpr, typ, nil)
return typ
}
T = check.typInternal(e, def)
assert(isTyped(T))
check.recordTypeAndValue(e, typexpr, T, nil)
return
// generic us like typ bit the type must be an (uninstantiated) generic type.
func (check *Checker) genericType(e ast.Expr) Type {
typ := check.typInternal(e, nil)
assert(isTyped(typ))
if typ != Typ[Invalid] && !isGeneric(typ) {
check.errorf(e.Pos(), "%s is not a generic type", typ)
typ = Typ[Invalid]
}
// TODO(gri) what is the correct call below?
check.recordTypeAndValue(e, typexpr, typ, nil)
return typ
}
// funcType type-checks a function or method type.
@ -208,9 +216,18 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
}
// typInternal drives type checking of types.
// Must only be called by definedType.
// Must only be called by definedType or genericType.
//
func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
func (check *Checker) typInternal(e ast.Expr, def *Named) (T Type) {
if check.conf.Trace {
check.trace(e.Pos(), "expr %s", e)
check.indent++
defer func() {
check.indent--
check.trace(e.Pos(), "=> %s", T)
}()
}
switch e := e.(type) {
case *ast.BadExpr:
// ignore - error reported before
@ -250,19 +267,14 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
}
case *ast.CallExpr:
typ := check.typ(e.Fun) // TODO(gri) what about cycles?
typ := check.genericType(e.Fun) // TODO(gri) what about cycles?
if typ == Typ[Invalid] {
return typ // error already reported
}
named, _ := typ.(*Named)
if named == nil || named.obj == nil || !named.obj.IsParameterized() || named.targs != nil {
check.errorf(e.Pos(), "%s is not a parametrized type", typ)
return Typ[Invalid]
}
// the number of supplied types must match the number of type parameters
// TODO(gri) fold into code below - we want to eval args always
named, _ := typ.(*Named) // generic types are defined (= Named) types
tname := named.obj
if len(e.Args) != len(tname.tparams) {
// TODO(gri) provide better error message
@ -309,6 +321,7 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
return typ
case *ast.ParenExpr:
// TODO(gri) should we allow parenthesized generic (uninstantiated) types?
return check.definedType(e.X, def)
case *ast.ArrayType: