1
0
mirror of https://github.com/golang/go synced 2024-11-17 09:54:46 -07:00

go/types: add Checker.walkDecl to simplify checking declarations

Handling ast.GenDecls while typechecking is repetitive, and a source of
diffs compared to typechecking using the cmd/compile/internal/syntax
package, which unpacks declaration groups into individual declarations.

Refactor to extract the logic for walking declarations.  This introduces
a new AST abstraction: types.decl, which comes at some minor performance
cost. However, if we are to fully abstract the AST we will be paying
this cost anyway, and benchmarking suggests that the cost is negligible.

Change-Id: If73c30c3d08053ccf7bf21ef886f0452fdbf142e
Reviewed-on: https://go-review.googlesource.com/c/go/+/256298
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
Trust: Robert Findley <rfindley@google.com>
This commit is contained in:
Rob Findley 2020-09-21 08:47:34 -04:00 committed by Robert Findley
parent 44a15a7262
commit 79dbdf2a4c
2 changed files with 292 additions and 286 deletions

View File

@ -381,6 +381,76 @@ func firstInSrc(path []Object) int {
return fst return fst
} }
type (
decl interface {
node() ast.Node
}
importDecl struct{ spec *ast.ImportSpec }
constDecl struct {
spec *ast.ValueSpec
iota int
typ ast.Expr
init []ast.Expr
}
varDecl struct{ spec *ast.ValueSpec }
typeDecl struct{ spec *ast.TypeSpec }
funcDecl struct{ decl *ast.FuncDecl }
)
func (d importDecl) node() ast.Node { return d.spec }
func (d constDecl) node() ast.Node { return d.spec }
func (d varDecl) node() ast.Node { return d.spec }
func (d typeDecl) node() ast.Node { return d.spec }
func (d funcDecl) node() ast.Node { return d.decl }
func (check *Checker) walkDecls(decls []ast.Decl, f func(decl)) {
for _, d := range decls {
check.walkDecl(d, f)
}
}
func (check *Checker) walkDecl(d ast.Decl, f func(decl)) {
switch d := d.(type) {
case *ast.BadDecl:
// ignore
case *ast.GenDecl:
var last *ast.ValueSpec // last ValueSpec with type or init exprs seen
for iota, s := range d.Specs {
switch s := s.(type) {
case *ast.ImportSpec:
f(importDecl{s})
case *ast.ValueSpec:
switch d.Tok {
case token.CONST:
// determine which initialization expressions to use
switch {
case s.Type != nil || len(s.Values) > 0:
last = s
case last == nil:
last = new(ast.ValueSpec) // make sure last exists
}
check.arityMatch(s, last)
f(constDecl{spec: s, iota: iota, init: last.Values, typ: last.Type})
case token.VAR:
check.arityMatch(s, nil)
f(varDecl{s})
default:
check.invalidAST(s.Pos(), "invalid token %s", d.Tok)
}
case *ast.TypeSpec:
f(typeDecl{s})
default:
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
}
}
case *ast.FuncDecl:
f(funcDecl{d})
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
}
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) { func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
assert(obj.typ == nil) assert(obj.typ == nil)
@ -664,46 +734,28 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
} }
} }
func (check *Checker) declStmt(decl ast.Decl) { func (check *Checker) declStmt(d ast.Decl) {
pkg := check.pkg pkg := check.pkg
switch d := decl.(type) { check.walkDecl(d, func(d decl) {
case *ast.BadDecl: switch d := d.(type) {
// ignore case constDecl:
case *ast.GenDecl:
var last *ast.ValueSpec // last ValueSpec with type or init exprs seen
for iota, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ValueSpec:
switch d.Tok {
case token.CONST:
top := len(check.delayed) top := len(check.delayed)
// determine which init exprs to use
switch {
case s.Type != nil || len(s.Values) > 0:
last = s
case last == nil:
last = new(ast.ValueSpec) // make sure last exists
}
// declare all constants // declare all constants
lhs := make([]*Const, len(s.Names)) lhs := make([]*Const, len(d.spec.Names))
for i, name := range s.Names { for i, name := range d.spec.Names {
obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(iota))) obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota)))
lhs[i] = obj lhs[i] = obj
var init ast.Expr var init ast.Expr
if i < len(last.Values) { if i < len(d.init) {
init = last.Values[i] init = d.init[i]
} }
check.constDecl(obj, last.Type, init) check.constDecl(obj, d.typ, init)
} }
check.arityMatch(s, last)
// process function literals in init expressions before scope changes // process function literals in init expressions before scope changes
check.processDelayed(top) check.processDelayed(top)
@ -711,16 +763,16 @@ func (check *Checker) declStmt(decl ast.Decl) {
// inside a function begins at the end of the ConstSpec or VarSpec // inside a function begins at the end of the ConstSpec or VarSpec
// (ShortVarDecl for short variable declarations) and ends at the // (ShortVarDecl for short variable declarations) and ends at the
// end of the innermost containing block." // end of the innermost containing block."
scopePos := s.End() scopePos := d.spec.End()
for i, name := range s.Names { for i, name := range d.spec.Names {
check.declare(check.scope, name, lhs[i], scopePos) check.declare(check.scope, name, lhs[i], scopePos)
} }
case token.VAR: case varDecl:
top := len(check.delayed) top := len(check.delayed)
lhs0 := make([]*Var, len(s.Names)) lhs0 := make([]*Var, len(d.spec.Names))
for i, name := range s.Names { for i, name := range d.spec.Names {
lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil) lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil)
} }
@ -728,21 +780,21 @@ func (check *Checker) declStmt(decl ast.Decl) {
for i, obj := range lhs0 { for i, obj := range lhs0 {
var lhs []*Var var lhs []*Var
var init ast.Expr var init ast.Expr
switch len(s.Values) { switch len(d.spec.Values) {
case len(s.Names): case len(d.spec.Names):
// lhs and rhs match // lhs and rhs match
init = s.Values[i] init = d.spec.Values[i]
case 1: case 1:
// rhs is expected to be a multi-valued expression // rhs is expected to be a multi-valued expression
lhs = lhs0 lhs = lhs0
init = s.Values[0] init = d.spec.Values[0]
default: default:
if i < len(s.Values) { if i < len(d.spec.Values) {
init = s.Values[i] init = d.spec.Values[i]
} }
} }
check.varDecl(obj, lhs, s.Type, init) check.varDecl(obj, lhs, d.spec.Type, init)
if len(s.Values) == 1 { if len(d.spec.Values) == 1 {
// If we have a single lhs variable we are done either way. // If we have a single lhs variable we are done either way.
// If we have a single rhs expression, it must be a multi- // If we have a single rhs expression, it must be a multi-
// valued expression, in which case handling the first lhs // valued expression, in which case handling the first lhs
@ -757,40 +809,30 @@ func (check *Checker) declStmt(decl ast.Decl) {
} }
} }
check.arityMatch(s, nil)
// process function literals in init expressions before scope changes // process function literals in init expressions before scope changes
check.processDelayed(top) check.processDelayed(top)
// declare all variables // declare all variables
// (only at this point are the variable scopes (parents) set) // (only at this point are the variable scopes (parents) set)
scopePos := s.End() // see constant declarations scopePos := d.spec.End() // see constant declarations
for i, name := range s.Names { for i, name := range d.spec.Names {
// see constant declarations // see constant declarations
check.declare(check.scope, name, lhs0[i], scopePos) check.declare(check.scope, name, lhs0[i], scopePos)
} }
default: case typeDecl:
check.invalidAST(s.Pos(), "invalid token %s", d.Tok) obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil)
}
case *ast.TypeSpec:
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
// spec: "The scope of a type identifier declared inside a function // spec: "The scope of a type identifier declared inside a function
// begins at the identifier in the TypeSpec and ends at the end of // begins at the identifier in the TypeSpec and ends at the end of
// the innermost containing block." // the innermost containing block."
scopePos := s.Name.Pos() scopePos := d.spec.Name.Pos()
check.declare(check.scope, s.Name, obj, scopePos) check.declare(check.scope, d.spec.Name, obj, scopePos)
// mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) // mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl)
obj.setColor(grey + color(check.push(obj))) obj.setColor(grey + color(check.push(obj)))
check.typeDecl(obj, s.Type, nil, s.Assign.IsValid()) check.typeDecl(obj, d.spec.Type, nil, d.spec.Assign.IsValid())
check.pop().setColor(black) check.pop().setColor(black)
default: default:
check.invalidAST(s.Pos(), "const, type, or var declaration expected") check.invalidAST(d.node().Pos(), "unknown ast.Decl node %T", d.node())
}
}
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
} }
})
} }

View File

@ -235,26 +235,19 @@ func (check *Checker) collectObjects() {
// we get "." as the directory which is what we would want. // we get "." as the directory which is what we would want.
fileDir := dir(check.fset.Position(file.Name.Pos()).Filename) fileDir := dir(check.fset.Position(file.Name.Pos()).Filename)
for _, decl := range file.Decls { check.walkDecls(file.Decls, func(d decl) {
switch d := decl.(type) { switch d := d.(type) {
case *ast.BadDecl: case importDecl:
// ignore
case *ast.GenDecl:
var last *ast.ValueSpec // last ValueSpec with type or init exprs seen
for iota, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ImportSpec:
// import package // import package
path, err := validatedImportPath(s.Path.Value) path, err := validatedImportPath(d.spec.Path.Value)
if err != nil { if err != nil {
check.errorf(s.Path.Pos(), "invalid import path (%s)", err) check.errorf(d.spec.Path.Pos(), "invalid import path (%s)", err)
continue return
} }
imp := check.importPackage(s.Path.Pos(), path, fileDir) imp := check.importPackage(d.spec.Path.Pos(), path, fileDir)
if imp == nil { if imp == nil {
continue return
} }
// add package to list of explicit imports // add package to list of explicit imports
@ -267,25 +260,25 @@ func (check *Checker) collectObjects() {
// local name overrides imported package name // local name overrides imported package name
name := imp.name name := imp.name
if s.Name != nil { if d.spec.Name != nil {
name = s.Name.Name name = d.spec.Name.Name
if path == "C" { if path == "C" {
// match cmd/compile (not prescribed by spec) // match cmd/compile (not prescribed by spec)
check.errorf(s.Name.Pos(), `cannot rename import "C"`) check.errorf(d.spec.Name.Pos(), `cannot rename import "C"`)
continue return
} }
if name == "init" { if name == "init" {
check.errorf(s.Name.Pos(), "cannot declare init - must be func") check.errorf(d.spec.Name.Pos(), "cannot declare init - must be func")
continue return
} }
} }
obj := NewPkgName(s.Pos(), pkg, name, imp) obj := NewPkgName(d.spec.Pos(), pkg, name, imp)
if s.Name != nil { if d.spec.Name != nil {
// in a dot-import, the dot represents the package // in a dot-import, the dot represents the package
check.recordDef(s.Name, obj) check.recordDef(d.spec.Name, obj)
} else { } else {
check.recordImplicit(s, obj) check.recordImplicit(d.spec, obj)
} }
if path == "C" { if path == "C" {
@ -306,108 +299,83 @@ func (check *Checker) collectObjects() {
// the object may be imported into more than one file scope // the object may be imported into more than one file scope
// concurrently. See issue #32154.) // concurrently. See issue #32154.)
if alt := fileScope.Insert(obj); alt != nil { if alt := fileScope.Insert(obj); alt != nil {
check.errorf(s.Name.Pos(), "%s redeclared in this block", obj.Name()) check.errorf(d.spec.Name.Pos(), "%s redeclared in this block", obj.Name())
check.reportAltDecl(alt) check.reportAltDecl(alt)
} }
} }
} }
// add position to set of dot-import positions for this file // add position to set of dot-import positions for this file
// (this is only needed for "imported but not used" errors) // (this is only needed for "imported but not used" errors)
check.addUnusedDotImport(fileScope, imp, s.Pos()) check.addUnusedDotImport(fileScope, imp, d.spec.Pos())
} else { } else {
// declare imported package object in file scope // declare imported package object in file scope
// (no need to provide s.Name since we called check.recordDef earlier) // (no need to provide s.Name since we called check.recordDef earlier)
check.declare(fileScope, nil, obj, token.NoPos) check.declare(fileScope, nil, obj, token.NoPos)
} }
case constDecl:
case *ast.ValueSpec:
switch d.Tok {
case token.CONST:
// determine which initialization expressions to use
switch {
case s.Type != nil || len(s.Values) > 0:
last = s
case last == nil:
last = new(ast.ValueSpec) // make sure last exists
}
// declare all constants // declare all constants
for i, name := range s.Names { for i, name := range d.spec.Names {
obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(iota))) obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota)))
var init ast.Expr var init ast.Expr
if i < len(last.Values) { if i < len(d.init) {
init = last.Values[i] init = d.init[i]
} }
d := &declInfo{file: fileScope, typ: last.Type, init: init} d := &declInfo{file: fileScope, typ: d.typ, init: init}
check.declarePkgObj(name, obj, d) check.declarePkgObj(name, obj, d)
} }
check.arityMatch(s, last) case varDecl:
lhs := make([]*Var, len(d.spec.Names))
case token.VAR:
lhs := make([]*Var, len(s.Names))
// If there's exactly one rhs initializer, use // If there's exactly one rhs initializer, use
// the same declInfo d1 for all lhs variables // the same declInfo d1 for all lhs variables
// so that each lhs variable depends on the same // so that each lhs variable depends on the same
// rhs initializer (n:1 var declaration). // rhs initializer (n:1 var declaration).
var d1 *declInfo var d1 *declInfo
if len(s.Values) == 1 { if len(d.spec.Values) == 1 {
// The lhs elements are only set up after the for loop below, // The lhs elements are only set up after the for loop below,
// but that's ok because declareVar only collects the declInfo // but that's ok because declareVar only collects the declInfo
// for a later phase. // for a later phase.
d1 = &declInfo{file: fileScope, lhs: lhs, typ: s.Type, init: s.Values[0]} d1 = &declInfo{file: fileScope, lhs: lhs, typ: d.spec.Type, init: d.spec.Values[0]}
} }
// declare all variables // declare all variables
for i, name := range s.Names { for i, name := range d.spec.Names {
obj := NewVar(name.Pos(), pkg, name.Name, nil) obj := NewVar(name.Pos(), pkg, name.Name, nil)
lhs[i] = obj lhs[i] = obj
d := d1 di := d1
if d == nil { if di == nil {
// individual assignments // individual assignments
var init ast.Expr var init ast.Expr
if i < len(s.Values) { if i < len(d.spec.Values) {
init = s.Values[i] init = d.spec.Values[i]
} }
d = &declInfo{file: fileScope, typ: s.Type, init: init} di = &declInfo{file: fileScope, typ: d.spec.Type, init: init}
} }
check.declarePkgObj(name, obj, d) check.declarePkgObj(name, obj, di)
} }
case typeDecl:
check.arityMatch(s, nil) obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil)
check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, typ: d.spec.Type, alias: d.spec.Assign.IsValid()})
default: case funcDecl:
check.invalidAST(s.Pos(), "invalid token %s", d.Tok) info := &declInfo{file: fileScope, fdecl: d.decl}
} name := d.decl.Name.Name
obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil)
case *ast.TypeSpec: if d.decl.Recv == nil {
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type, alias: s.Assign.IsValid()})
default:
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
}
}
case *ast.FuncDecl:
name := d.Name.Name
obj := NewFunc(d.Name.Pos(), pkg, name, nil)
if d.Recv == nil {
// regular function // regular function
if name == "init" { if name == "init" {
// don't declare init functions in the package scope - they are invisible // don't declare init functions in the package scope - they are invisible
obj.parent = pkg.scope obj.parent = pkg.scope
check.recordDef(d.Name, obj) check.recordDef(d.decl.Name, obj)
// init functions must have a body // init functions must have a body
if d.Body == nil { if d.decl.Body == nil {
check.softErrorf(obj.pos, "missing function body") check.softErrorf(obj.pos, "missing function body")
} }
} else { } else {
check.declare(pkg.scope, d.Name, obj, token.NoPos) check.declare(pkg.scope, d.decl.Name, obj, token.NoPos)
} }
} else { } else {
// method // method
@ -417,20 +385,16 @@ func (check *Checker) collectObjects() {
if name != "_" { if name != "_" {
methods = append(methods, obj) methods = append(methods, obj)
} }
check.recordDef(d.Name, obj) check.recordDef(d.decl.Name, obj)
} }
info := &declInfo{file: fileScope, fdecl: d}
// Methods are not package-level objects but we still track them in the // Methods are not package-level objects but we still track them in the
// object map so that we can handle them like regular functions (if the // object map so that we can handle them like regular functions (if the
// receiver is invalid); also we need their fdecl info when associating // receiver is invalid); also we need their fdecl info when associating
// them with their receiver base type, below. // them with their receiver base type, below.
check.objMap[obj] = info check.objMap[obj] = info
obj.setOrder(uint32(len(check.objMap))) obj.setOrder(uint32(len(check.objMap)))
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
} }
})
} }
// verify that objects in package and file scopes have different names // verify that objects in package and file scopes have different names