Populate this
/super
in instance method activation frames
This change populates the special `this` and `super` variables in instance method activation frames. This occurs both during binding -- so that we can properly typecheck and verify references to them -- and during evaluation -- so we can actually operate on their values.
This commit is contained in:
parent
34e9eec4fb
commit
349e98abbd
11 changed files with 126 additions and 37 deletions
|
@ -243,6 +243,8 @@ func (b *binder) requireToken(node ast.Node, tok tokens.Token) symbols.Symbol {
|
|||
// A complex token is bound through the normal token binding lookup process.
|
||||
if sym := b.lookupSymbolToken(node, tok, true); sym != nil {
|
||||
return sym
|
||||
} else {
|
||||
b.Diag().Errorf(errors.ErrorSymbolNotFound.At(node), tok, "qualified token not found")
|
||||
}
|
||||
} else {
|
||||
// A simple token has no package, module, or class part. It refers to the symbol table.
|
||||
|
@ -252,7 +254,7 @@ func (b *binder) requireToken(node ast.Node, tok tokens.Token) symbols.Symbol {
|
|||
b.Diag().Errorf(errors.ErrorSymbolNotFound.At(node), tok, "simple name not found")
|
||||
}
|
||||
}
|
||||
return types.Any
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookupClassMember takes a class member token and binds it to a member of the class symbol. The type of the class
|
||||
|
@ -263,7 +265,7 @@ func (b *binder) requireClassMember(node ast.Node, class symbols.Type, tok token
|
|||
case symbols.ClassMember:
|
||||
return s
|
||||
default:
|
||||
contract.Failf("Expected symbol to be a class member: %v", tok)
|
||||
b.Diag().Errorf(errors.ErrorSymbolNotFound.At(node), tok, "class member not found")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -78,6 +78,15 @@ func (b *binder) bindClassMethod(node *ast.ClassMethod, parent *symbols.Class) *
|
|||
return sym
|
||||
}
|
||||
|
||||
func (b *binder) bindClassMethodBodies(class *symbols.Class) {
|
||||
for _, member := range class.Members {
|
||||
switch m := member.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
b.bindClassMethodBody(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *binder) bindClassMethodBody(method *symbols.ClassMethod) {
|
||||
glog.V(3).Infof("Binding class method '%v' body", method.Token())
|
||||
|
||||
|
@ -87,5 +96,19 @@ func (b *binder) bindClassMethodBody(method *symbols.ClassMethod) {
|
|||
b.ctx.Currclass = method.Parent
|
||||
defer func() { b.ctx.Currclass = priorclass }()
|
||||
|
||||
// Push a new activation frame and, if this isn't a static, register the special this/super variables.
|
||||
scope := b.ctx.Scope.Push(true)
|
||||
defer scope.Pop()
|
||||
if !method.Static() {
|
||||
// Register the "this" and, if relevant, "super" special variables.
|
||||
this := method.Parent.This
|
||||
contract.Assert(this != nil)
|
||||
b.ctx.Scope.MustRegister(this)
|
||||
super := method.Parent.Super
|
||||
if super != nil {
|
||||
b.ctx.Scope.MustRegister(super)
|
||||
}
|
||||
}
|
||||
|
||||
b.bindFunctionBody(method.Node)
|
||||
}
|
||||
|
|
|
@ -11,10 +11,9 @@ import (
|
|||
// bindFunctionBody binds a function body, including a scope, its parameters, and its expressions and statements.
|
||||
func (b *binder) bindFunctionBody(node ast.Function) {
|
||||
contract.Require(node != nil, "node")
|
||||
contract.Assertf(b.ctx.Scope.Frame, "Expected an activation frame at the top of the scope")
|
||||
|
||||
// Enter a new scope, bind the parameters, and then bind the body using a visitor.
|
||||
scope := b.ctx.Scope.Push(true)
|
||||
defer scope.Pop()
|
||||
params := node.GetParameters()
|
||||
if params != nil {
|
||||
for _, param := range *params {
|
||||
|
@ -22,7 +21,7 @@ func (b *binder) bindFunctionBody(node ast.Function) {
|
|||
ty := b.bindType(param.Type)
|
||||
sym := symbols.NewLocalVariableSym(param, ty)
|
||||
b.ctx.RegisterSymbol(param, sym)
|
||||
b.ctx.Scope.TryRegister(param, sym) // TODO: figure out whether to keep this.
|
||||
b.ctx.Scope.TryRegister(param, sym)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ func (b *binder) bindModuleMethod(node *ast.ModuleMethod, parent *symbols.Module
|
|||
return sym
|
||||
}
|
||||
|
||||
func (b *binder) bindModuleBodies(module *symbols.Module) {
|
||||
func (b *binder) bindModuleMethodBodies(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
|
||||
|
@ -107,17 +107,15 @@ func (b *binder) bindModuleBodies(module *symbols.Module) {
|
|||
case *symbols.ModuleMethod:
|
||||
b.bindModuleMethodBody(m)
|
||||
case *symbols.Class:
|
||||
for _, cmember := range m.Members {
|
||||
switch cm := cmember.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
b.bindClassMethodBody(cm)
|
||||
}
|
||||
}
|
||||
b.bindClassMethodBodies(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *binder) bindModuleMethodBody(method *symbols.ModuleMethod) {
|
||||
glog.V(3).Infof("Binding module method '%v' body", method.Token())
|
||||
// Push a new activation frame and bind the body.
|
||||
scope := b.ctx.Scope.Push(true)
|
||||
defer scope.Pop()
|
||||
b.bindFunctionBody(method.Node)
|
||||
}
|
||||
|
|
|
@ -134,6 +134,6 @@ func (b *binder) bindPackageModules(pkg *symbols.Package) {
|
|||
func (b *binder) bindPackageMethodBodies(pkg *symbols.Package) {
|
||||
contract.Require(pkg != nil, "pkg")
|
||||
for _, module := range pkg.Modules {
|
||||
b.bindModuleBodies(module)
|
||||
b.bindModuleMethodBodies(module)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,13 @@ func (s *Scope) Register(sym *symbols.LocalVariable) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// MustRegister registers a local with the given name; if it already exists, the function abandons.
|
||||
func (s *Scope) MustRegister(sym *symbols.LocalVariable) {
|
||||
if !s.Register(sym) {
|
||||
contract.Failf("Symbol already exists: %v", sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TryRegister registers a local with the given name; if it already exists, a compiler error is emitted.
|
||||
func (s *Scope) TryRegister(node ast.Node, sym *symbols.LocalVariable) {
|
||||
if !s.Register(sym) {
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
package binder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/marapongo/mu/pkg/compiler/ast"
|
||||
"github.com/marapongo/mu/pkg/compiler/errors"
|
||||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
|
@ -265,8 +267,8 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
|
|||
// TODO: what to do about readonly variables.
|
||||
var sym symbols.Symbol
|
||||
if node.Object == nil {
|
||||
// If there is no object, we either have a local variable reference or a module property or function. In
|
||||
// the former case, the token will be "simple"; in the latter case, it will be qualified.
|
||||
// If there is no object, we either have a "simple" local variable reference or a qualified module property or
|
||||
// function identifier. In both cases, requireToken will handle it for us.
|
||||
sym = a.b.requireToken(node.Name, node.Name.Tok)
|
||||
} else {
|
||||
// If there's an object, we are accessing a class member property or function.
|
||||
|
@ -280,12 +282,12 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
|
|||
ty = types.Any
|
||||
} else {
|
||||
switch s := sym.(type) {
|
||||
case ast.Function:
|
||||
ty = a.b.ctx.RequireFunction(s).FuncType()
|
||||
case ast.Variable:
|
||||
ty = a.b.ctx.RequireVariable(s).Type()
|
||||
case symbols.Function:
|
||||
ty = s.FuncType()
|
||||
case symbols.Variable:
|
||||
ty = s.Type()
|
||||
default:
|
||||
contract.Failf("Unrecognized load location symbol type: %v", sym.Token())
|
||||
contract.Failf("Unrecognized load location token '%v' symbol type: %v", sym.Token(), reflect.TypeOf(s))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ type Interpreter interface {
|
|||
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||
EvaluateModule(mod *symbols.Module, args core.Args) graph.Graph
|
||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||
EvaluateFunction(fnc symbols.Function, args core.Args) graph.Graph
|
||||
EvaluateFunction(fnc symbols.Function, this *Object, args core.Args) graph.Graph
|
||||
}
|
||||
|
||||
// New creates an interpreter that can be used to evaluate MuPackages.
|
||||
|
@ -85,7 +85,7 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) graph.Gr
|
|||
// Fetch the module's entrypoint function, erroring out if it doesn't have one.
|
||||
if ep, has := mod.Members[tokens.EntryPointFunction]; has {
|
||||
if epfnc, ok := ep.(symbols.Function); ok {
|
||||
return e.EvaluateFunction(epfnc, args)
|
||||
return e.EvaluateFunction(epfnc, nil, args)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) graph.Gr
|
|||
}
|
||||
|
||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, args core.Args) graph.Graph {
|
||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *Object, args core.Args) graph.Graph {
|
||||
glog.Infof("Evaluating function '%v'", fnc.Token())
|
||||
if glog.V(2) {
|
||||
defer glog.V(2).Infof("Evaluation of function '%v' completed w/ %v warnings and %v errors",
|
||||
|
@ -139,7 +139,7 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, args core.Args) graph
|
|||
|
||||
if e.Diag().Success() {
|
||||
// If the arguments bound correctly, make the call.
|
||||
_, uw := e.evalCall(fnc, argos...)
|
||||
_, uw := e.evalCall(fnc, this, argos...)
|
||||
if uw != nil {
|
||||
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
||||
contract.Assert(!uw.Break)
|
||||
|
@ -170,7 +170,7 @@ func (e *evaluator) popScope() {
|
|||
|
||||
// Functions
|
||||
|
||||
func (e *evaluator) evalCall(fnc symbols.Function, args ...*Object) (*Object, *Unwind) {
|
||||
func (e *evaluator) evalCall(fnc symbols.Function, this *Object, args ...*Object) (*Object, *Unwind) {
|
||||
// Save the prior func, set the new one, and restore upon exit.
|
||||
prior := fnc
|
||||
e.fnc = fnc
|
||||
|
@ -180,6 +180,29 @@ func (e *evaluator) evalCall(fnc symbols.Function, args ...*Object) (*Object, *U
|
|||
e.pushScope(true)
|
||||
defer e.popScope()
|
||||
|
||||
// If the target is an instance method, the "this" and "super" variables must be bound to values.
|
||||
if this != nil {
|
||||
switch f := fnc.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
contract.Assertf(!f.Static(), "Static methods don't have 'this' arguments, but we got a non-nil one")
|
||||
contract.Assertf(types.CanConvert(this.Type, f.Parent), "'this' argument was of the wrong type")
|
||||
e.ctx.Scope.Register(f.Parent.This)
|
||||
e.locals.InitValueReference(f.Parent.This, &Reference{obj: this, readonly: true})
|
||||
if f.Parent.Super != nil {
|
||||
e.ctx.Scope.Register(f.Parent.Super)
|
||||
e.locals.InitValueReference(f.Parent.Super, &Reference{obj: this, readonly: true})
|
||||
}
|
||||
default:
|
||||
contract.Failf("Only class methods should have 'this' arguments, but we got a non-nil one")
|
||||
}
|
||||
} else {
|
||||
// If no this was supplied, we expect that this is either not a class method, or it is a static.
|
||||
switch f := fnc.(type) {
|
||||
case *symbols.ClassMethod:
|
||||
contract.Assertf(f.Static(), "Instance methods require 'this' arguments, but we got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the arguments line up to the parameter slots and add them to the frame.
|
||||
node := fnc.FuncNode()
|
||||
params := node.GetParameters()
|
||||
|
@ -654,13 +677,8 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
|
|||
contract.Failf("Expected function expression to yield a function type")
|
||||
}
|
||||
|
||||
// If there is a 'this' it goes first in the arguments.
|
||||
var args []*Object
|
||||
if fnc.This != nil {
|
||||
args = append(args, fnc.This)
|
||||
}
|
||||
|
||||
// Now evaluate the arguments to the function, in order.
|
||||
var args []*Object
|
||||
if node.Arguments != nil {
|
||||
for _, arg := range *node.Arguments {
|
||||
argobj, uw := e.evalExpression(arg)
|
||||
|
@ -672,7 +690,7 @@ func (e *evaluator) evalInvokeFunctionExpression(node *ast.InvokeFunctionExpress
|
|||
}
|
||||
|
||||
// Finally, actually dispatch the call; this will create the activation frame, etc. for us.
|
||||
return e.evalCall(fnc.Func, args...)
|
||||
return e.evalCall(fnc.Func, fnc.This, args...)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*Object, *Unwind) {
|
||||
|
|
|
@ -53,6 +53,16 @@ func (s *localScope) GetValue(sym *symbols.LocalVariable) *Object {
|
|||
// GetValueReference returns a reference to the object for the given symbol. If init is true, and the value doesn't
|
||||
// exist, a new slot will be allocated. Otherwise, the return value is nil.
|
||||
func (s *localScope) GetValueReference(sym *symbols.LocalVariable, init bool) *Reference {
|
||||
return s.lookupValueReference(sym, nil, init)
|
||||
}
|
||||
|
||||
// InitValue registers a reference for a local variable, and asserts that none previously existed.
|
||||
func (s *localScope) InitValueReference(sym *symbols.LocalVariable, ref *Reference) {
|
||||
s.lookupValueReference(sym, ref, false)
|
||||
}
|
||||
|
||||
// lookupValueReference is used to lookup and initialize references using a single, shared routine.
|
||||
func (s *localScope) lookupValueReference(sym *symbols.LocalVariable, place *Reference, init bool) *Reference {
|
||||
// To get a value's reference, we must first find the position in the shadowed frames, so that its lifetime equals
|
||||
// the actual local variable symbol's lifetime. This ensures that once that frame is popped, so too is any value
|
||||
// associated with it; and similarly, that its value won't be popped until the frame containing the variable is.
|
||||
|
@ -73,9 +83,18 @@ func (s *localScope) GetValueReference(sym *symbols.LocalVariable, init bool) *R
|
|||
contract.Assert(lex != nil)
|
||||
|
||||
if ref, has := s.Values[sym]; has {
|
||||
contract.Assertf(place == nil, "Expected an empty value slot, given init usage; it was non-nil: %v", sym)
|
||||
return ref
|
||||
} else if place != nil {
|
||||
s.Values[sym] = place
|
||||
return place
|
||||
} else if init {
|
||||
ref := &Reference{}
|
||||
s.Values[sym] = ref
|
||||
return ref
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValue overwrites the current value, or adds a new entry, for the given symbol.
|
||||
|
|
|
@ -17,6 +17,8 @@ type Class struct {
|
|||
Extends Type
|
||||
Implements Types
|
||||
Members ClassMemberMap
|
||||
This *LocalVariable // the special "this" local for instance members.
|
||||
Super *LocalVariable // the special "super" local for instance members.
|
||||
}
|
||||
|
||||
var _ Symbol = (*Class)(nil)
|
||||
|
@ -42,7 +44,7 @@ func (node *Class) String() string { return string(node.Name()) }
|
|||
// 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)
|
||||
return &Class{
|
||||
class := &Class{
|
||||
Node: node,
|
||||
Nm: nm,
|
||||
Tok: tokens.Type(
|
||||
|
@ -56,6 +58,16 @@ func NewClassSym(node *ast.Class, parent *Module, extends Type, implements Types
|
|||
Implements: implements,
|
||||
Members: make(ClassMemberMap),
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return class
|
||||
}
|
||||
|
||||
// ClassMember is a marker interface for things that can be module members.
|
||||
|
@ -63,6 +75,7 @@ type ClassMember interface {
|
|||
Symbol
|
||||
classMember()
|
||||
Optional() bool
|
||||
Static() bool
|
||||
Default() *interface{}
|
||||
Type() Type
|
||||
MemberNode() ast.ClassMember
|
||||
|
@ -98,6 +111,7 @@ func (node *ClassProperty) Token() tokens.Token {
|
|||
func (node *ClassProperty) Tree() diag.Diagable { return node.Node }
|
||||
func (node *ClassProperty) classMember() {}
|
||||
func (node *ClassProperty) Optional() bool { return node.Node.Optional != nil && *node.Node.Optional }
|
||||
func (node *ClassProperty) Static() bool { return node.Node.Static != nil && *node.Node.Static }
|
||||
func (node *ClassProperty) Default() *interface{} { return node.Node.Default }
|
||||
func (node *ClassProperty) Type() Type { return node.Ty }
|
||||
func (node *ClassProperty) MemberNode() ast.ClassMember { return node.Node }
|
||||
|
@ -136,7 +150,8 @@ func (node *ClassMethod) Token() tokens.Token {
|
|||
}
|
||||
func (node *ClassMethod) Tree() diag.Diagable { return node.Node }
|
||||
func (node *ClassMethod) classMember() {}
|
||||
func (node *ClassMethod) Optional() bool { return true }
|
||||
func (node *ClassMethod) Optional() bool { return false }
|
||||
func (node *ClassMethod) Static() bool { return node.Node.Static != nil && *node.Node.Static }
|
||||
func (node *ClassMethod) Default() *interface{} { return nil }
|
||||
func (node *ClassMethod) Type() Type { return node.Ty }
|
||||
func (node *ClassMethod) MemberNode() ast.ClassMember { return node.Node }
|
||||
|
|
|
@ -20,6 +20,7 @@ var _ Symbol = (Variable)(nil)
|
|||
// LocalVariable is a fully bound local variable symbol.
|
||||
type LocalVariable struct {
|
||||
Node *ast.LocalVariable
|
||||
Nm tokens.Name
|
||||
Ty Type
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ var _ Symbol = (*LocalVariable)(nil)
|
|||
var _ Variable = (*LocalVariable)(nil)
|
||||
|
||||
func (node *LocalVariable) symbol() {}
|
||||
func (node *LocalVariable) Name() tokens.Name { return node.Node.Name.Ident }
|
||||
func (node *LocalVariable) Name() tokens.Name { return node.Nm }
|
||||
func (node *LocalVariable) Token() tokens.Token { return tokens.Token(node.Name()) }
|
||||
func (node *LocalVariable) Tree() diag.Diagable { return node.Node }
|
||||
func (node *LocalVariable) Type() Type { return node.Ty }
|
||||
|
@ -36,5 +37,10 @@ func (node *LocalVariable) String() string { return string(node.Name()) }
|
|||
|
||||
// NewLocalVariableSym returns a new LocalVariable symbol associated with the given AST node.
|
||||
func NewLocalVariableSym(node *ast.LocalVariable, ty Type) *LocalVariable {
|
||||
return &LocalVariable{Node: node, Ty: ty}
|
||||
return &LocalVariable{Node: node, Nm: node.Name.Ident, Ty: ty}
|
||||
}
|
||||
|
||||
// NewSpecialVariableSym returns a "special" LocalVariable symbol that has no corresponding AST node and has a name.
|
||||
func NewSpecialVariableSym(nm tokens.Name, ty Type) *LocalVariable {
|
||||
return &LocalVariable{Node: nil, Nm: nm, Ty: ty}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue