go/parser, go/types: parse/type-check interface (literal) type constraints

Accept (parse and type-check) parameterized interfaces with literal
type constraints, as in contracts. For instance, instead of

	contract C(T) {
		T add(T) T
		T int, string
	}

one can write

	type C(type T) interface {
		add(T) T
		type int, string
	}

Their use as type bounds still needs some work, though.

Change-Id: I9d59681dc0ba38054ee5627be141fc6e8a78381b
This commit is contained in:
Robert Griesemer 2019-12-03 15:43:51 -08:00
parent 6db0b6d33a
commit 704beb3881
8 changed files with 95 additions and 33 deletions

View File

@ -440,9 +440,11 @@ type (
}
// An InterfaceType node represents an interface type.
// The Types list is an experimental extension for interfaces that serve as type bounds (like contracts).
InterfaceType struct {
Interface token.Pos // position of "interface" keyword
Methods *FieldList // list of methods
Types []Expr // list of types (TODO(gri) for now they are all lumped together, loosing syntax info)
Incomplete bool // true if (source) methods are missing in the Methods list
}

View File

@ -1231,9 +1231,20 @@ func (p *parser) parseInterfaceType() *ast.InterfaceType {
pos := p.expect(token.INTERFACE)
lbrace := p.expect(token.LBRACE)
scope := ast.NewScope(nil) // interface scope
var list []*ast.Field
for p.tok == token.IDENT {
list = append(list, p.parseMethodSpec(scope))
var mlist []*ast.Field
var tlist []ast.Expr
L:
for {
switch p.tok {
case token.IDENT:
mlist = append(mlist, p.parseMethodSpec(scope))
case token.TYPE:
p.next()
tlist = append(tlist, p.parseTypeList()...)
p.expectSemi()
default:
break L
}
}
rbrace := p.expect(token.RBRACE)
@ -1241,9 +1252,10 @@ func (p *parser) parseInterfaceType() *ast.InterfaceType {
Interface: pos,
Methods: &ast.FieldList{
Opening: lbrace,
List: list,
List: mlist,
Closing: rbrace,
},
Types: tlist,
}
}

View File

@ -97,6 +97,10 @@ var valids = []string{
`package p; type C contract(T){ T int, imported.T, chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`package p; func _(type T1, T2 interface{})(x T1) T2`,
`package p; func _(type T1 interface{ m() }, T2, T3 interface{})(x T1, y T3) T2`,
// interfaces as contracts (experimental)
`package p; type _ interface{type int}`,
`package p; type _ interface{type int, float32; type bool; m(); type string;}`,
}
func TestValid(t *testing.T) {

View File

@ -79,24 +79,7 @@ func (check *Checker) contractType(contr *Contract, e *ast.ContractType) {
switch nmethods {
case 0:
// type constraints
for _, texpr := range c.Types {
if texpr == nil {
check.invalidAST(pos, "missing type constraint")
continue
}
typ := check.typ(texpr)
// A type constraint may be a predeclared type or a
// composite type composed only of predeclared types.
// TODO(gri) should we keep this restriction?
var why string
if !check.typeConstraint(typ, &why) {
check.errorf(texpr.Pos(), "invalid type constraint %s (%s)", typ, why)
continue
}
// add type
iface.types = append(iface.types, typ)
}
iface.types = check.collectTypeConstraints(pos, iface.types, c.Types)
case 1:
// method constraint
@ -161,6 +144,27 @@ func (check *Checker) contractType(contr *Contract, e *ast.ContractType) {
contr.IFaces = ifaces
}
func (check *Checker) collectTypeConstraints(pos token.Pos, list []Type, types []ast.Expr) []Type {
for _, texpr := range types {
if texpr == nil {
check.invalidAST(pos, "missing type constraint")
continue
}
typ := check.typ(texpr)
// A type constraint may be a predeclared type or a
// composite type composed only of predeclared types.
// TODO(gri) should we keep this restriction?
var why string
if !check.typeConstraint(typ, &why) {
check.errorf(texpr.Pos(), "invalid type constraint %s (%s)", typ, why)
continue
}
// add type
list = append(list, typ)
}
return list
}
// TODO(gri) does this simply check for the absence of defined types?
// (if so, should choose a better name)
func (check *Checker) typeConstraint(typ Type, why *string) bool {
@ -213,7 +217,8 @@ func (check *Checker) typeConstraint(typ Type, why *string) bool {
*why = check.sprintf("%s is not a type", t)
return false
case *TypeParam:
// ok
// TODO(gri) should this be ok? need a good use case
// ok for now
default:
unreachable()
}

View File

@ -98,12 +98,7 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) {
case *ast.CallExpr:
WriteExpr(buf, x.Fun)
buf.WriteByte('(')
for i, arg := range x.Args {
if i > 0 {
buf.WriteString(", ")
}
WriteExpr(buf, arg)
}
writeExprList(buf, x.Args)
if x.Ellipsis.IsValid() {
buf.WriteString("...")
}
@ -144,6 +139,13 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) {
case *ast.InterfaceType:
buf.WriteString("interface{")
writeFieldList(buf, x.Methods, "; ", true)
if len(x.Types) > 0 {
if len(x.Methods.List) > 0 {
buf.WriteString("; ")
}
buf.WriteString("type ")
writeExprList(buf, x.Types)
}
buf.WriteByte('}')
case *ast.MapType:
@ -232,3 +234,12 @@ func writeIdentList(buf *bytes.Buffer, list []*ast.Ident) {
buf.WriteString(x.Name)
}
}
func writeExprList(buf *bytes.Buffer, list []ast.Expr) {
for i, x := range list {
if i > 0 {
buf.WriteString(", ")
}
WriteExpr(buf, x)
}
}

View File

@ -161,3 +161,25 @@ func _(type F, C FloatComplex)(c C) {
var fim F = F(im)
c = C(complex(fre, fim))
}
// --------------------------------------------------------------------------------------
// Parameterized interfaces as contracts
type Int interface {
type int8, int16, int32, int64, int
type uint8, uint16, uint32, uint64, uint
}
type Adder(type T) interface {
Add(T) T
}
func adderSum(type T Adder)(data []T) T {
var s T
for _, x := range data {
// TODO(gri) make this work
// s = s.Add(x)
_ = x
}
return s
}

View File

@ -201,11 +201,14 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
if !empty && len(t.types) > 0 {
buf.WriteString("; ")
}
for i, typ := range t.types {
if i > 0 {
buf.WriteString(", ")
if len(t.types) > 0 {
buf.WriteString("type ")
for i, typ := range t.types {
if i > 0 {
buf.WriteString(", ")
}
writeType(buf, typ, qf, visited)
}
writeType(buf, typ, qf, visited)
empty = false
}
if !empty && len(t.embeddeds) > 0 {

View File

@ -623,6 +623,9 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
}
}
// type constraints
ityp.types = check.collectTypeConstraints(iface.Pos(), ityp.types, iface.Types)
if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 {
// empty interface
ityp.allMethods = markComplete