From b0f51cc3b06ce6caaee20882c71f47a63836ac8f Mon Sep 17 00:00:00 2001 From: Ashley Hauck Date: Thu, 25 Oct 2018 18:25:34 +0000 Subject: [PATCH] Bug 1499448 - Implement syntax for public and private fields. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D8887 --HG-- extra : moz-landing-system : lando --- js/src/builtin/ReflectParse.cpp | 30 ++- js/src/frontend/BytecodeCompiler.h | 10 +- js/src/frontend/BytecodeEmitter.cpp | 45 +++- js/src/frontend/FoldConstants.cpp | 18 +- js/src/frontend/FullParseHandler.h | 53 ++-- js/src/frontend/NameFunctions.cpp | 22 +- js/src/frontend/ParseNode.cpp | 19 ++ js/src/frontend/ParseNode.h | 88 +++++-- js/src/frontend/Parser.cpp | 75 ++++-- js/src/frontend/Parser.h | 5 +- js/src/frontend/SyntaxParseHandler.h | 8 +- js/src/frontend/TokenKind.h | 2 + js/src/frontend/TokenStream.cpp | 253 ++++++++++++++------ js/src/frontend/TokenStream.h | 25 +- js/src/js.msg | 3 + js/src/jsast.tbl | 1 + js/src/tests/jstests.list | 8 + js/src/tests/non262/fields/access.js | 19 ++ js/src/tests/non262/fields/basic.js | 14 ++ js/src/tests/non262/fields/error.js | 45 ++++ js/src/tests/non262/fields/field_types.js | 25 ++ js/src/tests/non262/fields/literal.js | 46 ++++ js/src/tests/non262/fields/mixed_methods.js | 11 + js/src/tests/non262/fields/quirks.js | 16 ++ js/src/vm/CompilationAndEvaluation.cpp | 2 +- js/src/vm/StringType.cpp | 2 +- 26 files changed, 687 insertions(+), 158 deletions(-) create mode 100644 js/src/tests/non262/fields/access.js create mode 100644 js/src/tests/non262/fields/basic.js create mode 100644 js/src/tests/non262/fields/error.js create mode 100644 js/src/tests/non262/fields/field_types.js create mode 100644 js/src/tests/non262/fields/literal.js create mode 100644 js/src/tests/non262/fields/mixed_methods.js create mode 100644 js/src/tests/non262/fields/quirks.js diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 773a1570a27e..46b20ce84a8d 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -549,7 +549,7 @@ 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); @@ -1652,9 +1652,9 @@ NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool } bool -NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst) +NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) { - return newArray(methods, dst); + return newArray(members, dst); } bool @@ -2392,7 +2392,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); } @@ -2620,26 +2620,31 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) case ParseNodeKind::Class: return classDefinition(&pn->as(), false, dst); - case ParseNodeKind::ClassMethodList: + case ParseNodeKind::ClassMemberList: { - ListNode* methodList = &pn->as(); - NodeVector methods(cx); - if (!methods.reserve(methodList->count())) { + ListNode* memberList = &pn->as(); + NodeVector members(cx); + if (!members.reserve(memberList->count())) { return false; } - for (ParseNode* item : methodList->contents()) { + for (ParseNode* item : memberList->contents()) { + if (item->is()) { + // TODO(khyperia): Implement private field access. + return false; + } + ClassMethod* method = &item->as(); - MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos)); + MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos)); RootedValue prop(cx); if (!classMethod(method, &prop)) { return false; } - methods.infallibleAppend(prop); + members.infallibleAppend(prop); } - return builder.classMethods(methods, dst); + return builder.classMembers(members, dst); } default: @@ -2960,6 +2965,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case ParseNodeKind::Dot: { PropertyAccess* prop = &pn->as(); + // TODO(khyperia): Implement private field access. MOZ_ASSERT(prop->pn_pos.encloses(prop->expression().pn_pos)); RootedValue expr(cx); diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index f38d1baf02b3..c461c257d151 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -121,14 +121,22 @@ CreateScriptSourceObject(JSContext* cx, const JS::ReadOnlyCompileOptions& option 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 25a74700a6e6..7c1fe30575e5 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1043,6 +1043,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) return true; case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::PrivateName: // no side effects, unlike ParseNodeKind::Name case ParseNodeKind::String: case ParseNodeKind::TemplateString: MOZ_ASSERT(pn->is()); @@ -1486,17 +1487,18 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case ParseNodeKind::ForOf: // by ParseNodeKind::For case ParseNodeKind::ForHead: // by ParseNodeKind::For case ParseNodeKind::ClassMethod: // by ParseNodeKind::Class + case ParseNodeKind::ClassField: // by ParseNodeKind::Class case ParseNodeKind::ClassNames: // by ParseNodeKind::Class - case ParseNodeKind::ClassMethodList: // by ParseNodeKind::Class - case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import + case ParseNodeKind::ClassMemberList: // by ParseNodeKind::Class + case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import - case ParseNodeKind::ExportBatchSpec:// by ParseNodeKind::Export - case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export + case ParseNodeKind::ExportBatchSpec: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export - case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate - case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget - case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others - case ParseNodeKind::PropertyName: // by ParseNodeKind::Dot + case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate + case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget + case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others + case ParseNodeKind::PropertyName: // by ParseNodeKind::Dot MOZ_CRASH("handled by parent nodes"); case ParseNodeKind::Limit: // invalid sentinel value @@ -1789,6 +1791,8 @@ BytecodeEmitter::emitPropLHS(PropertyAccess* prop) } while (true) { + // TODO(khyperia): Implement private field access. + // Walk back up the list, emitting annotated name ops. if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) { return false; @@ -1810,6 +1814,7 @@ bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec) { PropertyAccess* prop = &incDec->kid()->as(); + // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); ParseNodeKind kind = incDec->getKind(); PropOpEmitter poe(this, @@ -2624,6 +2629,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri // // [Other] // // OBJ VAL PropertyAccess* prop = &target->as(); + // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, @@ -3993,6 +3999,7 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) switch (lhs->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &lhs->as(); + // TODO(khyperia): Implement private field access. if (!poe->emitGet(prop->key().atom())) { // [Super] // // THIS SUPERBASE PROP // // [Other] @@ -4066,6 +4073,7 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) switch (lhs->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &lhs->as(); + // TODO(khyperia): Implement private field access. if (!poe->emitAssignment(prop->key().atom())) { // VAL return false; } @@ -6392,6 +6400,7 @@ BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteProp)); PropertyAccess* propExpr = &deleteNode->kid()->as(); + // TODO(khyperia): Implement private field access. PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, propExpr->as().isSuper() @@ -6793,6 +6802,7 @@ BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNew case ParseNodeKind::Dot: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyAccess* prop = &callee->as(); + // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); @@ -7018,7 +7028,9 @@ BytecodeEmitter::emitCallOrNew(BinaryNode* callNode, cur->isKind(ParseNodeKind::Dot); cur = &cur->as().expression()) { - ParseNode* left = &cur->as().expression(); + PropertyAccess* prop = &cur->as(); + ParseNode* left = &prop->expression(); + // TODO(khyperia): Implement private field access. if (left->isKind(ParseNodeKind::Name) || left->isKind(ParseNodeKind::This) || left->isKind(ParseNodeKind::SuperBase)) { @@ -7302,6 +7314,10 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) { for (ParseNode* propdef : obj->contents()) { + if (propdef->is()) { + // TODO(khyperia): Implement private field access. + return false; + } if (!updateSourceCoordNotes(propdef->pn_pos.begin)) { return false; } @@ -8132,9 +8148,13 @@ BytecodeEmitter::emitClass(ClassNode* classNode) { ClassNames* names = classNode->names(); ParseNode* heritageExpression = classNode->heritage(); - ListNode* classMethods = classNode->methodList(); + ListNode* classMembers = classNode->memberList(); CodeNode* constructor = nullptr; - for (ParseNode* mn : classMethods->contents()) { + for (ParseNode* mn : classMembers->contents()) { + if (mn->is()) { + // TODO(khyperia): Implement private field access. + return false; + } ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && @@ -8321,7 +8341,7 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } RootedPlainObject obj(cx); - if (!emitPropertyList(classMethods, &obj, ClassBody)) { // ... CONSTRUCTOR HOMEOBJ + if (!emitPropertyList(classMembers, &obj, ClassBody)) { // ... CONSTRUCTOR HOMEOBJ return false; } @@ -8693,6 +8713,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: case ParseNodeKind::Dot: { PropertyAccess* prop = &pn->as(); + // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::Get, diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index f7c88deaa67e..563ce90e890b 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -365,6 +365,7 @@ ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result) case ParseNodeKind::Arguments: case ParseNodeKind::Call: case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: case ParseNodeKind::TemplateString: case ParseNodeKind::TemplateStringList: case ParseNodeKind::TaggedTemplate: @@ -386,7 +387,8 @@ ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result) case ParseNodeKind::ForOf: case ParseNodeKind::ForHead: case ParseNodeKind::ClassMethod: - case ParseNodeKind::ClassMethodList: + case ParseNodeKind::ClassField: + case ParseNodeKind::ClassMemberList: case ParseNodeKind::ClassNames: case ParseNodeKind::NewTarget: case ParseNodeKind::ImportMeta: @@ -1631,6 +1633,7 @@ Fold(JSContext* cx, ParseNode** pnp, PerHandlerParser& parser) return true; case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::PrivateName: case ParseNodeKind::String: case ParseNodeKind::TemplateString: MOZ_ASSERT(pn->is()); @@ -1759,7 +1762,7 @@ Fold(JSContext* cx, ParseNode** pnp, PerHandlerParser& parser) case ParseNodeKind::Array: case ParseNodeKind::Object: case ParseNodeKind::StatementList: - case ParseNodeKind::ClassMethodList: + case ParseNodeKind::ClassMemberList: case ParseNodeKind::TemplateStringList: case ParseNodeKind::Var: case ParseNodeKind::Const: @@ -1849,6 +1852,17 @@ Fold(JSContext* cx, ParseNode** pnp, PerHandlerParser& parser) Fold(cx, node->unsafeRightReference(), parser); } + case ParseNodeKind::ClassField: { + ClassField* node = &pn->as(); + if (node->hasInitializer()) { + if (!Fold(cx, node->unsafeInitializerReference(), parser)) { + return false; + } + } + + return true; + } + case ParseNodeKind::NewTarget: case ParseNodeKind::ImportMeta: { #ifdef DEBUG diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index ae40fb6528cb..7e023b4efc6e 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -264,7 +264,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!elision) { return false; } - addList(/* list = */ literal, /* child = */ elision); + addList(/* list = */ literal, /* kid = */ elision); literal->setHasArrayHoleOrSpread(); literal->setHasNonConstInitializer(); return true; @@ -277,7 +277,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!spread) { return false; } - addList(/* list = */ literal, /* child = */ spread); + addList(/* list = */ literal, /* kid = */ spread); literal->setHasArrayHoleOrSpread(); literal->setHasNonConstInitializer(); return true; @@ -287,7 +287,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!element->isConstant()) { literal->setHasNonConstInitializer(); } - addList(/* list = */ literal, /* child = */ element); + addList(/* list = */ literal, /* kid = */ element); } BinaryNodeType newCall(Node callee, Node args) { @@ -310,11 +310,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_(ParseNodeKind::Object, TokenPos(begin, begin + 1)); } - ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { - return new_(name, heritage, methodBlock, pos); + ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) { + return new_(name, heritage, memberBlock, pos); } - ListNodeType newClassMethodList(uint32_t begin) { - return new_(ParseNodeKind::ClassMethodList, TokenPos(begin, begin + 1)); + ListNodeType newClassMemberList(uint32_t begin) { + return new_(ParseNodeKind::ClassMemberList, TokenPos(begin, begin + 1)); } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return new_(outer, inner, pos); @@ -339,7 +339,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!mutation) { return false; } - addList(/* list = */ literal, /* child = */ mutation); + addList(/* list = */ literal, /* kid = */ mutation); return true; } @@ -357,7 +357,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) literal->setHasNonConstInitializer(); } - addList(/* list = */ literal, /* child = */ propdef); + addList(/* list = */ literal, /* kid = */ propdef); } MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, Node val) { @@ -380,7 +380,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!propdef) { return false; } - addList(/* list = */ literal, /* child = */ propdef); + addList(/* list = */ literal, /* kid = */ propdef); return true; } @@ -392,7 +392,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!spread) { return false; } - addList(/* list = */ literal, /* child = */ spread); + addList(/* list = */ literal, /* kid = */ spread); return true; } @@ -408,15 +408,15 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return false; } - addList(/* list = */ literal, /* child = */ propdef); + addList(/* list = */ literal, /* kid = */ propdef); return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key, + MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, CodeNodeType funNode, AccessorType atype, bool isStatic) { - MOZ_ASSERT(methodList->isKind(ParseNodeKind::ClassMethodList)); + MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList)); MOZ_ASSERT(isUsableAsObjectPropertyName(key)); checkAndSetIsDirectRHSAnonFunction(funNode); @@ -426,7 +426,22 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) if (!classMethod) { return false; } - addList(/* list = */ methodList, /* child = */ classMethod); + addList(/* list = */ memberList, /* kid = */ classMethod); + return true; + } + + MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, + Node name, Node initializer) + { + MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList)); + MOZ_ASSERT(isUsableAsObjectPropertyName(name)); + + ClassField* classField = new_(name, initializer); + + if (!classField) { + return false; + } + addList(/* list = */ memberList, /* kid = */ classField); return true; } @@ -466,7 +481,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) void addStatementToList(ListNodeType list, Node stmt) { MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList)); - addList(/* list = */ list, /* child = */ stmt); + addList(/* list = */ list, /* kid = */ stmt); if (isFunctionStmt(stmt)) { // Notify the emitter that the block contains body-level function @@ -483,7 +498,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) { MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList)); - addList(/* list = */ list, /* child = */ caseClause); + addList(/* list = */ list, /* kid = */ caseClause); if (caseClause->statementList()->hasTopLevelFunctionDeclarations()) { list->setHasTopLevelFunctionDeclarations(); @@ -735,11 +750,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) funbox->functionNode = funNode; } void addFunctionFormalParameter(CodeNodeType funNode, Node argpn) { - addList(/* list = */ funNode->body(), /* child = */ argpn); + addList(/* list = */ funNode->body(), /* kid = */ argpn); } void setFunctionBody(CodeNodeType funNode, LexicalScopeNodeType body) { MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody)); - addList(/* list = */ funNode->body(), /* child = */ body); + addList(/* list = */ funNode->body(), /* kid = */ body); } CodeNodeType newModule(const TokenPos& pos) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 69efb9c457b5..ee759bcfdbaa 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -90,6 +90,7 @@ class NameResolver } case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: *foundName = true; return buf->append(n->as().atom()); @@ -148,6 +149,7 @@ class NameResolver } switch (cur->getKind()) { + case ParseNodeKind::PrivateName: case ParseNodeKind::Name: return cur; /* found the initialized declaration */ case ParseNodeKind::This: return cur; /* Setting a property of 'this'. */ case ParseNodeKind::Function: return nullptr; /* won't find an assignment or declaration */ @@ -447,6 +449,7 @@ class NameResolver break; case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::PrivateName: // TODO(khyperia): Implement private field access. case ParseNodeKind::String: case ParseNodeKind::TemplateString: MOZ_ASSERT(cur->is()); @@ -540,6 +543,21 @@ class NameResolver break; } + case ParseNodeKind::ClassField: { + ClassField* node = &cur->as(); + if (!resolve(&node->name(), prefix)) { + return false; + } + + if (node->hasInitializer()) { + if (!resolve(&node->initializer(), prefix)) { + return false; + } + } + + break; + } + case ParseNodeKind::Elem: { PropertyByValue* elem = &cur->as(); if (!elem->isSuper() && !resolve(&elem->expression(), prefix)) { @@ -701,7 +719,7 @@ class NameResolver return false; } } - if (!resolve(classNode->methodList(), prefix)) { + if (!resolve(classNode->memberList(), prefix)) { return false; } break; @@ -809,7 +827,7 @@ class NameResolver break; case ParseNodeKind::Object: - case ParseNodeKind::ClassMethodList: + case ParseNodeKind::ClassMemberList: for (ParseNode* element : cur->as().contents()) { if (!resolve(element, prefix)) { return false; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 06082e9e7ec2..8d3638e0292d 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -174,6 +174,9 @@ ParseNode::dump(GenericPrinter& out, int indent) case PN_NAME: as().dump(out, indent); return; + case PN_FIELD: + as().dump(out, indent); + return; case PN_NUMBER: as().dump(out, indent); return; @@ -347,6 +350,7 @@ NameNode::dump(GenericPrinter& out, int indent) return; case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: // atom() already includes the '#', no need to specially include it. case ParseNodeKind::PropertyName: if (!atom()) { out.put("#"); @@ -388,6 +392,21 @@ NameNode::dump(GenericPrinter& out, int indent) } } +void +ClassField::dump(GenericPrinter& out, int indent) +{ + out.printf("("); + if (hasInitializer()) { + indent += 2; + } + DumpParseTree(&name(), out, indent); + if (hasInitializer()) { + IndentNewLine(out, indent); + DumpParseTree(&initializer(), out, indent); + } + out.printf(")"); +} + void LexicalScopeNode::dump(GenericPrinter& out, int indent) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 81b1041ce9aa..5888e5fa43e3 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -68,6 +68,7 @@ class ObjectBox; F(Arguments, PN_LIST) \ F(Name, PN_NAME) \ F(ObjectPropertyName, PN_NAME) \ + F(PrivateName, PN_NAME) \ F(ComputedName, PN_UNARY) \ F(Number, PN_NUMBER) \ F(String, PN_NAME) \ @@ -128,7 +129,8 @@ class ObjectBox; F(MutateProto, PN_UNARY) \ F(Class, PN_TERNARY) \ F(ClassMethod, PN_BINARY) \ - F(ClassMethodList, PN_LIST) \ + F(ClassField, PN_FIELD) \ + F(ClassMemberList, PN_LIST) \ F(ClassNames, PN_BINARY) \ F(NewTarget, PN_BINARY) \ F(PosHolder, PN_NULLARY) \ @@ -253,15 +255,15 @@ IsTypeofKind(ParseNodeKind kind) * kid1: ClassNames for class name. can be null for anonymous class. * kid2: expression after `extends`. null if no expression * kid3: either of - * * ClassMethodList, if anonymous class - * * LexicalScopeNode which contains ClassMethodList as scopeBody, + * * ClassMemberList, if anonymous class + * * LexicalScopeNode which contains ClassMemberList as scopeBody, * if named class * 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 - * ClassMethodList (ListNode) - * head: list of N ClassMethod nodes + * ClassMemberList (ListNode) + * head: list of N ClassMethod or ClassField nodes * count: N >= 0 * ClassMethod (ClassMethod) * name: propertyName @@ -520,6 +522,7 @@ enum ParseNodeArity PN_CODE, /* module or function definition node */ PN_LIST, /* generic singly linked list */ PN_NAME, /* name, label, string */ + PN_FIELD, /* field name, optional initializer */ PN_NUMBER, /* numeric literal */ PN_REGEXP, /* regexp literal */ PN_LOOP, /* loop control (break/continue) */ @@ -532,6 +535,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) \ @@ -713,6 +717,12 @@ class ParseNode ParseNode* initOrStmt; /* var initializer, argument default, * or label statement target */ } name; + struct { + private: + friend class ClassField; + ParseNode* name; + ParseNode* initializer; /* field initializer - optional */ + } field; struct { private: friend class RegExpLiteral; @@ -972,7 +982,7 @@ class BinaryNode : public ParseNode } // Methods used by FoldConstants.cpp. - // caller are responsible for keeping the list consistent. + // callers are responsible for keeping the list consistent. ParseNode** unsafeLeftReference() { return &pn_u.binary.left; } @@ -1179,7 +1189,7 @@ class ListNode : public ParseNode MOZ_MUST_USE bool hasNonConstInitializer() const { MOZ_ASSERT(isKind(ParseNodeKind::Array) || isKind(ParseNodeKind::Object) || - isKind(ParseNodeKind::ClassMethodList)); + isKind(ParseNodeKind::ClassMemberList)); return pn_u.list.xflags & hasNonConstInitializerBit; } @@ -1196,7 +1206,7 @@ class ListNode : public ParseNode void setHasNonConstInitializer() { MOZ_ASSERT(isKind(ParseNodeKind::Array) || isKind(ParseNodeKind::Object) || - isKind(ParseNodeKind::ClassMethodList)); + isKind(ParseNodeKind::ClassMemberList)); pn_u.list.xflags |= hasNonConstInitializerBit; } @@ -1960,6 +1970,48 @@ class ClassMethod : public BinaryNode } }; +class ClassField : public ParseNode +{ + public: + ClassField(ParseNode* name, ParseNode* initializer) + : ParseNode(ParseNodeKind::ClassField, JSOP_NOP, PN_FIELD, + initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos)) + { + pn_u.field.name = name; + pn_u.field.initializer = initializer; + } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::ClassField); + } + + ParseNode& name() const { + return *pn_u.field.name; + } + + bool hasInitializer() const { + return pn_u.field.initializer != nullptr; + } + + ParseNode& initializer() const { + return *pn_u.field.initializer; + } + +#ifdef DEBUG + void dump(GenericPrinter& out, int indent); +#endif + + // Methods used by FoldConstants.cpp. + // callers are responsible for keeping the list consistent. + ParseNode** unsafeNameReference() { + return &pn_u.field.name; + } + + ParseNode** unsafeInitializerReference() { + return &pn_u.field.initializer; + } +}; + class SwitchStatement : public BinaryNode { public: @@ -2046,13 +2098,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(ParseNodeKind::Class, names, heritage, methodsOrBlock, pos) + : TernaryNode(ParseNodeKind::Class, names, heritage, membersOrBlock, pos) { MOZ_ASSERT_IF(names, names->is()); - MOZ_ASSERT(methodsOrBlock->is() || - methodsOrBlock->isKind(ParseNodeKind::ClassMethodList)); + MOZ_ASSERT(membersOrBlock->is() || + membersOrBlock->isKind(ParseNodeKind::ClassMemberList)); } static bool test(const ParseNode& node) { @@ -2067,14 +2119,14 @@ class ClassNode : public TernaryNode ParseNode* heritage() const { return kid2(); } - ListNode* methodList() const { - ParseNode* methodsOrBlock = kid3(); - if (methodsOrBlock->isKind(ParseNodeKind::ClassMethodList)) { - return &methodsOrBlock->as(); + ListNode* memberList() const { + ParseNode* membersOrBlock = kid3(); + if (membersOrBlock->isKind(ParseNodeKind::ClassMemberList)) { + return &membersOrBlock->as(); } - ListNode* list = &methodsOrBlock->as().scopeBody()->as(); - MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMethodList)); + ListNode* list = &membersOrBlock->as().scopeBody()->as(); + MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMemberList)); return list; } Handle scopeBindings() const { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 1a9a98251782..44a6261fd8b9 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5017,7 +5017,7 @@ GeneralParser::objectBindingPattern(DeclarationKind kind, TokenPos namePos = anyChars.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(); } @@ -8027,8 +8027,8 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, MUST_MATCH_TOKEN(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_CLASS); - ListNodeType classMethods = handler.newClassMethodList(pos().begin); - if (!classMethods) { + ListNodeType classMembers = handler.newClassMemberList(pos().begin); + if (!classMembers) { return null(); } @@ -8072,11 +8072,44 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, } PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom); + Node propName = propertyName(yieldHandling, PropertyNameInClass, declKind, classMembers, &propType, &propAtom); if (!propName) { return null(); } + if (propType == PropertyType::Field) { + if (isStatic) { + errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); + return null(); + } + if (!tokenStream.getToken(&tt)) { + return null(); + } + Node initializer = null(); + if (tt == TokenKind::Assign) { + initializer = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!tokenStream.getToken(&tt)) { + return null(); + } + } + + // TODO(khyperia): Implement ASI + if (tt != TokenKind::Semi) { + error(JSMSG_MISSING_SEMI_FIELD); + return null(); + } + + if (!handler.addClassFieldDefinition(classMembers, propName, initializer)) { + return null(); + } + + // TODO(khyperia): Change the below to `continue;` once fields are + // fully supported in the backend. We can't fail in BytecodeCompiler + // because of lazy parsing. + errorAt(nameOffset, JSMSG_FIELDS_NOT_SUPPORTED); + return null(); + } + if (propType != PropertyType::Getter && propType != PropertyType::Setter && propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && propType != PropertyType::AsyncMethod && @@ -8133,7 +8166,7 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, } AccessorType atype = ToAccessorType(propType); - if (!handler.addClassMethodDefinition(classMethods, propName, funNode, atype, isStatic)) { + if (!handler.addClassMethodDefinition(classMembers, propName, funNode, atype, isStatic)) { return null(); } } @@ -8149,7 +8182,7 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, } Node nameNode = null(); - Node methodsOrBlock = classMethods; + Node membersOrBlock = classMembers; if (name) { // The inner name is immutable. if (!noteDeclaredName(name, DeclarationKind::Const, namePos)) { @@ -8161,12 +8194,12 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, return null(); } - Node classBlock = finishLexicalScope(*innerScope, classMethods); + Node classBlock = finishLexicalScope(*innerScope, classMembers); if (!classBlock) { return null(); } - methodsOrBlock = classBlock; + membersOrBlock = classBlock; // Pop the inner scope. innerScope.reset(); @@ -8193,7 +8226,7 @@ GeneralParser::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock, + return handler.newClass(nameNode, classHeritage, membersOrBlock, TokenPos(classStartOffset, classEndOffset)); } @@ -9604,6 +9637,7 @@ GeneralParser::memberExpr(YieldHandling yieldHandling, if (!tokenStream.getToken(&tt)) { return null(); } + if (TokenKindIsPossibleIdentifierName(tt)) { PropertyName* field = anyChars.currentName(); if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { @@ -9817,7 +9851,7 @@ GeneralParser::checkLabelOrIdentifierReference(PropertyName* tt = hint; } - if (tt == TokenKind::Name) { + if (tt == TokenKind::Name || tt == TokenKind::PrivateName) { return true; } if (TokenKindIsContextualKeyword(tt)) { @@ -10275,10 +10309,11 @@ GeneralParser::arrayInitializer(YieldHandling yieldHandling, template typename ParseHandler::Node GeneralParser::propertyName(YieldHandling yieldHandling, - const Maybe& maybeDecl, - ListNodeType propList, - PropertyType* propType, - MutableHandleAtom propAtom) + PropertyNameContext propertyNameContext, + const Maybe& maybeDecl, + ListNodeType propList, + PropertyType* propType, + MutableHandleAtom propAtom) { TokenKind ltok; if (!tokenStream.getToken(<ok)) { @@ -10449,6 +10484,16 @@ GeneralParser::propertyName(YieldHandling yieldHandling, return propName; } + if (propertyNameContext == PropertyNameInClass && (tt == TokenKind::Semi || tt == TokenKind::Assign)) { + if (isGenerator || isAsync) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + anyChars.ungetToken(); + *propType = PropertyType::Field; + return propName; + } + if (TokenKindIsPossibleIdentifierName(ltok) && (tt == TokenKind::Comma || tt == TokenKind::RightCurly || tt == TokenKind::Assign)) { @@ -10567,7 +10612,7 @@ GeneralParser::objectLiteral(YieldHandling yieldHandling, TokenPos namePos = anyChars.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(); } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 8651e42d14e0..5a3734828b9d 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -240,7 +240,8 @@ enum class PropertyType { AsyncMethod, AsyncGeneratorMethod, Constructor, - DerivedConstructor + DerivedConstructor, + Field, }; enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword }; @@ -1243,7 +1244,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) bool checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt, DeclarationKind kind, TokenPos pos); + enum PropertyNameContext { PropertyNameInLiteral, PropertyNameInPattern, PropertyNameInClass }; Node propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const mozilla::Maybe& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 1b3304ba5f5f..5873f6177fd7 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -279,7 +279,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) BinaryNodeType newTaggedTemplate(Node tag, 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; } @@ -305,11 +305,15 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) CodeNodeType funNode, AccessorType atype) { return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key, + MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, CodeNodeType funNode, AccessorType atype, bool isStatic) { return true; } + MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, + Node name, Node 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; } diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 1ed2dbbc5ee8..18559329ee2c 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -73,6 +73,7 @@ macro(LeftParen, "'('") \ macro(RightParen, "')'") \ macro(Name, "identifier") \ + macro(PrivateName, "private identifier") \ macro(Number, "numeric literal") \ macro(String, "string literal") \ \ @@ -322,6 +323,7 @@ inline MOZ_MUST_USE bool TokenKindIsPossibleIdentifier(TokenKind tt) { return tt == TokenKind::Name || + tt == TokenKind::PrivateName || TokenKindIsContextualKeyword(tt) || TokenKindIsStrictReservedWord(tt); } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index de37a479414b..90765127d30b 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -119,36 +119,28 @@ FindReservedWord(const Utf8Unit* units, 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()); -} - -template -static bool -IsIdentifierImpl(const CharT* chars, size_t length) -{ - using namespace js; - - if (length == 0) { - return false; - } - - if (!unicode::IsIdentifierStart(char16_t(*chars))) { - return false; - } - - const CharT* end = chars + length; - while (++chars != end) { - if (!unicode::IsIdentifierPart(char16_t(*chars))) { - return false; + 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); } - return true; + 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); } static uint32_t @@ -171,15 +163,71 @@ GetSingleCodePoint(const char16_t** p, const char16_t* end) return codePoint; } -static bool -IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) -{ - using namespace js; +namespace js { - if (IsIdentifierImpl(chars, length)) { - return true; +namespace frontend { + +bool +IsIdentifier(JSLinearString* str) +{ + JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(str); + if (str->hasLatin1Chars()) { + return IsIdentifier(str->latin1Chars(nogc), str->length()); + } + return IsIdentifier(str->twoByteChars(nogc), str->length()); +} + +bool +IsIdentifierNameOrPrivateName(JSLinearString* str) +{ + JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(str); + if (str->hasLatin1Chars()) { + return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length()); + } + return IsIdentifierNameOrPrivateName(str->twoByteChars(nogc), str->length()); +} + +bool +IsIdentifier(const Latin1Char* chars, size_t length) +{ + if (length == 0) { + return false; } + if (!unicode::IsIdentifierStart(char16_t(*chars))) { + return false; + } + + const Latin1Char* end = chars + length; + while (++chars != end) { + if (!unicode::IsIdentifierPart(char16_t(*chars))) { + return false; + } + } + + return true; +} + +bool +IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length) +{ + if (length == 0) { + return false; + } + + if (char16_t(*chars) == '#') { + ++chars; + --length; + } + + return IsIdentifier(chars, length); +} + +bool +IsIdentifier(const char16_t* chars, size_t length) +{ if (length == 0) { return false; } @@ -203,37 +251,45 @@ IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) return true; } -namespace js { - -namespace frontend { - bool -IsIdentifier(JSLinearString* str) +IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length) { - JS::AutoCheckCannotGC nogc; - MOZ_ASSERT(str); - if (str->hasLatin1Chars()) { - return ::IsIdentifierImpl(str->latin1Chars(nogc), str->length()); + if (length == 0) { + return false; } - return ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); -} -bool -IsIdentifier(const char* chars, size_t length) -{ - return ::IsIdentifierImpl(chars, length); -} + const char16_t* p = chars; + const char16_t* end = chars + length; + uint32_t codePoint; -bool -IsIdentifier(const char16_t* chars, size_t length) -{ - return ::IsIdentifierImpl(chars, length); + codePoint = GetSingleCodePoint(&p, end); + if (codePoint == '#') { + if (length == 1) { + return false; + } + + codePoint = GetSingleCodePoint(&p, end); + } + + if (!unicode::IsIdentifierStart(codePoint)) { + return false; + } + + while (p < end) { + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierPart(codePoint)) { + return false; + } + } + + return true; } bool IsKeyword(JSLinearString* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) { return TokenKindIsKeyword(rw->tokentype); } @@ -243,17 +299,19 @@ IsKeyword(JSLinearString* str) TokenKind ReservedWordTokenKind(PropertyName* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) { return rw->tokentype; } - return TokenKind::Name; + return visibility == NameVisibility::Private ? TokenKind::PrivateName : TokenKind::Name; } const char* ReservedWordToCharZ(PropertyName* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) { return ReservedWordToCharZ(rw->tokentype); } @@ -1660,6 +1718,42 @@ GeneralTokenStreamChars::matchUnicodeEscapeIdent(uint32_t* return false; } +template +MOZ_MUST_USE bool +TokenStreamSpecific::matchIdentifierStart(IdentifierEscapes* sawEscape) +{ + int32_t unit = getCodeUnit(); + if (unicode::IsIdentifierStart(char16_t(unit))) { + *sawEscape = IdentifierEscapes::None; + return true; + } + + if (unit == '\\') { + *sawEscape = IdentifierEscapes::SawUnicodeEscape; + + uint32_t codePoint; + uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint); + if (escapeLength != 0) { + return true; + } + + // We could point "into" a mistyped escape, e.g. for "\u{41H}" we + // could point at the 'H'. But we don't do that now, so the code + // unit after the '\' isn't necessarily bad, so just point at the + // start of the actually-invalid escape. + ungetCodeUnit('\\'); + error(JSMSG_BAD_ESCAPE); + return false; + } + + *sawEscape = IdentifierEscapes::None; + + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + error(JSMSG_MISSING_PRIVATE_NAME); + return false; +} + template bool TokenStreamSpecific::getDirectives(bool isMultiline, @@ -1931,9 +2025,11 @@ TokenStreamSpecific::putIdentInCharBuffer(const Unit* iden template MOZ_MUST_USE bool TokenStreamSpecific::identifierName(TokenStart start, - const Unit* identStart, - IdentifierEscapes escaping, - Modifier modifier, TokenKind* out) + const Unit* identStart, + IdentifierEscapes escaping, + Modifier modifier, + NameVisibility visibility, + TokenKind* out) { // Run the bad-token code for every path out of this function except the // two success-cases. @@ -1995,11 +2091,14 @@ TokenStreamSpecific::identifierName(TokenStart start, const Unit* chars = identStart; size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart; - // Represent reserved words lacking escapes as reserved word tokens. - if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { - noteBadToken.release(); - newSimpleToken(rw->tokentype, start, modifier, out); - return true; + // Private identifiers start with a '#', and so cannot be reserved words. + if (visibility == NameVisibility::Public) { + // Represent reserved words lacking escapes as reserved word tokens. + if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { + noteBadToken.release(); + newSimpleToken(rw->tokentype, start, modifier, out); + return true; + } } atom = atomizeSourceChars(anyCharsAccess().cx, MakeSpan(chars, length)); @@ -2009,7 +2108,16 @@ TokenStreamSpecific::identifierName(TokenStart start, } noteBadToken.release(); - newNameToken(atom->asPropertyName(), start, modifier, out); + if (visibility == NameVisibility::Private) { + MOZ_ASSERT(identStart[0] == static_cast('#'), "Private identifier starts with #"); + newPrivateNameToken(atom->asPropertyName(), start, modifier, out); + + // TODO(khypera): Delete the below once private names are supported. + errorAt(start.offset(), JSMSG_FIELDS_NOT_SUPPORTED); + return false; + } else { + newNameToken(atom->asPropertyName(), start, modifier, out); + } return true; } @@ -2440,7 +2548,7 @@ TokenStreamSpecific::getTokenInternal(TokenKind* const ttp "or else we'll fail to maintain line-info/flags " "for EOL here"); - return identifierName(start, identStart, IdentifierEscapes::None, modifier, ttp); + return identifierName(start, identStart, IdentifierEscapes::None, modifier, NameVisibility::Public, ttp); } error(JSMSG_ILLEGAL_CHARACTER); @@ -2489,7 +2597,8 @@ TokenStreamSpecific::getTokenInternal(TokenKind* const ttp if (c1kind == Ident) { TokenStart start(this->sourceUnits, -1); return identifierName(start, this->sourceUnits.addressOfNextCodeUnit() - 1, - IdentifierEscapes::None, modifier, ttp); + IdentifierEscapes::None, modifier, + NameVisibility::Public, ttp); } // Look for a decimal number. @@ -2682,6 +2791,16 @@ TokenStreamSpecific::getTokenInternal(TokenKind* const ttp simpleKind = TokenKind::Dot; break; + case '#': { + TokenStart start(this->sourceUnits, -1); + const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + IdentifierEscapes sawEscape; + if (!matchIdentifierStart(&sawEscape)) { + return badToken(); + } + return identifierName(start, identStart, sawEscape, modifier, NameVisibility::Private, ttp); + } + case '=': if (matchCodeUnit('=')) { simpleKind = matchCodeUnit('=') ? TokenKind::StrictEq : TokenKind::Eq; @@ -2705,7 +2824,7 @@ TokenStreamSpecific::getTokenInternal(TokenKind* const ttp if (uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint)) { return identifierName(start, this->sourceUnits.addressOfNextCodeUnit() - escapeLength - 1, - IdentifierEscapes::SawUnicodeEscape, modifier, ttp); + IdentifierEscapes::SawUnicodeEscape, modifier, NameVisibility::Public, ttp); } // We could point "into" a mistyped escape, e.g. for "\u{41H}" we diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 7efb953cde03..4d02cc969efd 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -289,6 +289,8 @@ enum class InvalidEscapeType { // The only escapes found in IdentifierName are of the Unicode flavor. enum class IdentifierEscapes { None, SawUnicodeEscape }; +enum class NameVisibility { Public, Private }; + class TokenStreamShared; struct Token @@ -406,7 +408,7 @@ struct Token // Mutators void setName(PropertyName* name) { - MOZ_ASSERT(type == TokenKind::Name); + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); u.name = name; } @@ -432,7 +434,7 @@ struct Token // Type-safe accessors PropertyName* name() const { - MOZ_ASSERT(type == TokenKind::Name); + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); return u.name->JSAtom::asPropertyName(); // poor-man's type verification } @@ -627,7 +629,7 @@ class TokenStreamAnyChars public: PropertyName* currentName() const { - if (isCurrentTokenType(TokenKind::Name)) { + if (isCurrentTokenType(TokenKind::Name) || isCurrentTokenType(TokenKind::PrivateName)) { return currentToken().name(); } @@ -636,7 +638,7 @@ class TokenStreamAnyChars } bool currentNameHasEscapes() const { - if (isCurrentTokenType(TokenKind::Name)) { + if (isCurrentTokenType(TokenKind::Name) || isCurrentTokenType(TokenKind::PrivateName)) { TokenPos pos = currentToken().pos; return (pos.end - pos.begin) != currentToken().name()->length(); } @@ -1927,6 +1929,15 @@ class GeneralTokenStreamChars token->setName(name); } + void newPrivateNameToken(PropertyName* name, + TokenStart start, + TokenStreamShared::Modifier modifier, + TokenKind* out) + { + Token* token = newToken(TokenKind::PrivateName, start, modifier, out); + token->setName(name); + } + void newRegExpToken(RegExpFlag reflags, TokenStart start, TokenKind* out) { Token* token = newToken(TokenKind::RegExp, start, TokenStreamShared::Operand, out); @@ -2000,6 +2011,7 @@ class GeneralTokenStreamChars uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint); bool matchUnicodeEscapeIdent(uint32_t* codePoint); + bool matchIdentifierStart(); /** * If possible, compute a line of context for an otherwise-filled-in |err| @@ -2335,6 +2347,7 @@ class MOZ_STACK_CLASS TokenStreamSpecific using GeneralCharsBase::matchUnicodeEscapeIdStart; using GeneralCharsBase::newAtomToken; using GeneralCharsBase::newNameToken; + using GeneralCharsBase::newPrivateNameToken; using GeneralCharsBase::newNumberToken; using GeneralCharsBase::newRegExpToken; using GeneralCharsBase::newSimpleToken; @@ -2670,7 +2683,9 @@ class MOZ_STACK_CLASS TokenStreamSpecific MOZ_MUST_USE bool identifierName(TokenStart start, const Unit* identStart, IdentifierEscapes escaping, Modifier modifier, - TokenKind* out); + NameVisibility visibility, TokenKind* out); + + MOZ_MUST_USE bool matchIdentifierStart(IdentifierEscapes* sawEscape); MOZ_MUST_USE bool getTokenInternal(TokenKind* const ttp, const Modifier modifier); diff --git a/js/src/js.msg b/js/src/js.msg index f8888f0d0e48..8973a8069a04 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -265,6 +265,7 @@ MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'fro 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_BAD_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid escape sequence") +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") @@ -355,6 +356,8 @@ MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property na MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration") MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed within functions") 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") // UTF-8 source text encoding errors MSG_DEF(JSMSG_BAD_LEADING_UTF8_UNIT, 1, JSEXN_SYNTAXERR, "{0} byte doesn't begin a valid UTF-8 code point") diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl index aa36b70748c4..85b6cafc9ba6 100644 --- a/js/src/jsast.tbl +++ b/js/src/jsast.tbl @@ -78,4 +78,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/tests/jstests.list b/js/src/tests/jstests.list index 076e5c47109f..94cc5eb6bf88 100644 --- a/js/src/tests/jstests.list +++ b/js/src/tests/jstests.list @@ -461,6 +461,14 @@ skip script test262/intl402/DateTimeFormat/prototype/resolvedOptions/order.js skip script test262/intl402/PluralRules/prototype/resolvedOptions/order.js skip script test262/intl402/NumberFormat/prototype/resolvedOptions/order.js +# Fields are not fully implemented yet +skip script non262/fields/access.js +skip script non262/fields/basic.js +skip script non262/fields/error.js +skip script non262/fields/field_types.js +skip script non262/fields/literal.js +skip script non262/fields/mixed_methods.js +skip script non262/fields/quirks.js ########################################################### # Tests disabled due to issues in test262 importer script # diff --git a/js/src/tests/non262/fields/access.js b/js/src/tests/non262/fields/access.js new file mode 100644 index 000000000000..f1670a46dbe3 --- /dev/null +++ b/js/src/tests/non262/fields/access.js @@ -0,0 +1,19 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +class C { + x = 5; +} + +c = new C(); + +reportCompare(c.x, undefined); // TODO +//reportCompare(c.x, 5); + +class D { + #y = 5; +} + +d = new D(); + +reportCompare(d.#y, undefined); // TODO +//reportCompare(d.#y, 5); diff --git a/js/src/tests/non262/fields/basic.js b/js/src/tests/non262/fields/basic.js new file mode 100644 index 000000000000..e27f956e33c8 --- /dev/null +++ b/js/src/tests/non262/fields/basic.js @@ -0,0 +1,14 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +class C { + x; + y = 2; +} + +class D { + #x; + #y = 2; +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/fields/error.js b/js/src/tests/non262/fields/error.js new file mode 100644 index 000000000000..74ea76b7b040 --- /dev/null +++ b/js/src/tests/non262/fields/error.js @@ -0,0 +1,45 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +let source = `class C { + x +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `class C { + -2; + -2 = 2; +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `class C { + x += 2; +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `class C { + #2; +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `class C { + #["h" + "i"]; +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `class C { + #"hi"; +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `function f() { +class C { + #"should still throw error during lazy parse"; +} +}`; +assertThrowsInstanceOf(() => Function(source), SyntaxError); + +source = `#outside;`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/fields/field_types.js b/js/src/tests/non262/fields/field_types.js new file mode 100644 index 000000000000..f6012ab588f9 --- /dev/null +++ b/js/src/tests/non262/fields/field_types.js @@ -0,0 +1,25 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +class C { + [Math.sqrt(4)]; + [Math.sqrt(8)] = 5 + 2; + "hi"; + "bye" = {}; + 2 = 2; + 0x101 = 2; + 0o101 = 2; + 0b101 = 2; + NaN = 0; // actually the field called "NaN", not the number + Infinity = 50; // actually the field called "Infinity", not the number + // all the keywords below are proper fields (?!?) + with = 0; + //static = 0; // doesn't work yet + async = 0; + get = 0; + set = 0; + export = 0; + function = 0; +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/fields/literal.js b/js/src/tests/non262/fields/literal.js new file mode 100644 index 000000000000..a9835e3eb9fd --- /dev/null +++ b/js/src/tests/non262/fields/literal.js @@ -0,0 +1,46 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +source = `var y = { + x; +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +// This is legal, and is equivalent to `var y = { x: x };` +// source = `var y = { +// x +// }`; +// assertThrowsInstanceOf(() => eval(source), SyntaxError); + +source = `var y = { + #x; +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +// Temporarily disabled due to the same reason above. +// source = `var y = { +// #x +// }`; +// assertThrowsInstanceOf(() => eval(source), SyntaxError); + +source = `var y = { + x = 2; +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +source = `var y = { + x = 2 +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +source = `var y = { + #x = 2; +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +source = `var y = { + #x = 2 +}`; +assertThrowsInstanceOf(() => eval(source), SyntaxError); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/fields/mixed_methods.js b/js/src/tests/non262/fields/mixed_methods.js new file mode 100644 index 000000000000..cdcb15639e23 --- /dev/null +++ b/js/src/tests/non262/fields/mixed_methods.js @@ -0,0 +1,11 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +class C { + x; + y(){} + z = 2; + w(){}; +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/fields/quirks.js b/js/src/tests/non262/fields/quirks.js new file mode 100644 index 000000000000..b57c0316ecb7 --- /dev/null +++ b/js/src/tests/non262/fields/quirks.js @@ -0,0 +1,16 @@ +// * * * THIS TEST IS DISABLED - Fields are not fully implemented yet + +class C { + x;;;; + y + ; +} + +class D { + x = 5; + y = (x += 1); + // TODO: Assert values of x and y +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/CompilationAndEvaluation.cpp b/js/src/vm/CompilationAndEvaluation.cpp index 4d3609ca8168..1cc2efe2abb1 100644 --- a/js/src/vm/CompilationAndEvaluation.cpp +++ b/js/src/vm/CompilationAndEvaluation.cpp @@ -325,7 +325,7 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, } // If name is not valid identifier - if (!js::frontend::IsIdentifier(name, nameLen)) { + if (!js::frontend::IsIdentifier(reinterpret_cast(name), nameLen)) { isInvalidName = true; } } diff --git a/js/src/vm/StringType.cpp b/js/src/vm/StringType.cpp index 900b127d7568..c5c6a918f02d 100644 --- a/js/src/vm/StringType.cpp +++ b/js/src/vm/StringType.cpp @@ -2210,7 +2210,7 @@ js::IdToPrintableUTF8(JSContext* cx, HandleId id, IdToPrintableBehavior behavior // ToString() throws a TypeError, therefore require that callers // request source representation when |id| is a property key. MOZ_ASSERT_IF(behavior == IdToPrintableBehavior::IdIsIdentifier, - JSID_IS_ATOM(id) && frontend::IsIdentifier(JSID_TO_ATOM(id))); + JSID_IS_ATOM(id) && frontend::IsIdentifierNameOrPrivateName(JSID_TO_ATOM(id))); RootedValue v(cx, IdToValue(id)); JSString* str;