go/types: collect function type parameters during resolve phase

- move init and method parameter checks to resolve phase as well
- more consistent error messages
- more tests

Change-Id: I6cb147b35385541ca5d7d7e3f87159df84cced76
This commit is contained in:
Robert Griesemer 2019-06-28 17:55:17 -07:00
parent db132013f4
commit fa30b485bc
5 changed files with 44 additions and 25 deletions

View File

@ -660,11 +660,7 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature)
obj.typ = sig // guard against cycles
fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.TParams, fdecl.Type)
if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
// ok to continue
}
check.funcType(sig, fdecl.Recv, obj.scope, obj.tparams, fdecl.Type)
// function body must be type-checked after global declarations
// (functions implemented elsewhere have no body)
@ -787,7 +783,9 @@ func (check *Checker) declStmt(decl ast.Decl) {
case *ast.TypeSpec:
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
obj.scope, obj.tparams = check.collectTypeParams(check.scope, s, s.TParams)
if s.TParams != nil {
obj.scope, obj.tparams = check.collectTypeParams(check.scope, s, s.TParams)
}
// spec: "The scope of a type identifier declared inside a function
// begins at the identifier in the TypeSpec and ends at the end of
// the innermost containing block."

View File

@ -213,7 +213,6 @@ func (*Const) isDependency() {} // a constant may be a dependency of an initiali
// A TypeName represents a name for a (defined or alias) type.
type TypeName struct {
object
// TODO(gri) For Funcs, we have the type parameters on the signature. Revisit that decision.
scope *Scope // type parameter scope; or nil
tparams []*TypeName // type parameters from left to right; or nil
}
@ -301,7 +300,9 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
// An abstract method may belong to many interfaces due to embedding.
type Func struct {
object
hasPtrRecv bool // only valid for methods that don't have a type yet
hasPtrRecv bool // only valid for methods that don't have a type yet
scope *Scope // type parameter scope; or nil
tparams []*TypeName // type parameters from left to right; or nil
}
// NewFunc returns a new function with the given signature, representing
@ -312,7 +313,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
if sig != nil {
typ = sig
}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false}
return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false, nil, nil}
}
// FullName returns the package- or receiver-type-qualified name of

View File

@ -215,6 +215,7 @@ func (check *Checker) collectObjects() {
}
var methods []*Func // list of methods with non-blank _ names
var fileScopes []*Scope
for fileNo, file := range check.files {
// The package identifier denotes the current package,
// but there is no corresponding package object.
@ -228,6 +229,7 @@ func (check *Checker) collectObjects() {
pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
}
fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo))
fileScopes = append(fileScopes, fileScope)
check.recordScope(file, fileScope)
// determine file directory, necessary to resolve imports
@ -386,7 +388,9 @@ func (check *Checker) collectObjects() {
case *ast.TypeSpec:
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
obj.scope, obj.tparams = check.collectTypeParams(pkg.scope, s, s.TParams)
if s.TParams != nil {
obj.scope, obj.tparams = check.collectTypeParams(pkg.scope, s, s.TParams)
}
check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s})
default:
@ -400,18 +404,32 @@ func (check *Checker) collectObjects() {
if d.Recv == nil {
// regular function
if name == "init" {
if d.TParams != nil {
check.softErrorf(d.TParams.Pos(), "func init must have no type parameters")
}
if t := d.Type; t.Params.NumFields() != 0 || t.Results != nil {
check.softErrorf(d.Pos(), "func init must have no arguments and no return values")
}
// don't declare init functions in the package scope - they are invisible
obj.parent = pkg.scope
check.recordDef(d.Name, obj)
// init functions must have a body
if d.Body == nil {
// TODO(gri) make this error message consistent with the others above
check.softErrorf(obj.pos, "missing function body")
}
} else {
if d.TParams != nil {
obj.scope, obj.tparams = check.collectTypeParams(pkg.scope, d, d.TParams)
}
check.declare(pkg.scope, d.Name, obj, token.NoPos)
}
} else {
// method
if d.TParams != nil {
// TODO(gri) should this be done in the parser (and this an invalidAST error)?
check.softErrorf(d.TParams.Pos(), "method must have no type parameters")
}
// (Methods with blank _ names are never found; no need to collect
// them for later type association. They will still be type-checked
// with all the other functions.)
@ -435,7 +453,7 @@ func (check *Checker) collectObjects() {
}
// verify that objects in package and file scopes have different names
for _, scope := range check.pkg.scope.children /* file scopes */ {
for _, scope := range fileScopes {
for _, obj := range scope.elems {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
if pkg, ok := obj.(*PkgName); ok {
@ -473,10 +491,6 @@ func (check *Checker) collectObjects() {
}
func (check *Checker) collectTypeParams(parent *Scope, node ast.Node, list *ast.FieldList) (scope *Scope, tparams []*TypeName) {
if list.NumFields() == 0 {
return
}
scope = NewScope(parent, node.Pos(), node.End(), "type parameters")
check.recordScope(node, scope)

View File

@ -111,3 +111,15 @@ var _ = f8(1, 2.3, 3.4, 4 /* ERROR does not match */ )
var _ = f8(int, float64)(1, 2.3, 3.4, 4)
var _ = f8(int, float64)(0, 0, nil...) // test case for #18268
// init function and methods cannot have type parameters
func init() {}
func init(/* ERROR func init must have no type parameters */ type)() {}
func init(/* ERROR func init must have no type parameters */ type P)() {}
type T struct {}
func (T) m1() {}
func (T) m2( /* ERROR method must have no type parameters */ type)() {}
func (T) m3( /* ERROR method must have no type parameters */ type P)() {}

View File

@ -143,13 +143,11 @@ func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) {
}
// funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, tpar *ast.FieldList, ftyp *ast.FuncType) {
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, scope *Scope, tparams []*TypeName, ftyp *ast.FuncType) {
// type parameters are in a scope enclosing the function scope
// TODO(gri) get rid of fake ast.Ident - only here to satisfy collectTypeParams
scope, tparams := check.collectTypeParams(check.scope, new(ast.Ident), tpar)
if scope != nil {
// TODO(gri) push/pop both (type parameter and function) scopes
if tparams != nil {
check.scope = scope
// TODO(gri) push/pop both (type parameter and function) scopes
// defer check.closeScope()
}
@ -209,10 +207,6 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, tpar *ast
}
}
sig.recv = recv
// A method cannot have type parameters - this should be checked by the parser.
if len(tparams) > 0 {
check.invalidAST(tpar.Pos(), "method cannot have type parameters")
}
}
sig.scope = scope
@ -301,7 +295,7 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type {
case *ast.FuncType:
typ := new(Signature)
def.setUnderlying(typ)
check.funcType(typ, nil, nil, e)
check.funcType(typ, nil, nil, nil, e)
return typ
case *ast.InterfaceType: