Fix more phasing issues

This change fixes a few more phasing issues in the compiler.  Namely,
it now splits all passes into three distinct phases:

1. Declarations: simply populating names.

2. Definitions: chasing down any references to other names from those
   declared entities.  For instance, base classes, other modules, etc.

3. Bodies: fully type-checking everything else, which will depend
   upon both declarations and definitions being fully present.
This commit is contained in:
joeduffy 2017-02-09 12:24:02 -08:00
parent 9bcfbe859f
commit a91f3e6050
4 changed files with 176 additions and 171 deletions

View file

@ -10,32 +10,43 @@ import (
"github.com/marapongo/mu/pkg/util/contract"
)
func (b *binder) bindClass(node *ast.Class, parent *symbols.Module) *symbols.Class {
func (b *binder) pushClass(class *symbols.Class) func() {
priorclass := b.ctx.Currclass
b.ctx.Currclass = class
return func() { b.ctx.Currclass = priorclass }
}
func (b *binder) bindClassDeclaration(node *ast.Class, parent *symbols.Module) *symbols.Class {
glog.V(3).Infof("Binding module '%v' class '%v'", parent.Name(), node.Name.Ident)
// Bind base type tokens to actual symbols.
extends := b.ctx.LookupType(node.Extends)
var implements symbols.Types
if node.Implements != nil {
for _, impltok := range *node.Implements {
if impl := b.ctx.LookupType(impltok); impl != nil {
implements = append(implements, impl)
}
}
}
// Now create a class symbol. This is required as a parent for the members.
class := symbols.NewClassSym(node, parent, extends, implements)
// Now create an empty class symbol. This is required as a parent for the members.
class := symbols.NewClassSym(node, parent, nil, nil)
b.ctx.RegisterSymbol(node, class)
return class
}
func (b *binder) bindClassDefinition(class *symbols.Class) {
// Bind base type tokens to actual symbols.
if class.Node.Extends != nil {
class.SetBase(b.ctx.LookupType(class.Node.Extends))
}
if class.Node.Implements != nil {
for _, impltok := range *class.Node.Implements {
if impl := b.ctx.LookupType(impltok); impl != nil {
class.Implements = append(class.Implements, impl)
}
}
}
b.bindClassMembers(class)
}
func (b *binder) bindClassMembers(class *symbols.Class) {
// Set the current class in the context so we can e.g. enforce accessibility.
priorclass := b.ctx.Currclass
b.ctx.Currclass = class
defer func() { b.ctx.Currclass = priorclass }()
pop := b.pushClass(class)
defer pop()
// Bind each member at the symbolic level; in particular, we do not yet bind bodies of methods.
if class.Node.Members != nil {
@ -81,7 +92,7 @@ func (b *binder) bindClassMethod(node *ast.ClassMethod, parent *symbols.Class) *
return sym
}
func (b *binder) bindClassMethodBodies(class *symbols.Class) {
func (b *binder) bindClassBodies(class *symbols.Class) {
for _, member := range symbols.StableClassMemberMap(class.Members) {
switch m := class.Members[member].(type) {
case *symbols.ClassMethod:

View file

@ -3,23 +3,102 @@
package binder
import (
"reflect"
"github.com/golang/glog"
"github.com/marapongo/mu/pkg/compiler/ast"
"github.com/marapongo/mu/pkg/compiler/symbols"
"github.com/marapongo/mu/pkg/util/contract"
)
// createModule simply creates a module symbol with its immutable information. In particular, it doesn't yet bind
// members, since inter-module references must be resolved in later passes.
func (b *binder) createModule(node *ast.Module, parent *symbols.Package) *symbols.Module {
glog.V(3).Infof("Creating package '%v' module '%v'", parent.Name(), node.Name.Ident)
func (b *binder) pushModule(module *symbols.Module) func() {
priormodule := b.ctx.Currmodule
b.ctx.Currmodule = module
return func() { b.ctx.Currmodule = priormodule }
}
func (b *binder) bindModuleDeclarations(node *ast.Module, parent *symbols.Package) *symbols.Module {
glog.V(3).Infof("Binding package '%v' module '%v' decls", parent.Name(), node.Name.Ident)
// Create the module symbol and register it.
module := symbols.NewModuleSym(node, parent)
b.ctx.RegisterSymbol(node, module)
// Now bind module member declarations; this just populates the top-level declaration symbolic information, without
// any inter-dependencies on other module declarations that may not have yet been completed.
b.bindModuleMemberDeclarations(module)
return module
}
// bindModuleMemberDeclarations binds a module's member names. This must be done before binding definitions because
// they could mention other members whose symbolic information may not have been registered yet. Note that class
// definitions are not yet bound during this pass, since they could reference module members not yet bound.
func (b *binder) bindModuleMemberDeclarations(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' member decls", module.Token())
// Set the current module in the context so we can e.g. enforce accessibility.
pop := b.pushModule(module)
defer pop()
// Now bind all member declarations.
if module.Node.Members != nil {
members := *module.Node.Members
for _, nm := range ast.StableModuleMembers(members) {
switch m := members[nm].(type) {
case *ast.Class:
module.Members[nm] = b.bindClassDeclaration(m, module)
case *ast.Export:
module.Members[nm] = b.bindExportDeclaration(m, module)
case *ast.ModuleMethod:
module.Members[nm] = b.bindModuleMethodDeclaration(m, module)
case *ast.ModuleProperty:
module.Members[nm] = b.bindModulePropertyDeclaration(m, module)
default:
contract.Failf("Unrecognized module member type: %v", reflect.TypeOf(m))
}
}
}
}
func (b *binder) bindExportDeclaration(node *ast.Export, parent *symbols.Module) *symbols.Export {
glog.V(3).Infof("Binding module '%v' export '%v' decl", parent.Name(), node.Name.Ident)
// Simply register an empty export, unlinked to the referent yet.
sym := symbols.NewExportSym(node, parent, nil)
b.ctx.RegisterSymbol(node, sym)
return sym
}
func (b *binder) bindModulePropertyDeclaration(node *ast.ModuleProperty,
parent *symbols.Module) *symbols.ModuleProperty {
glog.V(3).Infof("Binding module '%v' property '%v' decl", parent.Name(), node.Name.Ident)
// Simply create an untyped property declaration. The type lookup will happen in a subsequent pass.
sym := symbols.NewModulePropertySym(node, parent, nil)
b.ctx.RegisterSymbol(node, sym)
return sym
}
func (b *binder) bindModuleMethodDeclaration(node *ast.ModuleMethod,
parent *symbols.Module) *symbols.ModuleMethod {
glog.V(3).Infof("Binding module '%v' method '%v' decl", parent.Name(), node.Name.Ident)
// Simply create a function declaration without any type. That will happen in a subsequent pass.
sym := symbols.NewModuleMethodSym(node, parent, nil)
b.ctx.RegisterSymbol(node, sym)
return sym
}
func (b *binder) bindModuleDefinitions(module *symbols.Module) {
// Now we can bind module imports.
b.bindModuleImports(module)
// And finish binding the members themselves.
b.bindModuleMemberDefinitions(module)
}
// bindModuleImports binds module import tokens to their symbols. This is done as a second pass just in case there are
// inter-module dependencies.
func (b *binder) bindModuleImports(module *symbols.Module) {
@ -33,131 +112,58 @@ func (b *binder) bindModuleImports(module *symbols.Module) {
}
}
// bindModuleClassNames binds a module's classes. This must be done before binding variables and exports since they
// might mention classes by name, and so the symbolic information must have been registered beforehand. Note that class
// definitions are not yet bound during this pass, since they could reference module members not yet bound.
func (b *binder) bindModuleClasses(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' classes", module.Token())
// bindModuleMemberDefinitions finishes binding module members, by doing lookups sensitive to the definition pass.
func (b *binder) bindModuleMemberDefinitions(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' member defns", module.Token())
// Set the current module in the context so we can e.g. enforce accessibility.
priormodule := b.ctx.Currmodule
b.ctx.Currmodule = module
defer func() { b.ctx.Currmodule = priormodule }()
pop := b.pushModule(module)
defer pop()
// Now bind all class members.
if module.Node.Members != nil {
members := *module.Node.Members
for _, nm := range ast.StableModuleMembers(members) {
member := members[nm]
if class, isclass := member.(*ast.Class); isclass {
module.Members[nm] = b.bindClass(class, module)
}
}
}
}
// bindModuleClassMembers binds all class member definitions within a module (function signatures and varaibles),
// but doesn't actually bind any function bodies yet.
func (b *binder) bindModuleClassMembers(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' class members", module.Token())
// Set the current module in the context so we can e.g. enforce accessibility.
priormodule := b.ctx.Currmodule
b.ctx.Currmodule = module
defer func() { b.ctx.Currmodule = priormodule }()
// Now bind all class member definitions.
// Now bind all member definitions.
for _, nm := range symbols.StableModuleMemberMap(module.Members) {
if class, isclass := module.Members[nm].(*symbols.Class); isclass {
b.bindClassMembers(class)
switch m := module.Members[nm].(type) {
case *symbols.Class:
b.bindClassDefinition(m)
case *symbols.Export:
b.bindExportDefinition(m)
case *symbols.ModuleMethod:
b.bindModuleMethodDefinition(m)
case *symbols.ModuleProperty:
b.bindModulePropertyDefinition(m)
default:
contract.Failf("Unrecognized module member type: %v", reflect.TypeOf(m))
}
}
}
// bindModuleMembers binds a module's property and method members. This must be done after binding classes, and before
// binding exports, so that any classes referenced are found (and because exports might refer to these).
func (b *binder) bindModuleMembers(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' methods and properties", module.Token())
func (b *binder) bindExportDefinition(export *symbols.Export) {
glog.V(3).Infof("Binding module export '%v' defn", export.Token)
// Set the current module in the context so we can e.g. enforce accessibility.
priormodule := b.ctx.Currmodule
b.ctx.Currmodule = module
defer func() { b.ctx.Currmodule = priormodule }()
// Now bind all module methods and properties.
if module.Node.Members != nil {
members := *module.Node.Members
for _, nm := range ast.StableModuleMembers(members) {
member := members[nm]
if method, ismethod := member.(*ast.ModuleMethod); ismethod {
module.Members[nm] = b.bindModuleMethod(method, module)
} else if property, isproperty := member.(*ast.ModuleProperty); isproperty {
module.Members[nm] = b.bindModuleProperty(property, module)
}
}
}
// To bind an export definition, simply look up the referent symbol and associate this name with it.
export.Referent = b.ctx.LookupSymbol(export.Node.Referent, export.Node.Referent.Tok, true)
}
// bindModuleExports binds a module's exports. This must be done after binding classes and members, since an export
// might refer to those by symbolic token reference. This can also safely be done before method bodies.
func (b *binder) bindModuleExports(module *symbols.Module) {
glog.V(3).Infof("Binding module '%v' methods and properties", module.Token())
func (b *binder) bindModulePropertyDefinition(property *symbols.ModuleProperty) {
glog.V(3).Infof("Binding module property '%v' defn", property.Token)
// Set the current module in the context so we can e.g. enforce accessibility.
priormodule := b.ctx.Currmodule
b.ctx.Currmodule = module
defer func() { b.ctx.Currmodule = priormodule }()
// Now bind all module methods and properties.
if module.Node.Members != nil {
members := *module.Node.Members
for _, nm := range ast.StableModuleMembers(members) {
member := members[nm]
if export, isexport := member.(*ast.Export); isexport {
module.Members[nm] = b.bindExport(export, module)
}
}
}
// Look up this node's type and remember the type on the symbol.
property.Ty = b.ctx.LookupType(property.Node.Type)
}
func (b *binder) bindExport(node *ast.Export, parent *symbols.Module) *symbols.Export {
glog.V(3).Infof("Binding module '%v' export '%v'", parent.Name(), node.Name.Ident)
func (b *binder) bindModuleMethodDefinition(method *symbols.ModuleMethod) {
glog.V(3).Infof("Binding module method '%v' defn", method.Token)
// To bind an export, simply look up the referent symbol and associate this name with it.
if refsym := b.ctx.LookupSymbol(node.Referent, node.Referent.Tok, true); refsym != nil {
sym := symbols.NewExportSym(node, parent, refsym)
b.ctx.RegisterSymbol(node, sym)
return sym
}
return nil
}
func (b *binder) bindModuleProperty(node *ast.ModuleProperty, parent *symbols.Module) *symbols.ModuleProperty {
glog.V(3).Infof("Binding module '%v' property '%v'", parent.Name(), node.Name.Ident)
// Look up this node's type and inject it into the type table.
ty := b.ctx.LookupType(node.Type)
sym := symbols.NewModulePropertySym(node, parent, ty)
b.ctx.RegisterSymbol(node, sym)
return sym
}
func (b *binder) bindModuleMethod(node *ast.ModuleMethod, parent *symbols.Module) *symbols.ModuleMethod {
glog.V(3).Infof("Binding module '%v' method '%v'", parent.Name(), node.Name.Ident)
// Make a function type out of this method and inject it into the type table.
ty := b.ctx.LookupFunctionType(node)
sym := symbols.NewModuleMethodSym(node, parent, ty)
b.ctx.RegisterSymbol(node, sym)
// Make a function type out of this method and store it on the symbol.
method.Type = b.ctx.LookupFunctionType(method.Node)
// Note that we don't actually bind the body of this method yet. Until we have gone ahead and injected *all*
// top-level symbols into the type table, we would potentially encounter missing intra-module symbols.
return sym
}
// bindModuleMethodBodies binds both the module's direct methods in addition to class methods. This must be done after
// bindModuleBodies binds both the module's direct methods in addition to class methods. This must be done after
// all top-level symbolic information has been bound, in case definitions, statements, and expressions depend upon them.
func (b *binder) bindModuleMethodBodies(module *symbols.Module) {
func (b *binder) bindModuleBodies(module *symbols.Module) {
// Set the current module in the context so we can e.g. enforce accessibility. We need to do this again while
// binding the module bodies so that the correct context is reestablished for lookups, etc.
priormodule := b.ctx.Currmodule
@ -171,7 +177,7 @@ func (b *binder) bindModuleMethodBodies(module *symbols.Module) {
case *symbols.ModuleMethod:
b.bindModuleMethodBody(m)
case *symbols.Class:
b.bindClassMethodBodies(m)
b.bindClassBodies(m)
}
}
}

View file

@ -53,16 +53,16 @@ func (b *binder) resolveBindPackage(pkg *pack.Package, pkgurl pack.PackageURL) *
// Resolve all package dependencies.
b.resolvePackageDeps(pkgsym)
// Now bind all of the package's modules (if any). This pass does not yet actually bind class members yet.
b.bindPackageModules(pkgsym)
// Now bind all of the package's declarations (if any). This pass does not yet actually bind definitions.
b.bindPackageDeclarations(pkgsym)
// Next go ahead and bind class members. This happens in a full pass after resolving all modules, because this
// phase might reference members that are only now exposed after the above phase.
b.bindPackageClassMembers(pkgsym)
// Next go ahead and bind definitions. This happens in a full pass after resolving all modules, because this
// phase might reference other declarations that are only now exposed after the above phase.
b.bindPackageDefinitions(pkgsym)
// Finally, bind all of the package's method bodies. This second pass is required to ensure that inter-module
// dependencies can resolve to symbols, after reaching the symbol-level fixed point above.
b.bindPackageMethodBodies(pkgsym)
b.bindPackageBodies(pkgsym)
return respkg
}
@ -137,53 +137,32 @@ func (b *binder) resolveDep(dep pack.PackageURL) *symbols.ResolvedPackage {
return nil
}
// bindPackageModules recursively binds all modules and stores them in the given package. Note that this does not yet
// bind module bodies -- both module and class methods -- because those require the top-levels for all modules first.
func (b *binder) bindPackageModules(pkg *symbols.Package) {
// bindPackageDeclarations recursively binds all named entities and stores them in the given package. This doesn't yet
// bind definitions, because those require the top-level declarations for all modules and members to be available first.
func (b *binder) bindPackageDeclarations(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
if pkg.Node.Modules != nil {
// First, for each module, simply create the symbol. This ensures that inter-module references are found.
modules := *pkg.Node.Modules
for _, modtok := range ast.StableModules(modules) {
pkg.Modules[modtok] = b.createModule(modules[modtok], pkg)
}
// Now that every module has a symbol entry, bind all module imports.
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleImports(pkg.Modules[mod])
}
// Next, we must bind the top level class names. This is because inter-module references on classes might
// exist in many places (properties, signatures, exports, and so on).
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleClasses(pkg.Modules[mod])
}
// Now we can safely bind the property and method members.
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleMembers(pkg.Modules[mod])
}
// And finally, we can bind the exports, that might refer to all of the above.
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleExports(pkg.Modules[mod])
pkg.Modules[modtok] = b.bindModuleDeclarations(modules[modtok], pkg)
}
}
}
// bindPackageClassMembers binds all class member definitions within a package (function signatures and varaibles), but
// doesn't actually bind any function bodies yet.
func (b *binder) bindPackageClassMembers(pkg *symbols.Package) {
// bindPackageDefinitions binds all definitions within a package (classes, signatures, varaibles, etc), but doesn't
// actually bind any function bodies yet. The function bodies may depend upon information that depends upon information
// that isn't fully computed until after the definition pass has been completed.
func (b *binder) bindPackageDefinitions(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleClassMembers(pkg.Modules[mod])
b.bindModuleDefinitions(pkg.Modules[mod])
}
}
// bindPackageMethodBodies binds all method bodies, in a distinct pass, after binding all symbol-level information.
func (b *binder) bindPackageMethodBodies(pkg *symbols.Package) {
// bindPackageBodies binds all method bodies, in a distinct pass, after binding all symbol-level information.
func (b *binder) bindPackageBodies(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
for _, mod := range symbols.StableModuleMap(pkg.Modules) {
b.bindModuleMethodBodies(pkg.Modules[mod])
b.bindModuleBodies(pkg.Modules[mod])
}
}

View file

@ -6,6 +6,7 @@ import (
"github.com/marapongo/mu/pkg/compiler/ast"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/tokens"
"github.com/marapongo/mu/pkg/util/contract"
)
// Class is a fully bound class symbol.
@ -52,6 +53,17 @@ func (node *Class) GetInit() *ClassMethod {
return nil
}
// SetBase mutates the base class, in cases where it wasn't available at initialization time.
func (node *Class) SetBase(extends Type) {
contract.Assert(node.Extends == nil)
node.Extends = extends
// If this class extends something else, wire up the "super" variable too.
if extends != nil {
node.Super = NewSpecialVariableSym(tokens.SuperVariable, extends)
}
}
// NewClassSym returns a new Class symbol with the given node, parent, extends, and implements, and empty members.
func NewClassSym(node *ast.Class, parent *Module, extends Type, implements Types) *Class {
nm := tokens.TypeName(node.Name.Ident)
@ -65,7 +77,6 @@ func NewClassSym(node *ast.Class, parent *Module, extends Type, implements Types
),
),
Parent: parent,
Extends: extends,
Implements: implements,
Members: make(ClassMemberMap),
}
@ -73,10 +84,8 @@ func NewClassSym(node *ast.Class, parent *Module, extends Type, implements Types
// Populate the "this" variable for instance methods.
class.This = NewSpecialVariableSym(tokens.ThisVariable, class)
// If this class extends something else, wire up the "super" variable too.
if extends != nil {
class.Super = NewSpecialVariableSym(tokens.SuperVariable, extends)
}
// Set the base class, possibly initializing "super" if appropriate.
class.SetBase(extends)
return class
}