go/*: change representation of type lists in interface types, adjusted dependent code

Until now, types of type lists in interface types were collected in
a single expression list "Types". This made it not possible to gofmt
such interfaces while preserving the original layout of type lists.

This change represents types of type lists as part of an ast.FieldList.
The ast.InterfaceType.Methods field list now represents embedded interfaces,
methods, or type list types through ast.Fields. This preserves all position
information and thus permits accurate gofmt-ing.

The new representation is as follows: For an ast.Field f, if
- len(f.Names) == 0        : f.Type is an embedded interface
- f.Names[0].Name == "type": f.Type is a type of a type list
- otherwise                : f represents a method

Since "type" is a keyword, a Go field name cannot be "type".
Fields of types of type lists that share the same "type" keyword
in the source share the same f.Names[0] identifier (named "type"),
and the position of that identifier is the position of the "type"
keyword in the source.

Related changes:
- Adjusted go/parser to build the new representation.
- Adjusted go/printer and implemented formatting of type lists.
  (This is still not quite correct if there are comments, but
  that is fine-tuning).
- Adjusted go/types to work with the new representation.
- Implemented SplitFieldList and MergeFieldList in go2go
  translator to easily switch between old and new representation.
- Updated documentation.

Change-Id: I016e2ce5949bfe294509d703156c41d42cf7084e
This commit is contained in:
Robert Griesemer 2020-03-26 16:53:31 -07:00
parent e5e519e893
commit a31f6ae875
11 changed files with 165 additions and 54 deletions

View File

@ -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.

View File

@ -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:

View File

@ -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 = &copy
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) {

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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{}

View File

@ -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{}

View File

@ -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?)

View File

@ -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.

View File

@ -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)
}

View File

@ -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