Index: src/parsing/parser.cc |
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc |
index 8d1cac2bdd44e35b8057b23559284cabc83cb1e2..8bcd592cf5bdf563c88d295c7b17055002905e71 100644 |
--- a/src/parsing/parser.cc |
+++ b/src/parsing/parser.cc |
@@ -170,7 +170,7 @@ Expression* Parser::CallClassFieldInitializer(Scope* scope, |
Expression* Parser::RewriteSuperCall(Expression* super_call) { |
// TODO(bakkot) find a way to avoid this for classes without fields. |
- if (!allow_harmony_class_fields()) { |
+ if (!(allow_harmony_class_fields() || allow_harmony_private_class_fields())) { |
return super_call; |
} |
// This turns a super call `super()` into a do expression of the form |
@@ -596,6 +596,7 @@ Parser::Parser(ParseInfo* info) |
set_allow_harmony_restrictive_generators(FLAG_harmony_restrictive_generators); |
set_allow_harmony_trailing_commas(FLAG_harmony_trailing_commas); |
set_allow_harmony_class_fields(FLAG_harmony_class_fields); |
+ set_allow_harmony_private_class_fields(FLAG_harmony_private_class_fields); |
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount; |
++feature) { |
use_counts_[feature] = 0; |
@@ -3377,6 +3378,45 @@ DoExpression* Parser::ParseDoExpression(bool* ok) { |
return expr; |
} |
+const AstRawString* ClassPrivateFieldVariableName( |
+ AstValueFactory* ast_value_factory, const AstRawString* raw_name) { |
+ // Prepends '#' |
+ return ast_value_factory->ConcatStrings( |
+ ast_value_factory->number_sign_string(), raw_name); |
+} |
+ |
+Property* Parser::ParsePrivateFieldReference(Expression* base, bool* ok) { |
+ // PrimaryExpression :: |
+ // PrivateName |
+ |
+ // This desugars a private field reference like `#a` or `obj.#a` into |
+ // %ThrowIfMissingPrivateField(obj, #a)[#a] |
+ // Here #a is a variable which will resolve to a private symbol when inside of |
+ // a class which defines a private field named #a (otherwise attempting to |
+ // resolve it will throw a reference error). |
+ |
+ int pos = peek_position(); |
+ |
+ Expect(Token::HASH, CHECK_OK); |
+ const AstRawString* property_name = |
+ ParseIdentifierName(CHECK_OK); // TODO(bakkot) more complex names |
+ const AstRawString* symbol_var_name = |
+ ClassPrivateFieldVariableName(ast_value_factory(), property_name); |
+ |
+ ZoneList<Expression*>* args = new (zone()) ZoneList<Expression*>(2, zone()); |
+ args->Add(base, zone()); |
+ args->Add(NewUnresolved(symbol_var_name), zone()); |
+ Expression* call = factory()->NewCallRuntime( |
+ Runtime::kThrowIfMissingPrivateField, args, pos); |
+ // TODO(bakkot) ideally we'd avoid this runtime function, either by adding a |
+ // flag to the load/store IC or by adding a code stub for it. Currently it |
+ // means that every private field access requires this runtime call, which is |
+ // a slowdown we'd prefer to avoid. |
+ |
+ Expression* prop = NewUnresolved(symbol_var_name); |
+ return factory()->NewProperty(call, prop, pos); |
+} |
+ |
void Parser::ParseArrowFunctionFormalParameterList( |
ParserFormalParameters* parameters, Expression* expr, |
const Scanner::Location& params_loc, Scanner::Location* duplicate_loc, |
@@ -4256,6 +4296,7 @@ PreParser::PreParseResult Parser::ParseLazyFunctionBodyWithPreParser( |
SET_ALLOW(harmony_async_await); |
SET_ALLOW(harmony_trailing_commas); |
SET_ALLOW(harmony_class_fields); |
+ SET_ALLOW(harmony_private_class_fields); |
#undef SET_ALLOW |
} |
PreParser::PreParseResult result = reusable_preparser_->PreParseLazyFunction( |
@@ -4441,6 +4482,10 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
ZoneList<ClassLiteral::Property*>* properties = NewClassPropertyList(4); |
ZoneList<Expression*>* instance_field_initializers = |
new (zone()) ZoneList<Expression*>(0, zone()); |
+ ZoneList<Variable*>* private_field_symbol_vars = |
+ new (zone()) ZoneList<Variable*>(0, zone()); |
+ ZoneList<Variable*>* private_field_index_vars = |
+ new (zone()) ZoneList<Variable*>(0, zone()); |
FunctionLiteral* constructor = nullptr; |
bool has_seen_constructor = false; |
Variable* static_initializer_var = nullptr; |
@@ -4455,12 +4500,13 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
while (peek() != Token::RBRACE) { |
if (Check(Token::SEMICOLON)) continue; |
FuncNameInferrer::State fni_state(fni_); |
+ const AstRawString* private_name = nullptr; |
bool is_computed_name = false; // Classes do not care about computed |
// property names here. |
ExpressionClassifier property_classifier(this); |
- ClassLiteral::Property* property = |
- ParseClassPropertyDefinition(&checker, has_extends, &is_computed_name, |
- &has_seen_constructor, CHECK_OK); |
+ ClassLiteral::Property* property = ParseClassPropertyDefinition( |
+ &checker, has_extends, &private_name, &is_computed_name, |
+ &has_seen_constructor, CHECK_OK); |
RewriteNonPattern(CHECK_OK); |
impl()->AccumulateFormalParameterContainmentErrors(); |
@@ -4471,8 +4517,39 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
name != nullptr ? name : ast_value_factory()->empty_string()); |
} else { |
if (property->kind() == ClassLiteralProperty::FIELD) { |
- DCHECK(allow_harmony_class_fields()); |
- if (property->is_static()) { |
+ if (private_name != nullptr) { |
+ DCHECK(allow_harmony_private_class_fields()); |
+ DCHECK(property->key() == nullptr); |
+ DCHECK(!property->is_static()); |
+ |
+ const AstRawString* symbol_var_name = |
+ ClassPrivateFieldVariableName(ast_value_factory(), private_name); |
+ VariableProxy* symbol_var_proxy = NewUnresolved(symbol_var_name); |
+ Declaration* symbol_var_declaration = |
+ factory()->NewVariableDeclaration(symbol_var_proxy, scope(), |
+ kNoSourcePosition); |
+ Variable* symbol_var = |
+ Declare(symbol_var_declaration, DeclarationDescriptor::NORMAL, |
+ CONST, kCreatedInitialized, ok, scope()); |
+ private_field_symbol_vars->Add(symbol_var, zone()); |
+ |
+ const AstRawString* index_var_name = ClassFieldVariableName( |
+ true, ast_value_factory(), instance_field_initializers->length()); |
+ VariableProxy* index_var_proxy = NewUnresolved(index_var_name); |
+ Declaration* index_var_declaration = |
+ factory()->NewVariableDeclaration(index_var_proxy, scope(), |
+ kNoSourcePosition); |
+ Variable* index_var = |
+ Declare(index_var_declaration, DeclarationDescriptor::NORMAL, |
+ CONST, kCreatedInitialized, ok, scope()); |
+ private_field_index_vars->Add(index_var, zone()); |
+ |
+ instance_field_initializers->Add(property->value(), zone()); |
+ |
+ // Does not add to properties because no action is required from the |
+ // backend during class definition. |
+ } else if (property->is_static()) { |
+ DCHECK(allow_harmony_class_fields()); |
if (static_initializer_var == nullptr) { |
static_initializer_var = |
NewTemporary(ast_value_factory()->empty_string()); |
@@ -4489,7 +4566,9 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
Expression* call = factory()->NewCallRuntime(Runtime::kInlineCall, |
args, kNoSourcePosition); |
property->set_value(call); |
+ properties->Add(property, zone()); |
} else { |
+ DCHECK(allow_harmony_class_fields()); |
// if (is_computed_name) { // TODO(bakkot) figure out why this is |
// necessary for non-computed names in full-codegen |
ZoneList<Expression*>* to_name_args = |
@@ -4510,9 +4589,11 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
if (!ok) return nullptr; |
instance_field_initializers->Add(property->value(), zone()); |
property->set_value(factory()->NewVariableProxy(name_var)); |
+ properties->Add(property, zone()); |
} |
+ } else { |
+ properties->Add(property, zone()); |
} |
- properties->Add(property, zone()); |
} |
DCHECK_NOT_NULL(fni_); |
@@ -4523,7 +4604,8 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
int end_pos = scanner()->location().end_pos; |
bool has_instance_fields = instance_field_initializers->length() > 0; |
- DCHECK(!has_instance_fields || allow_harmony_class_fields()); |
+ DCHECK(!has_instance_fields || allow_harmony_class_fields() || |
+ allow_harmony_private_class_fields()); |
bool has_default_constructor = constructor == nullptr; |
if (has_default_constructor) { |
constructor = DefaultConstructor(name, has_extends, has_instance_fields, |
@@ -4542,6 +4624,79 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
proxy->var()->set_initializer_position(end_pos); |
} |
+ // The parser creates two variables for every private field, both of which |
+ // store the private symbol which serves as the key for that field. One of |
+ // them, 'symbol_var', is used when referencing private fields with '#a' in |
+ // normal code. The other, 'index_var', is used when initializing the field. |
+ // The later variable is necessary because the synthetic field initialization |
+ // function has available to it *only* the number of fields in the class. |
+ // The desugaring looks something like this: |
+ // |
+ // class C { |
+ // #foo = 0; |
+ // baz; |
+ // m(){ |
+ // this.#bar = 'a'; |
+ // #foo = 1; |
+ // } |
+ // #bar; |
+ // } |
+ // -- becomes -- |
+ // do { |
+ // class C { |
+ // m(){ |
+ // %ThrowIfMissingPrivateField(this, .bar-symbol)[.bar-symbol] = 'a'; |
+ // %ThrowIfMissingPrivateField(this, .foo-symbol)[.foo-symbol] = 1; |
+ // } |
+ // constructor(){ |
+ // .initialize.call(this); |
+ // } |
+ // } |
+ // |
+ // let .foo-symbol, .name-0, .init-0; |
+ // .foo-symbol = .name-0 = PrivateSymbol(); |
+ // .init-0 = ()=>0; |
+ // |
+ // let .name-1, .init-1; |
+ // // .name-1 is given value 'baz' by the backends during class definition |
+ // .init-1 = ()=>undefined; |
+ // |
+ // let .bar-symbol, .name-2, .init-2; |
+ // .bar-symbol = .name-2 = PrivateSymbol(); |
+ // .init-2 = ()=>undefined; |
+ // |
+ // .initialize() { |
+ // this[.name-0] = .init-0(); // Actually this is defineOwnProperty not = |
+ // this[.name-1] = .init-1(); |
+ // this[.name-2] = .init-2(); |
+ // } |
+ // |
+ // C; |
+ // } |
+ DCHECK(private_field_symbol_vars->length() == |
+ private_field_index_vars->length()); |
+ DCHECK(allow_harmony_private_class_fields() || |
+ private_field_symbol_vars->length() == 0); |
+ for (int i = 0; i < private_field_symbol_vars->length(); ++i) { |
+ VariableProxy* symbol_var_proxy = |
+ factory()->NewVariableProxy(private_field_symbol_vars->at(i)); |
+ VariableProxy* index_var_proxy = |
+ factory()->NewVariableProxy(private_field_index_vars->at(i)); |
+ |
+ ZoneList<Expression*>* args = new (zone()) ZoneList<Expression*>(1, zone()); |
+ args->Add(factory()->NewUndefinedLiteral(kNoSourcePosition), zone()); |
+ Expression* name = |
+ factory()->NewCallRuntime(Runtime::kCreatePrivateSymbol, args, pos); |
+ |
+ Assignment* inner_assignment = factory()->NewAssignment( |
+ Token::INIT, index_var_proxy, name, kNoSourcePosition); |
+ Assignment* outer_assignment = factory()->NewAssignment( |
+ Token::INIT, symbol_var_proxy, inner_assignment, kNoSourcePosition); |
+ do_block->statements()->Add( |
+ factory()->NewExpressionStatement(outer_assignment, kNoSourcePosition), |
+ zone()); |
+ } |
+ |
ClassLiteral* class_literal = factory()->NewClassLiteral( |
proxy, extends, constructor, properties, pos, end_pos); |
@@ -4557,7 +4712,7 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, |
class_literal, kNoSourcePosition), |
pos), |
zone()); |
- if (allow_harmony_class_fields() && |
+ if ((allow_harmony_class_fields() || allow_harmony_private_class_fields()) && |
(has_instance_fields || |
(extends != nullptr && !has_default_constructor))) { |
// Default constructors for derived classes without fields will not try to |