Skip to content

Commit

Permalink
Extend LambdaConstructor for more flexibility
Browse files Browse the repository at this point in the history
This lets us create constructors that behave differently when invoked
using "new" than when called directly. The native "Date" class is an
example. The default behavior, which uses the same behavior in either
case but can automatically throw an exception if only one form is
supported, still works.
  • Loading branch information
gbrail committed Sep 21, 2024
1 parent b08de52 commit a84fbba
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,30 @@ public LambdaConstructor(
this.flags = flags;
}

/**
* Create a new constructor that may be called using new or as a function, and exhibits
* different behavior for each.
*/
public LambdaConstructor(
Scriptable scope,
String name,
int length,
Callable target,
Constructable targetConstructor) {
super(scope, name, length, target);
this.targetConstructor = targetConstructor;
this.flags = CONSTRUCTOR_DEFAULT;
}

@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if ((flags & CONSTRUCTOR_FUNCTION) == 0) {
throw ScriptRuntime.typeErrorById("msg.constructor.no.function", getFunctionName());
}
return targetConstructor.construct(cx, scope, args);
if (target == null) {
return targetConstructor.construct(cx, scope, args);
}
return target.call(cx, scope, thisObj, args);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class LambdaFunction extends BaseFunction {
private static final long serialVersionUID = -8388132362854748293L;

// The target is expected to be a lambda -- lambdas should not be serialized.
private final transient Callable target;
protected final transient Callable target;
private final String name;
private final int length;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,22 @@ public void lambdaFunctionNoNew() {
+ "assertThrows(() => { new noNewFunc(); }, TypeError)");
}

@Test
public void lambdaSpecialConstructorCallConstructor() {
// This class has different functions for the constructor when invoked
// by "new" and when invoked as a function
SpecialConstructorClass.init(cx, root);
// Invoke the function via "new" and ensure that the constructor functionality is executed
eval("let o = new SpecialConstructorClass('foo');\n" + "assertEquals('foo', o.value);\n");
}

@Test
public void lambdaSpecialConstructorCallFunction() {
SpecialConstructorClass.init(cx, root);
// Invoke the function directly and ensure that the function functionality is executed
eval("let v = SpecialConstructorClass('foo');\n" + "assertEquals('You passed foo', v);\n");
}

private static class TestClass extends ScriptableObject {

private String instanceVal;
Expand Down Expand Up @@ -285,4 +301,53 @@ private static Object sayHello(Object[] args) {
return "Hello, " + ScriptRuntime.toString(args[0]) + '!';
}
}

private static class SpecialConstructorClass extends ScriptableObject {
private String value;

public static void init(Context cx, Scriptable scope) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
"SpecialConstructorClass",
1,
(Context lcx, Scriptable s, Scriptable thisObj, Object[] args) -> {
String arg = "";
if (args.length > 0) {
arg = ScriptRuntime.toString(args[0]);
}
return "You passed " + arg;
},
(Context lcx, Scriptable s, Object[] args) -> {
SpecialConstructorClass tc = new SpecialConstructorClass();
if (args.length > 0) {
tc.value = ScriptRuntime.toString(args[0]);
}
return tc;
});
constructor.definePrototypeProperty(
cx,
"value",
(Scriptable s) -> {
SpecialConstructorClass thisObj =
LambdaConstructor.convertThisObject(
s, SpecialConstructorClass.class);
return thisObj.value;
},
(Scriptable s, Object newVal) -> {
SpecialConstructorClass thisObj =
LambdaConstructor.convertThisObject(
s, SpecialConstructorClass.class);
thisObj.value = ScriptRuntime.toString(newVal);
},
0);
ScriptableObject.defineProperty(
scope, "SpecialConstructorClass", constructor, PERMANENT);
}

@Override
public String getClassName() {
return "SpecialConstructorClass";
}
}
}

0 comments on commit a84fbba

Please sign in to comment.