diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 6edcf474a6..16da0012f5 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -188,11 +188,14 @@ func isDirective(c string) bool { // in a signature. // Field.Names is nil for unnamed parameters (parameter lists which only contain types) // and embedded struct fields. In the latter case, the field name is the type name. +// Field.Names contains a single name "type" for elements of interface type lists. +// Types belonging to the same type list share the same "type" identifier which also +// records the position of that keyword. // 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 name; or nil + Names []*Ident // field/method/(type) parameter names, or type "type"; or nil + Type Expr // field/method/parameter type, type list type, or contract name; or nil Tag *BasicLit // field tag; or nil Comment *CommentGroup // line comments; or nil } @@ -441,12 +444,10 @@ type ( } // An InterfaceType node represents an interface type. - // 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 - Types []Expr // list of types (TODO(gri) for now they are all lumped together, loosing syntax info) - Incomplete bool // true if (source) methods are missing in the Methods list + Methods *FieldList // list of embedded interfaces, methods, or types + Incomplete bool // true if (source) methods or types are missing in the Methods list } // A MapType node represents a map type. diff --git a/src/go/go2go/instantiate.go b/src/go/go2go/instantiate.go index f508eb9606..1090465365 100644 --- a/src/go/go2go/instantiate.go +++ b/src/go/go2go/instantiate.go @@ -191,13 +191,13 @@ func (t *translator) instantiateTypeDecl(qid qualifiedIdent, typ *types.Named, a tparams := rtyp.(*ast.CallExpr).Args ta := typeArgsFromExprs(t, astTypes, typeTypes, tparams) newDecl := &ast.FuncDecl{ - Doc: mast.Doc, + Doc: mast.Doc, Recv: &ast.FieldList{ Opening: mast.Recv.Opening, List: []*ast.Field{ { - Doc: mast.Recv.List[0].Doc, - Names: []*ast.Ident{ + Doc: mast.Recv.List[0].Doc, + Names: []*ast.Ident{ mast.Recv.List[0].Names[0], }, Type: newRtype, @@ -788,15 +788,15 @@ func (t *translator) instantiateExpr(ta *typeArgs, e ast.Expr) ast.Expr { Results: results, } case *ast.InterfaceType: - methods := t.instantiateFieldList(ta, e.Methods) - types, typesChanged := t.instantiateExprList(ta, e.Types) + eMethods, eTypes := splitFieldList(e.Methods) + methods := t.instantiateFieldList(ta, eMethods) + types, typesChanged := t.instantiateExprList(ta, eTypes) if methods == e.Methods && !typesChanged { return e } return &ast.InterfaceType{ Interface: e.Interface, - Methods: methods, - Types: types, + Methods: mergeFieldList(methods, types), Incomplete: e.Incomplete, } case *ast.MapType: diff --git a/src/go/go2go/rewrite.go b/src/go/go2go/rewrite.go index 1e0bf423a0..3d76dc55e2 100644 --- a/src/go/go2go/rewrite.go +++ b/src/go/go2go/rewrite.go @@ -23,7 +23,6 @@ var config = printer.Config{ Tabwidth: 8, } - // isParameterizedFuncDecl reports whether fd is a parameterized function. func isParameterizedFuncDecl(fd *ast.FuncDecl, info *types.Info) bool { if fd.Type.TParams != nil { @@ -182,7 +181,7 @@ func rewriteAST(fset *token.FileSet, importer *Importer, importPath string, file if addImportableName { file.Decls = append(file.Decls, &ast.GenDecl{ - Tok: token.TYPE, + Tok: token.TYPE, Specs: []ast.Spec{ &ast.TypeSpec{ Name: ast.NewIdent(t.importableName()), @@ -255,7 +254,7 @@ func rewriteAST(fset *token.FileSet, importer *Importer, importPath string, file switch tok { case token.CONST, token.VAR: spec = &ast.ValueSpec{ - Names: []*ast.Ident{ + Names: []*ast.Ident{ ast.NewIdent("_"), }, Values: []ast.Expr{ @@ -278,7 +277,7 @@ func rewriteAST(fset *token.FileSet, importer *Importer, importPath string, file } file.Decls = append(file.Decls, &ast.GenDecl{ - Tok: tok, + Tok: tok, Specs: []ast.Spec{spec}, }) } @@ -536,8 +535,9 @@ func (t *translator) translateExpr(pe *ast.Expr) { t.translateFieldList(e.Params) t.translateFieldList(e.Results) case *ast.InterfaceType: - t.translateFieldList(e.Methods) - t.translateExprList(e.Types) + methods, types := splitFieldList(e.Methods) + t.translateFieldList(methods) + t.translateExprList(types) case *ast.MapType: t.translateExpr(&e.Key) t.translateExpr(&e.Value) @@ -548,6 +548,42 @@ func (t *translator) translateExpr(pe *ast.Expr) { } } +// TODO(iant) refactor code and get rid of this? +func splitFieldList(fl *ast.FieldList) (methods *ast.FieldList, types []ast.Expr) { + if fl == nil { + return + } + var mfields []*ast.Field + for _, f := range fl.List { + if len(f.Names) > 0 && f.Names[0].Name == "type" { + // type list type + types = append(types, f.Type) + } else { + mfields = append(mfields, f) + } + } + copy := *fl + copy.List = mfields + methods = © + return +} + +// TODO(iant) refactor code and get rid of this? +func mergeFieldList(methods *ast.FieldList, types []ast.Expr) (fl *ast.FieldList) { + fl = methods + if len(types) == 0 { + return + } + if fl == nil { + fl = new(ast.FieldList) + } + name := []*ast.Ident{ast.NewIdent("type")} + for _, typ := range types { + fl.List = append(fl.List, &ast.Field{Names: name, Type: typ}) + } + return +} + // translateExprList translate an expression list from Go with // contracts to Go 1. func (t *translator) translateExprList(el []ast.Expr) { diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 5b5564e99f..7a8c67d08b 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -1053,7 +1053,7 @@ func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field { } } else { // embedded, possibly parameterized interface - // (using the enclosing parentheses to distinguish it from a method declaration) + // (using enclosing parentheses to distinguish it from a method declaration) typ = p.parseType(true) } p.expectSemi() // call before accessing p.linecomment @@ -1072,16 +1072,21 @@ func (p *parser) parseInterfaceType() *ast.InterfaceType { pos := p.expect(token.INTERFACE) lbrace := p.expect(token.LBRACE) scope := ast.NewScope(nil) // interface scope - var mlist []*ast.Field - var tlist []ast.Expr + var list []*ast.Field L: for { switch p.tok { case token.IDENT, token.LPAREN: - mlist = append(mlist, p.parseMethodSpec(scope)) + list = append(list, p.parseMethodSpec(scope)) case token.TYPE: + // all types in a type list share the same field name "type" + // (since type is a keyword, a Go program cannot have that field name) + name := []*ast.Ident{&ast.Ident{NamePos: p.pos, Name: "type"}} p.next() - tlist = append(tlist, p.parseTypeList()...) + // add each type as a field named "type" + for _, typ := range p.parseTypeList() { + list = append(list, &ast.Field{Names: name, Type: typ}) + } p.expectSemi() default: break L @@ -1093,10 +1098,9 @@ L: Interface: pos, Methods: &ast.FieldList{ Opening: lbrace, - List: mlist, + List: list, Closing: rbrace, }, - Types: tlist, } } diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index efcba273b8..f339322cb1 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -473,10 +473,18 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) } p.expr(f.Type) } else { // interface - if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { - // method - p.expr(f.Names[0]) - p.signature(ftyp) + if len(f.Names) > 0 { + // type list type or method + name := f.Names[0] // "type" or method name + p.expr(name) + if name.Name == "type" { + // type list type + p.print(blank) + p.expr(f.Type) + } else { + // method + p.signature(f.Type.(*ast.FuncType)) // don't print "func" + } } else { // embedded interface p.expr(f.Type) @@ -544,19 +552,47 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) } else { // interface var line int + var prev *ast.Ident // previous "type" identifier for i, f := range list { + var name *ast.Ident // first name, or nil + if len(f.Names) > 0 { + name = f.Names[0] + } if i > 0 { - p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) + // don't do a line break (min == 0) if we are printing a list of types + // TODO(gri) this doesn't work quite right if the list of types is + // spread across multiple lines + min := 1 + if prev != nil && name == prev { + min = 0 + } + p.linebreak(p.lineFor(f.Pos()), min, ignore, p.linesFrom(line) > 0) } p.setComment(f.Doc) p.recordLine(&line) - if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { - // method - p.expr(f.Names[0]) - p.signature(ftyp) + if name != nil { + // type list type or method + if name.Name == "type" { + // type list type + if name == prev { + // type is part of a list of types + p.print(token.COMMA, blank) + } else { + // type starts a new list of types + p.print(name, blank) + } + p.expr(f.Type) + prev = name + } else { + // method + p.expr(name) + p.signature(f.Type.(*ast.FuncType)) // don't print "func" + prev = nil + } } else { // embedded interface p.expr(f.Type) + prev = nil } p.setComment(f.Comment) } @@ -956,7 +992,6 @@ 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) diff --git a/src/go/printer/testdata/contracts.golden b/src/go/printer/testdata/contracts.golden index 00137185b2..f45340a072 100644 --- a/src/go/printer/testdata/contracts.golden +++ b/src/go/printer/testdata/contracts.golden @@ -36,8 +36,16 @@ type _(type T) interface{} type _(type T) interface { m(T) } +type _ interface{ type int } // one-liner type _ interface { - // cannot print interface type lists yet + // TODO(gri) comments before type lists are not handled correctly yet + m1() + type int + type bool, string, error + m2() + m3() + type float32, float64, complex64, complex128 + m4() } type _(type T) struct{} diff --git a/src/go/printer/testdata/contracts.input b/src/go/printer/testdata/contracts.input index c3a8f10566..7d8e9096c8 100644 --- a/src/go/printer/testdata/contracts.input +++ b/src/go/printer/testdata/contracts.input @@ -36,8 +36,18 @@ type _(type T) interface{} type _(type T) interface{ m(T) } -type _ interface { - type int // cannot print interface type lists yet +type _ interface { type int } // one-liner +type _ interface{ + // TODO(gri) comments before type lists are not handled correctly yet + m1() + type int + type bool, string, error + m2() + m3() + type float32, + float64, + complex64, complex128 + m4() } type _(type T) struct{} diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 48531f9ed9..9f8ef96d10 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -4,7 +4,6 @@ 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?) diff --git a/src/go/types/README b/src/go/types/README index 52e5caf3eb..d287663c4d 100644 --- a/src/go/types/README +++ b/src/go/types/README @@ -77,8 +77,6 @@ idea how to implement that but we can easily type-check it). MAJOR KNOWN ISSUES -- importing of packages exporting generic code is not implemented - in the type-checker - various type-specific operations (such as sending a message, type assertions, etc.) on expressions of a generic type don't work yet (but are relatively easy to implement going forward) @@ -86,7 +84,6 @@ MAJOR KNOWN ISSUES better in a real implementation (the subscript numbers on type parameters are there to visually identify different parameters with the same name) -- gofmt works only partly with parameterized code See also the NOTES file for a more up-to-date documentation of the current state and issues. diff --git a/src/go/types/exprstring.go b/src/go/types/exprstring.go index 87cd9c57b4..98d41dcfc2 100644 --- a/src/go/types/exprstring.go +++ b/src/go/types/exprstring.go @@ -129,7 +129,7 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) { case *ast.StructType: buf.WriteString("struct{") - writeFieldList(buf, x.Fields, "; ", false) + writeFieldList(buf, x.Fields.List, "; ", false) buf.WriteByte('}') case *ast.FuncType: @@ -137,14 +137,28 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) { writeSigExpr(buf, x) case *ast.InterfaceType: + // separate type list types from method list + // TODO(gri) we can get rid of this extra code if writeExprList does the separation + var types []ast.Expr + var methods []*ast.Field + for _, f := range x.Methods.List { + if len(f.Names) > 1 && f.Names[0].Name == "type" { + // type list type + types = append(types, f.Type) + } else { + // method or embedded interface + methods = append(methods, f) + } + } + buf.WriteString("interface{") - writeFieldList(buf, x.Methods, "; ", true) - if len(x.Types) > 0 { - if len(x.Methods.List) > 0 { + writeFieldList(buf, methods, "; ", true) + if len(types) > 0 { + if len(methods) > 0 { buf.WriteString("; ") } buf.WriteString("type ") - writeExprList(buf, x.Types) + writeExprList(buf, types) } buf.WriteByte('}') @@ -171,7 +185,7 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) { func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { buf.WriteByte('(') - writeFieldList(buf, sig.Params, ", ", false) + writeFieldList(buf, sig.Params.List, ", ", false) buf.WriteByte(')') res := sig.Results @@ -190,12 +204,12 @@ func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { // multiple or named result(s) buf.WriteByte('(') - writeFieldList(buf, res, ", ", false) + writeFieldList(buf, res.List, ", ", false) buf.WriteByte(')') } -func writeFieldList(buf *bytes.Buffer, fields *ast.FieldList, sep string, iface bool) { - for i, f := range fields.List { +func writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { + for i, f := range list { if i > 0 { buf.WriteString(sep) } diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index b16381e581..a9ee169c09 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -658,9 +658,11 @@ func (check *Checker) declareInSet(oset *objset, pos token.Pos, obj Object) bool } func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) { + var types []ast.Expr for _, f := range iface.Methods.List { if len(f.Names) > 0 { - // We have a method with name f.Names[0]. + // We have a method with name f.Names[0], or a type + // of a type list (name.Name == "type"). // (The parser ensures that there's only one method // and we don't care if a constructed AST has more.) name := f.Names[0] @@ -669,6 +671,11 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d continue // ignore } + if name.Name == "type" { + types = append(types, f.Type) + continue + } + typ := check.typ(f.Type) sig, _ := typ.(*Signature) if sig == nil { @@ -708,7 +715,7 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d } // type constraints - ityp.types = check.collectTypeConstraints(iface.Pos(), ityp.types, iface.Types) + ityp.types = check.collectTypeConstraints(iface.Pos(), ityp.types, types) if len(ityp.methods) == 0 && len(ityp.types) == 0 && len(ityp.embeddeds) == 0 { // empty interface