go.tools/go/types: clean up method type checking
- treat receivers like ordinary parameters when checking signatures; concentrate all respective checks in one place - continue to type-check methods even if receiver base type does not satisfy receiver type criteria (comes for free) - don't ignore blank _ methods anymore - various related cleanups As a consequence, the resolving needs one less internal phase. R=adonovan CC=golang-dev https://golang.org/cl/11591045
This commit is contained in:
parent
9ac8940d29
commit
2bff3a03e7
|
@ -32,7 +32,8 @@ import (
|
||||||
// Check type-checks a package and returns the resulting package object,
|
// Check type-checks a package and returns the resulting package object,
|
||||||
// or a nil package and the first error. The package is specified by a
|
// or a nil package and the first error. The package is specified by a
|
||||||
// list of *ast.Files and corresponding file set, and the import path
|
// list of *ast.Files and corresponding file set, and the import path
|
||||||
// the package is identified with. The path must not be empty or dot (".").
|
// the package is identified with. The clean path must not be empty or
|
||||||
|
// dot (".").
|
||||||
//
|
//
|
||||||
// For more control over type-checking and results, use Config.Check.
|
// For more control over type-checking and results, use Config.Check.
|
||||||
func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error) {
|
func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error) {
|
||||||
|
@ -106,10 +107,11 @@ type Info struct {
|
||||||
// If Implicits != nil, it records the object for each node that implicitly
|
// If Implicits != nil, it records the object for each node that implicitly
|
||||||
// declares objects. The following node and object types may appear:
|
// declares objects. The following node and object types may appear:
|
||||||
//
|
//
|
||||||
// node obj
|
// node declared object
|
||||||
|
//
|
||||||
// *ast.ImportSpec *Package (imports w/o renames), or imported objects (dot-imports)
|
// *ast.ImportSpec *Package (imports w/o renames), or imported objects (dot-imports)
|
||||||
// *ast.CaseClause type-specific variable introduced for each single-type type switch clause
|
// *ast.CaseClause type-specific *Var introduced for each single-type type switch clause
|
||||||
// *ast.Field anonymous struct field or parameter, embedded interface
|
// *ast.Field anonymous struct field or parameter *Var
|
||||||
//
|
//
|
||||||
Implicits map[ast.Node]Object
|
Implicits map[ast.Node]Object
|
||||||
}
|
}
|
||||||
|
@ -117,7 +119,7 @@ type Info struct {
|
||||||
// Check type-checks a package and returns the resulting package object, the first
|
// Check type-checks a package and returns the resulting package object, the first
|
||||||
// error if any, and if info != nil, additional type information. The package is
|
// error if any, and if info != nil, additional type information. The package is
|
||||||
// specified by a list of *ast.Files and corresponding file set, and the import
|
// specified by a list of *ast.Files and corresponding file set, and the import
|
||||||
// path the package is identified with. The path must not be empty or dot (".").
|
// path the package is identified with. The clean path must not be empty or dot (".").
|
||||||
func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
|
func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
|
||||||
return conf.check(path, fset, files, info)
|
return conf.check(path, fset, files, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This file implements the Check function, which typechecks a package.
|
// This file implements the Check function, which drives type-checking.
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
@ -29,31 +29,31 @@ const retainASTLinks = true
|
||||||
|
|
||||||
// exprInfo stores type and constant value for an untyped expression.
|
// exprInfo stores type and constant value for an untyped expression.
|
||||||
type exprInfo struct {
|
type exprInfo struct {
|
||||||
isLhs bool // expression is lhs operand of a shift with delayed type check
|
isLhs bool // expression is lhs operand of a shift with delayed type-check
|
||||||
typ *Basic
|
typ *Basic
|
||||||
val exact.Value // constant value; or nil (if not a constant)
|
val exact.Value // constant value; or nil (if not a constant)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A checker is an instance of the type checker.
|
// A checker is an instance of the type-checker.
|
||||||
type checker struct {
|
type checker struct {
|
||||||
conf *Config
|
conf *Config
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
Info
|
pkg *Package // current package
|
||||||
|
|
||||||
// lazily initialized
|
methods map[string][]*Func // maps type names to associated methods
|
||||||
pkg *Package // current package
|
|
||||||
firsterr error // first error encountered
|
|
||||||
methods map[*TypeName]*Scope // maps type names to associated methods
|
|
||||||
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
|
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
|
||||||
untyped map[ast.Expr]exprInfo // map of expressions without final type
|
untyped map[ast.Expr]exprInfo // map of expressions without final type
|
||||||
|
|
||||||
objMap map[Object]*decl // if set we are in the package-global declaration phase (otherwise all objects seen must be declared)
|
firsterr error // first error encountered
|
||||||
topScope *Scope // topScope for lookups, non-global declarations
|
Info // collected type info
|
||||||
iota exact.Value // value of iota in a constant declaration; nil otherwise
|
|
||||||
|
objMap map[Object]*declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
|
||||||
|
topScope *Scope // topScope for lookups, non-global declarations
|
||||||
|
iota exact.Value // value of iota in a constant declaration; nil otherwise
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
funclist []function // list of functions/methods with correct signatures and non-empty bodies
|
funclist []function // list of functions/methods with correct signatures and non-empty bodies
|
||||||
funcsig *Signature // signature of currently typechecked function
|
funcsig *Signature // signature of currently type-checked function
|
||||||
|
|
||||||
// debugging
|
// debugging
|
||||||
indent int // indentation for tracing
|
indent int // indentation for tracing
|
||||||
|
@ -64,7 +64,7 @@ func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
|
||||||
conf: conf,
|
conf: conf,
|
||||||
fset: fset,
|
fset: fset,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
methods: make(map[*TypeName]*Scope),
|
methods: make(map[string][]*Func),
|
||||||
conversions: make(map[*ast.CallExpr]bool),
|
conversions: make(map[*ast.CallExpr]bool),
|
||||||
untyped: make(map[ast.Expr]exprInfo),
|
untyped: make(map[ast.Expr]exprInfo),
|
||||||
}
|
}
|
||||||
|
@ -97,15 +97,15 @@ func (check *checker) recordImplicit(node ast.Node, obj Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type function struct {
|
type function struct {
|
||||||
file *Scope // only valid if resolve is set
|
file *Scope
|
||||||
obj *Func // for debugging/tracing only
|
obj *Func // for debugging/tracing only
|
||||||
sig *Signature
|
sig *Signature
|
||||||
body *ast.BlockStmt
|
body *ast.BlockStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// later adds a function with non-empty body to the list of functions
|
// later adds a function with non-empty body to the list of functions
|
||||||
// that need to be processed after all package-level declarations
|
// that need to be processed after all package-level declarations
|
||||||
// are typechecked.
|
// are type-checked.
|
||||||
//
|
//
|
||||||
func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) {
|
func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) {
|
||||||
// functions implemented elsewhere (say in assembly) have no body
|
// functions implemented elsewhere (say in assembly) have no body
|
||||||
|
@ -170,28 +170,8 @@ func (conf *Config) check(pkgPath string, fset *token.FileSet, files []*ast.File
|
||||||
check.Info = *info
|
check.Info = *info
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) resolveFiles needs to be split up and renamed (cleanup)
|
|
||||||
check.resolveFiles(files[:i])
|
check.resolveFiles(files[:i])
|
||||||
|
|
||||||
// typecheck all function/method bodies
|
|
||||||
// (funclist may grow when checking statements - do not use range clause!)
|
|
||||||
for i := 0; i < len(check.funclist); i++ {
|
|
||||||
f := check.funclist[i]
|
|
||||||
if trace {
|
|
||||||
s := "<function literal>"
|
|
||||||
if f.obj != nil {
|
|
||||||
s = f.obj.name
|
|
||||||
}
|
|
||||||
fmt.Println("---", s)
|
|
||||||
}
|
|
||||||
check.topScope = f.sig.scope // open the function scope
|
|
||||||
check.funcsig = f.sig
|
|
||||||
check.stmtList(f.body.List)
|
|
||||||
if f.sig.results.Len() > 0 && f.body != nil && !check.isTerminating(f.body, "") {
|
|
||||||
check.errorf(f.body.Rbrace, "missing return")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remaining untyped expressions must indeed be untyped
|
// remaining untyped expressions must indeed be untyped
|
||||||
if debug {
|
if debug {
|
||||||
for x, info := range check.untyped {
|
for x, info := range check.untyped {
|
||||||
|
|
|
@ -181,12 +181,10 @@ func (obj *Var) String() string { return obj.toString("var", obj.typ) }
|
||||||
// A Func represents a declared function.
|
// A Func represents a declared function.
|
||||||
type Func struct {
|
type Func struct {
|
||||||
object
|
object
|
||||||
|
|
||||||
decl *ast.FuncDecl // TODO(gri) can we get rid of this field?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFunc(pos token.Pos, pkg *Package, name string, typ Type) *Func {
|
func NewFunc(pos token.Pos, pkg *Package, name string, typ Type) *Func {
|
||||||
return &Func{object{nil, pos, pkg, name, typ}, nil}
|
return &Func{object{nil, pos, pkg, name, typ}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *Func) String() string { return obj.toString("func", obj.typ) }
|
func (obj *Func) String() string { return obj.toString("func", obj.typ) }
|
||||||
|
|
|
@ -6,6 +6,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -29,17 +30,12 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A decl describes a package-level const, type, var, or func declaration.
|
// A declInfo describes a package-level const, type, var, or func declaration.
|
||||||
type decl struct {
|
type declInfo struct {
|
||||||
file *Scope // scope of file containing this declaration
|
file *Scope // scope of file containing this declaration
|
||||||
typ ast.Expr // type, or nil
|
typ ast.Expr // type, or nil
|
||||||
init ast.Expr // initialization expression, or nil
|
init ast.Expr // initialization expression, or nil
|
||||||
}
|
fdecl *ast.FuncDecl // function declaration, or nil
|
||||||
|
|
||||||
// An mdecl describes a method declaration.
|
|
||||||
type mdecl struct {
|
|
||||||
file *Scope // scope of file containing this declaration
|
|
||||||
meth *ast.FuncDecl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A multiExpr describes the lhs variables and a single but
|
// A multiExpr describes the lhs variables and a single but
|
||||||
|
@ -83,19 +79,22 @@ func (check *checker) arityMatch(s, init *ast.ValueSpec) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gri) Split resolveFiles into smaller components.
|
||||||
|
|
||||||
func (check *checker) resolveFiles(files []*ast.File) {
|
func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
pkg := check.pkg
|
pkg := check.pkg
|
||||||
|
|
||||||
// Phase 1: Pre-declare all package-level objects so that they can be found
|
// Phase 1: Pre-declare all package-level objects so that they can be found
|
||||||
// independent of source order.
|
// independent of source order. Collect methods for a later phase.
|
||||||
|
|
||||||
var scopes []*Scope // corresponding file scope per file
|
var (
|
||||||
var objList []Object
|
fileScope *Scope // current file scope, used by declare
|
||||||
var objMap = make(map[Object]*decl)
|
scopes []*Scope // corresponding file scope per file
|
||||||
var methods []*mdecl
|
objList []Object // list of objects to type-check
|
||||||
var fileScope *Scope // current file scope, used by declare
|
objMap = make(map[Object]*declInfo) // declaration info for each object (incl. methods)
|
||||||
|
)
|
||||||
|
|
||||||
declare := func(ident *ast.Ident, obj Object, typ, init ast.Expr) {
|
declare := func(ident *ast.Ident, obj Object, typ, init ast.Expr, fdecl *ast.FuncDecl) {
|
||||||
assert(ident.Name == obj.Name())
|
assert(ident.Name == obj.Name())
|
||||||
|
|
||||||
// spec: "A package-scope or file-scope identifier with name init
|
// spec: "A package-scope or file-scope identifier with name init
|
||||||
|
@ -115,7 +114,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
objList = append(objList, obj)
|
objList = append(objList, obj)
|
||||||
objMap[obj] = &decl{fileScope, typ, init}
|
objMap[obj] = &declInfo{fileScope, typ, init, fdecl}
|
||||||
}
|
}
|
||||||
|
|
||||||
importer := check.conf.Import
|
importer := check.conf.Import
|
||||||
|
@ -209,7 +208,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
init = last.Values[i]
|
init = last.Values[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
declare(name, obj, last.Type, init)
|
declare(name, obj, last.Type, init, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
check.arityMatch(s, last)
|
check.arityMatch(s, last)
|
||||||
|
@ -238,7 +237,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare(name, obj, s.Type, init)
|
declare(name, obj, s.Type, init, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
check.arityMatch(s, nil)
|
check.arityMatch(s, nil)
|
||||||
|
@ -249,7 +248,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
|
|
||||||
case *ast.TypeSpec:
|
case *ast.TypeSpec:
|
||||||
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
||||||
declare(s.Name, obj, s.Type, nil)
|
declare(s.Name, obj, s.Type, nil, nil)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
|
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
|
||||||
|
@ -257,14 +256,39 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if d.Recv != nil {
|
obj := NewFunc(d.Name.Pos(), pkg, d.Name.Name, nil)
|
||||||
// collect method
|
if d.Recv == nil {
|
||||||
methods = append(methods, &mdecl{fileScope, d})
|
// regular function
|
||||||
|
declare(d.Name, obj, nil, nil, d)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
obj := NewFunc(d.Name.Pos(), pkg, d.Name.Name, nil)
|
|
||||||
obj.decl = d
|
// Methods are associated with the receiver base type
|
||||||
declare(d.Name, obj, nil, nil)
|
// which we don't have yet. Instead, collect methods
|
||||||
|
// with receiver base type name so that they are known
|
||||||
|
// when the receiver base type is type-checked.
|
||||||
|
|
||||||
|
// The receiver type must be one of the following
|
||||||
|
// - *ast.Ident
|
||||||
|
// - *ast.StarExpr{*ast.Ident}
|
||||||
|
// - *ast.BadExpr (parser error)
|
||||||
|
typ := d.Recv.List[0].Type
|
||||||
|
if ptr, _ := typ.(*ast.StarExpr); ptr != nil {
|
||||||
|
typ = ptr.X
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate method with receiver base type name, if possible.
|
||||||
|
// Methods with receiver types that are not type names, that
|
||||||
|
// are blank _ names, or methods with blank names are ignored;
|
||||||
|
// the respective error will be reported when the method signature
|
||||||
|
// is type-checked.
|
||||||
|
if ident, _ := typ.(*ast.Ident); ident != nil && ident.Name != "_" && obj.name != "_" {
|
||||||
|
check.methods[ident.Name] = append(check.methods[ident.Name], obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect methods like other objects.
|
||||||
|
objList = append(objList, obj)
|
||||||
|
objMap[obj] = &declInfo{fileScope, nil, nil, d}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
||||||
|
@ -272,7 +296,8 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Objects in file scopes and package scopes must have different names.
|
// Phase 2: Verify that objects in package and file scopes have different names.
|
||||||
|
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
for _, obj := range scope.entries {
|
for _, obj := range scope.entries {
|
||||||
if alt := pkg.scope.Lookup(nil, obj.Name()); alt != nil {
|
if alt := pkg.scope.Lookup(nil, obj.Name()); alt != nil {
|
||||||
|
@ -281,64 +306,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3: Associate methods with types.
|
// Phase 3: Typecheck all objects in objList, but not function bodies.
|
||||||
// We do this after all top-level type names have been collected.
|
|
||||||
|
|
||||||
for _, meth := range methods {
|
|
||||||
m := meth.meth
|
|
||||||
// The receiver type must be one of the following:
|
|
||||||
// - *ast.Ident
|
|
||||||
// - *ast.StarExpr{*ast.Ident}
|
|
||||||
// - *ast.BadExpr (parser error)
|
|
||||||
typ := m.Recv.List[0].Type
|
|
||||||
if ptr, ok := typ.(*ast.StarExpr); ok {
|
|
||||||
typ = ptr.X
|
|
||||||
}
|
|
||||||
// determine receiver base type name
|
|
||||||
// Note: We cannot simply call check.typ because this will require
|
|
||||||
// check.objMap to be usable, which it isn't quite yet.
|
|
||||||
ident, ok := typ.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
// Disabled for now since the parser reports this error.
|
|
||||||
// check.errorf(typ.Pos(), "receiver base type must be an (unqualified) identifier")
|
|
||||||
continue // ignore this method
|
|
||||||
}
|
|
||||||
// determine receiver base type object
|
|
||||||
var tname *TypeName
|
|
||||||
if obj := pkg.scope.LookupParent(ident.Name); obj != nil {
|
|
||||||
obj, ok := obj.(*TypeName)
|
|
||||||
if !ok {
|
|
||||||
check.errorf(ident.Pos(), "%s is not a type", ident.Name)
|
|
||||||
continue // ignore this method
|
|
||||||
}
|
|
||||||
if obj.pkg != pkg {
|
|
||||||
check.errorf(ident.Pos(), "cannot define method on non-local type %s", ident.Name)
|
|
||||||
continue // ignore this method
|
|
||||||
}
|
|
||||||
tname = obj
|
|
||||||
} else {
|
|
||||||
// identifier not declared/resolved
|
|
||||||
if ident.Name == "_" {
|
|
||||||
check.errorf(ident.Pos(), "cannot use _ as value or type")
|
|
||||||
} else {
|
|
||||||
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
|
|
||||||
}
|
|
||||||
continue // ignore this method
|
|
||||||
}
|
|
||||||
// declare method in receiver base type scope
|
|
||||||
scope := check.methods[tname] // lazily allocated
|
|
||||||
if scope == nil {
|
|
||||||
scope = new(Scope)
|
|
||||||
check.methods[tname] = scope
|
|
||||||
}
|
|
||||||
fun := NewFunc(m.Name.Pos(), check.pkg, m.Name.Name, nil)
|
|
||||||
fun.decl = m
|
|
||||||
check.declare(scope, m.Name, fun)
|
|
||||||
// HACK(gri) change method parent scope to file scope containing the declaration
|
|
||||||
fun.parent = meth.file // remember the file scope
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 4) Typecheck all objects in objList but not function bodies.
|
|
||||||
|
|
||||||
check.objMap = objMap // indicate that we are checking global declarations (objects may not have a type yet)
|
check.objMap = objMap // indicate that we are checking global declarations (objects may not have a type yet)
|
||||||
for _, obj := range objList {
|
for _, obj := range objList {
|
||||||
|
@ -348,8 +316,31 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||||
}
|
}
|
||||||
check.objMap = nil // done with global declarations
|
check.objMap = nil // done with global declarations
|
||||||
|
|
||||||
// Phase 5) Typecheck all functions.
|
// At this point we may have a non-empty check.methods map; this means that not all
|
||||||
// - done by the caller for now
|
// entries were deleted at the end of declareType, because the respective receiver
|
||||||
|
// base types were not declared. In that case, an error was reported when declaring
|
||||||
|
// those methods. We can now safely discard this map.
|
||||||
|
check.methods = nil
|
||||||
|
|
||||||
|
// Phase 4: Typecheck all functions bodies.
|
||||||
|
|
||||||
|
// (funclist may grow when checking statements - cannot use range clause)
|
||||||
|
for i := 0; i < len(check.funclist); i++ {
|
||||||
|
f := check.funclist[i]
|
||||||
|
if trace {
|
||||||
|
s := "<function literal>"
|
||||||
|
if f.obj != nil {
|
||||||
|
s = f.obj.name
|
||||||
|
}
|
||||||
|
fmt.Println("---", s)
|
||||||
|
}
|
||||||
|
check.topScope = f.sig.scope // open the function scope
|
||||||
|
check.funcsig = f.sig
|
||||||
|
check.stmtList(f.body.List)
|
||||||
|
if f.sig.results.Len() > 0 && f.body != nil && !check.isTerminating(f.body, "") {
|
||||||
|
check.errorf(f.body.Rbrace, "missing return")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// declareObject completes the declaration of obj in its respective file scope.
|
// declareObject completes the declaration of obj in its respective file scope.
|
||||||
|
@ -373,7 +364,7 @@ func (check *checker) declareObject(obj Object, def *Named, cycleOk bool) {
|
||||||
case *TypeName:
|
case *TypeName:
|
||||||
check.declareType(obj, d.typ, def, cycleOk)
|
check.declareType(obj, d.typ, def, cycleOk)
|
||||||
case *Func:
|
case *Func:
|
||||||
check.declareFunc(obj)
|
check.declareFunc(obj, d.fdecl)
|
||||||
default:
|
default:
|
||||||
unreachable()
|
unreachable()
|
||||||
}
|
}
|
||||||
|
@ -488,73 +479,77 @@ func (check *checker) declareType(obj *TypeName, typ ast.Expr, def *Named, cycle
|
||||||
// the underlying type has been determined
|
// the underlying type has been determined
|
||||||
named.complete = true
|
named.complete = true
|
||||||
|
|
||||||
// typecheck associated method signatures
|
// type-check signatures of associated methods
|
||||||
if scope := check.methods[obj]; scope != nil {
|
methods := check.methods[obj.name]
|
||||||
switch t := named.underlying.(type) {
|
if len(methods) == 0 {
|
||||||
case *Struct:
|
return // no methods
|
||||||
// struct fields must not conflict with methods
|
|
||||||
if t.fields == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, f := range t.fields {
|
|
||||||
if m := scope.Lookup(nil, f.name); m != nil {
|
|
||||||
check.errorf(m.Pos(), "type %s has both field and method named %s", obj.name, f.name)
|
|
||||||
// ok to continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *Interface:
|
|
||||||
// methods cannot be associated with an interface type
|
|
||||||
for _, m := range scope.entries {
|
|
||||||
recv := m.(*Func).decl.Recv.List[0].Type
|
|
||||||
check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.name, obj.name)
|
|
||||||
// ok to continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// typecheck method signatures
|
|
||||||
var methods []*Func
|
|
||||||
if scope.NumEntries() > 0 {
|
|
||||||
for _, obj := range scope.entries {
|
|
||||||
m := obj.(*Func)
|
|
||||||
|
|
||||||
// set the correct file scope for checking this method type
|
|
||||||
fileScope := m.parent
|
|
||||||
assert(fileScope != nil)
|
|
||||||
oldScope := check.topScope
|
|
||||||
check.topScope = fileScope
|
|
||||||
|
|
||||||
sig := check.typ(m.decl.Type, nil, cycleOk).(*Signature)
|
|
||||||
params, _ := check.collectParams(sig.scope, m.decl.Recv, false)
|
|
||||||
|
|
||||||
check.topScope = oldScope // reset topScope
|
|
||||||
|
|
||||||
sig.recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
|
|
||||||
m.typ = sig
|
|
||||||
methods = append(methods, m)
|
|
||||||
check.later(m, sig, m.decl.Body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
named.methods = methods
|
|
||||||
delete(check.methods, obj) // we don't need this scope anymore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spec: "For a base type, the non-blank names of methods bound
|
||||||
|
// to it must be unique."
|
||||||
|
// => use a scope to determine redeclarations
|
||||||
|
scope := NewScope(nil)
|
||||||
|
|
||||||
|
// spec: "If the base type is a struct type, the non-blank method
|
||||||
|
// and field names must be distinct."
|
||||||
|
// => pre-populate the scope to find conflicts
|
||||||
|
if t, _ := named.underlying.(*Struct); t != nil {
|
||||||
|
for _, fld := range t.fields {
|
||||||
|
if fld.name != "_" {
|
||||||
|
scope.Insert(fld)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check each method
|
||||||
|
for _, m := range methods {
|
||||||
|
assert(m.name != "_") // _ methods were excluded before
|
||||||
|
mdecl := check.objMap[m]
|
||||||
|
alt := scope.Insert(m)
|
||||||
|
m.parent = mdecl.file // correct parent scope (scope.Insert used scope)
|
||||||
|
|
||||||
|
if alt != nil {
|
||||||
|
switch alt := alt.(type) {
|
||||||
|
case *Var:
|
||||||
|
check.errorf(m.pos, "field and method with the same name %s", m.name)
|
||||||
|
if pos := alt.pos; pos.IsValid() {
|
||||||
|
check.errorf(pos, "previous declaration of %s", m.name)
|
||||||
|
}
|
||||||
|
m = nil
|
||||||
|
case *Func:
|
||||||
|
check.errorf(m.pos, "method %s already declared for %s", m.name, named)
|
||||||
|
if pos := alt.pos; pos.IsValid() {
|
||||||
|
check.errorf(pos, "previous declaration of %s", m.name)
|
||||||
|
}
|
||||||
|
m = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check.recordObject(mdecl.fdecl.Name, m)
|
||||||
|
|
||||||
|
// If the method is valid, type-check its signature,
|
||||||
|
// and collect it with the named base type.
|
||||||
|
if m != nil {
|
||||||
|
check.declareObject(m, nil, true)
|
||||||
|
named.methods = append(named.methods, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(check.methods, obj.name) // we don't need them anymore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) declareFunc(obj *Func) {
|
func (check *checker) declareFunc(obj *Func, fdecl *ast.FuncDecl) {
|
||||||
// func declarations cannot use iota
|
// func declarations cannot use iota
|
||||||
assert(check.iota == nil)
|
assert(check.iota == nil)
|
||||||
|
|
||||||
fdecl := obj.decl
|
obj.typ = Typ[Invalid] // guard against cycles
|
||||||
// methods are typechecked when their receivers are typechecked
|
sig := check.funcType(fdecl.Recv, fdecl.Type, nil)
|
||||||
// TODO(gri) there is no reason to make this a special case: receivers are simply parameters
|
if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
|
||||||
if fdecl.Recv == nil {
|
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
|
||||||
obj.typ = Typ[Invalid] // guard against cycles
|
// ok to continue
|
||||||
sig := check.typ(fdecl.Type, nil, false).(*Signature)
|
|
||||||
if 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
|
|
||||||
}
|
|
||||||
obj.typ = sig
|
|
||||||
check.later(obj, sig, fdecl.Body)
|
|
||||||
}
|
}
|
||||||
|
obj.typ = sig
|
||||||
|
check.later(obj, sig, fdecl.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) declStmt(decl ast.Decl) {
|
func (check *checker) declStmt(decl ast.Decl) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ type T1 struct{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T1) m() {}
|
func (T1) m() {}
|
||||||
func (T1) m /* ERROR "redeclared" */ () {}
|
func (T1) m /* ERROR "already declared" */ () {}
|
||||||
func (x *T1) f /* ERROR "field and method" */ () {}
|
func (x *T1) f /* ERROR "field and method" */ () {}
|
||||||
|
|
||||||
// Conflict between embedded field and method name,
|
// Conflict between embedded field and method name,
|
||||||
|
@ -77,11 +77,11 @@ type T5 interface {
|
||||||
m() int
|
m() int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T5 /* ERROR "invalid receiver" */) m1() {}
|
func (T5 /* ERROR "invalid receiver" */ ) m1() {}
|
||||||
func (T5 /* ERROR "invalid receiver" */) m2() {}
|
func (T5 /* ERROR "invalid receiver" */ ) m2() {}
|
||||||
|
|
||||||
// Methods associated with non-local or unnamed types.
|
// Methods associated with non-local or unnamed types.
|
||||||
func (int /* ERROR "non-local type" */ ) m() {}
|
func (int /* ERROR "invalid receiver" */ ) m() {}
|
||||||
func ([ /* ERROR "identifier" */ ]int) m() {}
|
func ([ /* ERROR "identifier" */ ]int) m() {}
|
||||||
func (time /* ERROR "identifier" */ .Time) m() {}
|
func (time /* ERROR "identifier" */ .Time) m() {}
|
||||||
func (x interface /* ERROR "identifier" */ {}) m() {}
|
func (x interface /* ERROR "identifier" */ {}) m() {}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import "io"
|
||||||
|
|
||||||
const pi = 3.1415
|
const pi = 3.1415
|
||||||
|
|
||||||
func (T1) m /* ERROR "redeclared" */ () {}
|
func (T1) m /* ERROR "already declared" */ () {}
|
||||||
func (T2) m(io.Writer) {}
|
func (T2) m(io.Writer) {}
|
||||||
|
|
||||||
type T3 struct {
|
type T3 struct {
|
||||||
|
@ -35,3 +35,9 @@ const c_double /* ERROR "redeclared" */ = 0
|
||||||
type t_double /* ERROR "redeclared" */ int
|
type t_double /* ERROR "redeclared" */ int
|
||||||
var v_double /* ERROR "redeclared" */ int
|
var v_double /* ERROR "redeclared" */ int
|
||||||
func f_double /* ERROR "redeclared" */ () {}
|
func f_double /* ERROR "redeclared" */ () {}
|
||||||
|
|
||||||
|
// Blank methods need to be type-checked.
|
||||||
|
// Verify by checking that errors are reported.
|
||||||
|
func (T /* ERROR "undeclared" */ ) _() {}
|
||||||
|
func (T1) _(undeclared /* ERROR "undeclared" */ ) {}
|
||||||
|
func (T1) _() int { return "foo" /* ERROR "cannot convert" */ }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This file implements typechecking of identifiers and type expressions.
|
// This file implements type-checking of identifiers and type expressions.
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
"code.google.com/p/go.tools/go/exact"
|
"code.google.com/p/go.tools/go/exact"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ident typechecks identifier e and initializes x with the value or type of e.
|
// ident type-checks identifier e and initializes x with the value or type of e.
|
||||||
// If an error occurred, x.mode is set to invalid.
|
// If an error occurred, x.mode is set to invalid.
|
||||||
// For the meaning of def and cycleOk, see check.typ, below.
|
// For the meaning of def and cycleOk, see check.typ, below.
|
||||||
//
|
//
|
||||||
|
@ -90,11 +90,10 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
|
||||||
x.typ = typ
|
x.typ = typ
|
||||||
}
|
}
|
||||||
|
|
||||||
// typ typechecks the type expression e and initializes x with the type of e.
|
// typ type-checks the type expression e and returns its type, or Typ[Invalid].
|
||||||
// If an error occurred, x.mode is set to invalid.
|
|
||||||
// If def != nil, e is the type specification for the named type def, declared
|
// If def != nil, e is the type specification for the named type def, declared
|
||||||
// in a type declaration, and def.underlying will be set to the type of e before
|
// in a type declaration, and def.underlying will be set to the type of e before
|
||||||
// any components of e are typechecked.
|
// any components of e are type-checked.
|
||||||
// If cycleOk is set, e (or elements of e) may refer to a named type that is not
|
// If cycleOk is set, e (or elements of e) may refer to a named type that is not
|
||||||
// yet completely set up.
|
// yet completely set up.
|
||||||
//
|
//
|
||||||
|
@ -117,11 +116,71 @@ func (check *checker) typ(e ast.Expr, def *Named, cycleOk bool) Type {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// funcType type-checks a function or method type and returns its signature.
|
||||||
|
func (check *checker) funcType(recv *ast.FieldList, ftyp *ast.FuncType, def *Named) *Signature {
|
||||||
|
sig := new(Signature)
|
||||||
|
if def != nil {
|
||||||
|
def.underlying = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := NewScope(check.topScope)
|
||||||
|
if retainASTLinks {
|
||||||
|
scope.node = ftyp
|
||||||
|
}
|
||||||
|
recv_, _ := check.collectParams(scope, recv, false)
|
||||||
|
params, isVariadic := check.collectParams(scope, ftyp.Params, true)
|
||||||
|
results, _ := check.collectParams(scope, ftyp.Results, false)
|
||||||
|
|
||||||
|
if len(recv_) > 0 {
|
||||||
|
// There must be exactly one receiver.
|
||||||
|
if len(recv_) > 1 {
|
||||||
|
check.invalidAST(recv_[1].Pos(), "method must have exactly one receiver")
|
||||||
|
// ok to continue
|
||||||
|
}
|
||||||
|
recv := recv_[0]
|
||||||
|
// spec: "The receiver type must be of the form T or *T where T is a type name."
|
||||||
|
// (ignore invalid types - error was reported before)
|
||||||
|
if t, _ := deref(recv.typ); t != Typ[Invalid] {
|
||||||
|
ok := true
|
||||||
|
if T, _ := t.(*Named); T != nil {
|
||||||
|
// spec: "The type denoted by T is called the receiver base type; it must not
|
||||||
|
// be a pointer or interface type and it must be declared in the same package
|
||||||
|
// as the method."
|
||||||
|
switch T.underlying.(type) {
|
||||||
|
case *Pointer, *Interface:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
if T.obj.pkg != check.pkg {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// TODO(gri) provide better error message depending on error
|
||||||
|
check.errorf(recv.pos, "invalid receiver %s", recv)
|
||||||
|
// ok to continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sig.recv = recv
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.scope = scope
|
||||||
|
sig.params = NewTuple(params...)
|
||||||
|
sig.results = NewTuple(results...)
|
||||||
|
sig.isVariadic = isVariadic
|
||||||
|
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
// typ0 contains the core of type checking of types.
|
// typ0 contains the core of type checking of types.
|
||||||
// Must only be called by typ.
|
// Must only be called by typ.
|
||||||
//
|
//
|
||||||
func (check *checker) typ0(e ast.Expr, def *Named, cycleOk bool) Type {
|
func (check *checker) typ0(e ast.Expr, def *Named, cycleOk bool) Type {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
|
case *ast.BadExpr:
|
||||||
|
// ignore - error reported before
|
||||||
|
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
var x operand
|
var x operand
|
||||||
check.ident(&x, e, def, cycleOk)
|
check.ident(&x, e, def, cycleOk)
|
||||||
|
@ -213,22 +272,7 @@ func (check *checker) typ0(e ast.Expr, def *Named, cycleOk bool) Type {
|
||||||
return typ
|
return typ
|
||||||
|
|
||||||
case *ast.FuncType:
|
case *ast.FuncType:
|
||||||
typ := new(Signature)
|
return check.funcType(nil, e, def)
|
||||||
if def != nil {
|
|
||||||
def.underlying = typ
|
|
||||||
}
|
|
||||||
|
|
||||||
scope := NewScope(check.topScope)
|
|
||||||
if retainASTLinks {
|
|
||||||
scope.node = e
|
|
||||||
}
|
|
||||||
typ.scope = scope
|
|
||||||
params, isVariadic := check.collectParams(scope, e.Params, true)
|
|
||||||
results, _ := check.collectParams(scope, e.Results, false)
|
|
||||||
typ.params = NewTuple(params...)
|
|
||||||
typ.results = NewTuple(results...)
|
|
||||||
typ.isVariadic = isVariadic
|
|
||||||
return typ
|
|
||||||
|
|
||||||
case *ast.InterfaceType:
|
case *ast.InterfaceType:
|
||||||
typ := new(Interface)
|
typ := new(Interface)
|
||||||
|
@ -268,7 +312,7 @@ func (check *checker) typ0(e ast.Expr, def *Named, cycleOk bool) Type {
|
||||||
return Typ[Invalid]
|
return Typ[Invalid]
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeOrNil typechecks the type expression (or nil value) e
|
// typeOrNil type-checks the type expression (or nil value) e
|
||||||
// and returns the typ of e, or nil.
|
// and returns the typ of e, or nil.
|
||||||
// If e is neither a type nor nil, typOrNil returns Typ[Invalid].
|
// If e is neither a type nor nil, typOrNil returns Typ[Invalid].
|
||||||
//
|
//
|
||||||
|
@ -343,6 +387,7 @@ func (check *checker) collectMethods(recv Type, list *ast.FieldList, cycleOk boo
|
||||||
|
|
||||||
scope := NewScope(nil)
|
scope := NewScope(nil)
|
||||||
for _, f := range list.List {
|
for _, f := range list.List {
|
||||||
|
// TODO(gri) Consider calling funcType here.
|
||||||
typ := check.typ(f.Type, nil, cycleOk)
|
typ := check.typ(f.Type, nil, cycleOk)
|
||||||
// the parser ensures that f.Tag is nil and we don't
|
// the parser ensures that f.Tag is nil and we don't
|
||||||
// care if a constructed AST contains a non-nil tag
|
// care if a constructed AST contains a non-nil tag
|
||||||
|
|
Loading…
Reference in New Issue