From 3a40a4f856023fb61ac8c68504d01f64c3a85d63 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 19 Dec 2019 17:47:26 -0800 Subject: [PATCH] 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 --- src/go/ast/ast.go | 56 +++++++++++++------------ src/go/parser/parser.go | 68 ++++++++++++------------------- src/go/parser/short_test.go | 11 ++--- src/go/parser/testdata/linalg.go2 | 2 +- src/go/printer/nodes.go | 37 ++++++++--------- src/go/printer/printer.go | 5 +-- 6 files changed, 80 insertions(+), 99 deletions(-) diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index a931d1b48b..e4ff21d4a1 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -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 diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 3578a2f67a..d56df663cf 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -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 diff --git a/src/go/parser/short_test.go b/src/go/parser/short_test.go index b39fae7d76..22be6c8934 100644 --- a/src/go/parser/short_test.go +++ b/src/go/parser/short_test.go @@ -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)`, diff --git a/src/go/parser/testdata/linalg.go2 b/src/go/parser/testdata/linalg.go2 index cd758d627d..56ab0ed18e 100644 --- a/src/go/parser/testdata/linalg.go2 +++ b/src/go/parser/testdata/linalg.go2 @@ -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 } diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index c9c4483c3e..ed5c17f1b8 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -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) } diff --git a/src/go/printer/printer.go b/src/go/printer/printer.go index fb26c59568..9d0add40b6 100644 --- a/src/go/printer/printer.go +++ b/src/go/printer/printer.go @@ -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 {