mirror of https://github.com/golang/go.git
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:
parent
eacaaec439
commit
fefe08cbea
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */ ()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue