go/parser, go/types: permit parentheses around embedded contracts

For symmetry with embedding in structs and interfaces.
Fixed an incorrect error message print in the process.

Change-Id: I295685438a22971edc610e6c51cfefd286eaffba
This commit is contained in:
Robert Griesemer 2020-02-19 14:39:48 -08:00
parent 5cc90acd4b
commit 86c2daf9e9
4 changed files with 72 additions and 53 deletions

View File

@ -721,7 +721,8 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field {
typ = p.parseType(true)
}
} else {
// embedded type
// embedded, possibly parameterized type
// (using the enclosing parentheses to distinguish it from a named field declaration)
typ = p.parseType(true)
}
@ -1138,54 +1139,6 @@ func (p *parser) parseChanType(typeContext bool) *ast.ChanType {
return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}
}
// Constraint = TypeParam TypeOrMethod { "," TypeOrMethod } | ContractTypeName "(" [ TypeList [ "," ] ] ")" .
// TypeParam = Ident .
// TypeOrMethod = Type | MethodName Signature .
// ContractTypeName = TypeName.
func (p *parser) parseConstraint() *ast.Constraint {
if p.trace {
defer un(trace(p, "Constraint"))
}
tname := p.parseTypeName(nil)
if p.tok == token.LPAREN {
// ContractTypeName "(" [ TypeList [ "," ] ] ")"
return &ast.Constraint{Types: []ast.Expr{p.parseTypeInstance(tname)}}
}
param, isIdent := tname.(*ast.Ident)
if !isIdent {
p.errorExpected(tname.Pos(), "type parameter name")
param = &ast.Ident{NamePos: tname.Pos(), Name: "_"}
}
// list of type constraints or methods
var mnames []*ast.Ident
var types []ast.Expr
for {
var mname *ast.Ident
typ := p.parseType(false)
if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
mname = ident
scope := ast.NewScope(nil) // method scope
tparams, params := p.parseParameters(scope, methodTypeParamsOk|variadicOk, "method")
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, TParams: tparams, Params: params, Results: results}
}
mnames = append(mnames, mname)
types = append(types, typ)
if p.tok != token.COMMA {
break
}
p.next()
}
// param != nil
return &ast.Constraint{Param: param, MNames: mnames, Types: types}
}
func (p *parser) parseTypeInstance(typ ast.Expr) *ast.CallExpr {
if p.trace {
defer un(trace(p, "TypeInstantiation"))
@ -2625,6 +2578,61 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token
return spec
}
// Constraint = TypeParam TypeOrMethod { "," TypeOrMethod } | ContractName "(" [ TypeList [ "," ] ] ")" .
// TypeParam = Ident .
// TypeOrMethod = Type | MethodName Signature .
// ContractName = TypeName.
func (p *parser) parseConstraint() *ast.Constraint {
if p.trace {
defer un(trace(p, "Constraint"))
}
if p.tok == token.LPAREN {
// embedded, possibly parameterized contract
// (It's never a type but it looks like a possibly instantiated type, so
// let's parse it as such and have the type-checker complain if need be.)
return &ast.Constraint{Types: []ast.Expr{p.parseType(true)}}
}
tname := p.parseTypeName(nil)
if p.tok == token.LPAREN {
// ContractName "(" [ TypeList [ "," ] ] ")"
return &ast.Constraint{Types: []ast.Expr{p.parseTypeInstance(tname)}}
}
param, isIdent := tname.(*ast.Ident)
if !isIdent {
p.errorExpected(tname.Pos(), "type parameter name")
param = &ast.Ident{NamePos: tname.Pos(), Name: "_"}
}
// list of type constraints or methods
var mnames []*ast.Ident
var types []ast.Expr
for {
var mname *ast.Ident
typ := p.parseType(false)
if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
mname = ident
scope := ast.NewScope(nil) // method scope
tparams, params := p.parseParameters(scope, methodTypeParamsOk|variadicOk, "method")
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, TParams: tparams, Params: params, Results: results}
}
mnames = append(mnames, mname)
types = append(types, typ)
if p.tok != token.COMMA {
break
}
p.next()
}
// param != nil
return &ast.Constraint{Param: param, MNames: mnames, Types: types}
}
// ContractSpec = ident "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" .
func (p *parser) parseContractSpec(doc *ast.CommentGroup, pos token.Pos, keyword token.Token, _ int) ast.Spec {
if p.trace {
@ -2643,7 +2651,7 @@ func (p *parser) parseContractSpec(doc *ast.CommentGroup, pos token.Pos, keyword
}
p.next()
}
p.declare(nil, nil, scope, ast.Typ, tparams...) // TODO(gri) should really be something other that ast.Typ
p.declare(nil, nil, scope, ast.Typ, tparams...) // this should be something other that ast.Typ but we don't care (never used)
p.expect(token.RPAREN)
var constraints []*ast.Constraint

View File

@ -97,6 +97,7 @@ var valids = []string{
`package p; contract C(T){ T int, m(int), float64, n() float64, o(x string) rune }`,
`package p; contract C(T){ T int; T imported.T; T chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`package p; contract C(T){ T int, imported.T, chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`package p; contract C(T){ (C(T)); (((imported.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`,

View File

@ -111,10 +111,9 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
check.invalidAST(cdecl.Pos(), "contract contains incorrect (possibly embedded contract) entry")
continue
}
// TODO(gri) we can probably get away w/o checking this (even if the AST is broken)
econtr, _ := c.Types[0].(*ast.CallExpr)
econtr, _ := unparen(c.Types[0]).(*ast.CallExpr)
if econtr == nil {
check.invalidAST(c.Types[0].Pos(), "invalid embedded contract %s", econtr)
check.errorf(c.Types[0].Pos(), "%s is not a contract", c.Types[0])
continue
}

View File

@ -70,3 +70,14 @@ type NodeFace(type Edge) interface {
type EdgeFace(type Node) interface {
Nodes() (from, to Node)
}
// Contracts may embed other contracts.
// Parentheses are permitted (for symmetry with embedding in structs and interfaces)
// but never required.
contract _(T) {
Stringer(T)
(Sequence(T))
Graph /* ERROR not a contract */ (T, T)
( /* ERROR not a contract */ []int)
}