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:
joeduffy 2017-01-26 18:14:49 -08:00
parent 34e9eec4fb
commit 349e98abbd
11 changed files with 126 additions and 37 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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) {

View file

@ -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))
}
}

View file

@ -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) {

View file

@ -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.

View file

@ -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 }

View file

@ -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}
}