Extend LambdaConstructor for more flexibility

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.
This commit is contained in:
Greg Brail 2024-09-11 23:00:36 -07:00
Родитель 5c8707faa0
Коммит 5c413d16c8
3 изменённых файлов: 85 добавлений и 2 удалений

Просмотреть файл

@ -66,12 +66,30 @@ public class LambdaConstructor extends LambdaFunction {
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

Просмотреть файл

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

Просмотреть файл

@ -202,6 +202,22 @@ public class LambdaFunctionTest {
+ "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;
@ -285,4 +301,53 @@ public class LambdaFunctionTest {
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";
}
}
}