summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartok <martok@martoks-place.de>2023-04-06 03:10:50 +0200
committerMartok <martok@martoks-place.de>2023-05-01 17:16:17 +0200
commit3cbe292dc61a77fc1d1d55b46bf7898ec1acaef7 (patch)
tree6dccc14463a403a4d37b9286d7a34ff405d5fdf6
parent7647230e93f4e9c97e38eddf72351aeceb830b26 (diff)
downloaduxp-3cbe292dc61a77fc1d1d55b46bf7898ec1acaef7.tar.gz
Issue #2142 - Implement syntax for public/private fields and computed field names
This state still has the initializers scoped on .initializers local variable, which will be changed later. Based-on: m-c 1499448, 1530084, 1530832, 1529448 (partial), 1532921, 1528039, 1528038, 1535166, 1550628, 1535166, 1550628, 1541641, 1547133, 1540787, 1535804/9
-rw-r--r--js/src/builtin/ReflectParse.cpp87
-rw-r--r--js/src/frontend/BytecodeCompiler.h10
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp296
-rw-r--r--js/src/frontend/BytecodeEmitter.h9
-rw-r--r--js/src/frontend/FoldConstants.cpp18
-rw-r--r--js/src/frontend/FullParseHandler.h46
-rw-r--r--js/src/frontend/NameFunctions.cpp20
-rw-r--r--js/src/frontend/ParseNode.cpp11
-rw-r--r--js/src/frontend/ParseNode.h76
-rw-r--r--js/src/frontend/Parser.cpp775
-rw-r--r--js/src/frontend/Parser.h38
-rw-r--r--js/src/frontend/SharedContext.h10
-rw-r--r--js/src/frontend/SyntaxParseHandler.h9
-rw-r--r--js/src/frontend/TokenKind.h2
-rw-r--r--js/src/frontend/TokenStream.cpp178
-rw-r--r--js/src/frontend/TokenStream.h10
-rw-r--r--js/src/js.msg4
-rw-r--r--js/src/jsapi.cpp2
-rw-r--r--js/src/jsast.tbl1
-rw-r--r--js/src/jsscript.cpp24
-rw-r--r--js/src/jsscript.h39
-rw-r--r--js/src/vm/CommonPropertyNames.h2
-rw-r--r--js/src/vm/Opcodes.h4
-rw-r--r--js/src/wasm/AsmJS.cpp2
24 files changed, 1383 insertions, 290 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index 4aa7f1640b..2902d2b724 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -539,9 +539,11 @@ class NodeBuilder
MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage,
HandleValue block, TokenPos* pos, MutableHandleValue dst);
- MOZ_MUST_USE bool classMethods(NodeVector& methods, MutableHandleValue dst);
+ MOZ_MUST_USE bool classMembers(NodeVector& members, MutableHandleValue dst);
MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic,
TokenPos* pos, MutableHandleValue dst);
+ MOZ_MUST_USE bool classField(HandleValue name, HandleValue initializer,
+ TokenPos* pos, MutableHandleValue dst);
/*
* expressions
@@ -1721,9 +1723,23 @@ NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool
}
bool
-NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst)
+NodeBuilder::classField(HandleValue name, HandleValue initializer,
+ TokenPos* pos, MutableHandleValue dst)
{
- return newArray(methods, dst);
+ RootedValue cb(cx, callbacks[AST_CLASS_FIELD]);
+ if (!cb.isNull())
+ return callback(cb, name, initializer, pos, dst);
+
+ return newNode(AST_CLASS_FIELD, pos,
+ "name", name,
+ "init", initializer,
+ dst);
+}
+
+bool
+NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst)
+{
+ return newArray(members, dst);
}
bool
@@ -1853,6 +1869,7 @@ class ASTSerializer
bool property(ParseNode* pn, MutableHandleValue dst);
bool classMethod(ClassMethod* classMethod, MutableHandleValue dst);
+ bool classField(ClassField* classField, MutableHandleValue dst);
bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) {
if (!atom) {
@@ -2457,7 +2474,7 @@ ASTSerializer::classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst)
}
return optExpression(pn->heritage(), &heritage) &&
- statement(pn->methodList(), &classBody) &&
+ statement(pn->memberList(), &classBody) &&
builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst);
}
@@ -2676,24 +2693,34 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
case PNK_CLASS:
return classDefinition(&pn->as<ClassNode>(), false, dst);
- case PNK_CLASSMETHODLIST:
+ case PNK_CLASSMEMBERLIST:
{
- ListNode* methodList = &pn->as<ListNode>();
- NodeVector methods(cx);
- if (!methods.reserve(methodList->count()))
+ ListNode* memberList = &pn->as<ListNode>();
+ NodeVector members(cx);
+ if (!members.reserve(memberList->count()))
return false;
- for (ParseNode* item : methodList->contents()) {
- ClassMethod* method = &item->as<ClassMethod>();
- MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos));
+ for (ParseNode* item : memberList->contents()) {
+ if (item->is<ClassField>()) {
+ ClassField* field = &item->as<ClassField>();
+ MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos));
- RootedValue prop(cx);
- if (!classMethod(method, &prop))
- return false;
- methods.infallibleAppend(prop);
+ RootedValue prop(cx);
+ if (!classField(field, &prop))
+ return false;
+ members.infallibleAppend(prop);
+ } else {
+ ClassMethod* method = &item->as<ClassMethod>();
+ MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos));
+
+ RootedValue prop(cx);
+ if (!classMethod(method, &prop))
+ return false;
+ members.infallibleAppend(prop);
+ }
}
- return builder.classMethods(methods, dst);
+ return builder.classMembers(members, dst);
}
case PNK_NOP:
@@ -2733,6 +2760,34 @@ ASTSerializer::classMethod(ClassMethod* classMethod, MutableHandleValue dst)
}
bool
+ASTSerializer::classField(ClassField* classField, MutableHandleValue dst)
+{
+ RootedValue key(cx), val(cx);
+ // Dig through the lambda and get to the actual expression
+ if (classField->initializer()) {
+ ParseNode* value = classField->initializer()
+ ->body()
+ ->head()->as<LexicalScopeNode>()
+ .scopeBody()->as<ListNode>()
+ .head()->as<UnaryNode>()
+ .kid()->as<AssignmentNode>()
+ .right();
+ // RawUndefinedExpr is the node we use for "there is no initializer". If one
+ // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr
+ // node, but rather a variable reference.
+ // Behavior for "there is no initializer" should be { ..., "init": null }
+ if (value->getKind() != PNK_RAW_UNDEFINED) {
+ if (!expression(value, &val))
+ return false;
+ } else {
+ val.setNull();
+ }
+ }
+ return propertyName(&classField->name(), &key) &&
+ builder.classField(key, val, &classField->pn_pos, dst);
+}
+
+bool
ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst)
{
MOZ_ASSERT(!node->empty());
diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h
index 6d03f8f9fb..471e9e36d9 100644
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -110,14 +110,22 @@ CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& opt
bool
IsIdentifier(JSLinearString* str);
+bool
+IsIdentifierNameOrPrivateName(JSLinearString* str);
+
/*
* As above, but taking chars + length.
*/
bool
-IsIdentifier(const char* chars, size_t length);
+IsIdentifier(const Latin1Char* chars, size_t length);
bool
IsIdentifier(const char16_t* chars, size_t length);
+bool
+IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length);
+bool
+IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length);
+
/* True if str is a keyword. Defined in TokenStream.cpp. */
bool
IsKeyword(JSLinearString* str);
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index 51ebd81969..c18b5d933f 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -174,6 +174,10 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
innermostNestableControl(nullptr),
innermostEmitterScope_(nullptr),
innermostTDZCheckCache(nullptr),
+ fieldInitializers_(parent
+ ? parent->fieldInitializers_
+ : lazyScript ? lazyScript->getFieldInitializers()
+ : FieldInitializers::Invalid()),
#ifdef DEBUG
unstableEmitterScope(false),
#endif
@@ -1058,6 +1062,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
return true;
case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_PRIVATE_NAME: // no side effects, unlike PNK_NAME
case PNK_STRING:
case PNK_TEMPLATE_STRING:
MOZ_ASSERT(pn->is<NameNode>());
@@ -1515,8 +1520,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR
case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR
case PNK_CLASSMETHOD: // by PNK_CLASS
+ case PNK_CLASSFIELD: // by PNK_CLASS
case PNK_CLASSNAMES: // by PNK_CLASS
- case PNK_CLASSMETHODLIST: // by PNK_CLASS
+ case PNK_CLASSMEMBERLIST: // by PNK_CLASS
case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT
case PNK_IMPORT_SPEC: // by PNK_IMPORT
case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT
@@ -2381,6 +2387,64 @@ BytecodeEmitter::emitScript(ParseNode* body)
return true;
}
+bool BytecodeEmitter::emitInitializeInstanceFields()
+{
+ MOZ_ASSERT(fieldInitializers_.valid);
+ size_t numFields = fieldInitializers_.numFieldInitializers;
+
+ if (numFields == 0) {
+ return true;
+ }
+
+ if (!emitGetName(cx->names().dotInitializers)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) {
+ if (fieldIndex < numFields - 1) {
+ // We DUP to keep the array around (it is consumed in the bytecode below)
+ // for next iterations of this loop, except for the last iteration, which
+ // avoids an extra POP at the end of the loop.
+ if (!emit1(JSOP_DUP)) {
+ // [stack] ARRAY ARRAY
+ return false;
+ }
+ }
+
+ if (!emitNumberOp(fieldIndex)) {
+ // [stack] ARRAY? ARRAY INDEX
+ return false;
+ }
+
+ // Don't use CALLELEM here, because the receiver of the call != the receiver
+ // of this getelem. (Specifically, the call receiver is `this`, and the
+ // receiver of this getelem is `.initializers`)
+ if (!emit1(JSOP_GETELEM)) {
+ // [stack] ARRAY? FUNC
+ return false;
+ }
+
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!emitGetName(cx->names().dotThis)) {
+ // [stack] ARRAY? FUNC THIS
+ return false;
+ }
+
+ if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) {
+ // [stack] ARRAY? RVAL
+ return false;
+ }
+
+ if (!emit1(JSOP_POP)) {
+ // [stack] ARRAY?
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool
BytecodeEmitter::emitFunctionScript(FunctionNode* funNode)
{
@@ -7718,6 +7782,12 @@ bool
BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type)
{
for (ParseNode* propdef : obj->contents()) {
+ if (propdef->is<ClassField>()) {
+ // Skip over class fields and emit them at the end. This is needed
+ // because they're all emitted into a single array, which is then stored
+ // into a local variable
+ continue;
+ }
if (!updateSourceCoordNotes(propdef->pn_pos.begin))
return false;
@@ -7877,9 +7947,186 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp,
return false;
}
}
+
+ if (obj->getKind() == PNK_CLASSMEMBERLIST) {
+ if (!emitCreateFieldKeys(obj))
+ return false;
+ if (!emitCreateFieldInitializers(obj))
+ return false;
+ }
+
+ return true;
+}
+
+FieldInitializers
+BytecodeEmitter::setupFieldInitializers(ListNode* classMembers)
+{
+ size_t numFields = 0;
+
+ for (ParseNode* propdef : classMembers->contents()) {
+ if (propdef->is<ClassField>()) {
+ FunctionNode* initializer = propdef->as<ClassField>().initializer();
+ // Don't include fields without initializers.
+ if (initializer != nullptr) {
+ numFields++;
+ }
+ continue;
+ }
+ }
+
+ return FieldInitializers(numFields);
+}
+
+// Purpose of .fieldKeys:
+// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not
+// object construction time. The transformation to do so is roughly as follows:
+//
+// class C {
+// [keyExpr] = valueExpr;
+// }
+// -->
+// let .fieldKeys = [keyExpr];
+// let .initializers = [
+// () => {
+// this[.fieldKeys[0]] = valueExpr;
+// }
+// ];
+// class C {
+// constructor() {
+// .initializers[0]();
+// }
+// }
+//
+// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [keyExpr, ...];`
+// See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part.
+bool
+BytecodeEmitter::emitCreateFieldKeys(ListNode* obj)
+{
+ size_t numFieldKeys = 0;
+ for (ParseNode* propdef : obj->contents()) {
+ if (propdef->is<ClassField>()) {
+ ClassField* field = &propdef->as<ClassField>();
+ if (field->name().getKind() == PNK_COMPUTED_NAME) {
+ numFieldKeys++;
+ }
+ }
+ }
+
+ if (numFieldKeys == 0)
+ return true;
+
+ NameOpEmitter noe(this, cx->names().dotFieldKeys,
+ NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs())
+ return false;
+
+ if (!emitUint32Operand(JSOP_NEWARRAY, numFieldKeys)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ size_t curFieldKeyIndex = 0;
+ for (ParseNode* propdef : obj->contents()) {
+ if (propdef->is<ClassField>()) {
+ ClassField* field = &propdef->as<ClassField>();
+ if (field->name().getKind() == PNK_COMPUTED_NAME) {
+ ParseNode* nameExpr = field->name().as<UnaryNode>().kid();
+
+ if (!emitTree(nameExpr)) {
+ // [stack] ARRAY KEY
+ return false;
+ }
+
+ if (!emit1(JSOP_TOID)) {
+ // [stack] ARRAY KEY
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldKeyIndex)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ curFieldKeyIndex++;
+ }
+ }
+ }
+ MOZ_ASSERT(curFieldKeyIndex == numFieldKeys);
+
+ if (!noe.emitAssignment()) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!emit1(JSOP_POP)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj)
+{
+ const FieldInitializers& fieldInitializers = fieldInitializers_;
+ MOZ_ASSERT(fieldInitializers.valid);
+ size_t numFields = fieldInitializers.numFieldInitializers;
+
+ if (numFields == 0)
+ return true;
+
+ // .initializers is a variable that stores an array of lambdas containing
+ // code (the initializer) for each field. Upon an object's construction,
+ // these lambdas will be called, defining the values.
+
+ NameOpEmitter noe(this, cx->names().dotInitializers,
+ NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) {
+ // [stack] CTOR? OBJ ARRAY
+ return false;
+ }
+
+ size_t curFieldIndex = 0;
+ for (ParseNode* propdef : obj->contents()) {
+ if (propdef->is<ClassField>()) {
+ FunctionNode* initializer = propdef->as<ClassField>().initializer();
+ if (initializer == nullptr) {
+ continue;
+ }
+
+ if (!emitTree(initializer)) {
+ // [stack] CTOR? OBJ ARRAY LAMBDA
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) {
+ // [stack] CTOR? OBJ ARRAY
+ return false;
+ }
+
+ curFieldIndex++;
+ }
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] CTOR? OBJ ARRAY
+ return false;
+ }
+
+ if (!emit1(JSOP_POP)) {
+ // [stack] CTOR? OBJ
+ return false;
+ }
+
return true;
}
+
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool
@@ -8414,6 +8661,11 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody)
{
FunctionBox* funbox = sc->asFunctionBox();
+ if (funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor) {
+ if (!emitInitializeInstanceFields())
+ return false;
+ }
+
if (!emitTree(funBody))
return false;
@@ -8484,6 +8736,21 @@ BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
return true;
}
+class AutoResetFieldInitializers
+{
+ BytecodeEmitter* bce;
+ FieldInitializers oldFieldInfo;
+
+ public:
+ AutoResetFieldInitializers(BytecodeEmitter* bce, FieldInitializers newFieldInfo)
+ : bce(bce), oldFieldInfo(bce->fieldInitializers_)
+ {
+ bce->fieldInitializers_ = newFieldInfo;
+ }
+
+ ~AutoResetFieldInitializers() { bce->fieldInitializers_ = oldFieldInfo; }
+};
+
// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
// (BindingClassDeclarationEvaluation).
bool
@@ -8491,21 +8758,26 @@ BytecodeEmitter::emitClass(ClassNode* classNode)
{
ClassNames* names = classNode->names();
ParseNode* heritageExpression = classNode->heritage();
- ListNode* classMethods = classNode->methodList();
+ ListNode* classMembers = classNode->memberList();
FunctionNode* constructor = nullptr;
- for (ParseNode* mn : classMethods->contents()) {
- ClassMethod& method = mn->as<ClassMethod>();
- ParseNode& methodName = method.name();
- if (!method.isStatic() &&
- (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) &&
- methodName.as<NameNode>().atom() == cx->names().constructor)
- {
- constructor = &method.method();
- break;
+ for (ParseNode* mn : classMembers->contents()) {
+ if (mn->is<ClassMethod>()) {
+ ClassMethod& method = mn->as<ClassMethod>();
+ ParseNode& methodName = method.name();
+ if (!method.isStatic() &&
+ (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) &&
+ methodName.as<NameNode>().atom() == cx->names().constructor)
+ {
+ constructor = &method.method();
+ break;
+ }
}
}
+ // set this->fieldInitializers_
+ AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers));
+
bool savedStrictness = sc->setLocalStrictMode(true);
Maybe<TDZCheckCache> tdzCache;
@@ -8577,7 +8849,7 @@ BytecodeEmitter::emitClass(ClassNode* classNode)
return false;
RootedPlainObject obj(cx);
- if (!emitPropertyList(classMethods, &obj, ClassBody))
+ if (!emitPropertyList(classMembers, &obj, ClassBody))
return false;
if (!emit1(JSOP_POP))
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 9cbf6ee38b..27f4e92cbd 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -177,6 +177,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
EmitterScope* innermostEmitterScope_;
TDZCheckCache* innermostTDZCheckCache;
+ /* field info for enclosing class */
+ FieldInitializers fieldInitializers_;
+
#ifdef DEBUG
bool unstableEmitterScope;
@@ -415,6 +418,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// encompasses the entire source.
MOZ_MUST_USE bool emitScript(ParseNode* body);
+ MOZ_MUST_USE bool emitInitializeInstanceFields();
+
// Emit function code for the tree rooted at body.
MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode);
@@ -514,6 +519,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp,
PropListType type);
+ FieldInitializers setupFieldInitializers(ListNode* classMembers);
+ MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj);
+ MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj);
+
// To catch accidental misuse, emitUint16Operand/emit3 assert that they are
// not used to unconditionally emit JSOP_GETLOCAL. Variable access should
// instead be emitted using EmitVarOp. In special cases, when the caller
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index 35e4b86e0b..eda40a0836 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -377,6 +377,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_OPTELEM:
case PNK_OPTCALL:
case PNK_NAME:
+ case PNK_PRIVATE_NAME:
case PNK_TEMPLATE_STRING:
case PNK_TEMPLATE_STRING_LIST:
case PNK_TAGGED_TEMPLATE:
@@ -400,8 +401,8 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_FORIN:
case PNK_FOROF:
case PNK_FORHEAD:
- case PNK_CLASSMETHOD:
- case PNK_CLASSMETHODLIST:
+ case PNK_CLASSFIELD:
+ case PNK_CLASSMEMBERLIST:
case PNK_CLASSNAMES:
case PNK_NEWTARGET:
case PNK_IMPORT_META:
@@ -1679,6 +1680,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
return true;
case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_PRIVATE_NAME:
case PNK_STRING:
case PNK_TEMPLATE_STRING:
MOZ_ASSERT(pn->is<NameNode>());
@@ -1810,7 +1812,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_OBJECT:
case PNK_ARRAYCOMP:
case PNK_STATEMENTLIST:
- case PNK_CLASSMETHODLIST:
+ case PNK_CLASSMEMBERLIST:
case PNK_CATCHLIST:
case PNK_TEMPLATE_STRING_LIST:
case PNK_VAR:
@@ -1902,6 +1904,16 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda);
}
+ case PNK_CLASSFIELD: {
+ ClassField* node = &pn->as<ClassField>();
+ if (node->initializer()) {
+ if (!Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
case PNK_NEWTARGET:
case PNK_IMPORT_META:{
#ifdef DEBUG
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
index a9f7c6de09..3f52e88d54 100644
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -367,11 +367,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return literal;
}
- ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) {
- return new_<ClassNode>(name, heritage, methodBlock, pos);
+ ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) {
+ return new_<ClassNode>(name, heritage, memberBlock, pos);
}
- ListNodeType newClassMethodList(uint32_t begin) {
- return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1));
+ ListNodeType newClassMemberList(uint32_t begin) {
+ return new_<ListNode>(PNK_CLASSMEMBERLIST, TokenPos(begin, begin + 1));
}
ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) {
return new_<ClassNames>(outer, inner, pos);
@@ -457,19 +457,28 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return true;
}
- MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key, FunctionNodeType funNode,
+ MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, FunctionNodeType funNode,
JSOp op, bool isStatic)
{
- MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
- MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
- key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
- key->isKind(PNK_STRING) ||
- key->isKind(PNK_COMPUTED_NAME));
+ MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST));
+ MOZ_ASSERT(isUsableAsObjectPropertyName(key));
ClassMethod* classMethod = new_<ClassMethod>(key, funNode, op, isStatic);
if (!classMethod)
return false;
- methodList->append(classMethod);
+ memberList->append(classMethod);
+ return true;
+ }
+
+ MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer)
+ {
+ MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST));
+ MOZ_ASSERT(isUsableAsObjectPropertyName(name));
+
+ ParseNode* classField = new_<ClassField>(name, initializer);
+ if (!classField)
+ return false;
+ memberList->append(classField);
return true;
}
@@ -732,8 +741,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
pn->setDirectRHSAnonFunction(true);
}
- FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) {
- return new_<FunctionNode>(syntaxKind, pos());
+ FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) {
+ return new_<FunctionNode>(syntaxKind, pos);
}
bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) {
@@ -819,6 +828,13 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return node->isKind(PNK_SUPERBASE);
}
+ bool isUsableAsObjectPropertyName(ParseNode* node) {
+ return node->isKind(PNK_NUMBER) ||
+ node->isKind(PNK_OBJECT_PROPERTY_NAME) ||
+ node->isKind(PNK_STRING) ||
+ node->isKind(PNK_COMPUTED_NAME);
+ }
+
inline MOZ_MUST_USE bool finishInitializerAssignment(NameNodeType nameNode, Node init);
void setBeginPosition(Node pn, Node oth) {
@@ -853,9 +869,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return new_<ListNode>(kind, op, pos());
}
- ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
+ ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) {
MOZ_ASSERT(!isDeclarationKind(kind));
- return new_<ListNode>(kind, op, TokenPos(begin, begin + 1));
+ return new_<ListNode>(kind, op, pos);
}
ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp
index ed3361c33a..6ddb554ad7 100644
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -83,6 +83,7 @@ class NameResolver
}
case PNK_NAME:
+ case PNK_PRIVATE_NAME:
*foundName = true;
return buf->append(n->as<NameNode>().atom());
@@ -136,6 +137,7 @@ class NameResolver
return cur;
switch (cur->getKind()) {
+ case PNK_PRIVATE_NAME:
case PNK_NAME: return cur; /* found the initialized declaration */
case PNK_THIS: return cur; /* Setting a property of 'this'. */
case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */
@@ -404,6 +406,7 @@ class NameResolver
break;
case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_PRIVATE_NAME:
case PNK_STRING:
case PNK_TEMPLATE_STRING:
MOZ_ASSERT(cur->is<NameNode>());
@@ -498,6 +501,19 @@ class NameResolver
break;
}
+ case PNK_CLASSFIELD: {
+ ClassField* node = &cur->as<ClassField>();
+ if (!resolve(&node->name(), prefix)) {
+ return false;
+ }
+ if (ParseNode* init = node->initializer()) {
+ if (!resolve(init, prefix)) {
+ return false;
+ }
+ }
+ break;
+ }
+
case PNK_ELEM: {
PropertyByValue* elem = &cur->as<PropertyByValue>();
if (!elem->isSuper() && !resolve(&elem->expression(), prefix))
@@ -643,7 +659,7 @@ class NameResolver
if (!resolve(heritage, prefix))
return false;
}
- if (!resolve(classNode->methodList(), prefix))
+ if (!resolve(classNode->memberList(), prefix))
return false;
break;
}
@@ -757,7 +773,7 @@ class NameResolver
}
case PNK_OBJECT:
- case PNK_CLASSMETHODLIST:
+ case PNK_CLASSMEMBERLIST:
for (ParseNode* element : cur->as<ListNode>().contents()) {
if (!resolve(element, prefix))
return false;
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index a19bdfc6eb..00bbd7afee 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -370,6 +370,14 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
return PushResult::Recyclable;
}
+ case PNK_CLASSFIELD: {
+ BinaryNode* bn = &pn->as<BinaryNode>();
+ stack->push(bn->left());
+ if (bn->right())
+ stack->push(bn->right());
+ return PushResult::Recyclable;
+ }
+
// Ternary nodes with all children non-null.
case PNK_CONDITIONAL: {
TernaryNode* tn = &pn->as<TernaryNode>();
@@ -494,7 +502,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_IMPORT_SPEC_LIST:
case PNK_EXPORT_SPEC_LIST:
case PNK_PARAMSBODY:
- case PNK_CLASSMETHODLIST:
+ case PNK_CLASSMEMBERLIST:
return PushListNodeChildren(&pn->as<ListNode>(), stack);
// Array comprehension nodes are lists with a single child:
@@ -881,6 +889,7 @@ NameNode::dump(int indent)
}
case PNK_NAME:
+ case PNK_PRIVATE_NAME: // atom() already includes the '#', no need to specially include it.
case PNK_PROPERTYNAME: {
if (!atom()) {
fprintf(stderr, "#<null name>");
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 2f6311eace..fffda6a73d 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -49,6 +49,7 @@ class ObjectBox;
F(CALL) \
F(ARGUMENTS) \
F(NAME) \
+ F(PRIVATE_NAME) \
F(OBJECT_PROPERTY_NAME) \
F(COMPUTED_NAME) \
F(NUMBER) \
@@ -116,7 +117,8 @@ class ObjectBox;
F(MUTATEPROTO) \
F(CLASS) \
F(CLASSMETHOD) \
- F(CLASSMETHODLIST) \
+ F(CLASSFIELD) \
+ F(CLASSMEMBERLIST) \
F(CLASSNAMES) \
F(NEWTARGET) \
F(POSHOLDER) \
@@ -251,19 +253,22 @@ IsTypeofKind(ParseNodeKind kind)
* kid1: PNK_CLASSNAMES for class name. can be null for anonymous class.
* kid2: expression after `extends`. null if no expression
* kid3: either of
- * * PNK_CLASSMETHODLIST, if anonymous class
- * * PNK_LEXICALSCOPE which contains PNK_CLASSMETHODLIST as scopeBody,
+ * * PNK_CLASSMEMBERLIST, if anonymous class
+ * * PNK_LEXICALSCOPE which contains PNK_CLASSMEMBERLIST as scopeBody,
* if named class
* PNK_CLASSNAMES (ClassNames)
* left: Name node for outer binding, or null if the class is an expression
* that doesn't create an outer binding
* right: Name node for inner binding
- * PNK_CLASSMETHODLIST (ListNode)
- * head: list of N PNK_CLASSMETHOD nodes
+ * PNK_CLASSMEMBERLIST (ListNode)
+ * head: list of N PNK_CLASSMETHOD or PNK_CLASSFIELD nodes
* count: N >= 0
* PNK_CLASSMETHOD (ClassMethod)
* name: propertyName
* method: methodDefinition
+ * PNK_CLASSFIELD (ClassField)
+ * name: fieldName
+ * initializer: field initializer or null
* PNK_MODULE (ModuleNode)
* body: statement list of the module
*
@@ -567,6 +572,7 @@ enum ParseNodeArity
macro(AssignmentNode, AssignmentNodeType, asAssignment) \
macro(CaseClause, CaseClauseType, asCaseClause) \
macro(ClassMethod, ClassMethodType, asClassMethod) \
+ macro(ClassField, ClassFieldType, asClassField) \
macro(ClassNames, ClassNamesType, asClassNames) \
macro(ForNode, ForNodeType, asFor) \
macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \
@@ -780,6 +786,12 @@ class ParseNode
} name;
struct {
private:
+ friend class ClassField;
+ ParseNode* name;
+ ParseNode* initializer; /* field initializer - optional */
+ } field;
+ struct {
+ private:
friend class RegExpLiteral;
ObjectBox* objbox;
} regexp;
@@ -1233,7 +1245,7 @@ class ListNode : public ParseNode
MOZ_MUST_USE bool hasNonConstInitializer() const {
MOZ_ASSERT(isKind(PNK_ARRAY) ||
isKind(PNK_OBJECT) ||
- isKind(PNK_CLASSMETHODLIST));
+ isKind(PNK_CLASSMEMBERLIST));
return pn_u.list.xflags & hasNonConstInitializerBit;
}
@@ -1250,7 +1262,7 @@ class ListNode : public ParseNode
void setHasNonConstInitializer() {
MOZ_ASSERT(isKind(PNK_ARRAY) ||
isKind(PNK_OBJECT) ||
- isKind(PNK_CLASSMETHODLIST));
+ isKind(PNK_CLASSMEMBERLIST));
pn_u.list.xflags |= hasNonConstInitializerBit;
}
@@ -1874,9 +1886,9 @@ class NullLiteral : public NullaryNode
}
};
-// This is only used internally, currently just for tagged templates.
-// It represents the value 'undefined' (aka `void 0`), like NullLiteral
-// represents the value 'null'.
+// This is only used internally, currently just for tagged templates and the
+// initial value of fields without initializers. It represents the value
+// 'undefined' (aka `void 0`), like NullLiteral represents the value 'null'.
class RawUndefinedLiteral : public NullaryNode
{
public:
@@ -2124,6 +2136,30 @@ class ClassMethod : public BinaryNode
}
};
+
+class ClassField : public BinaryNode
+{
+ public:
+ ClassField(ParseNode* name, ParseNode* initializer)
+ : BinaryNode(PNK_CLASSFIELD, JSOP_NOP,
+ initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos),
+ name, initializer)
+ {
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CLASSFIELD);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+
+ ParseNode& name() const { return *left(); }
+
+ FunctionNode* initializer() const {
+ return right() ? &right()->as<FunctionNode>() : nullptr;
+ }
+};
+
class SwitchStatement : public BinaryNode
{
public:
@@ -2207,13 +2243,13 @@ class ClassNames : public BinaryNode
class ClassNode : public TernaryNode
{
public:
- ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock,
+ ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* membersOrBlock,
const TokenPos& pos)
- : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos)
+ : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, membersOrBlock, pos)
{
MOZ_ASSERT_IF(names, names->is<ClassNames>());
- MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() ||
- methodsOrBlock->isKind(PNK_CLASSMETHODLIST));
+ MOZ_ASSERT(membersOrBlock->is<LexicalScopeNode>() ||
+ membersOrBlock->isKind(PNK_CLASSMEMBERLIST));
}
static bool test(const ParseNode& node) {
@@ -2228,13 +2264,13 @@ class ClassNode : public TernaryNode
ParseNode* heritage() const {
return kid2();
}
- ListNode* methodList() const {
- ParseNode* methodsOrBlock = kid3();
- if (methodsOrBlock->isKind(PNK_CLASSMETHODLIST))
- return &methodsOrBlock->as<ListNode>();
+ ListNode* memberList() const {
+ ParseNode* membersOrBlock = kid3();
+ if (membersOrBlock->isKind(PNK_CLASSMEMBERLIST))
+ return &membersOrBlock->as<ListNode>();
- ListNode* list = &methodsOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
- MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST));
+ ListNode* list = &membersOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
+ MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST));
return list;
}
Handle<LexicalScope::Data*> scopeBindings() const {
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index a4d761a2a1..77fa337fb7 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -514,6 +514,7 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt
allowNewTarget_ = sc->allowNewTarget();
allowSuperProperty_ = sc->allowSuperProperty();
allowSuperCall_ = sc->allowSuperCall();
+ allowArguments_ = sc->allowArguments();
needsThisTDZChecks_ = sc->needsThisTDZChecks();
thisBinding_ = sc->thisBinding();
} else {
@@ -550,6 +551,16 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt
}
void
+FunctionBox::initFieldInitializer(ParseContext* enclosing, bool hasHeritage)
+{
+ this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Expression);
+ allowSuperProperty_ = false;
+ allowSuperCall_ = false;
+ allowArguments_ = false;
+ needsThisTDZChecks_ = hasHeritage;
+}
+
+void
FunctionBox::initWithEnclosingScope(Scope* enclosingScope)
{
if (!function()->isArrow()) {
@@ -2294,7 +2305,7 @@ Parser<ParseHandler>::hasUsedFunctionSpecialName(HandlePropertyName name)
template <typename ParseHandler>
bool
-Parser<ParseHandler>::declareFunctionThis()
+Parser<ParseHandler>::declareFunctionThis(bool canSkipLazyClosedOverBindings)
{
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
@@ -2307,10 +2318,11 @@ Parser<ParseHandler>::declareFunctionThis()
HandlePropertyName dotThis = context->names().dotThis;
bool declareThis;
- if (handler.canSkipLazyClosedOverBindings())
+ if (canSkipLazyClosedOverBindings)
declareThis = funbox->function()->lazyScript()->hasThisBinding();
else
- declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor();
+ declareThis = hasUsedFunctionSpecialName(dotThis) ||
+ funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor;
if (declareThis) {
ParseContext::Scope& funScope = pc->functionScope();
@@ -2539,7 +2551,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun,
tokenStream.ungetToken();
}
- FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement);
+ FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos());
if (!funNode)
return null();
@@ -2585,7 +2597,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun,
template <typename ParseHandler>
bool
-Parser<ParseHandler>::declareFunctionArgumentsObject()
+Parser<ParseHandler>::declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings)
{
FunctionBox* funbox = pc->functionBox();
ParseContext::Scope& funScope = pc->functionScope();
@@ -2597,7 +2609,7 @@ Parser<ParseHandler>::declareFunctionArgumentsObject()
HandlePropertyName argumentsName = context->names().arguments;
bool tryDeclareArguments;
- if (handler.canSkipLazyClosedOverBindings())
+ if (canSkipLazyClosedOverBindings)
tryDeclareArguments = funbox->function()->lazyScript()->shouldDeclareArguments();
else
tryDeclareArguments = hasUsedFunctionSpecialName(argumentsName);
@@ -2672,6 +2684,10 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan
uint32_t startYieldOffset = pc->lastYieldOffset;
#endif
+ // One might expect noteUsedName(".initializers") here when parsing a
+ // constructor. See Parser<ParseHandler>::classDefinition on why
+ // it's not here.
+
Node body;
if (type == StatementListBody) {
bool inheritedStrict = pc->sc()->strict();
@@ -2756,9 +2772,10 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan
// finishing up the scope so these special bindings get marked as closed
// over if necessary. Arrow functions don't have these bindings.
if (kind != FunctionSyntaxKind::Arrow) {
- if (!declareFunctionArgumentsObject())
+ bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings();
+ if (!declareFunctionArgumentsObject(canSkipLazyClosedOverBindings))
return null();
- if (!declareFunctionThis())
+ if (!declareFunctionThis(canSkipLazyClosedOverBindings))
return null();
}
@@ -2769,7 +2786,7 @@ template <typename ParseHandler>
JSFunction*
Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
- HandleObject proto)
+ HandleObject proto /* = nullptr */)
{
MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, atom != nullptr);
@@ -3594,7 +3611,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict
syntaxKind = FunctionSyntaxKind::Arrow;
}
- FunctionNodeType funNode = handler.newFunction(syntaxKind);
+ FunctionNodeType funNode = handler.newFunction(syntaxKind, pos());
if (!funNode)
return null();
@@ -3878,7 +3895,7 @@ Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHa
return null();
}
- FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement);
+ FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos());
if (!funNode)
return null();
@@ -3918,7 +3935,7 @@ Parser<ParseHandler>::functionExpr(uint32_t toStringStart, InvokedPrediction inv
tokenStream.ungetToken();
}
- FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression);
+ FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, pos());
if (!funNode)
return null();
@@ -4442,7 +4459,7 @@ Parser<ParseHandler>::objectBindingPattern(DeclarationKind kind, YieldHandling y
TokenPos namePos = tokenStream.nextToken().pos;
PropertyType propType;
- Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
+ Node propName = propertyName(yieldHandling, PropertyNameInPattern, declKind, literal, &propType, &propAtom);
if (!propName)
return null();
if (propType == PropertyType::Normal) {
@@ -7378,6 +7395,224 @@ JSOpFromPropertyType(PropertyType propType)
}
template <typename ParseHandler>
+bool
+Parser<ParseHandler>::classMember(YieldHandling yieldHandling, DefaultHandling defaultHandling,
+ const ParseContext::ClassStatement& classStmt,
+ HandlePropertyName className,
+ uint32_t classStartOffset, bool hasHeritage,
+ size_t& numFields, size_t& numFieldKeys,
+ ListNodeType& classMembers, bool* done)
+{
+ *done = false;
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return false;
+
+ if (tt == TOK_RC) {
+ *done = true;
+ return true;
+ }
+
+ if (tt == TOK_SEMI)
+ return true;
+
+ bool isStatic = false;
+ if (tt == TOK_STATIC) {
+ if (!tokenStream.peekToken(&tt))
+ return false;
+ if (tt == TOK_RC) {
+ tokenStream.consumeKnownToken(tt);
+ error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt));
+ return false;
+ }
+
+ if (tt != TOK_LP) {
+ isStatic = true;
+ } else {
+ tokenStream.ungetToken();
+ }
+ } else {
+ tokenStream.ungetToken();
+ }
+
+ uint32_t propNameOffset;
+ if (!tokenStream.peekOffset(&propNameOffset))
+ return false;
+
+ RootedAtom propAtom(context);
+ PropertyType propType;
+ Node propName = propertyName(yieldHandling, PropertyNameInClass, /* maybeDecl = */ Nothing(),
+ classMembers, &propType, &propAtom);
+ if (!propName)
+ return false;
+
+ if (propType == PropertyType::Field) {
+ if (!options().fieldsEnabledOption) {
+ errorAt(propNameOffset, JSMSG_FIELDS_NOT_SUPPORTED);
+ return false;
+ }
+
+ if (isStatic) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ if (propAtom == context->names().constructor) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ if (!abortIfSyntaxParser())
+ return false;
+
+ numFields++;
+
+ FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, propName,
+ propAtom, numFieldKeys);
+ if (!initializer)
+ return false;
+
+ if (!tokenStream.getToken(&tt)) {
+ return false;
+ }
+
+ // TODO(khyperia): Implement ASI
+ if (tt != TOK_SEMI) {
+ error(JSMSG_MISSING_SEMI_FIELD);
+ return false;
+ }
+
+ return handler.addClassFieldDefinition(classMembers, propName, initializer);
+ }
+
+ if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
+ propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
+ propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod &&
+ propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor)
+ {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ if (propType == PropertyType::Getter)
+ propType = PropertyType::GetterNoExpressionClosure;
+ if (propType == PropertyType::Setter)
+ propType = PropertyType::SetterNoExpressionClosure;
+
+ bool isConstructor = !isStatic && propAtom == context->names().constructor;
+ if (isConstructor) {
+ if (propType != PropertyType::Method) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+ if (classStmt.constructorBox) {
+ errorAt(propNameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor");
+ return false;
+ }
+ propType = hasHeritage ? PropertyType::DerivedConstructor
+ : PropertyType::Constructor;
+ } else if (isStatic && propAtom == context->names().prototype) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ RootedAtom funName(context);
+ switch (propType) {
+ case PropertyType::GetterNoExpressionClosure:
+ case PropertyType::SetterNoExpressionClosure:
+ if (!tokenStream.isCurrentTokenType(TOK_RB)) {
+ funName = prefixAccessorName(propType, propAtom);
+ if (!funName)
+ return false;
+ }
+ break;
+ case PropertyType::Constructor:
+ case PropertyType::DerivedConstructor:
+ funName = className;
+ break;
+ default:
+ if (!tokenStream.isCurrentTokenType(TOK_RB))
+ funName = propAtom;
+ }
+
+ // Calling toString on constructors need to return the source text for
+ // the entire class. The end offset is unknown at this point in
+ // parsing and will be amended when class parsing finishes below.
+ FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : propNameOffset,
+ propType, funName);
+ if (!funNode)
+ return false;
+
+ handler.checkAndSetIsDirectRHSAnonFunction(funNode);
+
+ JSOp op = JSOpFromPropertyType(propType);
+ return handler.addClassMethodDefinition(classMembers, propName, funNode, op, isStatic);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::finishClassConstructor(const ParseContext::ClassStatement& classStmt,
+ HandlePropertyName className, uint32_t classStartOffset,
+ uint32_t classEndOffset, size_t numFields,
+ ListNodeType& classMembers)
+{
+ // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or
+ // JSOP_DERIVEDCONSTRUCTOR due to needing to emit calls to the field
+ // initializers in the constructor. So, synthesize a new one.
+ if (classStmt.constructorBox == nullptr && numFields > 0) {
+ // synthesizeConstructor assigns to classStmt.constructorBox
+ FunctionNodeType synthesizedCtor = synthesizeConstructor(className, classStartOffset);
+ if (!synthesizedCtor) {
+ return false;
+ }
+
+ MOZ_ASSERT(classStmt.constructorBox != nullptr);
+
+ // Note: the *function* has the name of the class, but the *property*
+ // containing the function has the name "constructor"
+ Node constructorNameNode = handler.newObjectLiteralPropertyName(context->names().constructor, pos());
+ if (!constructorNameNode) {
+ return false;
+ }
+
+ if (!handler.addClassMethodDefinition(classMembers, constructorNameNode,
+ synthesizedCtor, JSOP_INITPROP,
+ /* isStatic = */ false)) {
+ return false;
+ }
+ }
+
+ if (FunctionBox* ctorbox = classStmt.constructorBox) {
+ // Amend the toStringEnd offset for the constructor now that we've
+ // finished parsing the class.
+ ctorbox->toStringEnd = classEndOffset;
+
+ if (numFields > 0) {
+ // Field initialization need access to `this`.
+ ctorbox->setHasThisBinding();
+ }
+
+ // Set the same information, but on the lazyScript.
+ if (ctorbox->function()->isInterpretedLazy()) {
+ ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset);
+
+ if (numFields > 0) {
+ ctorbox->function()->lazyScript()->setHasThisBinding();
+ }
+
+ // Field initializers can be retrieved if the class and constructor are
+ // being compiled at the same time, but we need to stash the field
+ // information if the constructor is being compiled lazily.
+ FieldInitializers fieldInfo(numFields);
+ ctorbox->function()->lazyScript()->setFieldInitializers(fieldInfo);
+ }
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
typename ParseHandler::ClassNodeType
Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
@@ -7392,14 +7627,14 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
if (!tokenStream.getToken(&tt))
return null();
- RootedPropertyName name(context);
+ RootedPropertyName className(context);
if (TokenKindIsPossibleIdentifier(tt)) {
- name = bindingIdentifier(yieldHandling);
- if (!name)
+ className = bindingIdentifier(yieldHandling);
+ if (!className)
return null();
} else if (classContext == ClassStatement) {
if (defaultHandling == AllowDefaultName) {
- name = context->names().starDefaultStar;
+ className = context->names().starDefaultStar;
tokenStream.ungetToken();
} else {
// Class statements must have a bound name
@@ -7411,188 +7646,122 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
tokenStream.ungetToken();
}
- // Push a ParseContext::ClassStatement to keep track of the constructor
- // funbox.
- ParseContext::ClassStatement classStmt(pc);
-
- RootedAtom propAtom(context);
-
- // A named class creates a new lexical scope with a const binding of the
- // class name for the "inner name".
- Maybe<ParseContext::Statement> innerScopeStmt;
- Maybe<ParseContext::Scope> innerScope;
- if (name) {
- innerScopeStmt.emplace(pc, StatementKind::Block);
- innerScope.emplace(this);
- if (!innerScope->init(pc))
- return null();
- }
-
// Because the binding definitions keep track of their blockId, we need to
// create at least the inner binding later. Keep track of the name's position
// in order to provide it for the nodes created later.
TokenPos namePos = pos();
+ // Push a ParseContext::ClassStatement to keep track of the constructor
+ // funbox.
+ ParseContext::ClassStatement classStmt(pc);
+
+ NameNodeType innerName;
+ Node nameNode = null();
Node classHeritage = null();
- bool hasHeritage;
- if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
- return null();
- if (hasHeritage) {
- if (!tokenStream.getToken(&tt))
- return null();
- classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt);
- if (!classHeritage)
+ LexicalScopeNodeType classBlock = null();
+ uint32_t classEndOffset;
+ {
+ // A named class creates a new lexical scope with a const binding of the
+ // class name for the "inner name".
+ ParseContext::Statement innerScopeStmt(pc, StatementKind::Block);
+ ParseContext::Scope innerScope(this);
+ if (!innerScope.init(pc))
return null();
- }
- if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) {
- return null();
- }
-
- ListNodeType classMethods = handler.newClassMethodList(pos().begin);
- if (!classMethods)
- return null();
-
- Maybe<DeclarationKind> declKind = Nothing();
- for (;;) {
- TokenKind tt;
- if (!tokenStream.getToken(&tt))
+ bool hasHeritage;
+ if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
return null();
- if (tt == TOK_RC)
- break;
-
- if (tt == TOK_SEMI)
- continue;
-
- bool isStatic = false;
- if (tt == TOK_STATIC) {
- if (!tokenStream.peekToken(&tt))
+ if (hasHeritage) {
+ if (!tokenStream.getToken(&tt))
return null();
- if (tt == TOK_RC) {
- tokenStream.consumeKnownToken(tt);
- error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt));
+ classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt);
+ if (!classHeritage)
return null();
- }
-
- if (tt != TOK_LP) {
- isStatic = true;
- } else {
- tokenStream.ungetToken();
- }
- } else {
- tokenStream.ungetToken();
}
- uint32_t nameOffset;
- if (!tokenStream.peekOffset(&nameOffset))
- return null();
-
- PropertyType propType;
- Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom);
- if (!propName)
+ if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) {
return null();
+ }
- if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
- propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
- propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod &&
- propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor)
- {
- errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
+ ListNodeType classMembers = handler.newClassMemberList(pos().begin);
+ if (!classMembers)
return null();
- }
- if (propType == PropertyType::Getter)
- propType = PropertyType::GetterNoExpressionClosure;
- if (propType == PropertyType::Setter)
- propType = PropertyType::SetterNoExpressionClosure;
+ size_t numFields = 0;
+ size_t numFieldKeys = 0;
+ for (;;) {
+ bool done;
+ if (!classMember(yieldHandling, defaultHandling, classStmt, className,
+ classStartOffset, hasHeritage, numFields, numFieldKeys,
+ classMembers, &done))
+ return null();
+ if (done)
+ break;
+ }
- bool isConstructor = !isStatic && propAtom == context->names().constructor;
- if (isConstructor) {
- if (propType != PropertyType::Method) {
- errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
+ if (numFields > 0) {
+ // .initializers is always closed over by the constructor when there are
+ // fields with initializers. However, there's some strange circumstances
+ // which prevents us from using the normal noteUsedName() system. We
+ // cannot call noteUsedName(".initializers") when parsing the constructor,
+ // because .initializers should be marked as used *only if* there are
+ // fields with initializers. Even if we haven't seen any fields yet,
+ // there may be fields after the constructor.
+ // Consider the following class:
+ //
+ // class C {
+ // constructor() {
+ // // do we noteUsedName(".initializers") here?
+ // }
+ // // ... because there might be some fields down here.
+ // }
+ //
+ // So, instead, at the end of class parsing (where we are now), we do some
+ // tricks to pretend that noteUsedName(".initializers") was called in the
+ // constructor.
+ if (!usedNames.markAsAlwaysClosedOver(context, context->names().dotInitializers,
+ pc->scriptId(),
+ pc->innermostScope()->id()))
return null();
- }
- if (classStmt.constructorBox) {
- errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor");
+ if (!noteDeclaredName(context->names().dotInitializers,
+ DeclarationKind::Var, namePos))
return null();
- }
- propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor;
- } else if (isStatic && propAtom == context->names().prototype) {
- errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
- return null();
}
- RootedAtom funName(context);
- switch (propType) {
- case PropertyType::GetterNoExpressionClosure:
- case PropertyType::SetterNoExpressionClosure:
- if (!tokenStream.isCurrentTokenType(TOK_RB)) {
- funName = prefixAccessorName(propType, propAtom);
- if (!funName)
- return null();
- }
- break;
- case PropertyType::Constructor:
- case PropertyType::DerivedConstructor:
- funName = name;
- break;
- default:
- if (!tokenStream.isCurrentTokenType(TOK_RB))
- funName = propAtom;
+ if (numFieldKeys > 0) {
+ if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos))
+ return null();
}
-
- // Calling toString on constructors need to return the source text for
- // the entire class. The end offset is unknown at this point in
- // parsing and will be amended when class parsing finishes below.
- FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : nameOffset,
- propType, funName);
- if (!funNode)
- return null();
-
- handler.checkAndSetIsDirectRHSAnonFunction(funNode);
-
- JSOp op = JSOpFromPropertyType(propType);
- if (!handler.addClassMethodDefinition(classMethods, propName, funNode, op, isStatic))
+ classEndOffset = pos().end;
+ if (!finishClassConstructor(classStmt, className, classStartOffset,
+ classEndOffset, numFields, classMembers))
return null();
- }
-
- // Amend the toStringEnd offset for the constructor now that we've
- // finished parsing the class.
- uint32_t classEndOffset = pos().end;
- if (FunctionBox* ctorbox = classStmt.constructorBox) {
- if (ctorbox->function()->isInterpretedLazy())
- ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset);
- ctorbox->toStringEnd = classEndOffset;
- }
- Node nameNode = null();
- Node methodsOrBlock = classMethods;
- if (name) {
- // The inner name is immutable.
- if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
- return null();
+ if (className) {
+ // The inner name is immutable.
+ if (!noteDeclaredName(className, DeclarationKind::Const, namePos))
+ return null();
- NameNodeType innerName = newName(name, namePos);
- if (!innerName)
- return null();
+ innerName = newName(className, namePos);
+ if (!innerName)
+ return null();
+ }
- LexicalScopeNodeType classBlock = finishLexicalScope(*innerScope, classMethods);
+ classBlock = finishLexicalScope(innerScope, classMembers);
if (!classBlock)
return null();
- methodsOrBlock = classBlock;
-
// Pop the inner scope.
- innerScope.reset();
- innerScopeStmt.reset();
+ }
+ if (className) {
NameNodeType outerName = null();
if (classContext == ClassStatement) {
// The outer name is mutable.
- if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
+ if (!noteDeclaredName(className, DeclarationKind::Let, namePos))
return null();
- outerName = newName(name, namePos);
+ outerName = newName(className, namePos);
if (!outerName)
return null();
}
@@ -7604,11 +7773,265 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
- return handler.newClass(nameNode, classHeritage, methodsOrBlock,
+ return handler.newClass(nameNode, classHeritage, classBlock,
TokenPos(classStartOffset, classEndOffset));
}
template <class ParseHandler>
+typename ParseHandler::FunctionNodeType
+Parser<ParseHandler>::synthesizeConstructor(HandleAtom className, uint32_t classNameOffset)
+{
+ FunctionSyntaxKind functionSyntaxKind = FunctionSyntaxKind::ClassConstructor;
+
+ // Create the function object.
+ RootedFunction fun(context, newFunction(className, functionSyntaxKind,
+ GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction));
+ if (!fun)
+ return null();
+
+ // Create the top-level field initializer node.
+ FunctionNodeType funNode = handler.newFunction(functionSyntaxKind, pos());
+ if (!funNode)
+ return null();
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox = newFunctionBox(funNode, fun, classNameOffset,
+ directives, GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction, false);
+ if (!funbox)
+ return null();
+ funbox->initWithEnclosingParseContext(pc, functionSyntaxKind);
+ handler.setFunctionBox(funNode, funbox);
+ funbox->setEnd(pos().end);
+
+ // Push a ParseContext on to the stack.
+ ParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init())
+ return null();
+
+ TokenPos synthesizedBodyPos = TokenPos(classNameOffset, classNameOffset + 1);
+ // Create a ListNode for the parameters + body (there are no parameters).
+ ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, synthesizedBodyPos);
+ if (!argsbody)
+ return null();
+ handler.setFunctionFormalParametersAndBody(funNode, argsbody);
+ funbox->function()->setArgCount(0);
+ funbox->setStart(tokenStream);
+
+ // Push a LexicalScope on to the stack.
+ ParseContext::Scope lexicalScope(this);
+ if (!lexicalScope.init(pc))
+ return null();
+
+ auto stmtList = handler.newStatementList(synthesizedBodyPos);
+ if (!stmtList)
+ return null();
+
+ if (!noteUsedName(context->names().dotThis))
+ return null();
+
+ // One might expect a noteUsedName(".initializers") here. See comment in
+ // GeneralParser<ParseHandler, Unit>::classDefinition on why it's not here.
+
+ bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings();
+ if (!declareFunctionThis(canSkipLazyClosedOverBindings))
+ return null();
+
+ auto initializerBody = finishLexicalScope(lexicalScope, stmtList);
+ if (!initializerBody)
+ return null();
+ handler.setBeginPosition(initializerBody, stmtList);
+ handler.setEndPosition(initializerBody, stmtList);
+
+ handler.setFunctionBody(funNode, initializerBody);
+
+ if (!finishFunction())
+ return null();
+
+ // This function is asserted to set classStmt->constructorBox - however, it's
+ // not directly set in this function, but rather in
+ // initWithEnclosingParseContext.
+
+ return funNode;
+}
+
+template <class ParseHandler>
+typename ParseHandler::FunctionNodeType
+Parser<ParseHandler>::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage,
+ Node propName, HandleAtom propAtom, size_t& numFieldKeys)
+{
+ bool hasInitializer = false;
+ if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
+ return null();
+
+ TokenPos firstTokenPos;
+ if (hasInitializer) {
+ firstTokenPos = pos();
+ } else {
+ // the location of the "initializer" should be a zero-width span:
+ // class C {
+ // x /* here */ ;
+ // }
+ uint32_t endPos = pos().end;
+ firstTokenPos = TokenPos(endPos, endPos);
+ }
+
+ // Create the function object.
+ RootedFunction fun(context,
+ newFunction(propAtom, FunctionSyntaxKind::Expression,
+ GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction));
+ if (!fun)
+ return null();
+
+ // Create the top-level field initializer node.
+ FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, firstTokenPos);
+ if (!funNode)
+ return null();
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox = newFunctionBox(funNode, fun, firstTokenPos.begin, directives,
+ GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction, false);
+ if (!funbox)
+ return null();
+ funbox->initFieldInitializer(pc, hasHeritage);
+ handler.setFunctionBox(funNode, funbox);
+ funbox->setStart(tokenStream, firstTokenPos);
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc;
+ ParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init())
+ return null();
+
+ // Push a VarScope on to the stack.
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc))
+ return null();
+
+ // Push a LexicalScope on to the stack.
+ ParseContext::Scope lexicalScope(this);
+ if (!lexicalScope.init(pc))
+ return null();
+
+ Node initializerExpr;
+ TokenPos wholeInitializerPos;
+ if (hasInitializer) {
+ // Parse the expression for the field initializer.
+ {
+ AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, AwaitIsName);
+ initializerExpr = assignExpr(InAllowed, YieldIsName, TripledotProhibited);
+ if (!initializerExpr)
+ return null();
+ }
+ wholeInitializerPos = pos();
+ wholeInitializerPos.begin = firstTokenPos.begin;
+ } else {
+ initializerExpr = handler.newRawUndefinedLiteral(firstTokenPos);
+ if (!initializerExpr)
+ return null();
+ wholeInitializerPos = firstTokenPos;
+ }
+
+ // Update the end position of the parse node.
+ handler.setEndPosition(funNode, wholeInitializerPos.end);
+ funbox->setEnd(pos().end);
+
+ // Create a ListNode for the parameters + body (there are no parameters)
+ ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, wholeInitializerPos);
+ if (!argsbody)
+ return null();
+ handler.setFunctionFormalParametersAndBody(funNode, argsbody);
+ funbox->function()->setArgCount(0);
+
+ funbox->usesThis = true;
+ NameNodeType thisName = newThisName();
+ if (!thisName)
+ return null();
+
+ // Build `this.field` expression.
+ ThisLiteralType propAssignThis = handler.newThisLiteral(wholeInitializerPos, thisName);
+ if (!propAssignThis)
+ return null();
+
+ Node propAssignFieldAccess;
+ uint32_t indexValue;
+ if (!propAtom) {
+ // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what
+ // .fieldKeys means and its purpose.
+ Node dotFieldKeys = newInternalDotName(context->names().dotFieldKeys);
+ if (!dotFieldKeys)
+ return null();
+
+ double fieldKeyIndex = numFieldKeys;
+ numFieldKeys++;
+ Node fieldKeyIndexNode = handler.newNumber(fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos);
+ if (!fieldKeyIndexNode)
+ return null();
+
+ Node fieldKeyValue = handler.newPropertyByValue(dotFieldKeys, fieldKeyIndexNode, wholeInitializerPos.end);
+ if (!fieldKeyValue)
+ return null();
+
+ propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, fieldKeyValue, wholeInitializerPos.end);
+ if (!propAssignFieldAccess)
+ return null();
+ } else if (propAtom->isIndex(&indexValue)) {
+ propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, propName, wholeInitializerPos.end);
+ if (!propAssignFieldAccess)
+ return null();
+ } else {
+ NameNodeType propAssignName = handler.newPropertyName(propAtom->asPropertyName(), wholeInitializerPos);
+ if (!propAssignName)
+ return null();
+
+ propAssignFieldAccess = handler.newPropertyAccess(propAssignThis, propAssignName);
+ if (!propAssignFieldAccess)
+ return null();
+ }
+
+ // Synthesize an assignment expression for the property.
+ AssignmentNodeType initializerAssignment = handler.newAssignment(PNK_ASSIGN,
+ propAssignFieldAccess, initializerExpr,
+ JSOP_NOP);
+ if (!initializerAssignment)
+ return null();
+
+ bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings();
+ if (!declareFunctionThis(canSkipLazyClosedOverBindings))
+ return null();
+
+ UnaryNodeType exprStatement = handler.newExprStatement(initializerAssignment, wholeInitializerPos.end);
+ if (!exprStatement)
+ return null();
+
+ ListNodeType statementList = handler.newStatementList(wholeInitializerPos);
+ if (!statementList)
+ return null();
+ handler.addStatementToList(statementList, exprStatement);
+
+ // Set the function's body to the field assignment.
+ LexicalScopeNodeType initializerBody = finishLexicalScope(lexicalScope, statementList);
+ if (!initializerBody) {
+ return null();
+ }
+
+ handler.setFunctionBody(funNode, initializerBody);
+
+ if (!finishFunction())
+ return null();
+
+ if (!leaveInnerFunction(outerpc))
+ return null();
+
+ return funNode;
+}
+
+template <class ParseHandler>
bool
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
{
@@ -8543,7 +8966,7 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl
}
}
- FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow);
+ FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow, pos());
if (!funNode)
return null();
@@ -8940,7 +9363,7 @@ template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin)
{
- FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression);
+ FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression, pos());
if (!genfn)
return null();
@@ -9698,7 +10121,12 @@ Parser<ParseHandler>::checkLabelOrIdentifierReference(PropertyName* ident,
tt = hint;
}
- if (tt == TOK_NAME)
+ if (!pc->sc()->allowArguments() && ident == context->names().arguments) {
+ error(JSMSG_BAD_ARGUMENTS);
+ return false;
+ }
+
+ if (tt == TOK_NAME || tt == TOK_PRIVATE_NAME)
return true;
if (TokenKindIsContextualKeyword(tt)) {
if (tt == TOK_YIELD) {
@@ -10068,6 +10496,7 @@ DoubleToAtom(ExclusiveContext* cx, double value)
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::propertyName(YieldHandling yieldHandling,
+ PropertyNameContext propertyNameContext,
const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList,
PropertyType* propType, MutableHandleAtom propAtom)
{
@@ -10226,6 +10655,16 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling,
return propName;
}
+ if (propertyNameContext == PropertyNameInClass && (tt == TOK_SEMI || tt == TOK_ASSIGN)) {
+ if (isGenerator || isAsync) {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ tokenStream.ungetToken();
+ *propType = PropertyType::Field;
+ return propName;
+ }
+
if (TokenKindIsPossibleIdentifierName(ltok) &&
(tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN))
{
@@ -10326,7 +10765,7 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError*
TokenPos namePos = tokenStream.nextToken().pos;
PropertyType propType;
- Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
+ Node propName = propertyName(yieldHandling, PropertyNameInLiteral, declKind, literal, &propType, &propAtom);
if (!propName)
return null();
@@ -10557,7 +10996,7 @@ Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType prop
YieldHandling yieldHandling = GetYieldHandling(generatorKind);
- FunctionNodeType funNode = handler.newFunction(syntaxKind);
+ FunctionNodeType funNode = handler.newFunction(syntaxKind, pos());
if (!funNode)
return null();
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index bd111e54fe..f7feefcaab 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -583,7 +583,8 @@ enum class PropertyType {
AsyncMethod,
AsyncGeneratorMethod,
Constructor,
- DerivedConstructor
+ DerivedConstructor,
+ Field,
};
// Specify a value for an ES6 grammar parametrization. We have no enum for
@@ -720,6 +721,15 @@ class UsedNameTracker
MOZ_MUST_USE bool noteUse(ExclusiveContext* cx, JSAtom* name,
uint32_t scriptId, uint32_t scopeId);
+ MOZ_MUST_USE bool markAsAlwaysClosedOver(ExclusiveContext* cx, JSAtom* name,
+ uint32_t scriptId, uint32_t scopeId) {
+ // This marks a variable as always closed over:
+ // UsedNameInfo::noteBoundInScope only checks if scriptId and scopeId are
+ // greater than the current scriptId/scopeId, so do a simple increment to
+ // make that so.
+ return noteUse(cx, name, scriptId + 1, scopeId + 1);
+ }
+
struct RewindToken
{
private:
@@ -1171,7 +1181,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
*/
JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
- HandleObject proto);
+ HandleObject proto = nullptr);
void trace(JSTracer* trc);
@@ -1478,6 +1488,24 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
enum ClassContext { ClassStatement, ClassExpression };
ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext,
DefaultHandling defaultHandling);
+ MOZ_MUST_USE bool classMember(YieldHandling yieldHandling,
+ DefaultHandling defaultHandling,
+ const ParseContext::ClassStatement& classStmt,
+ HandlePropertyName className,
+ uint32_t classStartOffset, bool hasHeritage,
+ size_t& numFields,
+ size_t& numFieldKeys,
+ ListNodeType& classMembers, bool* done);
+ MOZ_MUST_USE bool finishClassConstructor(
+ const ParseContext::ClassStatement& classStmt,
+ HandlePropertyName className, uint32_t classStartOffset,
+ uint32_t classEndOffset, size_t numFieldsWithInitializers,
+ ListNodeType& classMembers);
+
+ FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage,
+ Node name, HandleAtom atom, size_t& numFieldKeys);
+ FunctionNodeType synthesizeConstructor(HandleAtom className,
+ uint32_t classNameOffset);
bool checkLabelOrIdentifierReference(PropertyName* ident,
uint32_t offset,
@@ -1522,8 +1550,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
bool matchInOrOf(bool* isForInp, bool* isForOfp);
bool hasUsedFunctionSpecialName(HandlePropertyName name);
- bool declareFunctionArgumentsObject();
- bool declareFunctionThis();
+ bool declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings);
+ bool declareFunctionThis(bool canSkipLazyClosedOverBindings);
NameNodeType newInternalDotName(HandlePropertyName name);
NameNodeType newThisName();
NameNodeType newDotGeneratorName();
@@ -1596,7 +1624,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(ParseContext::Scope& scope);
LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body);
+ enum PropertyNameContext { PropertyNameInLiteral, PropertyNameInPattern, PropertyNameInClass };
Node propertyName(YieldHandling yieldHandling,
+ PropertyNameContext propertyNameContext,
const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType propList,
PropertyType* propType, MutableHandleAtom propAtom);
UnaryNodeType computedPropertyName(YieldHandling yieldHandling,
diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h
index 81eb0885b0..edb2b93788 100644
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -243,6 +243,7 @@ class SharedContext
bool allowNewTarget_;
bool allowSuperProperty_;
bool allowSuperCall_;
+ bool allowArguments_;
bool inWith_;
bool needsThisTDZChecks_;
@@ -262,6 +263,7 @@ class SharedContext
allowNewTarget_(false),
allowSuperProperty_(false),
allowSuperCall_(false),
+ allowArguments_(true),
inWith_(false),
needsThisTDZChecks_(false)
{ }
@@ -286,6 +288,7 @@ class SharedContext
bool allowNewTarget() const { return allowNewTarget_; }
bool allowSuperProperty() const { return allowSuperProperty_; }
bool allowSuperCall() const { return allowSuperCall_; }
+ bool allowArguments() const { return allowArguments_; }
bool inWith() const { return inWith_; }
bool needsThisTDZChecks() const { return needsThisTDZChecks_; }
@@ -452,6 +455,7 @@ class FunctionBox : public ObjectBox, public SharedContext
void initFromLazyFunction();
void initStandaloneFunction(Scope* enclosingScope);
void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
+ void initFieldInitializer(ParseContext* enclosing, bool hasHeritage);
ObjectBox* toObjectBox() override { return this; }
JSFunction* function() const { return &object->as<JSFunction>(); }
@@ -563,7 +567,11 @@ class FunctionBox : public ObjectBox, public SharedContext
}
void setStart(const TokenStream& tokenStream) {
- bufStart = tokenStream.currentToken().pos.begin;
+ setStart(tokenStream, tokenStream.currentToken().pos);
+ }
+
+ void setStart(const TokenStream& tokenStream, const TokenPos& tokenPos) {
+ bufStart = tokenPos.begin;
tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn);
}
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
index d8bc3e4959..607cff48f3 100644
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -312,7 +312,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
Node newGenExp(Node callee, Node args) { return NodeGeneric; }
ListNodeType newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
- ListNodeType newClassMethodList(uint32_t begin) { return NodeGeneric; }
+ ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; }
ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; }
ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; }
@@ -331,7 +331,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, NameNodeType expr) { return true; }
MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; }
MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op) { return true; }
- MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; }
+ MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; }
+ MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer) { return true; }
UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; }
UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; }
UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; }
@@ -417,7 +418,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
void checkAndSetIsDirectRHSAnonFunction(Node pn) {}
- FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) { return NodeFunctionDefinition; }
+ FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { return NodeFunctionDefinition; }
bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) { return true; }
void setFunctionFormalParametersAndBody(FunctionNodeType funNode, ListNodeType paramsBody) {}
@@ -468,7 +469,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
MOZ_ASSERT(kind != PNK_CONST);
return NodeGeneric;
}
- ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
+ ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) {
return newList(kind, op);
}
ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
index 745e1b6987..f6f947f608 100644
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -71,6 +71,7 @@
macro(LP, "'('") \
macro(RP, "')'") \
macro(NAME, "identifier") \
+ macro(PRIVATE_NAME, "private identifier") \
macro(NUMBER, "numeric literal") \
macro(STRING, "string literal") \
\
@@ -322,6 +323,7 @@ inline MOZ_MUST_USE bool
TokenKindIsPossibleIdentifier(TokenKind tt)
{
return tt == TOK_NAME ||
+ tt == TOK_PRIVATE_NAME ||
TokenKindIsContextualKeyword(tt) ||
TokenKindIsStrictReservedWord(tt);
}
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index a537c5f275..e358faef09 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -93,18 +93,35 @@ FindReservedWord(const CharT* s, size_t length)
}
static const ReservedWordInfo*
-FindReservedWord(JSLinearString* str)
+FindReservedWord(JSLinearString* str, js::frontend::NameVisibility* visibility)
{
JS::AutoCheckCannotGC nogc;
- return str->hasLatin1Chars()
- ? FindReservedWord(str->latin1Chars(nogc), str->length())
- : FindReservedWord(str->twoByteChars(nogc), str->length());
+ if (str->hasLatin1Chars()) {
+ const JS::Latin1Char* chars = str->latin1Chars(nogc);
+ size_t length = str->length();
+ if (length > 0 && chars[0] == '#') {
+ *visibility = js::frontend::NameVisibility::Private;
+ return nullptr;
+ }
+ *visibility = js::frontend::NameVisibility::Public;
+ return FindReservedWord(chars, length);
+ }
+
+ const char16_t* chars = str->twoByteChars(nogc);
+ size_t length = str->length();
+ if (length > 0 && chars[0] == '#') {
+ *visibility = js::frontend::NameVisibility::Private;
+ return nullptr;
+ }
+ *visibility = js::frontend::NameVisibility::Public;
+ return FindReservedWord(chars, length);
}
template <typename CharT>
static bool
IsIdentifier(const CharT* chars, size_t length)
{
+ // Generic version for latin1 in char* and UCS-2 in char16_t*
if (length == 0)
return false;
@@ -138,15 +155,53 @@ GetSingleCodePoint(const char16_t** p, const char16_t* end)
return codePoint;
}
-static bool
-IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length)
+namespace js {
+
+namespace frontend {
+
+// Latin1 Variants
+
+bool
+IsIdentifier(const Latin1Char* chars, size_t length)
{
- if (IsIdentifier(chars, length))
- return true;
+ return ::IsIdentifier(chars, length);
+}
+bool
+IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length)
+{
if (length == 0)
return false;
+ if (char16_t(*chars) == '#') {
+ ++chars;
+ --length;
+ }
+
+ return IsIdentifier(chars, length);
+}
+
+// UTF-16 Versions
+
+bool
+IsIdentifier(const char16_t* chars, size_t length)
+{
+ return ::IsIdentifier(chars, length);
+}
+
+bool
+IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length)
+{
+ if (length == 0)
+ return false;
+ // XXX Revisit if this is still faster.
+ // Assumption is that iterating the string twice in the rare worst case (not a valid UCS-2
+ // identifier, but valid in UTF-16) is on average better than parsing UTF-16 code points
+ // individually for every input.
+ if (IsIdentifier(chars, length)) {
+ return true;
+ }
+
const char16_t* p = chars;
const char16_t* end = chars + length;
uint32_t codePoint;
@@ -165,55 +220,74 @@ IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length)
}
bool
-frontend::IsIdentifier(JSLinearString* str)
+IsIdentifierNameOrPrivateNameMaybeNonBMP(const char16_t* chars, size_t length)
{
- JS::AutoCheckCannotGC nogc;
- return str->hasLatin1Chars()
- ? ::IsIdentifier(str->latin1Chars(nogc), str->length())
- : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length());
+ if (length == 0)
+ return false;
+
+ // '#' is always just one character in either UCS-2 or UTF-16, so compare it directly.
+ if (char16_t(*chars) == '#') {
+ ++chars;
+ --length;
+ }
+
+ return IsIdentifierMaybeNonBMP(chars, length);
}
bool
-frontend::IsIdentifier(const char* chars, size_t length)
+IsIdentifier(JSLinearString* str)
{
- return ::IsIdentifier(chars, length);
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return IsIdentifier(str->latin1Chars(nogc), str->length());
+
+ }
+ return IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length());
}
bool
-frontend::IsIdentifier(const char16_t* chars, size_t length)
+IsIdentifierNameOrPrivateName(JSLinearString* str)
{
- return ::IsIdentifier(chars, length);
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length());
+
+ }
+ return IsIdentifierNameOrPrivateNameMaybeNonBMP(str->twoByteChars(nogc), str->length());
}
bool
-frontend::IsKeyword(JSLinearString* str)
+IsKeyword(JSLinearString* str)
{
- if (const ReservedWordInfo* rw = FindReservedWord(str))
+ NameVisibility visibility;
+ if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility))
return TokenKindIsKeyword(rw->tokentype);
return false;
}
TokenKind
-frontend::ReservedWordTokenKind(PropertyName* str)
+ReservedWordTokenKind(PropertyName* str)
{
- if (const ReservedWordInfo* rw = FindReservedWord(str))
+ NameVisibility visibility;
+ if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility))
return rw->tokentype;
- return TOK_NAME;
+ return visibility == NameVisibility::Private ? TOK_PRIVATE_NAME : TOK_NAME;
}
const char*
-frontend::ReservedWordToCharZ(PropertyName* str)
+ReservedWordToCharZ(PropertyName* str)
{
- if (const ReservedWordInfo* rw = FindReservedWord(str))
+ NameVisibility visibility;
+ if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility))
return ReservedWordToCharZ(rw->tokentype);
return nullptr;
}
const char*
-frontend::ReservedWordToCharZ(TokenKind tt)
+ReservedWordToCharZ(TokenKind tt)
{
MOZ_ASSERT(tt != TOK_NAME);
switch (tt) {
@@ -226,6 +300,10 @@ frontend::ReservedWordToCharZ(TokenKind tt)
return nullptr;
}
+} // namespace frontend
+
+} // namespace js
+
PropertyName*
TokenStream::reservedWordToPropertyName(TokenKind tt) const
{
@@ -592,7 +670,7 @@ TokenStream::TokenBuf::findEOLMax(size_t start, size_t max)
if (n >= max)
break;
n++;
-
+
// This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in
// string and template literals. These code points do affect line and
// column coordinates, even as they encode their literal values.
@@ -1302,6 +1380,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
bool hasExp;
DecimalPoint decimalPoint;
const char16_t* identStart;
+ NameVisibility identVisibility;
bool hadUnicodeEscape;
// Check if in the middle of a template string. Have to get this out of
@@ -1346,6 +1425,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
if (unicode::IsUnicodeIDStart(char16_t(c))) {
identStart = userbuf.addressOfNextRawChar() - 1;
hadUnicodeEscape = false;
+ identVisibility = NameVisibility::Public;
goto identifier;
}
@@ -1356,6 +1436,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
{
identStart = userbuf.addressOfNextRawChar() - 2;
hadUnicodeEscape = false;
+ identVisibility = NameVisibility::Public;
goto identifier;
}
}
@@ -1404,6 +1485,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
tp = newToken(-1);
identStart = userbuf.addressOfNextRawChar() - 1;
hadUnicodeEscape = false;
+ identVisibility = NameVisibility::Public;
identifier:
for (;;) {
@@ -1445,11 +1527,14 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
length = userbuf.addressOfNextRawChar() - identStart;
}
- // Represent reserved words as reserved word tokens.
- if (!hadUnicodeEscape) {
- if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
- tp->type = rw->tokentype;
- goto out;
+ // Private identifiers start with a '#', and so cannot be reserved words.
+ if (identVisibility == NameVisibility::Public) {
+ // Represent reserved words as reserved word tokens.
+ if (!hadUnicodeEscape) {
+ if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
+ tp->type = rw->tokentype;
+ goto out;
+ }
}
}
@@ -1457,7 +1542,17 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
if (!atom) {
goto error;
}
- tp->type = TOK_NAME;
+ if (identVisibility == NameVisibility::Private) {
+ MOZ_ASSERT(identStart[0] == '#', "Private identifier starts with #");
+ tp->type = TOK_PRIVATE_NAME;
+
+ if (!options().fieldsEnabledOption) {
+ reportError(JSMSG_FIELDS_NOT_SUPPORTED);
+ goto error;
+ }
+ } else {
+ tp->type = TOK_NAME;
+ }
tp->setName(atom->asPropertyName());
goto out;
}
@@ -1762,11 +1857,28 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
if (escapeLength > 0) {
identStart = userbuf.addressOfNextRawChar() - escapeLength - 1;
hadUnicodeEscape = true;
+ identVisibility = NameVisibility::Public;
goto identifier;
}
goto badchar;
}
+ case '#': {
+ // TODO: This does not handle escaped private property names due to being extremely difficult
+ // in the current state of the tokenizer. If #1351107 is ported, it becomes straightforward.
+ c = getCharIgnoreEOL();
+ // '$' and '_' are not in IsUnicodeIDStart
+ c1kind = FirstCharKind(firstCharKinds[c]);
+ if (c1kind == Ident || unicode::IsUnicodeIDStart(char16_t(c))) {
+ identStart = userbuf.addressOfNextRawChar() - 2;
+ hadUnicodeEscape = false;
+ identVisibility = NameVisibility::Private;
+ goto identifier;
+ }
+ ungetCharIgnoreEOL(c);
+ goto badchar;
+ }
+
case '|':
if (matchChar('|'))
tp->type = TOK_OR;
@@ -2235,7 +2347,7 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
updateFlagsForEOL();
} else if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) {
// U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR encode
- // their literal values in template literals and (as of the
+ // their literal values in template literals and (as of the
// JSON superset proposal) string literals, but they still count
// as line terminators when computing line/column coordinates.
updateLineInfoForEOL();
diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h
index 067f11c8d8..e7a3f7b808 100644
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -92,6 +92,8 @@ enum class InvalidEscapeType {
Octal
};
+enum class NameVisibility { Public, Private };
+
class TokenStream;
struct Token
@@ -185,7 +187,7 @@ struct Token
// Mutators
void setName(PropertyName* name) {
- MOZ_ASSERT(type == TOK_NAME);
+ MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME);
u.name = name;
}
@@ -211,7 +213,7 @@ struct Token
// Type-safe accessors
PropertyName* name() const {
- MOZ_ASSERT(type == TOK_NAME);
+ MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME);
return u.name->JSAtom::asPropertyName(); // poor-man's type verification
}
@@ -344,7 +346,7 @@ class MOZ_STACK_CLASS TokenStream
public:
PropertyName* currentName() const {
- if (isCurrentTokenType(TOK_NAME)) {
+ if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) {
return currentToken().name();
}
@@ -353,7 +355,7 @@ class MOZ_STACK_CLASS TokenStream
}
bool currentNameHasEscapes() const {
- if (isCurrentTokenType(TOK_NAME)) {
+ if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) {
TokenPos pos = currentToken().pos;
return (pos.end - pos.begin) != currentToken().name()->length();
}
diff --git a/js/src/js.msg b/js/src/js.msg
index 413469b808..8e08e213db 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -216,6 +216,7 @@ MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch state
MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'")
MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods")
MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors")
+MSG_DEF(JSMSG_BAD_ARGUMENTS, 0, JSEXN_SYNTAXERR, "arguments is not valid in fields")
MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension")
MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list")
MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression")
@@ -264,6 +265,7 @@ MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'fr
MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *")
MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}")
MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
+MSG_DEF(JSMSG_MISSING_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "'#' not followed by identifier")
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character")
MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module")
MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
@@ -360,6 +362,8 @@ MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allo
MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain")
MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain")
MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
+MSG_DEF(JSMSG_MISSING_SEMI_FIELD, 0, JSEXN_SYNTAXERR, "missing ; after field definition")
+MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "fields are not currently supported")
// asm.js
MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}")
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 98a940904d..6b2a719d7e 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4430,7 +4430,7 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain,
return false;
// If name is not valid identifier
- if (!js::frontend::IsIdentifier(name, nameLen))
+ if (!js::frontend::IsIdentifier(reinterpret_cast<const Latin1Char*>(name), nameLen))
isInvalidName = true;
}
diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl
index 24814c3973..ba6b60b68c 100644
--- a/js/src/jsast.tbl
+++ b/js/src/jsast.tbl
@@ -86,4 +86,5 @@ ASTDEF(AST_COMPUTED_NAME, "ComputedName", "computedNam
ASTDEF(AST_CLASS_STMT, "ClassStatement", "classStatement")
ASTDEF(AST_CLASS_METHOD, "ClassMethod", "classMethod")
+ASTDEF(AST_CLASS_FIELD, "ClassField", "classField")
/* AST_LIMIT = last + 1 */
diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp
index ddb33a4de0..1a148578a1 100644
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -238,6 +238,7 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri
uint32_t toStringEnd = script->toStringEnd();
uint32_t lineno = script->lineno();
uint32_t column = script->column();
+ uint32_t numFieldInitializers;
if (mode == XDR_ENCODE) {
packedFields = lazy->packedFields();
@@ -251,10 +252,17 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri
// relazify scripts with inner functions. See
// JSFunction::createScriptForLazilyInterpretedFunction.
MOZ_ASSERT(lazy->numInnerFunctions() == 0);
+ if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) {
+ numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers;
+ } else {
+ numFieldInitializers = UINT32_MAX;
+ }
}
if (!xdr->codeUint64(&packedFields))
return false;
+ if (!xdr->codeUint32(&numFieldInitializers))
+ return false;
if (mode == XDR_DECODE) {
RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap());
@@ -265,6 +273,9 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri
return false;
lazy->setToStringEnd(toStringEnd);
+ if (numFieldInitializers != UINT32_MAX) {
+ lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers));
+ }
// As opposed to XDRLazyScript, we need to restore the runtime bits
// of the script, as we are trying to match the fact this function
@@ -975,6 +986,7 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
uint32_t lineno;
uint32_t column;
uint64_t packedFields;
+ uint32_t numFieldInitializers;
if (mode == XDR_ENCODE) {
// Note: it's possible the LazyScript has a non-null script_ pointer
@@ -990,13 +1002,19 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
lineno = lazy->lineno();
column = lazy->column();
packedFields = lazy->packedFields();
+ if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) {
+ numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers;
+ } else {
+ numFieldInitializers = UINT32_MAX;
+ }
}
if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) ||
!xdr->codeUint32(&toStringStart) ||
!xdr->codeUint32(&toStringEnd) ||
!xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) ||
- !xdr->codeUint64(&packedFields))
+ !xdr->codeUint64(&packedFields) ||
+ !xdr->codeUint32(&numFieldInitializers))
{
return false;
}
@@ -1007,6 +1025,9 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
if (!lazy)
return false;
lazy->setToStringEnd(toStringEnd);
+ if (numFieldInitializers != UINT32_MAX) {
+ lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers));
+ }
fun->initLazyScript(lazy);
}
}
@@ -4110,6 +4131,7 @@ LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields,
sourceObject_(nullptr),
table_(table),
packedFields_(packedFields),
+ fieldInitializers_(FieldInitializers::Invalid()),
begin_(begin),
end_(end),
toStringStart_(toStringStart),
diff --git a/js/src/jsscript.h b/js/src/jsscript.h
index e2225f0cda..fbdd658b79 100644
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1999,11 +1999,39 @@ static_assert(sizeof(JSScript) % js::gc::CellSize == 0,
namespace js {
+struct FieldInitializers
+{
+#ifdef DEBUG
+ bool valid;
+#endif
+ // This struct will eventually have a vector of constant values for optimizing
+ // field initializers.
+ size_t numFieldInitializers;
+
+ explicit FieldInitializers(size_t numFieldInitializers)
+ :
+#ifdef DEBUG
+ valid(true),
+#endif
+ numFieldInitializers(numFieldInitializers) {
+ }
+
+ static FieldInitializers Invalid() { return FieldInitializers(); }
+
+ private:
+ FieldInitializers()
+ :
+#ifdef DEBUG
+ valid(false),
+#endif
+ numFieldInitializers(0) {
+ }
+};
+
// Information about a script which may be (or has been) lazily compiled to
// bytecode from its source.
class LazyScript : public gc::TenuredCell
{
- private:
// If non-nullptr, the script has been compiled and this is a forwarding
// pointer to the result. This is a weak pointer: after relazification, we
// can collect the script if there are no other pointers to it.
@@ -2030,7 +2058,6 @@ class LazyScript : public gc::TenuredCell
uint32_t padding;
#endif
- private:
static const uint32_t NumClosedOverBindingsBits = 20;
static const uint32_t NumInnerFunctionsBits = 20;
@@ -2072,6 +2099,8 @@ class LazyScript : public gc::TenuredCell
uint64_t packedFields_;
};
+ FieldInitializers fieldInitializers_;
+
// Source location for the script.
// See the comment in JSScript for the details.
uint32_t begin_;
@@ -2297,6 +2326,12 @@ class LazyScript : public gc::TenuredCell
p_.hasThisBinding = true;
}
+ void setFieldInitializers(FieldInitializers fieldInitializers) {
+ fieldInitializers_ = fieldInitializers;
+ }
+
+ FieldInitializers getFieldInitializers() const { return fieldInitializers_; }
+
const char* filename() const {
return scriptSource()->filename();
}
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 3304ae0305..f69dfe3ca9 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -102,6 +102,8 @@
macro(dotAll, dotAll, "dotAll") \
macro(dotGenerator, dotGenerator, ".generator") \
macro(dotThis, dotThis, ".this") \
+ macro(dotInitializers, dotInitializers, ".initializers") \
+ macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \
macro(each, each, "each") \
macro(elementType, elementType, "elementType") \
macro(else, else_, "else") \
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h
index 8624b5345e..f6856f2290 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1890,6 +1890,7 @@
macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \
/*
* Pops the top of stack value, pushes property of it onto the stack.
+ * Requires the value under 'obj' to be the receiver of the following call.
*
* Like JSOP_GETPROP but for call context.
* Category: Literals
@@ -1974,7 +1975,8 @@
\
/*
* Pops the top two values on the stack as 'propval' and 'obj', pushes
- * 'propval' property of 'obj' onto the stack.
+ * 'propval' property of 'obj' onto the stack. Requires the value under
+ * 'obj' to be the receiver of the following call.
*
* Like JSOP_GETELEM but for call context.
* Category: Literals
diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp
index eb74cd4afa..4e00a25206 100644
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -7046,7 +7046,7 @@ ParseFunction(ModuleValidator& m, FunctionNode** funNodeOut, unsigned* line)
return false;
FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement;
- FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind);
+ FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind, m.parser().pos());
if (!funNode)
return false;