diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 10c82dbebd..6edcf474a6 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -918,11 +918,23 @@ type ( ) type Constraint struct { + // Invariant: Param == nil || len(MNames) == len(Types) + // MNames entries will be nil if we have a list of types 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) } +func (c *Constraint) Pos() token.Pos { + if c.Param != nil { + return c.Param.Pos() + } + if len(c.MNames) > 0 { + return c.MNames[0].Pos() + } + return c.Types[0].Pos() +} + // Pos and End implementations for spec nodes. func (s *ImportSpec) Pos() token.Pos { diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 6eeb9df96f..5b5564e99f 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -2607,6 +2607,10 @@ func (p *parser) parseConstraint() *ast.Constraint { } // list of type constraints or methods + // invariant: len(mnames) == len(types) + // TODO(gri) We probably can simplify this again and only accept either + // a (single) method or a type list. It seems unlikely that + // people insist on writing T m1(), m2() . var mnames []*ast.Ident var types []ast.Expr for { diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index ce476d786b..efcba273b8 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -956,6 +956,7 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { case *ast.InterfaceType: p.print(token.INTERFACE) p.fieldList(x.Methods, false, x.Incomplete) + // TODO(gri) implement printing of type lists case *ast.MapType: p.print(token.MAP, token.LBRACK) @@ -1069,27 +1070,25 @@ func (p *printer) expr(x ast.Expr) { p.expr1(x, token.LowestPrec, depth) } -// TODO(gri) complete this func (p *printer) constraint(x *ast.Constraint) { - if x.Param != nil { - p.linebreak(p.lineFor(x.Types[0].Pos()), 1, ignore, false) - p.print(indent, x.Param, blank) - if len(x.MNames) > 0 { - // method names - for i, m := range x.MNames { - p.print(m) - t := x.Types[i].(*ast.FuncType) - p.signature(t) - //p.print(token.COMMA) - } - } - p.print(unindent) - } else if len(x.Types) > 0 { - p.linebreak(p.lineFor(x.Types[0].Pos()), 1, ignore, false) - p.print(indent) - p.exprList(token.NoPos, x.Types, 1, 0, token.NoPos, false) - p.print(unindent) + p.print(x.Pos()) + if x.Param == nil { + // embedded contract + p.expr(x.Types[0]) + return } + // method or list of types + p.print(x.Param, blank) + if len(x.MNames) > 0 && x.MNames[0] != nil { + // single method + // For now we only support a single method + // even though the parser is more relaxed. + p.print(x.MNames[0]) + p.signature(x.Types[0].(*ast.FuncType)) + return + } + // list of types + p.exprList(token.NoPos, x.Types, 1, 0, token.NoPos, false) } // ---------------------------------------------------------------------------- @@ -1636,16 +1635,17 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { } p.print(token.RPAREN) if len(s.Constraints) > 0 { - p.print(blank) - } - p.print(s.Lbrace, token.LBRACE) - for _, c := range s.Constraints { - p.constraint(c) - } - if len(s.Constraints) > 0 { + p.print(blank, s.Lbrace, token.LBRACE, indent) + for _, c := range s.Constraints { + p.linebreak(p.lineFor(c.Pos()), 1, ignore, false) + p.constraint(c) + } + p.print(unindent) p.linebreak(p.lineFor(s.Rbrace), 1, ignore, true) + p.print(s.Rbrace, token.RBRACE) + } else { + p.print(s.Lbrace, token.LBRACE, s.Rbrace, token.RBRACE) } - p.print(s.Rbrace, token.RBRACE) p.setComment(s.Comment) default: diff --git a/src/go/printer/testdata/contracts.golden b/src/go/printer/testdata/contracts.golden index 499261d0e5..00137185b2 100644 --- a/src/go/printer/testdata/contracts.golden +++ b/src/go/printer/testdata/contracts.golden @@ -13,7 +13,31 @@ contract ( ) contract _(T) { + T int + T float32, string, struct{} + (C0) + C0() + C1(T) + C2(T, T, T) T m() + T int +} + +contract _(A, B, C) { + A a() // only a will be printed for now + B a() + + // a 1st comment + // a 2nd comment + C a() +} + +type _(type T) interface{} +type _(type T) interface { + m(T) +} +type _ interface { + // cannot print interface type lists yet } type _(type T) struct{} diff --git a/src/go/printer/testdata/contracts.input b/src/go/printer/testdata/contracts.input index eec6c5555c..c3a8f10566 100644 --- a/src/go/printer/testdata/contracts.input +++ b/src/go/printer/testdata/contracts.input @@ -13,7 +13,31 @@ contract( ) contract _(T) { + T int + T float32, string, struct{} + (C0) + C0() + C1(T) + C2(T, T, T) T m() + T int +} + +contract _(A, B, C) { + A a(), b(), c() // only a will be printed for now + B a() + + // a 1st comment + // a 2nd comment + C a() +} + +type _(type T) interface{} +type _(type T) interface{ + m(T) +} +type _ interface { + type int // cannot print interface type lists yet } type _(type T) struct{} diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 55b54dea7f..48531f9ed9 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -4,6 +4,7 @@ so we have a better track record. I only switched to this file in Nov 2019, henc ---------------------------------------------------------------------------------------------------- TODO +- go/printer: implement printing of type lists - review use of Contract.TParams field - it seems like it's only needed for length checks? - review handling of fields of instantiated generic types (do we need to make them non-parameterized, similar to what we did for the embedded interfaces created by contract embedding?) @@ -124,9 +125,25 @@ DESIGN/IMPLEMENTATION a method, even if they are not used by the method. Since the receiver acts like an implicit declaration of those type parameters, they may be blank, as with any other declaration. -- 3/20/2020: Local type declarations with an underlying type that is type parameter lose the +- 3/20/2020: Local type declarations with an underlying type that is a type parameter lose the methods declared with the type parameter bound. But they don't lose the properties of the underlying type, i.e., the properties of the type parameter bound's type list. This is something to consider if we were contemplating moving to a methods-only approach (no type lists), even though local type declarations are exceedingly rare if they exist at all in the wild. + +- 3/24/2020: Implemented initial support for bidirection type unification which could make + type inference more powerful if we decided to go that route. Specifically, this would + permit type inference for the type parameters of a generic function argument. Given: + func h(f func(int)); func g(type T)(T); one could allow the call: h(g) and the type argument + T of g would be inferred to be int. While not hard to implement, this would be a special case + of the rule that all generic types/functions must be instantiated before they are used except + for function calls where the type arguments can be inferred from the actual arguments. + Decided that for now we leave things as is, since it's not clear the extra complexity is + worth the (probably small) convenience. + +- 3/25/2020: We can probably simplify the contract syntax again and only permit one of three + possible constraint entries: 1) an embedded contract, 2) a type parameter followed by a + method signature, and 3) a type parameter followed by a type list. This is what the type + checker currently supports and the printer can print. (The parser still accepts a list of + method signatures or types, freely mixed.)