go/ast, go/parser, go/printer: treat contract decl as declaration, not type (cleanup)

Progress snapshot.

This change makes a contract declaration a proper declaration (as it is
envisioned in the design draft). Specifically, contracts cannot be used
in type position anymore, and a contract declaration is not treated as
a type declaration with the type being a contract.

The change has not been pushed through go/types yet; this change will
temporarily brake go/types.

Change-Id: Ia4034caebab07dac449a02cdd362d6ce5d61c4a3
This commit is contained in:
Robert Griesemer 2019-12-19 17:47:26 -08:00
parent bf01ee1864
commit 3a40a4f856
6 changed files with 80 additions and 99 deletions

View File

@ -192,7 +192,7 @@ func isDirective(c string) bool {
type Field struct {
Doc *CommentGroup // associated documentation; or nil
Names []*Ident // field/method/(type) parameter names; or nil
Type Expr // field/method/parameter type or contract; or nil
Type Expr // field/method/parameter type or contract name; or nil
Tag *BasicLit // field tag; or nil
Comment *CommentGroup // line comments; or nil
}
@ -440,7 +440,7 @@ type (
}
// An InterfaceType node represents an interface type.
// The Types list is an experimental extension for interfaces that serve as type bounds (like contracts).
// The Types list is an experimental extension for interfaces that serve as type bounds (as in contracts).
InterfaceType struct {
Interface token.Pos // position of "interface" keyword
Methods *FieldList // list of methods
@ -462,23 +462,8 @@ type (
Dir ChanDir // channel direction
Value Expr // value type
}
// A ContractType node represents a contract.
ContractType struct {
Contract token.Pos // position of "contract" pseudo keyword
TParams []*Ident // list of (incoming) type parameters; or nil
Lbrace token.Pos // position of "{"
Constraints []*Constraint // list of constraints
Rbrace token.Pos // position of "}"
}
)
type Constraint struct {
Param *Ident // constrained type parameter; or nil (for embedded contracts)
MNames []*Ident // list of method names; or nil (for embedded contracts or type constraints)
Types []Expr // embedded contract (single *CallExpr), list of types, or list of method signatures (*FuncType)
}
// Pos and End implementations for expression/type nodes.
func (x *BadExpr) Pos() token.Pos { return x.From }
@ -513,7 +498,6 @@ func (x *FuncType) Pos() token.Pos {
func (x *InterfaceType) Pos() token.Pos { return x.Interface }
func (x *MapType) Pos() token.Pos { return x.Map }
func (x *ChanType) Pos() token.Pos { return x.Begin }
func (x *ContractType) Pos() token.Pos { return x.Contract }
func (x *BadExpr) End() token.Pos { return x.To }
func (x *Ident) End() token.Pos { return token.Pos(int(x.NamePos) + len(x.Name)) }
@ -547,7 +531,6 @@ func (x *FuncType) End() token.Pos {
func (x *InterfaceType) End() token.Pos { return x.Methods.End() }
func (x *MapType) End() token.Pos { return x.Value.End() }
func (x *ChanType) End() token.Pos { return x.Value.End() }
func (x *ContractType) End() token.Pos { return x.Rbrace }
// exprNode() ensures that only expression/type nodes can be
// assigned to an Expr.
@ -575,7 +558,6 @@ func (*FuncType) exprNode() {}
func (*InterfaceType) exprNode() {}
func (*MapType) exprNode() {}
func (*ChanType) exprNode() {}
func (*ContractType) exprNode() {}
// ----------------------------------------------------------------------------
// Convenience functions for Idents
@ -921,8 +903,25 @@ type (
Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
Comment *CommentGroup // line comments; or nil
}
// A ContractSpec node represents a contract declaration.
ContractSpec struct {
Doc *CommentGroup // associated documentation; or nil
Name *Ident // contract name
TParams []*Ident // list of (incoming) type parameters; or nil
Lbrace token.Pos // position of "{"
Constraints []*Constraint // list of constraints
Rbrace token.Pos // position of "}"
Comment *CommentGroup // line comments; or nil
}
)
type Constraint struct {
Param *Ident // constrained type parameter; or nil (for embedded contracts)
MNames []*Ident // list of method names; or nil (for embedded contracts or type constraints)
Types []Expr // embedded contract (single *CallExpr), list of types, or list of method signatures (*FuncType)
}
// Pos and End implementations for spec nodes.
func (s *ImportSpec) Pos() token.Pos {
@ -931,8 +930,9 @@ func (s *ImportSpec) Pos() token.Pos {
}
return s.Path.Pos()
}
func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() }
func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() }
func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() }
func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() }
func (s *ContractSpec) Pos() token.Pos { return s.Name.Pos() }
func (s *ImportSpec) End() token.Pos {
if s.EndPos != 0 {
@ -950,14 +950,16 @@ func (s *ValueSpec) End() token.Pos {
}
return s.Names[len(s.Names)-1].End()
}
func (s *TypeSpec) End() token.Pos { return s.Type.End() }
func (s *TypeSpec) End() token.Pos { return s.Type.End() }
func (s *ContractSpec) End() token.Pos { return s.Rbrace }
// specNode() ensures that only spec nodes can be
// assigned to a Spec.
//
func (*ImportSpec) specNode() {}
func (*ValueSpec) specNode() {}
func (*TypeSpec) specNode() {}
func (*ImportSpec) specNode() {}
func (*ValueSpec) specNode() {}
func (*TypeSpec) specNode() {}
func (*ContractSpec) specNode() {}
// A declaration is represented by one of the following declaration nodes.
//
@ -984,7 +986,7 @@ type (
GenDecl struct {
Doc *CommentGroup // associated documentation; or nil
TokPos token.Pos // position of Tok
Tok token.Token // IMPORT, CONST, TYPE, VAR
Tok token.Token // IMPORT, CONST, TYPE, VAR, or IDENT (for "contract" pseudo keyword)
Lparen token.Pos // position of '(', if any
Specs []Spec
Rparen token.Pos // position of ')', if any

View File

@ -1298,37 +1298,6 @@ func (p *parser) parseChanType(typeContext bool) *ast.ChanType {
return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}
}
// ContractType = "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" .
// (The "contract" keyword is already consumed.)
func (p *parser) parseContractType(pos token.Pos) *ast.ContractType {
if p.trace {
defer un(trace(p, "ContractType"))
}
var params []*ast.Ident
p.expect(token.LPAREN)
scope := ast.NewScope(nil) // contract scope
for p.tok != token.RPAREN && p.tok != token.EOF {
params = append(params, p.parseIdent())
if !p.atComma("contract parameter list", token.RPAREN) {
break
}
p.next()
}
p.declare(nil, nil, scope, ast.Typ, params...)
p.expect(token.RPAREN)
var constraints []*ast.Constraint
lbrace := p.expect(token.LBRACE)
for p.tok != token.RBRACE && p.tok != token.EOF {
constraints = append(constraints, p.parseConstraint())
p.expectSemi()
}
rbrace := p.expect(token.RBRACE)
return &ast.ContractType{Contract: pos, TParams: params, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace}
}
// Constraint = TypeParam TypeOrMethod { "," TypeOrMethod } | ContractTypeName "(" [ TypeList [ "," ] ] ")" .
// TypeParam = Ident .
// TypeOrMethod = Type | MethodName Signature .
@ -1416,13 +1385,6 @@ func (p *parser) parseTypeInstance(typ ast.Expr) *ast.CallExpr {
func (p *parser) tryIdentOrType(typeContext bool) ast.Expr {
switch p.tok {
case token.IDENT:
// TODO(gri) we need to be smarter about this to avoid problems with existing code
if p.lit == "contract" {
pos := p.pos
p.next()
typ := p.parseContractType(pos)
return typ
}
typ := p.parseTypeName(nil)
if typeContext && (useBrackets && p.tok == token.LBRACK || p.tok == token.LPAREN) {
typ = p.parseTypeInstance(typ)
@ -2846,6 +2808,7 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token
return spec
}
// ContractType = ident "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" .
func (p *parser) parseContractSpec(doc *ast.CommentGroup, pos token.Pos, _ token.Token, _ int) ast.Spec {
if p.trace {
defer un(trace(p, "ContractSpec"))
@ -2854,10 +2817,31 @@ func (p *parser) parseContractSpec(doc *ast.CommentGroup, pos token.Pos, _ token
// For now we represent a contract specification like a type representation.
// They cannot have "outer" type parameters, though.
ident := p.parseIdent()
typ := p.parseContractType(pos)
spec := &ast.TypeSpec{Doc: doc, Name: ident, Type: typ}
p.declare(spec, nil, p.topScope, ast.Typ, ident)
p.expectSemi() // call before accessing p.linecomment
var tparams []*ast.Ident
p.expect(token.LPAREN)
scope := ast.NewScope(nil) // contract scope
for p.tok != token.RPAREN && p.tok != token.EOF {
tparams = append(tparams, p.parseIdent())
if !p.atComma("contract parameter list", token.RPAREN) {
break
}
p.next()
}
p.declare(nil, nil, scope, ast.Typ, tparams...) // TODO(gri) should really be something other that ast.Typ
p.expect(token.RPAREN)
var constraints []*ast.Constraint
lbrace := p.expect(token.LBRACE)
for p.tok != token.RBRACE && p.tok != token.EOF {
constraints = append(constraints, p.parseConstraint())
p.expectSemi()
}
rbrace := p.expect(token.RBRACE)
spec := &ast.ContractSpec{Doc: doc, Name: ident, TParams: tparams, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace}
p.declare(spec, nil, p.topScope, ast.Typ, ident) // TODO(gri) should really be something other that ast.Typ
p.expectSemi() // call before accessing p.linecomment
spec.Comment = p.lineComment
return spec

View File

@ -90,11 +90,8 @@ var valids = []string{
}`,
`package p; contract C(T){ T m(int), n() float64, o(x string) rune }`,
`package p; contract C(T){ T int, m(int), float64, n() float64, o(x string) rune }`,
`package p; type C contract(){}`,
`package p; type C contract(T, S, R,){}`,
`package p; type C contract(T){ T (m(x, int)); }`,
`package p; type C contract(T){ T int; T imported.T; T chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`package p; type C contract(T){ T int, imported.T, chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`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; func _(type T1, T2 interface{})(x T1) T2`,
`package p; func _(type T1 interface{ m() }, T2, T3 interface{})(x T1, y T3) T2`,
@ -162,8 +159,8 @@ var invalids = []string{
`package p; func f() { defer func() {} /* ERROR HERE "function must be invoked" */ }`,
`package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`,
//`package p; func f(x func(), u v func /* ERROR "missing ','" */ ()){}`,
`package p; type C contract(T, T /* ERROR "T redeclared" */ ) {}`,
`package p; type C contract(T) { imported /* ERROR "expected type parameter name" */ .T int }`,
`package p; contract C(T, T /* ERROR "T redeclared" */ ) {}`,
`package p; contract C(T) { imported /* ERROR "expected type parameter name" */ .T int }`,
// issue 8656
`package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`,

View File

@ -49,7 +49,7 @@ contract Complex(T) {
// ordered numeric types.
type OrderedAbs(type T OrderedNumeric) T
func (a OrderedAbs(T)) Abs() T {
func (a OrderedAbs(T)) Abs() OrderedAbs(T) {
if a < 0 {
return -a
}

View File

@ -971,23 +971,6 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
p.print(blank)
p.expr(x.Value)
case *ast.ContractType:
if p.mode&noContractKeyword == 0 {
p.print(&ast.Ident{NamePos: x.Contract, Name: "contract"})
}
p.print(token.LPAREN)
for i, par := range x.TParams {
if i > 0 {
p.print(token.COMMA, blank)
}
p.print(par)
}
p.print(token.RPAREN, x.Lbrace, token.LBRACE)
for _, c := range x.Constraints {
p.constraint(c)
}
p.print(x.Rbrace, token.RBRACE)
default:
panic("unreachable")
}
@ -1633,6 +1616,23 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {
p.expr(s.Type)
p.setComment(s.Comment)
case *ast.ContractSpec:
p.setComment(s.Doc)
p.expr(s.Name)
p.print(token.LPAREN)
for i, par := range s.TParams {
if i > 0 {
p.print(token.COMMA, blank)
}
p.print(par)
}
p.print(token.RPAREN, s.Lbrace, token.LBRACE)
for _, c := range s.Constraints {
p.constraint(c)
}
p.print(s.Rbrace, token.RBRACE)
p.setComment(s.Comment)
default:
panic("unreachable")
}
@ -1643,8 +1643,7 @@ func (p *printer) genDecl(d *ast.GenDecl) {
// Contract declarations rely on the pseudo-keyword (identifier) "contract";
// in the AST the respective token is ast.IDENT. Catch and correct this here.
if d.Tok == token.IDENT {
p.print(&ast.Ident{NamePos: d.Pos(), Name: "contract"}, noContractKeyword)
defer p.print(noContractKeyword)
p.print(&ast.Ident{NamePos: d.Pos(), Name: "contract"})
} else {
p.print(d.Pos(), d.Tok)
}

View File

@ -38,9 +38,8 @@ const (
type pmode int
const (
noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment
noExtraLinebreak // disables extra line break after /*-style comment
noContractKeyword // disables printing of "contract" pseudo-keyword when printing a contract type
noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment
noExtraLinebreak // disables extra line break after /*-style comment
)
type commentInfo struct {