mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 17:17:42 +00:00
LibJS: Use a synthetic constructor if class with parent doesn't have one
We already did this but it called the @@iterator method of %Array.prototype% visible to the user for example by overriding that method. This should not be visible so we use a special version of SuperCall now.
This commit is contained in:
parent
b79f03182d
commit
ae349ec6a8
4 changed files with 100 additions and 4 deletions
|
@ -463,7 +463,22 @@ Completion SuperCall::execute(Interpreter& interpreter, GlobalObject& global_obj
|
||||||
|
|
||||||
// 4. Let argList be ? ArgumentListEvaluation of Arguments.
|
// 4. Let argList be ? ArgumentListEvaluation of Arguments.
|
||||||
MarkedVector<Value> arg_list(vm.heap());
|
MarkedVector<Value> arg_list(vm.heap());
|
||||||
TRY(argument_list_evaluation(interpreter, global_object, m_arguments, arg_list));
|
if (m_is_synthetic == IsPartOfSyntheticConstructor::Yes) {
|
||||||
|
// NOTE: This is the case where we have a fake constructor(...args) { super(...args); } which
|
||||||
|
// shouldn't call @@iterator of %Array.prototype%.
|
||||||
|
VERIFY(m_arguments.size() == 1);
|
||||||
|
VERIFY(m_arguments[0].is_spread);
|
||||||
|
auto const& argument = m_arguments[0];
|
||||||
|
auto value = MUST(argument.value->execute(interpreter, global_object)).release_value();
|
||||||
|
VERIFY(value.is_object() && is<Array>(value.as_object()));
|
||||||
|
|
||||||
|
auto& array_value = static_cast<Array const&>(value.as_object());
|
||||||
|
auto length = MUST(length_of_array_like(global_object, array_value));
|
||||||
|
for (size_t i = 0; i < length; ++i)
|
||||||
|
arg_list.append(array_value.get_without_side_effects(PropertyKey { i }));
|
||||||
|
} else {
|
||||||
|
TRY(argument_list_evaluation(interpreter, global_object, m_arguments, arg_list));
|
||||||
|
}
|
||||||
|
|
||||||
// 5. If IsConstructor(func) is false, throw a TypeError exception.
|
// 5. If IsConstructor(func) is false, throw a TypeError exception.
|
||||||
if (!func || !Value(func).is_constructor())
|
if (!func || !Value(func).is_constructor())
|
||||||
|
@ -1827,7 +1842,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::class_definition_e
|
||||||
vm.running_execution_context().private_environment = outer_private_environment;
|
vm.running_execution_context().private_environment = outer_private_environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: Step 14.a is done in the parser. But maybe it shouldn't?
|
// FIXME: Step 14.a is done in the parser. By using a synthetic super(...args) which does not call @@iterator of %Array.prototype%
|
||||||
auto class_constructor_value = TRY(m_constructor->execute(interpreter, global_object)).release_value();
|
auto class_constructor_value = TRY(m_constructor->execute(interpreter, global_object)).release_value();
|
||||||
|
|
||||||
update_function_name(class_constructor_value, class_name);
|
update_function_name(class_constructor_value, class_name);
|
||||||
|
|
|
@ -1492,17 +1492,34 @@ public:
|
||||||
|
|
||||||
class SuperCall final : public Expression {
|
class SuperCall final : public Expression {
|
||||||
public:
|
public:
|
||||||
|
// This is here to be able to make a constructor like
|
||||||
|
// constructor(...args) { super(...args); } which does not use @@iterator of %Array.prototype%.
|
||||||
|
enum class IsPartOfSyntheticConstructor {
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
};
|
||||||
|
|
||||||
SuperCall(SourceRange source_range, Vector<CallExpression::Argument> arguments)
|
SuperCall(SourceRange source_range, Vector<CallExpression::Argument> arguments)
|
||||||
: Expression(source_range)
|
: Expression(source_range)
|
||||||
, m_arguments(move(arguments))
|
, m_arguments(move(arguments))
|
||||||
|
, m_is_synthetic(IsPartOfSyntheticConstructor::No)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SuperCall(SourceRange source_range, IsPartOfSyntheticConstructor is_part_of_synthetic_constructor, CallExpression::Argument constructor_argument)
|
||||||
|
: Expression(source_range)
|
||||||
|
, m_arguments({ move(constructor_argument) })
|
||||||
|
, m_is_synthetic(IsPartOfSyntheticConstructor::Yes)
|
||||||
|
{
|
||||||
|
VERIFY(is_part_of_synthetic_constructor == IsPartOfSyntheticConstructor::Yes);
|
||||||
|
}
|
||||||
|
|
||||||
virtual Completion execute(Interpreter&, GlobalObject&) const override;
|
virtual Completion execute(Interpreter&, GlobalObject&) const override;
|
||||||
virtual void dump(int indent) const override;
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<CallExpression::Argument> const m_arguments;
|
Vector<CallExpression::Argument> const m_arguments;
|
||||||
|
IsPartOfSyntheticConstructor const m_is_synthetic;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AssignmentOp {
|
enum class AssignmentOp {
|
||||||
|
|
|
@ -1313,9 +1313,16 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
if (!super_class.is_null()) {
|
if (!super_class.is_null()) {
|
||||||
// Set constructor to the result of parsing the source text
|
// Set constructor to the result of parsing the source text
|
||||||
// constructor(... args){ super (...args);}
|
// constructor(... args){ super (...args);}
|
||||||
|
// However: The most notable distinction is that while the aforementioned ECMAScript
|
||||||
|
// source text observably calls the @@iterator method on %Array.prototype%,
|
||||||
|
// this function does not.
|
||||||
|
// So we use a custom version of SuperCall which doesn't use the @@iterator
|
||||||
|
// method on %Array.prototype% visibly.
|
||||||
|
FlyString argument_name = "args";
|
||||||
auto super_call = create_ast_node<SuperCall>(
|
auto super_call = create_ast_node<SuperCall>(
|
||||||
{ m_state.current_token.filename(), rule_start.position(), position() },
|
{ m_state.current_token.filename(), rule_start.position(), position() },
|
||||||
Vector { CallExpression::Argument { create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, "args"), true } });
|
SuperCall::IsPartOfSyntheticConstructor::Yes,
|
||||||
|
CallExpression::Argument { create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, "args"), true });
|
||||||
// NOTE: While the JS approximation above doesn't do `return super(...args)`, the
|
// NOTE: While the JS approximation above doesn't do `return super(...args)`, the
|
||||||
// abstract closure is expected to capture and return the result, so we do need a
|
// abstract closure is expected to capture and return the result, so we do need a
|
||||||
// return statement here to create the correct completion.
|
// return statement here to create the correct completion.
|
||||||
|
@ -1323,7 +1330,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
|
|
||||||
constructor = create_ast_node<FunctionExpression>(
|
constructor = create_ast_node<FunctionExpression>(
|
||||||
{ m_state.current_token.filename(), rule_start.position(), position() }, class_name, "",
|
{ m_state.current_token.filename(), rule_start.position(), position() }, class_name, "",
|
||||||
move(constructor_body), Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, FunctionKind::Normal,
|
move(constructor_body), Vector { FunctionNode::Parameter { move(argument_name), nullptr, true } }, 0, FunctionKind::Normal,
|
||||||
/* is_strict_mode */ true, /* might_need_arguments_object */ false, /* contains_direct_call_to_eval */ false);
|
/* is_strict_mode */ true, /* might_need_arguments_object */ false, /* contains_direct_call_to_eval */ false);
|
||||||
} else {
|
} else {
|
||||||
constructor = create_ast_node<FunctionExpression>(
|
constructor = create_ast_node<FunctionExpression>(
|
||||||
|
|
|
@ -449,3 +449,60 @@ test("super outside of derived class fails to parse", () => {
|
||||||
new J();
|
new J();
|
||||||
}).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here");
|
}).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("When no constructor on deriving class @@iterator of %Array.prototype% is not visibly called", () => {
|
||||||
|
const oldIterator = Array.prototype[Symbol.iterator];
|
||||||
|
var calls = 0;
|
||||||
|
Array.prototype[Symbol.iterator] = function () {
|
||||||
|
++calls;
|
||||||
|
expect().fail("Called @@iterator");
|
||||||
|
};
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
constructor(value1, value2) {
|
||||||
|
this.value1 = value1;
|
||||||
|
this.value2 = value2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Derived extends Base {}
|
||||||
|
|
||||||
|
const noArgumentsDerived = new Derived();
|
||||||
|
expect(noArgumentsDerived.value1).toBeUndefined();
|
||||||
|
expect(noArgumentsDerived.value2).toBeUndefined();
|
||||||
|
expect(noArgumentsDerived).toBeInstanceOf(Base);
|
||||||
|
expect(noArgumentsDerived).toBeInstanceOf(Derived);
|
||||||
|
|
||||||
|
const singleArgumentDerived = new Derived(1);
|
||||||
|
expect(singleArgumentDerived.value1).toBe(1);
|
||||||
|
expect(singleArgumentDerived.value2).toBeUndefined();
|
||||||
|
|
||||||
|
const singleArrayArgumentDerived = new Derived([1, 2]);
|
||||||
|
expect(singleArrayArgumentDerived.value1).toEqual([1, 2]);
|
||||||
|
expect(singleArrayArgumentDerived.value2).toBeUndefined();
|
||||||
|
|
||||||
|
const doubleArgumentDerived = new Derived(1, 2);
|
||||||
|
expect(doubleArgumentDerived.value1).toBe(1);
|
||||||
|
expect(doubleArgumentDerived.value2).toBe(2);
|
||||||
|
|
||||||
|
expect(calls).toBe(0);
|
||||||
|
|
||||||
|
class Derived2 extends Base {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
new Derived2();
|
||||||
|
}).toThrowWithMessage(ExpectationError, "Called @@iterator");
|
||||||
|
|
||||||
|
expect(calls).toBe(1);
|
||||||
|
|
||||||
|
Array.prototype[Symbol.iterator] = oldIterator;
|
||||||
|
|
||||||
|
// Now Derived2 is fine again.
|
||||||
|
expect(new Derived2()).toBeInstanceOf(Derived2);
|
||||||
|
|
||||||
|
expect(calls).toBe(1);
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue