Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 123 additions & 74 deletions src/core/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3267,6 +3267,20 @@ static codeinfo compiler_arglist(compiler *c, syntaxtreenode *node, registerindx
return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions);
}

bool _islocalfunction(compiler *c, value symbol) {
functionstate *fc = compiler_currentfunctionstate(c);
// Go backwards to prioritize recent def'ns
for (functionstate *f=fc;
f>c->fstack; // Note inequality: don't include the global functionstate
f--) {
for (int i=f->functionref.count-1; i>=0; i--) { // Go backwards
functionref *ref=&f->functionref.data[i];
if (MORPHO_ISEQUAL(ref->symbol, symbol)) return true;
}
}
return false;
}

/** Is this a method invocation? */
static bool compiler_isinvocation(compiler *c, syntaxtreenode *call) {
bool isinvocation=false;
Expand All @@ -3284,6 +3298,14 @@ static bool compiler_isinvocation(compiler *c, syntaxtreenode *call) {
/* Check that the method is a symbol */
method=compiler_getnode(c, selector->right);
if (method->type==NODE_SYMBOL) isinvocation=true;
} else if (selector->type==NODE_SYMBOL) {
objectclass *klass = compiler_getcurrentclass(c);

if (klass &&
!_islocalfunction(c, selector->content) &&
dictionary_get(&klass->methods, selector->content, NULL)) {
isinvocation=true;
}
}
return isinvocation;
}
Expand Down Expand Up @@ -3375,75 +3397,98 @@ static codeinfo compiler_call(compiler *c, syntaxtreenode *node, registerindx re

#include <stdint.h>

/** Compiles a method invocation */
/* Compiles a method invocation:
node | node
/ \ | / \
DOT args | method args
/ \ | (self)
object method */
static codeinfo compiler_invoke(compiler *c, syntaxtreenode *node, registerindx reqout) {
unsigned int ninstructions=0;
codeinfo object=CODEINFO_EMPTY;

/* Get the selector node */
syntaxtreenode *selector=compiler_getnode(c, node->left);

/* Retrieve the selector node */
syntaxtreenode *selectornode=compiler_getnode(c, node->left),
*methodnode=NULL,
*objectnode=NULL;

if (selectornode->type==NODE_DOT) {
objectnode=compiler_getnode(c, selectornode->left);
methodnode=compiler_getnode(c, selectornode->right);
} else if (selectornode->type==NODE_SYMBOL) {
methodnode=selectornode;
}

compiler_beginargs(c);

registerindx rSel = compiler_regalloctop(c);
registerindx rObj = compiler_regalloctop(c);

syntaxtreenode *methodnode=compiler_getnode(c, selector->right);
// Fetch the method
codeinfo cSel = CODEINFO(CONSTANT, 0, 0);
cSel.dest = compiler_addsymbol(c, methodnode, methodnode->content);
codeinfo method=compiler_movetoregister(c, methodnode, cSel, rSel);
ninstructions+=method.ninstructions;

/* Fetch the object. We patch to ensure that builtin classes are prioritized over constructor functions. */
syntaxtreenode *objectnode=compiler_getnode(c, selector->left);
if (objectnode->type==NODE_SYMBOL) {
value klass=builtin_findclass(objectnode->content);
if (MORPHO_ISCLASS(klass)) {
registerindx kindx = compiler_addconstant(c, objectnode, klass, true, false);
object=CODEINFO(CONSTANT, kindx, 0);
// Fetch the object
if (objectnode) {
bool invokeclass=false;
// Patch to ensure that builtin classes are prioritized over constructor functions.
if (objectnode->type==NODE_SYMBOL) {
value klass=builtin_findclass(objectnode->content);
if (MORPHO_ISCLASS(klass)) {
registerindx kindx = compiler_addconstant(c, objectnode, klass, true, false);
object=CODEINFO(CONSTANT, kindx, 0);
invokeclass=true;
}
}
}

// Otherwise just fetch the object normally
if (object.returntype==REGISTER && object.dest==REGISTER_UNALLOCATED) {
object=compiler_nodetobytecode(c, selector->left, rObj);

// Otherwise just fetch the object normally
if (!invokeclass) {
object=compiler_nodetobytecode(c, selectornode->left, rObj);
ninstructions+=object.ninstructions;
}

// Ensure register allocations remain correct
if (object.returntype==REGISTER && object.dest!=rObj) {
compiler_regfreetemp(c, object.dest);
compiler_regtempwithindx(c, rObj); // Ensure rObj remains allocated
}
} else { // If no objectnode, fetch self from r0
object=CODEINFO(REGISTER, 0 /* <- r0 */, 0);
}

ninstructions+=object.ninstructions;
if (object.returntype==REGISTER && object.dest!=rObj) {
compiler_regfreetemp(c, object.dest);
compiler_regtempwithindx(c, rObj); // Ensure rObj remains allocated
}
object=compiler_movetoregister(c, selector, object, rObj);
// Move the object into place for the invocation
object=compiler_movetoregister(c, selectornode, object, rObj);
ninstructions+=object.ninstructions;

/* Compile the arguments */
// Compile the arguments
codeinfo args = CODEINFO_EMPTY;
if (node->right!=SYNTAXTREE_UNCONNECTED) args=compiler_nodetobytecode(c, node->right, REGISTER_UNALLOCATED);
ninstructions+=args.ninstructions;

/* Remember the last argument */
// Remember the last argument
registerindx lastarg=compiler_regtop(c);

/* Check we don't have too many arguments */
// Check we don't have too many arguments
if (lastarg-rSel>MORPHO_MAXARGS) {
compiler_error(c, node, COMPILE_TOOMANYARGS);
return CODEINFO_EMPTY;
}

compiler_endargs(c);

/* Generate the call instruction */
// Generate the call instruction
int nposn=0, nopt=0;
compiler_regcountargs(c, object.dest+1, lastarg, &nposn, &nopt);
compiler_addinstruction(c, ENCODE(OP_INVOKE, rSel, nposn, nopt), node);
ninstructions++;

/* Free all the registers used for the call */
// Free all the registers used for the call
compiler_regfreetemp(c, rSel);
compiler_regfreetoend(c, rObj+1);

/* Move the result to the requested register */
// Move the result to the requested register
if (reqout!=REGISTER_UNALLOCATED && object.dest!=reqout) {
compiler_addinstruction(c, ENCODE_DOUBLE(OP_MOV, reqout, rObj), node);
ninstructions++;
Expand Down Expand Up @@ -3544,54 +3589,59 @@ void compiler_overridemethod(compiler *c, syntaxtreenode *node, objectfunction *
}

/** Compiles a list of method declarations. */
static codeinfo compiler_method(compiler *c, syntaxtreenode *node, registerindx reqout) {
static codeinfo compiler_classbody(compiler *c, syntaxtreeindx startindx, registerindx reqout) {
codeinfo out;
unsigned int ninstructions=0;
objectclass *klass=compiler_getcurrentclass(c);

switch (node->type) {
case NODE_FUNCTION:
{
/* Store the current method so that compiler_function can recognize that
it is in a method definition */
c->currentmethod=node;

/* Compile the method declaration */
out=compiler_function(c, node, reqout);
ninstructions+=out.ninstructions;

/* Insert the compiled function into the method dictionary, making sure the method name is interned */
objectfunction *method = compiler_getpreviousfunction(c);
if (method) {
value omethod = MORPHO_OBJECT(method);
value symbol = program_internsymbol(c->out, node->content),
prev=MORPHO_NIL;

if (dictionary_get(&klass->methods, symbol, &prev)) {
compiler_overridemethod(c, node, method, prev); // Override or create a metafunction
} else dictionary_insert(&klass->methods, symbol, omethod); // Just insert
}
syntaxtreenodetype seqtype[] = { NODE_SEQUENCE };
varray_syntaxtreeindx entries;
varray_syntaxtreeindxinit(&entries);

syntaxtree_flatten(compiler_getsyntaxtree(c), startindx, 1, seqtype, &entries);

// Pass through body declaration to ensure all method labels are defined
for (int i=0; i<entries.count; i++) {
syntaxtreenode *node=syntaxtree_nodefromindx(compiler_getsyntaxtree(c), entries.data[i]);

if (node->type==NODE_FUNCTION) {
if (!dictionary_get(&klass->methods, node->content, NULL)) {
value symbol = program_internsymbol(c->out, node->content);
dictionary_insert(&klass->methods, symbol, MORPHO_NIL);
}
break;
case NODE_SEQUENCE:
{
syntaxtreenode *child=NULL;
if (node->left!=SYNTAXTREE_UNCONNECTED) {
child=compiler_getnode(c, node->left);
out=compiler_method(c, child, reqout);
ninstructions+=out.ninstructions;
}
if (node->right!=SYNTAXTREE_UNCONNECTED) {
child=compiler_getnode(c, node->right);
out=compiler_method(c, child, reqout);
ninstructions+=out.ninstructions;
}
} else UNREACHABLE("Incorrect node type found in class declaration");
}

// Now compile method definitions
for (int i=0; i<entries.count; i++) {
syntaxtreenode *node=syntaxtree_nodefromindx(compiler_getsyntaxtree(c), entries.data[i]);

if (node->type==NODE_FUNCTION) {
// Store the current method so that compiler_function can recognize that
// it is in a method definition
c->currentmethod=node;

// Compile the method declaration
out=compiler_function(c, node, reqout);
ninstructions+=out.ninstructions;

// Insert the compiled function into the method dictionary, making sure the method name is interned
objectfunction *method = compiler_getpreviousfunction(c);
if (method) {
value omethod = MORPHO_OBJECT(method);
value symbol = program_internsymbol(c->out, node->content),
prev=MORPHO_NIL;

dictionary_get(&klass->methods, symbol, &prev);

if (MORPHO_ISNIL(prev)) { // Just insert if we don't have any definition
dictionary_insert(&klass->methods, symbol, omethod);
} else compiler_overridemethod(c, node, method, prev); // Override or create a metafunction
}
break;
default:
UNREACHABLE("Incorrect node type found in class declaration");
break;
}
}

varray_syntaxtreeindxclear(&entries);

return CODEINFO(REGISTER, REGISTER_UNALLOCATED, ninstructions);
}
Expand Down Expand Up @@ -3678,10 +3728,9 @@ static codeinfo compiler_class(compiler *c, syntaxtreenode *node, registerindx r
compiler_error(c, node, COMPILE_CLSSLNRZ, MORPHO_GETCSTRING(klass->name));
}

/* Compile method declarations */
/* Compile the body */
if (node->right!=SYNTAXTREE_UNCONNECTED) {
syntaxtreenode *child = compiler_getnode(c, node->right);
mout=compiler_method(c, child, reqout);
mout=compiler_classbody(c, node->right, reqout);
ninstructions+=mout.ninstructions;
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1886,7 +1886,7 @@ bool morpho_call(vm *v, value f, int nargs, value *args, value *ret) {
} else morpho_runtimeerror(v, VM_NOINITIALIZER, MORPHO_GETCSTRING(klass->name));

if (success) {
vm_bindobject(v, obj);
vm_bindobjectwithoutcollect(v, obj); // 4/2/25 Changed to disable collection because we can't guarantee the external context of the caller (e.g. apply)
*ret = obj;
} else morpho_freeobject(obj);
} else morpho_runtimeerror(v, VM_INSTANTIATEFAILED);
Expand Down
23 changes: 23 additions & 0 deletions test/class/local_fn_supersedes_method_call.morpho
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// A local function supercedes a method call

class A {
foo() {
fn goo(x) {
print "Boo"
}

goo("Hoo")
}

goo(x) {
print x
}
}

var a = A()

a.foo()
// expect: Boo

a.goo("Woo")
// expect: Woo
23 changes: 23 additions & 0 deletions test/class/method_call_supersedes_global_fn.morpho
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// A method reference supersedes a global function definition

fn goo(x) {
print "Boo"
}

class A {
foo() {
goo("Hoo")
}

goo(x) {
print x
}
}

var a = A()

a.foo()
// expect: Hoo

a.goo("Woo")
// expect: Woo
21 changes: 21 additions & 0 deletions test/class/no_self_for_method_call.morpho
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Avoid needing self in a method call

class A {
foo() {
self.goo("Foo")
goo("Hoo")
}

goo(x) {
print x
}
}

var a = A()

a.foo()
// expect: Foo
// expect: Hoo

a.goo("Woo")
// expect: Woo