Skip to content

Commit

Permalink
Allow 'super' in property accesses (both direct, and indexed).
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Apr 7, 2024
1 parent be765b2 commit ac05d29
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public class CounterThisBenchmark extends TruffleBenchmark {
" } " +
"} " +
"class CounterDirect extends DirectBase { " +
" increment() { " +
" return super.increment(); " +
" } " +
" getCount() { " +
" return super.getCount(); " +
" } " +
"} " +
"class IndexedBase { " +
" constructor() { " +
Expand All @@ -34,6 +40,12 @@ public class CounterThisBenchmark extends TruffleBenchmark {
" } " +
"} " +
"class CounterIndexed extends IndexedBase { " +
" increment() { " +
" return super['increment'](); " +
" } " +
" getCount() { " +
" return super['getCount'](); " +
" } " +
"}";

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ expr5 : expr5 '.' ID #PropertyReadExpr5
;
expr6 : literal #LiteralExpr6
| 'this' #ThisExpr6
| 'super' #SuperExpr6
| ID #ReferenceExpr6
| '[' (expr1 (',' expr1)*)? ']' #ArrayLiteralExpr6
| 'new' constr=expr6 ('('(expr1 (',' expr1)*)?')')? #NewExpr6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.endoflineblog.truffle.part_13.exceptions.EasyScriptException;
import com.endoflineblog.truffle.part_13.nodes.exprs.EasyScriptExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.SuperExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.properties.CommonReadPropertyNode;
import com.endoflineblog.truffle.part_13.runtime.EasyScriptTruffleStrings;
import com.oracle.truffle.api.dsl.Cached;
Expand Down Expand Up @@ -133,6 +134,13 @@ public Object evaluateAsReceiver(VirtualFrame frame) {
@Override
public Object evaluateAsFunction(VirtualFrame frame, Object receiver) {
Object property = this.getIndexExpr().executeGeneric(frame);
return this.readIndexOrProperty(receiver, property);
EasyScriptExprNode arrayExpr = this.getArrayExpr();
// if we're reading a property of 'super',
// we know we need to look in its parent prototype,
// and not in 'this' (which will be used as the method receiver)
Object propertyTarget = arrayExpr instanceof SuperExprNode
? ((SuperExprNode) arrayExpr).readParentPrototype(receiver)
: receiver;
return this.readIndexOrProperty(propertyTarget, property);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected Object writeTruffleStringPropertyCached(
* The uncached version of the specialization for writing a string property of an object,
* in code like {@code [1, 2]['abc'] = 3}.
*/
@Specialization(replaces = "writeTruffleStringPropertyCached", limit = "2")
@Specialization(replaces = "writeTruffleStringPropertyCached")
protected Object writeTruffleStringPropertyUncached(
Object target, TruffleString propertyName, Object rvalue,
@Cached TruffleString.ToJavaStringNode toJavaStringNode,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.endoflineblog.truffle.part_13.nodes.exprs.objects;

import com.endoflineblog.truffle.part_13.exceptions.EasyScriptException;
import com.endoflineblog.truffle.part_13.nodes.exprs.EasyScriptExprNode;
import com.endoflineblog.truffle.part_13.runtime.ClassPrototypeChainObject;
import com.endoflineblog.truffle.part_13.runtime.ClassPrototypeObject;
import com.endoflineblog.truffle.part_13.runtime.JavaScriptObject;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;

/**
* The Node implementing the 'super' expression.
*/
public final class SuperExprNode extends EasyScriptExprNode {
static abstract class ReadParentPrototypeNode extends Node {
abstract Object executeReadParentPrototype(Object object);

@Specialization
protected Object parentPrototypeOfJavaScriptObject(JavaScriptObject javaScriptObject) {
ClassPrototypeObject classPrototypeObject = javaScriptObject.classPrototypeObject;
if (classPrototypeObject instanceof ClassPrototypeChainObject) {
return ((ClassPrototypeChainObject) classPrototypeObject).superClassPrototype;
} else {
throw new EasyScriptException("Class '" + classPrototypeObject.className +
"' does not have a superclass that can be accessed with 'super'");
}
}

@Specialization
protected void parentPrototypeOfNonJavaScriptObject(Object object) {
throw new EasyScriptException("Cannot access prototype with 'super' of: " + object);
}
}

@SuppressWarnings("FieldMayBeFinal")
@Child
private ThisExprNode thisExprNode = new ThisExprNode();

@SuppressWarnings("FieldMayBeFinal")
@Child
private ReadParentPrototypeNode readParentPrototypeNode = SuperExprNodeFactory.ReadParentPrototypeNodeGen.create();

@Override
public Object executeGeneric(VirtualFrame frame) {
// executeGeneric() simply returns 'this'
// (that will be the method that property access Nodes use to establish the method call receiver,
// in their evaluateAsReceiver() methods)
return this.thisExprNode.executeGeneric(frame);
}

public Object readParentPrototype(Object thisValue) {
// this method is called from the property access Nodes
// to find the parent prototype
return this.readParentPrototypeNode.executeReadParentPrototype(thisValue);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.endoflineblog.truffle.part_13.nodes.exprs.properties;

import com.endoflineblog.truffle.part_13.nodes.exprs.EasyScriptExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.SuperExprNode;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
Expand Down Expand Up @@ -42,6 +43,13 @@ public Object evaluateAsReceiver(VirtualFrame frame) {

@Override
public Object evaluateAsFunction(VirtualFrame frame, Object receiver) {
return this.readProperty(receiver);
EasyScriptExprNode targetExpr = this.getTargetExpr();
// if we're reading a property of 'super',
// we know we need to look in its parent prototype,
// and not in 'this' (which will be used as the method receiver)
Object propertyTarget = targetExpr instanceof SuperExprNode
? ((SuperExprNode) targetExpr).readParentPrototype(receiver)
: receiver;
return this.readProperty(propertyTarget);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.endoflineblog.truffle.part_13.nodes.exprs.literals.UndefinedLiteralExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.ClassDeclExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.NewExprNodeGen;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.SuperExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.objects.ThisExprNode;
import com.endoflineblog.truffle.part_13.nodes.exprs.properties.PropertyReadExprNodeGen;
import com.endoflineblog.truffle.part_13.nodes.exprs.properties.PropertyWriteExprNodeGen;
Expand Down Expand Up @@ -532,6 +533,8 @@ private EasyScriptExprNode parseExpr6(EasyScriptParser.Expr6Context expr6) {
return this.parseLiteralExpr((EasyScriptParser.LiteralExpr6Context) expr6);
} else if (expr6 instanceof EasyScriptParser.ThisExpr6Context) {
return new ThisExprNode();
} else if (expr6 instanceof EasyScriptParser.SuperExpr6Context) {
return new SuperExprNode();
} else if (expr6 instanceof EasyScriptParser.ReferenceExpr6Context) {
return this.parseReference(((EasyScriptParser.ReferenceExpr6Context) expr6).ID().getText());
} else if (expr6 instanceof EasyScriptParser.ArrayLiteralExpr6Context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@ExportLibrary(InteropLibrary.class)
public final class ClassPrototypeChainObject extends ClassPrototypeObject {
final ClassPrototypeObject superClassPrototype;
public final ClassPrototypeObject superClassPrototype;

public ClassPrototypeChainObject(Shape shape, String className,
ClassPrototypeObject superClassPrototype) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
*/
@ExportLibrary(InteropLibrary.class)
public class JavaScriptObject extends InteropDynamicObject {
// this can't be private, because it's used in specialization guard expressions
final ClassPrototypeObject classPrototypeObject;
public final ClassPrototypeObject classPrototypeObject;

public JavaScriptObject(Shape shape, ClassPrototypeObject classPrototypeObject) {
super(shape);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ void methods_are_inherited() {
assertEquals("A", result.toString());
}

@Test
void super_reads_property_of_parent_prototype() {
Value result = this.context.eval("ezs", "" +
"class Base { " +
" m() { " +
" return 'Base'; " +
" } " +
"} " +
"class Derived extends Base { " +
" m() { " +
" return 'Derived'; " +
" } " +
" callSuperM() { " +
" return super.m(); " +
" } " +
" callThisM() { " +
" return this.m(); " +
" } " +
"} " +
"const obj = new Derived(); " +
"obj.callSuperM() + '_' + obj.callThisM();"
);

assertEquals("Base_Derived", result.toString());
}

@Test
void extending_non_existent_class_is_an_error() {
try {
Expand Down Expand Up @@ -69,6 +95,12 @@ public void benchmark_returns_its_input() {
" } " +
"} " +
"class Counter extends BaseCounter { " +
" increment() { " +
" return super.increment(); " +
" } " +
" getCount() { " +
" return super['getCount'](); " +
" } " +
"} " +
"function countWithThisInFor(n) { " +
" const counter = new Counter(); " +
Expand Down

0 comments on commit ac05d29

Please sign in to comment.