go/*: switch back to () parentheses, fill in various missing pieces

This change switches parsing back to using () parentheses for
type parameters. The parser now also accepts contracts in
parametrized type declarations, and top-level declared
contracts as defined in the design draft:

contract C(T1, T2) { ... }

(Internally, they are mapped to an ast.ContractType as before.)

Added more tests, incl. map.go2 from the design draft, and removed
some unused parser functions.

Passes parser and types tests.

Known issue: Composite literals with instantiated composite literal
types are not recognized properly: T(P){...} fails to parse.
This commit is contained in:
Robert Griesemer 2019-05-14 18:00:59 -07:00
parent 869c7a4e29
commit 6069527287
6 changed files with 220 additions and 224 deletions

View File

@ -257,11 +257,12 @@ func (f *FieldList) NumFields() int {
return n
}
// A TypeParamList represents a list of type parameters, enclosed by parentheses.
// A TypeParamList represents a list of type parameters with contract, enclosed by parentheses.
type TypeParamList struct {
Lparen token.Pos // position of "("
Names []*Ident // type parameter names; or nil
Rparen token.Pos // position of ")"
Lparen token.Pos // position of "("
Names []*Ident // type parameter names; or nil
Contract Expr // contract; or nil
Rparen token.Pos // position of ")"
}
func (t *TypeParamList) Pos() token.Pos { return t.Lparen }
@ -464,7 +465,7 @@ type (
// A ContractType node represents a contract.
ContractType struct {
Contract token.Pos // position of "contract" pseudo keeyword
Contract token.Pos // position of "contract" pseudo keyword
Params *TypeParamList // list of type parameters; non-nil
Lbrace token.Pos // position of "{"
Constraints []*Constraint // list of constraints
@ -913,11 +914,12 @@ type (
// A TypeSpec node represents a type declaration (TypeSpec production).
TypeSpec struct {
Doc *CommentGroup // associated documentation; or nil
Name *Ident // type name
Assign token.Pos // position of '=', if any
Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
Comment *CommentGroup // line comments; or nil
Doc *CommentGroup // associated documentation; or nil
Name *Ident // type name
TPar *TypeParamList // type parameters; or nil
Assign token.Pos // position of '=', if any
Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
Comment *CommentGroup // line comments; or nil
}
)

View File

@ -180,7 +180,7 @@ func TestErrors(t *testing.T) {
}
for _, fi := range list {
name := fi.Name()
if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") {
if !fi.IsDir() && !strings.HasPrefix(name, ".") && (strings.HasSuffix(name, ".src") || strings.HasSuffix(name, ".go2")) {
checkErrors(t, filepath.Join(testdata, name), nil)
}
}

View File

@ -26,7 +26,7 @@ import (
"unicode"
)
const useBrackets = true
const useBrackets = false
// The parser structure holds the parser's internal state.
type parser struct {
@ -706,54 +706,6 @@ func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident {
return idents
}
func (p *parser) parseFieldDeclOld(scope *ast.Scope) *ast.Field {
if p.trace {
defer un(trace(p, "FieldDecl"))
}
doc := p.leadComment
// 1st FieldDecl
// A type name used as an anonymous field looks like a field identifier.
var list []ast.Expr
for {
list = append(list, p.parseVarType(false))
if p.tok != token.COMMA {
break
}
p.next()
}
typ := p.tryVarType(false)
// analyze case
var idents []*ast.Ident
if typ != nil {
// IdentifierList Type
idents = p.makeIdentList(list)
} else {
// ["*"] TypeName (AnonymousField)
typ = list[0] // we always have at least one element
if n := len(list); n > 1 {
p.errorExpected(p.pos, "type")
typ = &ast.BadExpr{From: p.pos, To: p.pos}
} else if !isTypeName(deref(typ)) {
p.errorExpected(typ.Pos(), "anonymous field")
typ = &ast.BadExpr{From: typ.Pos(), To: p.safePos(typ.End())}
}
}
tag := p.parseOptionalTag()
p.expectSemi() // call before accessing p.linecomment
field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment}
p.declare(field, nil, scope, ast.Var, idents...)
p.resolve(typ)
return field
}
func (p *parser) parseOptionalTag() (tag *ast.BasicLit) {
if p.trace {
defer un(trace(p, "OptionalTag"))
@ -966,35 +918,6 @@ func (p *parser) parsePointerType(typeContext bool) *ast.StarExpr {
return &ast.StarExpr{Star: star, X: base}
}
// If the result is an identifier, it is not resolved.
func (p *parser) tryVarType(isParam bool) ast.Expr {
if isParam && p.tok == token.ELLIPSIS {
pos := p.pos
p.next()
typ := p.tryIdentOrType(true) // don't use parseType so we can provide better error message
if typ != nil {
p.resolve(typ)
} else {
p.error(pos, "'...' parameter is missing type")
typ = &ast.BadExpr{From: pos, To: p.pos}
}
return &ast.Ellipsis{Ellipsis: pos, Elt: typ}
}
return p.tryIdentOrType(true)
}
// If the result is an identifier, it is not resolved.
func (p *parser) parseVarType(isParam bool) ast.Expr {
typ := p.tryVarType(isParam)
if typ == nil {
pos := p.pos
p.errorExpected(pos, "type")
p.next() // make progress
typ = &ast.BadExpr{From: pos, To: p.pos}
}
return typ
}
func (p *parser) parseDotsType() *ast.Ellipsis {
if p.trace {
defer un(trace(p, "DotsType"))
@ -1025,8 +948,12 @@ func (p *parser) parseParamDeclOrNil() (f field) {
f.typ = p.parseType(true)
case token.LBRACK:
// name[type1, type2, ...] or name []type or name [len]type
f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name)
if useBrackets {
// name[type1, type2, ...] or name []type or name [len]type
f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name)
} else {
f.typ = p.parseType(true)
}
case token.ELLIPSIS:
// name ...type
@ -1154,66 +1081,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [
return
}
func (p *parser) parseParameterListOld(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) {
if p.trace {
defer un(trace(p, "ParameterList"))
}
// 1st ParameterDecl
// A list of identifiers looks like a list of type names.
var list []ast.Expr
for {
list = append(list, p.parseVarType(ellipsisOk))
if p.tok != token.COMMA {
break
}
p.next()
if p.tok == token.RPAREN {
break
}
}
// analyze case
if typ := p.tryVarType(ellipsisOk); typ != nil {
// IdentifierList Type
idents := p.makeIdentList(list)
field := &ast.Field{Names: idents, Type: typ}
params = append(params, field)
// Go spec: The scope of an identifier denoting a function
// parameter or result variable is the function body.
p.declare(field, nil, scope, ast.Var, idents...)
p.resolve(typ)
if !p.atComma("parameter list", token.RPAREN) {
return
}
p.next()
for p.tok != token.RPAREN && p.tok != token.EOF {
idents := p.parseIdentList()
typ := p.parseVarType(ellipsisOk)
field := &ast.Field{Names: idents, Type: typ}
params = append(params, field)
// Go spec: The scope of an identifier denoting a function
// parameter or result variable is the function body.
p.declare(field, nil, scope, ast.Var, idents...)
p.resolve(typ)
if !p.atComma("parameter list", token.RPAREN) {
break
}
p.next()
}
return
}
// Type { "," Type } (anonymous parameters)
params = make([]*ast.Field, len(list))
for i, typ := range list {
p.resolve(typ)
params[i] = &ast.Field{Type: typ}
}
return
}
func (p *parser) parseTypeParams(scope *ast.Scope, contractOk bool) *ast.TypeParamList {
func (p *parser) parseTypeParams(scope *ast.Scope) *ast.TypeParamList {
if p.trace {
defer un(trace(p, "TypeParams"))
}
@ -1228,19 +1096,20 @@ func (p *parser) parseTypeParams(scope *ast.Scope, contractOk bool) *ast.TypePar
}
var names []*ast.Ident
var contract ast.Expr
if p.tok != token.RBRACK && p.tok != token.RPAREN {
names = p.parseIdentList()
}
if contractOk && p.tok == token.IDENT {
if p.tok == token.IDENT {
// contract
p.parseType(true)
contract = p.parseType(true)
}
if lbrack.IsValid() {
rbrack = p.expect(token.RBRACK)
}
tparams := &ast.TypeParamList{Lparen: lbrack, Names: names, Rparen: rbrack}
tparams := &ast.TypeParamList{Lparen: lbrack, Names: names, Contract: contract, Rparen: rbrack}
p.declare(tparams, nil, scope, ast.Typ, names...)
return tparams
}
@ -1253,14 +1122,14 @@ func (p *parser) parseParameters(scope *ast.Scope, typeParamsOk, ellipsisOk bool
var lparen token.Pos
if useBrackets && p.tok == token.LBRACK {
// assume [type T](params) syntax
tparams = p.parseTypeParams(scope, true)
tparams = p.parseTypeParams(scope)
lparen = p.expect(token.LPAREN)
} else {
// assume (type T)(params) syntax
lparen = p.expect(token.LPAREN)
if p.tok == token.TYPE {
p.next()
tparams = p.parseTypeParams(scope, true)
tparams = p.parseTypeParams(scope)
rparen := p.expect(token.RPAREN)
// fix tparams
@ -1407,14 +1276,13 @@ func (p *parser) parseChanType(typeContext bool) *ast.ChanType {
return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}
}
// ContractType = "contract" "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" .
// ContractType = "(" [ IdentList [ "," ] ] ")" "{" { Constraint ";" } "}" .
// (The "contract" keyword is already consumed.)
func (p *parser) parseContractType() *ast.ContractType {
if p.trace {
defer un(trace(p, "ContractType"))
}
pos := p.expect(token.IDENT) // TODO(gri) check that it is "contract"
var names []*ast.Ident
lparen := p.expect(token.LPAREN)
scope := ast.NewScope(nil) // contract scope
@ -1437,12 +1305,11 @@ func (p *parser) parseContractType() *ast.ContractType {
}
rbrace := p.expect(token.RBRACE)
return &ast.ContractType{Contract: pos, Params: params, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace}
return &ast.ContractType{Params: params, Lbrace: lbrace, Constraints: constraints, Rbrace: rbrace}
}
// Constraint = TypeParam TypeConstraint | TypeParam MethodName Signature | ContractTypeName "(" [ TypeList [ "," ] ] ")" .
// Constraint = TypeParam Type | TypeParam MethodName Signature | ContractTypeName "(" [ TypeList [ "," ] ] ")" .
// TypeParam = Ident .
// TypeConstraint = Type | "0" | "0.0" | "0i" | "==" | "!=" .
// ContractTypeName = TypeName.
func (p *parser) parseConstraint() *ast.Constraint {
if p.trace {
@ -1461,37 +1328,16 @@ func (p *parser) parseConstraint() *ast.Constraint {
param = &ast.Ident{NamePos: tname.Pos(), Name: "_"}
}
// type constraint or method
var mname *ast.Ident
var typ ast.Expr
switch p.tok {
case token.INT:
typ = p.wantLit("0")
case token.FLOAT:
typ = p.wantLit("0.0")
case token.IMAG:
typ = p.wantLit("0i")
case token.EQL:
mname = &ast.Ident{NamePos: p.pos, Name: "=="}
p.next()
case token.NEQ:
mname = &ast.Ident{NamePos: p.pos, Name: "!="}
p.next()
default:
// type constraint or method
typ = p.parseType(false)
if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
mname = ident
scope := ast.NewScope(nil) // method scope
_, params := p.parseParameters(scope, false, true)
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results}
}
typ := p.parseType(false)
if ident, isIdent := typ.(*ast.Ident); isIdent && p.tok == token.LPAREN {
// method
mname = ident
scope := ast.NewScope(nil) // method scope
_, params := p.parseParameters(scope, false, true)
results := p.parseResult(scope, true)
typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results}
}
// param != nil
@ -1528,13 +1374,6 @@ func (p *parser) parseTypeInstance(typ ast.Expr) *ast.CallExpr {
return &ast.CallExpr{Fun: typ, Lparen: opening, Args: list, Rparen: closing}
}
func (p *parser) wantLit(lit string) *ast.BasicLit {
if p.lit != lit {
p.errorExpected(p.pos, lit)
}
return p.parseOperand(false).(*ast.BasicLit)
}
// If the result is an identifier, it is not resolved.
// typeContext controls whether a trailing type parameter list (opening "(")
// following a type is consumed. We need this to disambiguate an expression
@ -1546,7 +1385,11 @@ func (p *parser) tryIdentOrType(typeContext bool) ast.Expr {
case token.IDENT:
// TODO(gri) we need to be smarter about this to avoid problems with existing code
if p.lit == "contract" {
return p.parseContractType()
pos := p.pos
p.next()
typ := p.parseContractType()
typ.Contract = pos // set keyword position
return typ
}
typ := p.parseTypeName(nil)
if typeContext && (useBrackets && p.tok == token.LBRACK || p.tok == token.LPAREN) {
@ -2032,6 +1875,7 @@ L:
x = p.parseCallOrConversion(p.checkExprOrType(x))
case token.LBRACE:
// TODO(gri) currently this doesn't accept instantiated types
// Use code from cmd/compile/internal/syntax/parser.go.
if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
if lhs {
p.resolve(x)
@ -2919,17 +2763,18 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.
if p.tok == token.TYPE {
// parametrized type
p.next()
scope := ast.NewScope(nil)
tparams := p.parseTypeParams(scope, false)
p.openScope()
tparams := p.parseTypeParams(p.topScope)
tparams.Lparen = lbrack
tparams.Rparen = p.expect(token.RBRACK)
// TODO(gri) record scope and tparams with spec
spec.TPar = tparams
if p.tok == token.ASSIGN {
// type alias
spec.Assign = p.pos
p.next()
}
spec.Type = p.parseType(true)
p.closeScope()
} else {
// array type
alen := p.parseArrayLen()
@ -2944,17 +2789,18 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.
if p.tok == token.TYPE {
// parametrized type
p.next()
scope := ast.NewScope(nil)
tparams := p.parseTypeParams(scope, false)
p.openScope()
tparams := p.parseTypeParams(p.topScope)
tparams.Lparen = lparen
tparams.Rparen = p.expect(token.RPAREN)
// TODO(gri) record scope and tparams with spec
spec.TPar = tparams
if p.tok == token.ASSIGN {
// type alias
spec.Assign = p.pos
p.next()
}
spec.Type = p.parseType(true)
p.closeScope()
} else {
// parenthesized type
typ := p.parseType(true)
@ -2978,6 +2824,23 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.
return spec
}
func (p *parser) parseContractSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec {
if p.trace {
defer un(trace(p, "ContractSpec"))
}
// For now we represent a contract specification like a type representation.
// They cannot have "outer" type parameters, though.
ident := p.parseIdent()
typ := p.parseContractType()
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
spec.Comment = p.lineComment
return spec
}
func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl {
if p.trace {
defer un(trace(p, "GenDecl("+keyword.String()+")"))
@ -3087,6 +2950,14 @@ func (p *parser) parseDecl(sync map[token.Token]bool) ast.Decl {
case token.FUNC:
return p.parseFuncDecl()
case token.IDENT:
// TODO(gri) we need to be smarter about this to avoid problems with existing code
if p.lit == "contract" {
f = p.parseContractSpec
break
}
fallthrough
default:
pos := p.pos
p.errorExpected(pos, "declaration")

View File

@ -50,35 +50,43 @@ var valids = []string{
`package p; type (T = p.T; _ = struct{}; x = *T)`,
`package p; type T (*int)`,
`package p; type T(type P) struct { P }`,
`package p; type T(type P comparable) struct { P }`,
`package p; type T(type P comparable(P)) struct { P }`,
`package p; type T(type P1, P2) struct { P1; f []P2 }`,
`package p; type T[type] struct { P }`,
`package p; type T[type P] struct { P }`,
`package p; type T[type P1, P2] struct { P1; f []P2 }`,
// `package p; type T[type] struct { P }`,
// `package p; type T[type P] struct { P }`,
// `package p; type T[type P1, P2] struct { P1; f []P2 }`,
`package p; var _ = [](T(int)){}`,
`package p; var _ = func()T(nil)`,
`package p; func _(type)()`,
`package p; func _(type)()()`,
`package p; func _(T[P])`,
`package p; func _(T (P))`,
`package p; func _((T(P)))`,
`package p; func _(T []E)`,
`package p; func _(T [P]E)`,
`package p; func _(T[P1, P2, P3])`,
`package p; func _(x T(P1, P2, P3))`,
`package p; func _((T(P1, P2, P3)))`,
`package p; func _(type A, B)(a A) B`,
`package p; func _(type A, B C)(a A) B`,
`package p; func _(type A, B C(A, B))(a A) B`,
`package p; type _ struct { T[P] }`,
`package p; type _ struct { T []E }`,
`package p; type _ struct { T [P]E }`,
`package p; type _ struct { imported.T[P] }`,
`package p; type _ struct { imported.T[P1, P2] }`,
`package p; func _[type]()`,
`package p; func _[type]()()`,
`package p; func _[type A, B](a A) B`,
`package p; func _[type A, B C](a A) B`,
`package p; func _[type A, B C(A, B)](a A) B`,
// `package p; type _ struct { T[P] }`,
// `package p; type _ struct { T []E }`,
// `package p; type _ struct { T [P]E }`,
// `package p; type _ struct { imported.T[P] }`,
// `package p; type _ struct { imported.T[P1, P2] }`,
// `package p; func _[type]()`,
// `package p; func _[type]()()`,
// `package p; func _[type A, B](a A) B`,
// `package p; func _[type A, B C](a A) B`,
// `package p; func _[type A, B C(A, B)](a A) B`,
`package p; contract C(){}`,
`package p; contract C(T, S, R,){}`,
`package p; contract C(T){ T (m(x, int)); }`,
`package p; contract (C1(){}; C2(){})`,
`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 0.0; T ==; T m(x int) float64; C0(); imported.C1(int, T,) }`,
`package p; type C contract(T){ T int; T imported.T; T chan<-int; T m(x int) float64; C0(); imported.C1(int, T,) }`,
}
func TestValid(t *testing.T) {
@ -87,6 +95,12 @@ func TestValid(t *testing.T) {
}
}
// TestSingle is useful to track down a problem with a single short test program.
func TestSingle(t *testing.T) {
const src = `package p; func _((T(a, b,)))`
checkErrors(t, src, src)
}
var invalids = []string{
`foo /* ERROR "expected 'package'" */ !`,
`package p; func f() { if { /* ERROR "missing condition" */ } };`,
@ -136,7 +150,6 @@ var invalids = []string{
//`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; type C contract(T) { T 00.i /* ERROR "expected 0i" */ }`,
// issue 8656
`package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`,

111
src/go/parser/testdata/map.go2 vendored Normal file
View File

@ -0,0 +1,111 @@
// Package orderedmap provides an ordered map, implemented as a binary tree.
package orderedmap
import "chans"
// Map is an ordered map.
type Map(type K, V) struct {
root *node(K, V)
compare func(K, K) int
}
// node is the type of a node in the binary tree.
type node(type K, V) struct {
key K
val V
left, right *node(K, V)
}
// New returns a new map.
func New(type K, V)(compare func(K, K) int) *Map(K, V) {
// TODO
// return &Map(K, V){compare: compare}
}
// find looks up key in the map, and returns either a pointer
// to the node holding key, or a pointer to the location where
// such a node would go.
func (m *Map(K, V)) find(key K) **node(K, V) {
pn := &m.root
for *pn != nil {
switch cmp := m.compare(key, (*pn).key); {
case cmp < 0:
pn = &(*pn).left
case cmp > 0:
pn = &(*pn).right
default:
return pn
}
}
return pn
}
// Insert inserts a new key/value into the map.
// If the key is already present, the value is replaced.
// Returns true if this is a new key, false if already present.
func (m *Map(K, V)) Insert(key K, val V) bool {
pn := m.find(key)
if *pn != nil {
(*pn).val = val
return false
}
// TODO
// *pn = &node(K, V){key: key, val: val}
return true
}
// Find returns the value associated with a key, or zero if not present.
// The found result reports whether the key was found.
func (m *Map(K, V)) Find(key K) (V, bool) {
pn := m.find(key)
if *pn == nil {
var zero V // see the discussion of zero values, above
return zero, false
}
return (*pn).val, true
}
// keyValue is a pair of key and value used when iterating.
type keyValue(type K, V) struct {
key K
val V
}
// InOrder returns an iterator that does an in-order traversal of the map.
func (m *Map(K, V)) InOrder() *Iterator(K, V) {
sender, receiver := chans.Ranger(keyValue(K, V))()
var f func(*node(K, V)) bool
f = func(n *node(K, V)) bool {
if n == nil {
return true
}
// Stop sending values if sender.Send returns false,
// meaning that nothing is listening at the receiver end.
return f(n.left) &&
// TODO
// sender.Send(keyValue(K, V){n.key, n.val}) &&
f(n.right)
}
go func() {
f(m.root)
sender.Close()
}()
return &Iterator{receiver}
}
// Iterator is used to iterate over the map.
type Iterator(type K, V) struct {
r *chans.Receiver(keyValue(K, V))
}
// Next returns the next key and value pair, and a boolean indicating
// whether they are valid or whether we have reached the end.
func (it *Iterator(K, V)) Next() (K, V, bool) {
keyval, ok := it.r.Next()
if !ok {
var zerok K
var zerov V
return zerok, zerov, false
}
return keyval.key, keyval.val, true
}

View File

@ -99,7 +99,6 @@ var tests = [][]string{
{"testdata/issue28251.src"},
{"testdata/issue6977.src"},
{"testdata/typeparams.src"},
{"testdata/typeparams2.src"},
}
var fset = token.NewFileSet()