mirror of https://github.com/golang/go.git
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:
parent
6db0b6d33a
commit
704beb3881
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue