mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 12:37:40 +00:00
LibJS: Add parsing and evaluation of private fields and methods
This commit is contained in:
parent
c7a6572789
commit
16cc82460f
9 changed files with 368 additions and 69 deletions
|
@ -1091,6 +1091,9 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
|
||||||
property_name = PropertyName::from_value(global_object, value);
|
property_name = PropertyName::from_value(global_object, value);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return Reference {};
|
return Reference {};
|
||||||
|
} else if (is<PrivateIdentifier>(*m_property)) {
|
||||||
|
auto& private_identifier = static_cast<PrivateIdentifier const&>(*m_property);
|
||||||
|
return make_private_reference(interpreter.vm(), base_value, private_identifier.string());
|
||||||
} else {
|
} else {
|
||||||
property_name = verify_cast<Identifier>(*m_property).string();
|
property_name = verify_cast<Identifier>(*m_property).string();
|
||||||
TRY_OR_DISCARD(require_object_coercible(global_object, base_value));
|
TRY_OR_DISCARD(require_object_coercible(global_object, base_value));
|
||||||
|
@ -1166,8 +1169,14 @@ Value ClassElement::execute(Interpreter&, GlobalObject&) const
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
static ThrowCompletionOr<PropertyName> class_key_to_property_name(Interpreter& interpreter, GlobalObject& global_object, Expression const& key)
|
static ThrowCompletionOr<ClassElement::ClassElementName> class_key_to_property_name(Interpreter& interpreter, GlobalObject& global_object, Expression const& key)
|
||||||
{
|
{
|
||||||
|
if (is<PrivateIdentifier>(key)) {
|
||||||
|
auto& private_identifier = static_cast<PrivateIdentifier const&>(key);
|
||||||
|
auto* private_environment = interpreter.vm().running_execution_context().private_environment;
|
||||||
|
VERIFY(private_environment);
|
||||||
|
return ClassElement::ClassElementName { private_environment->resolve_private_identifier(private_identifier.string()) };
|
||||||
|
}
|
||||||
|
|
||||||
auto prop_key = key.execute(interpreter, global_object);
|
auto prop_key = key.execute(interpreter, global_object);
|
||||||
if (auto* exception = interpreter.exception())
|
if (auto* exception = interpreter.exception())
|
||||||
|
@ -1179,7 +1188,7 @@ static ThrowCompletionOr<PropertyName> class_key_to_property_name(Interpreter& i
|
||||||
auto property_key = PropertyName::from_value(global_object, prop_key);
|
auto property_key = PropertyName::from_value(global_object, prop_key);
|
||||||
if (auto* exception = interpreter.exception())
|
if (auto* exception = interpreter.exception())
|
||||||
return throw_completion(exception->value());
|
return throw_completion(exception->value());
|
||||||
return property_key;
|
return ClassElement::ClassElementName { property_key };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 15.4.5 Runtime Semantics: MethodDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation
|
// 15.4.5 Runtime Semantics: MethodDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation
|
||||||
|
@ -1194,34 +1203,61 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassMethod::class_element_evaluatio
|
||||||
auto& method_function = static_cast<ECMAScriptFunctionObject&>(method_value.as_function());
|
auto& method_function = static_cast<ECMAScriptFunctionObject&>(method_value.as_function());
|
||||||
method_function.set_home_object(&target);
|
method_function.set_home_object(&target);
|
||||||
|
|
||||||
auto set_function_name = [&](String prefix) {
|
auto set_function_name = [&](String prefix = "") {
|
||||||
String property_name;
|
auto property_name = property_key.visit(
|
||||||
// FIXME: Handle PrivateNames as well.
|
[&](PropertyName const& property_name) -> String {
|
||||||
if (property_key.is_symbol())
|
if (property_name.is_symbol()) {
|
||||||
property_name = String::formatted("[{}]", property_key.as_symbol()->description());
|
auto description = property_name.as_symbol()->description();
|
||||||
else
|
if (description.is_empty())
|
||||||
property_name = property_key.to_string();
|
return "";
|
||||||
update_function_name(method_value, String::formatted("{} {}", prefix, property_name));
|
return String::formatted("[{}]", description);
|
||||||
|
} else {
|
||||||
|
return property_name.to_string();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](PrivateName const& private_name) -> String {
|
||||||
|
return private_name.description;
|
||||||
|
});
|
||||||
|
|
||||||
|
update_function_name(method_value, String::formatted("{}{}{}", prefix, prefix.is_empty() ? "" : " ", property_name));
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (kind()) {
|
if (property_key.has<PropertyName>()) {
|
||||||
case ClassMethod::Kind::Method:
|
auto& property_name = property_key.get<PropertyName>();
|
||||||
TRY(target.define_property_or_throw(property_key, { .value = method_value, .writable = true, .enumerable = false, .configurable = true }));
|
switch (kind()) {
|
||||||
break;
|
case ClassMethod::Kind::Method:
|
||||||
case ClassMethod::Kind::Getter:
|
set_function_name();
|
||||||
set_function_name("get");
|
TRY(target.define_property_or_throw(property_name, { .value = method_value, .writable = true, .enumerable = false, .configurable = true }));
|
||||||
TRY(target.define_property_or_throw(property_key, { .get = &method_function, .enumerable = true, .configurable = true }));
|
break;
|
||||||
break;
|
case ClassMethod::Kind::Getter:
|
||||||
case ClassMethod::Kind::Setter:
|
set_function_name("get");
|
||||||
set_function_name("set");
|
TRY(target.define_property_or_throw(property_name, { .get = &method_function, .enumerable = true, .configurable = true }));
|
||||||
TRY(target.define_property_or_throw(property_key, { .set = &method_function, .enumerable = true, .configurable = true }));
|
break;
|
||||||
break;
|
case ClassMethod::Kind::Setter:
|
||||||
default:
|
set_function_name("set");
|
||||||
VERIFY_NOT_REACHED();
|
TRY(target.define_property_or_throw(property_name, { .set = &method_function, .enumerable = true, .configurable = true }));
|
||||||
}
|
break;
|
||||||
// FIXME: Return PrivateElement for private methods
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
return ClassValue { normal_completion({}) };
|
return ClassValue { normal_completion({}) };
|
||||||
|
} else {
|
||||||
|
auto& private_name = property_key.get<PrivateName>();
|
||||||
|
switch (kind()) {
|
||||||
|
case Kind::Method:
|
||||||
|
set_function_name();
|
||||||
|
return ClassValue { PrivateElement { private_name, PrivateElement::Kind::Method, method_value } };
|
||||||
|
case Kind::Getter:
|
||||||
|
set_function_name("get");
|
||||||
|
return ClassValue { PrivateElement { private_name, PrivateElement::Kind::Accessor, Accessor::create(interpreter.vm(), &method_function, nullptr) } };
|
||||||
|
case Kind::Setter:
|
||||||
|
set_function_name("set");
|
||||||
|
return ClassValue { PrivateElement { private_name, PrivateElement::Kind::Accessor, Accessor::create(interpreter.vm(), nullptr, &method_function) } };
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 15.7.10 Runtime Semantics: ClassFieldDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation
|
// 15.7.10 Runtime Semantics: ClassFieldDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation
|
||||||
|
@ -1232,8 +1268,14 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation
|
||||||
if (m_initializer) {
|
if (m_initializer) {
|
||||||
auto copy_initializer = m_initializer;
|
auto copy_initializer = m_initializer;
|
||||||
auto body = create_ast_node<ExpressionStatement>(m_initializer->source_range(), copy_initializer.release_nonnull());
|
auto body = create_ast_node<ExpressionStatement>(m_initializer->source_range(), copy_initializer.release_nonnull());
|
||||||
|
auto name = property_key.visit(
|
||||||
|
[&](PropertyName const& property_name) -> String {
|
||||||
|
return property_name.is_number() ? property_name.to_string() : property_name.to_string_or_symbol().to_display_string();
|
||||||
|
},
|
||||||
|
[&](PrivateName const& private_name) -> String {
|
||||||
|
return private_name.description;
|
||||||
|
});
|
||||||
// FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
|
// FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
|
||||||
auto name = property_key.is_number() ? property_key.to_string() : property_key.to_string_or_symbol().to_display_string();
|
|
||||||
initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), name, *body, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Regular, false, false);
|
initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), name, *body, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Regular, false, false);
|
||||||
initializer->set_home_object(&target);
|
initializer->set_home_object(&target);
|
||||||
}
|
}
|
||||||
|
@ -1246,6 +1288,23 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Optional<FlyString> nullopt_or_private_identifier_description(Expression const& expression)
|
||||||
|
{
|
||||||
|
if (is<PrivateIdentifier>(expression))
|
||||||
|
return static_cast<PrivateIdentifier const&>(expression).string();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<FlyString> ClassField::private_bound_identifier() const
|
||||||
|
{
|
||||||
|
return nullopt_or_private_identifier_description(*m_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<FlyString> ClassMethod::private_bound_identifier() const
|
||||||
|
{
|
||||||
|
return nullopt_or_private_identifier_description(*m_key);
|
||||||
|
}
|
||||||
|
|
||||||
// 15.7.11 Runtime Semantics: ClassStaticBlockDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation
|
// 15.7.11 Runtime Semantics: ClassStaticBlockDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classstaticblockdefinitionevaluation
|
||||||
ThrowCompletionOr<ClassElement::ClassValue> StaticInitializer::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& home_object) const
|
ThrowCompletionOr<ClassElement::ClassValue> StaticInitializer::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& home_object) const
|
||||||
{
|
{
|
||||||
|
@ -1291,8 +1350,6 @@ Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_o
|
||||||
// 15.7.14 Runtime Semantics: ClassDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
|
// 15.7.14 Runtime Semantics: ClassDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
|
||||||
ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name, FlyString const& class_name) const
|
ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name, FlyString const& class_name) const
|
||||||
{
|
{
|
||||||
// FIXME: Clean up this mix of "spec", "somewhat spec", and "not spec at all".
|
|
||||||
|
|
||||||
auto& vm = interpreter.vm();
|
auto& vm = interpreter.vm();
|
||||||
auto* environment = vm.lexical_environment();
|
auto* environment = vm.lexical_environment();
|
||||||
VERIFY(environment);
|
VERIFY(environment);
|
||||||
|
@ -1309,7 +1366,11 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete
|
||||||
auto* outer_private_environment = vm.running_execution_context().private_environment;
|
auto* outer_private_environment = vm.running_execution_context().private_environment;
|
||||||
auto* class_private_environment = new_private_environment(vm, outer_private_environment);
|
auto* class_private_environment = new_private_environment(vm, outer_private_environment);
|
||||||
|
|
||||||
// FIXME: Append names to private environment
|
for (auto const& element : m_elements) {
|
||||||
|
auto opt_private_name = element.private_bound_identifier();
|
||||||
|
if (opt_private_name.has_value())
|
||||||
|
class_private_environment->add_private_name({}, opt_private_name.release_value());
|
||||||
|
}
|
||||||
|
|
||||||
auto* proto_parent = vm.current_realm()->global_object().object_prototype();
|
auto* proto_parent = vm.current_realm()->global_object().object_prototype();
|
||||||
|
|
||||||
|
@ -1385,16 +1446,38 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete
|
||||||
|
|
||||||
using StaticElement = Variant<ClassElement::ClassFieldDefinition, ECMAScriptFunctionObject*>;
|
using StaticElement = Variant<ClassElement::ClassFieldDefinition, ECMAScriptFunctionObject*>;
|
||||||
|
|
||||||
|
Vector<PrivateElement> static_private_methods;
|
||||||
|
Vector<PrivateElement> instance_private_methods;
|
||||||
Vector<ClassElement::ClassFieldDefinition> instance_fields;
|
Vector<ClassElement::ClassFieldDefinition> instance_fields;
|
||||||
Vector<StaticElement> static_elements;
|
Vector<StaticElement> static_elements;
|
||||||
|
|
||||||
// FIXME: Do things with private methods, and fields here
|
|
||||||
for (auto const& element : m_elements) {
|
for (auto const& element : m_elements) {
|
||||||
// Note: All ClassElementEvaluation start with evaluating the name (or we fake it).
|
// Note: All ClassElementEvaluation start with evaluating the name (or we fake it).
|
||||||
auto element_value = TRY(element.class_element_evaluation(interpreter, global_object, element.is_static() ? *class_constructor : *prototype));
|
auto element_value = TRY(element.class_element_evaluation(interpreter, global_object, element.is_static() ? *class_constructor : *prototype));
|
||||||
// FIXME: If element is a private element
|
|
||||||
|
|
||||||
if (auto* class_field_definition_ptr = element_value.get_pointer<ClassElement::ClassFieldDefinition>()) {
|
if (element_value.has<PrivateElement>()) {
|
||||||
|
auto& container = element.is_static() ? static_private_methods : instance_private_methods;
|
||||||
|
|
||||||
|
auto& private_element = element_value.get<PrivateElement>();
|
||||||
|
|
||||||
|
auto added_to_existing = false;
|
||||||
|
// FIXME: We can skip this loop in most cases.
|
||||||
|
for (auto& existing : container) {
|
||||||
|
if (existing.key == private_element.key) {
|
||||||
|
VERIFY(existing.kind == PrivateElement::Kind::Accessor);
|
||||||
|
VERIFY(private_element.kind == PrivateElement::Kind::Accessor);
|
||||||
|
auto& accessor = private_element.value.as_accessor();
|
||||||
|
if (!accessor.getter())
|
||||||
|
existing.value.as_accessor().set_setter(accessor.setter());
|
||||||
|
else
|
||||||
|
existing.value.as_accessor().set_getter(accessor.getter());
|
||||||
|
added_to_existing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added_to_existing)
|
||||||
|
container.append(move(element_value.get<PrivateElement>()));
|
||||||
|
} else if (auto* class_field_definition_ptr = element_value.get_pointer<ClassElement::ClassFieldDefinition>()) {
|
||||||
if (element.is_static())
|
if (element.is_static())
|
||||||
static_elements.append(move(*class_field_definition_ptr));
|
static_elements.append(move(*class_field_definition_ptr));
|
||||||
else
|
else
|
||||||
|
@ -1414,19 +1497,19 @@ ThrowCompletionOr<Value> ClassExpression::class_definition_evaluation(Interprete
|
||||||
if (!binding_name.is_null())
|
if (!binding_name.is_null())
|
||||||
MUST(class_scope->initialize_binding(global_object, binding_name, class_constructor));
|
MUST(class_scope->initialize_binding(global_object, binding_name, class_constructor));
|
||||||
|
|
||||||
// FIXME: Set the [[PrivateMethods]]
|
|
||||||
for (auto& field : instance_fields)
|
for (auto& field : instance_fields)
|
||||||
class_constructor->add_field(field.name, field.initializer);
|
class_constructor->add_field(field.name, field.initializer);
|
||||||
|
|
||||||
|
for (auto& private_method : instance_private_methods)
|
||||||
|
class_constructor->add_private_method(private_method);
|
||||||
|
|
||||||
|
for (auto& method : static_private_methods)
|
||||||
|
class_constructor->private_method_or_accessor_add(move(method));
|
||||||
|
|
||||||
for (auto& element : static_elements) {
|
for (auto& element : static_elements) {
|
||||||
TRY(element.visit(
|
TRY(element.visit(
|
||||||
[&](ClassElement::ClassFieldDefinition const& field) -> ThrowCompletionOr<void> {
|
[&](ClassElement::ClassFieldDefinition const& field) -> ThrowCompletionOr<void> {
|
||||||
Value field_value = js_undefined();
|
return TRY(class_constructor->define_field(field.name, field.initializer));
|
||||||
if (field.initializer)
|
|
||||||
field_value = TRY(call(global_object, field.initializer, class_constructor_value));
|
|
||||||
|
|
||||||
TRY(class_constructor->create_data_property_or_throw(field.name, field_value));
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
[&](ECMAScriptFunctionObject* static_block_function) -> ThrowCompletionOr<void> {
|
[&](ECMAScriptFunctionObject* static_block_function) -> ThrowCompletionOr<void> {
|
||||||
// We discard any value returned here.
|
// We discard any value returned here.
|
||||||
|
@ -1979,6 +2062,19 @@ void Identifier::dump(int indent) const
|
||||||
outln("Identifier \"{}\"", m_string);
|
outln("Identifier \"{}\"", m_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value PrivateIdentifier::execute(Interpreter&, GlobalObject&) const
|
||||||
|
{
|
||||||
|
// Note: This should be handled by either the member expression this is part of
|
||||||
|
// or the binary expression in the case of `#foo in bar`.
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrivateIdentifier::dump(int indent) const
|
||||||
|
{
|
||||||
|
print_indent(indent);
|
||||||
|
outln("PrivateIdentifier \"{}\"", m_string);
|
||||||
|
}
|
||||||
|
|
||||||
void SpreadExpression::dump(int indent) const
|
void SpreadExpression::dump(int indent) const
|
||||||
{
|
{
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
|
@ -2527,6 +2623,17 @@ Value MemberExpression::execute(Interpreter& interpreter, GlobalObject& global_o
|
||||||
return reference.get_value(global_object);
|
return reference.get_value(global_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MemberExpression::ends_in_private_name() const
|
||||||
|
{
|
||||||
|
if (is_computed())
|
||||||
|
return false;
|
||||||
|
if (is<PrivateIdentifier>(*m_property))
|
||||||
|
return true;
|
||||||
|
if (is<MemberExpression>(*m_property))
|
||||||
|
return static_cast<MemberExpression const&>(*m_property).ends_in_private_name();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void OptionalChain::dump(int indent) const
|
void OptionalChain::dump(int indent) const
|
||||||
{
|
{
|
||||||
print_indent(indent);
|
print_indent(indent);
|
||||||
|
|
|
@ -1000,6 +1000,23 @@ private:
|
||||||
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
|
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PrivateIdentifier final : public Expression {
|
||||||
|
public:
|
||||||
|
explicit PrivateIdentifier(SourceRange source_range, FlyString string)
|
||||||
|
: Expression(source_range)
|
||||||
|
, m_string(move(string))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FlyString const& string() const { return m_string; }
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FlyString m_string;
|
||||||
|
};
|
||||||
|
|
||||||
class ClassElement : public ASTNode {
|
class ClassElement : public ASTNode {
|
||||||
public:
|
public:
|
||||||
ClassElement(SourceRange source_range, bool is_static)
|
ClassElement(SourceRange source_range, bool is_static)
|
||||||
|
@ -1019,15 +1036,19 @@ public:
|
||||||
virtual ElementKind class_element_kind() const = 0;
|
virtual ElementKind class_element_kind() const = 0;
|
||||||
bool is_static() const { return m_is_static; }
|
bool is_static() const { return m_is_static; }
|
||||||
|
|
||||||
|
using ClassElementName = Variant<PropertyName, PrivateName>;
|
||||||
|
|
||||||
struct ClassFieldDefinition {
|
struct ClassFieldDefinition {
|
||||||
PropertyName name;
|
ClassElementName name;
|
||||||
ECMAScriptFunctionObject* initializer { nullptr };
|
ECMAScriptFunctionObject* initializer { nullptr };
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use the Completion also as a ClassStaticBlockDefinition Record.
|
// We use the Completion also as a ClassStaticBlockDefinition Record.
|
||||||
using ClassValue = Variant<ClassFieldDefinition, Completion>;
|
using ClassValue = Variant<ClassFieldDefinition, Completion, PrivateElement>;
|
||||||
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const = 0;
|
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const = 0;
|
||||||
|
|
||||||
|
virtual Optional<FlyString> private_bound_identifier() const { return {}; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_is_static { false };
|
bool m_is_static { false };
|
||||||
};
|
};
|
||||||
|
@ -1054,6 +1075,7 @@ public:
|
||||||
|
|
||||||
virtual void dump(int indent) const override;
|
virtual void dump(int indent) const override;
|
||||||
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override;
|
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override;
|
||||||
|
virtual Optional<FlyString> private_bound_identifier() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
|
@ -1078,6 +1100,7 @@ public:
|
||||||
|
|
||||||
virtual void dump(int indent) const override;
|
virtual void dump(int indent) const override;
|
||||||
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter& interpreter, GlobalObject& object, Object& home_object) const override;
|
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter& interpreter, GlobalObject& object, Object& home_object) const override;
|
||||||
|
virtual Optional<FlyString> private_bound_identifier() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullRefPtr<Expression> m_key;
|
NonnullRefPtr<Expression> m_key;
|
||||||
|
@ -1536,6 +1559,8 @@ public:
|
||||||
|
|
||||||
String to_string_approximation() const;
|
String to_string_approximation() const;
|
||||||
|
|
||||||
|
bool ends_in_private_name() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_member_expression() const override { return true; }
|
virtual bool is_member_expression() const override { return true; }
|
||||||
|
|
||||||
|
|
|
@ -850,6 +850,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
NonnullRefPtrVector<ClassElement> elements;
|
NonnullRefPtrVector<ClassElement> elements;
|
||||||
RefPtr<Expression> super_class;
|
RefPtr<Expression> super_class;
|
||||||
RefPtr<FunctionExpression> constructor;
|
RefPtr<FunctionExpression> constructor;
|
||||||
|
HashTable<FlyString> found_private_names;
|
||||||
|
|
||||||
String class_name = expect_class_name || match_identifier() || match(TokenType::Yield) || match(TokenType::Await)
|
String class_name = expect_class_name || match_identifier() || match(TokenType::Yield) || match(TokenType::Await)
|
||||||
? consume_identifier_reference().value().to_string()
|
? consume_identifier_reference().value().to_string()
|
||||||
|
@ -881,6 +882,13 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
|
|
||||||
consume(TokenType::CurlyOpen);
|
consume(TokenType::CurlyOpen);
|
||||||
|
|
||||||
|
HashTable<StringView> referenced_private_names;
|
||||||
|
HashTable<StringView>* outer_referenced_private_names = m_state.referenced_private_names;
|
||||||
|
m_state.referenced_private_names = &referenced_private_names;
|
||||||
|
ScopeGuard restore_private_name_table = [&] {
|
||||||
|
m_state.referenced_private_names = outer_referenced_private_names;
|
||||||
|
};
|
||||||
|
|
||||||
while (!done() && !match(TokenType::CurlyClose)) {
|
while (!done() && !match(TokenType::CurlyClose)) {
|
||||||
RefPtr<Expression> property_key;
|
RefPtr<Expression> property_key;
|
||||||
bool is_static = false;
|
bool is_static = false;
|
||||||
|
@ -899,7 +907,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView name;
|
StringView name;
|
||||||
if (match_property_key()) {
|
if (match_property_key() || match(TokenType::PrivateIdentifier)) {
|
||||||
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
|
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
|
||||||
if (match(TokenType::Identifier)) {
|
if (match(TokenType::Identifier)) {
|
||||||
consume();
|
consume();
|
||||||
|
@ -923,12 +931,49 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match_property_key()) {
|
if (match_property_key() || match(TokenType::PrivateIdentifier)) {
|
||||||
switch (m_state.current_token.type()) {
|
switch (m_state.current_token.type()) {
|
||||||
case TokenType::Identifier:
|
case TokenType::Identifier:
|
||||||
name = consume().value();
|
name = consume().value();
|
||||||
property_key = create_ast_node<StringLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
|
property_key = create_ast_node<StringLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
|
||||||
break;
|
break;
|
||||||
|
case TokenType::PrivateIdentifier:
|
||||||
|
name = consume().value();
|
||||||
|
if (name == "#constructor")
|
||||||
|
syntax_error("Private property with name '#constructor' is not allowed");
|
||||||
|
|
||||||
|
if (method_kind != ClassMethod::Kind::Method) {
|
||||||
|
// It is a Syntax Error if PrivateBoundIdentifiers of ClassElementList contains any duplicate entries,
|
||||||
|
// unless the name is used once for a getter and once for a setter and in no other entries,
|
||||||
|
// and the getter and setter are either both static or both non-static.
|
||||||
|
|
||||||
|
for (auto& element : elements) {
|
||||||
|
auto private_name = element.private_bound_identifier();
|
||||||
|
if (!private_name.has_value() || private_name.value() != name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (element.class_element_kind() != ClassElement::ElementKind::Method
|
||||||
|
|| element.is_static() != is_static) {
|
||||||
|
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY(is<ClassMethod>(element));
|
||||||
|
auto& class_method_element = static_cast<ClassMethod const&>(element);
|
||||||
|
|
||||||
|
if (class_method_element.kind() == ClassMethod::Kind::Method || class_method_element.kind() == method_kind) {
|
||||||
|
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found_private_names.set(name);
|
||||||
|
} else if (found_private_names.set(name) != AK::HashSetResult::InsertedNewEntry) {
|
||||||
|
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
property_key = create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
|
||||||
|
break;
|
||||||
case TokenType::StringLiteral: {
|
case TokenType::StringLiteral: {
|
||||||
auto string_literal = parse_string_literal(consume());
|
auto string_literal = parse_string_literal(consume());
|
||||||
name = string_literal->value();
|
name = string_literal->value();
|
||||||
|
@ -1064,6 +1109,16 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We could be in a subclass defined within the main class so must move all non declared private names to outer.
|
||||||
|
for (auto& private_name : referenced_private_names) {
|
||||||
|
if (found_private_names.contains(private_name))
|
||||||
|
continue;
|
||||||
|
if (outer_referenced_private_names)
|
||||||
|
outer_referenced_private_names->set(private_name);
|
||||||
|
else // FIXME: Make these error appear in the appropriate places.
|
||||||
|
syntax_error(String::formatted("Reference to undeclared private field or method '{}'", private_name));
|
||||||
|
}
|
||||||
|
|
||||||
return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements));
|
return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1264,6 +1319,11 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
||||||
if (is<Identifier>(*rhs) && m_state.strict_mode) {
|
if (is<Identifier>(*rhs) && m_state.strict_mode) {
|
||||||
syntax_error("Delete of an unqualified identifier in strict mode.", rhs_start);
|
syntax_error("Delete of an unqualified identifier in strict mode.", rhs_start);
|
||||||
}
|
}
|
||||||
|
if (is<MemberExpression>(*rhs)) {
|
||||||
|
auto& member_expression = static_cast<MemberExpression const&>(*rhs);
|
||||||
|
if (member_expression.ends_in_private_name())
|
||||||
|
syntax_error("Private fields cannot be deleted");
|
||||||
|
}
|
||||||
return create_ast_node<UnaryExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UnaryOp::Delete, move(rhs));
|
return create_ast_node<UnaryExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UnaryOp::Delete, move(rhs));
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -1711,8 +1771,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
||||||
return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity);
|
return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity);
|
||||||
case TokenType::Period:
|
case TokenType::Period:
|
||||||
consume();
|
consume();
|
||||||
if (!match_identifier_name())
|
if (match(TokenType::PrivateIdentifier)) {
|
||||||
|
if (!is_private_identifier_valid())
|
||||||
|
syntax_error(String::formatted("Reference to undeclared private field or method '{}'", m_state.current_token.value()));
|
||||||
|
else if (is<SuperExpression>(*lhs))
|
||||||
|
syntax_error(String::formatted("Cannot access private field or method '{}' on super", m_state.current_token.value()));
|
||||||
|
|
||||||
|
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
|
||||||
|
} else if (!match_identifier_name()) {
|
||||||
expected("IdentifierName");
|
expected("IdentifierName");
|
||||||
|
}
|
||||||
|
|
||||||
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
|
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
|
||||||
case TokenType::BracketOpen: {
|
case TokenType::BracketOpen: {
|
||||||
consume(TokenType::BracketOpen);
|
consume(TokenType::BracketOpen);
|
||||||
|
@ -1780,6 +1849,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Parser::is_private_identifier_valid() const
|
||||||
|
{
|
||||||
|
VERIFY(match(TokenType::PrivateIdentifier));
|
||||||
|
if (!m_state.referenced_private_names)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We might not have hit the declaration yet so class will check this in the end
|
||||||
|
m_state.referenced_private_names->set(m_state.current_token.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expression)
|
RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expression)
|
||||||
{
|
{
|
||||||
VERIFY(is<ArrayExpression>(expression) || is<ObjectExpression>(expression));
|
VERIFY(is<ArrayExpression>(expression) || is<ObjectExpression>(expression));
|
||||||
|
@ -3042,6 +3122,7 @@ bool Parser::match_expression() const
|
||||||
|| type == TokenType::TemplateLiteralStart
|
|| type == TokenType::TemplateLiteralStart
|
||||||
|| type == TokenType::NullLiteral
|
|| type == TokenType::NullLiteral
|
||||||
|| match_identifier()
|
|| match_identifier()
|
||||||
|
|| (type == TokenType::PrivateIdentifier && next_token().type() == TokenType::In)
|
||||||
|| type == TokenType::Await
|
|| type == TokenType::Await
|
||||||
|| type == TokenType::New
|
|| type == TokenType::New
|
||||||
|| type == TokenType::Class
|
|| type == TokenType::Class
|
||||||
|
|
|
@ -178,6 +178,7 @@ private:
|
||||||
bool match_identifier() const;
|
bool match_identifier() const;
|
||||||
bool match_identifier_name() const;
|
bool match_identifier_name() const;
|
||||||
bool match_property_key() const;
|
bool match_property_key() const;
|
||||||
|
bool is_private_identifier_valid() const;
|
||||||
bool match(TokenType type) const;
|
bool match(TokenType type) const;
|
||||||
bool done() const;
|
bool done() const;
|
||||||
void expected(const char* what);
|
void expected(const char* what);
|
||||||
|
@ -242,6 +243,8 @@ private:
|
||||||
ScopePusher* current_scope_pusher { nullptr };
|
ScopePusher* current_scope_pusher { nullptr };
|
||||||
|
|
||||||
HashMap<StringView, Optional<Position>> labels_in_scope;
|
HashMap<StringView, Optional<Position>> labels_in_scope;
|
||||||
|
HashTable<StringView>* referenced_private_names { nullptr };
|
||||||
|
|
||||||
bool strict_mode { false };
|
bool strict_mode { false };
|
||||||
bool allow_super_property_lookup { false };
|
bool allow_super_property_lookup { false };
|
||||||
bool allow_super_constructor_call { false };
|
bool allow_super_constructor_call { false };
|
||||||
|
|
|
@ -276,8 +276,8 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
|
||||||
visitor.visit(m_home_object);
|
visitor.visit(m_home_object);
|
||||||
|
|
||||||
for (auto& field : m_fields) {
|
for (auto& field : m_fields) {
|
||||||
if (field.name.is_symbol())
|
if (auto* property_name_ptr = field.name.get_pointer<PropertyName>(); property_name_ptr && property_name_ptr->is_symbol())
|
||||||
visitor.visit(field.name.as_symbol());
|
visitor.visit(property_name_ptr->as_symbol());
|
||||||
|
|
||||||
visitor.visit(field.initializer);
|
visitor.visit(field.initializer);
|
||||||
}
|
}
|
||||||
|
@ -729,17 +729,9 @@ void ECMAScriptFunctionObject::set_name(const FlyString& name)
|
||||||
VERIFY(success);
|
VERIFY(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7.3.31 DefineField ( receiver, fieldRecord ), https://tc39.es/ecma262/#sec-definefield
|
void ECMAScriptFunctionObject::add_field(ClassElement::ClassElementName property_key, ECMAScriptFunctionObject* initializer)
|
||||||
void ECMAScriptFunctionObject::InstanceField::define_field(VM& vm, Object& receiver) const
|
|
||||||
{
|
{
|
||||||
Value init_value = js_undefined();
|
m_fields.empend(property_key, initializer);
|
||||||
if (initializer) {
|
|
||||||
auto init_value_or_error = vm.call(*initializer, receiver.value_of());
|
|
||||||
if (init_value_or_error.is_error())
|
|
||||||
return;
|
|
||||||
init_value = init_value_or_error.release_value();
|
|
||||||
}
|
|
||||||
(void)receiver.create_data_property_or_throw(name, init_value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,14 +59,15 @@ public:
|
||||||
void set_home_object(Object* home_object) { m_home_object = home_object; }
|
void set_home_object(Object* home_object) { m_home_object = home_object; }
|
||||||
|
|
||||||
struct InstanceField {
|
struct InstanceField {
|
||||||
PropertyName name;
|
Variant<PropertyName, PrivateName> name;
|
||||||
ECMAScriptFunctionObject* initializer { nullptr };
|
ECMAScriptFunctionObject* initializer { nullptr };
|
||||||
|
|
||||||
void define_field(VM& vm, Object& receiver) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<InstanceField> const& fields() const { return m_fields; }
|
Vector<InstanceField> const& fields() const { return m_fields; }
|
||||||
void add_field(PropertyName property_key, ECMAScriptFunctionObject* initializer) { m_fields.empend(property_key, initializer); }
|
void add_field(Variant<PropertyName, PrivateName> property_key, ECMAScriptFunctionObject* initializer);
|
||||||
|
|
||||||
|
Vector<PrivateElement> const& private_methods() const { return m_private_methods; }
|
||||||
|
void add_private_method(PrivateElement method) { m_private_methods.append(move(method)); };
|
||||||
|
|
||||||
// This is for IsSimpleParameterList (static semantics)
|
// This is for IsSimpleParameterList (static semantics)
|
||||||
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
|
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
|
||||||
|
@ -97,6 +98,7 @@ private:
|
||||||
bool m_strict { false }; // [[Strict]]
|
bool m_strict { false }; // [[Strict]]
|
||||||
Object* m_home_object { nullptr }; // [[HomeObject]]
|
Object* m_home_object { nullptr }; // [[HomeObject]]
|
||||||
Vector<InstanceField> m_fields; // [[Fields]]
|
Vector<InstanceField> m_fields; // [[Fields]]
|
||||||
|
Vector<PrivateElement> m_private_methods; // [[PrivateMethods]]
|
||||||
bool m_is_class_constructor { false }; // [[IsClassConstructor]]
|
bool m_is_class_constructor { false }; // [[IsClassConstructor]]
|
||||||
|
|
||||||
FlyString m_name;
|
FlyString m_name;
|
||||||
|
|
|
@ -34,8 +34,9 @@ PrivateName PrivateEnvironment::resolve_private_identifier(FlyString const& iden
|
||||||
|
|
||||||
void PrivateEnvironment::add_private_name(Badge<ClassExpression>, FlyString description)
|
void PrivateEnvironment::add_private_name(Badge<ClassExpression>, FlyString description)
|
||||||
{
|
{
|
||||||
// FIXME: there is a exception for getter setter pairs.
|
if (!find_private_name(description).is_end())
|
||||||
VERIFY(find_private_name(description).is_end());
|
return;
|
||||||
|
|
||||||
m_private_names.empend(m_unique_id, move(description));
|
m_private_names.empend(m_unique_id, move(description));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -478,11 +478,11 @@ Reference VM::resolve_binding(FlyString const& name, Environment* environment)
|
||||||
// 7.3.32 InitializeInstanceElements ( O, constructor ), https://tc39.es/ecma262/#sec-initializeinstanceelements
|
// 7.3.32 InitializeInstanceElements ( O, constructor ), https://tc39.es/ecma262/#sec-initializeinstanceelements
|
||||||
ThrowCompletionOr<void> VM::initialize_instance_elements(Object& object, ECMAScriptFunctionObject& constructor)
|
ThrowCompletionOr<void> VM::initialize_instance_elements(Object& object, ECMAScriptFunctionObject& constructor)
|
||||||
{
|
{
|
||||||
for (auto& field : constructor.fields()) {
|
for (auto& method : constructor.private_methods())
|
||||||
field.define_field(*this, object);
|
TRY(object.private_method_or_accessor_add(method));
|
||||||
if (auto* exception = this->exception())
|
|
||||||
return JS::throw_completion(exception->value());
|
for (auto& field : constructor.fields())
|
||||||
}
|
TRY(object.define_field(field.name, field.initializer));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
test("basic functionality", () => {
|
||||||
|
class A {
|
||||||
|
#number = 3;
|
||||||
|
|
||||||
|
getNumber() {
|
||||||
|
return this.#number;
|
||||||
|
}
|
||||||
|
|
||||||
|
#string = "foo";
|
||||||
|
|
||||||
|
getString() {
|
||||||
|
return this.#string;
|
||||||
|
}
|
||||||
|
|
||||||
|
#uninitialized;
|
||||||
|
|
||||||
|
getUninitialized() {
|
||||||
|
return this.#uninitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a.getNumber()).toBe(3);
|
||||||
|
expect(a.getString()).toBe("foo");
|
||||||
|
expect(a.getUninitialized()).toBeUndefined();
|
||||||
|
|
||||||
|
expect("a.#number").not.toEval();
|
||||||
|
expect("a.#string").not.toEval();
|
||||||
|
expect("a.#uninitialized").not.toEval();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("initializer has correct this value", () => {
|
||||||
|
class A {
|
||||||
|
#thisVal = this;
|
||||||
|
|
||||||
|
getThisVal() {
|
||||||
|
return this.#thisVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#thisName = this.#thisVal;
|
||||||
|
|
||||||
|
getThisName() {
|
||||||
|
return this.#thisName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = new A();
|
||||||
|
expect(a.getThisVal()).toBe(a);
|
||||||
|
expect(a.getThisName()).toBe(a);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("static fields", () => {
|
||||||
|
class A {
|
||||||
|
static #simple = 1;
|
||||||
|
|
||||||
|
static getStaticSimple() {
|
||||||
|
return this.#simple;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #thisVal = this;
|
||||||
|
static #thisName = this.name;
|
||||||
|
static #thisVal2 = this.#thisVal;
|
||||||
|
|
||||||
|
static getThisVal() {
|
||||||
|
return this.#thisVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getThisName() {
|
||||||
|
return this.#thisName;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getThisVal2() {
|
||||||
|
return this.#thisVal2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(A.getStaticSimple()).toBe(1);
|
||||||
|
|
||||||
|
expect(A.getThisVal()).toBe(A);
|
||||||
|
expect(A.getThisName()).toBe("A");
|
||||||
|
expect(A.getThisVal2()).toBe(A);
|
||||||
|
|
||||||
|
expect("A.#simple").not.toEval();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cannot have static and non static field with the same description", () => {
|
||||||
|
expect("class A { static #simple; #simple; }").not.toEval();
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue