summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/DecoratorEmitter.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/frontend/DecoratorEmitter.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/frontend/DecoratorEmitter.cpp')
-rw-r--r--js/src/frontend/DecoratorEmitter.cpp840
1 files changed, 840 insertions, 0 deletions
diff --git a/js/src/frontend/DecoratorEmitter.cpp b/js/src/frontend/DecoratorEmitter.cpp
new file mode 100644
index 0000000000..f5771ba9e4
--- /dev/null
+++ b/js/src/frontend/DecoratorEmitter.cpp
@@ -0,0 +1,840 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/DecoratorEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/CallOrNewEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/ObjectEmitter.h"
+#include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h"
+#include "frontend/WhileEmitter.h"
+#include "vm/ThrowMsgKind.h"
+
+using namespace js;
+using namespace js::frontend;
+
+DecoratorEmitter::DecoratorEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
+ DecoratorEmitter::Kind kind, ParseNode* key, ListNode* decorators,
+ bool isStatic) {
+ MOZ_ASSERT(kind != Kind::Field);
+
+ // The DecoratorEmitter expects the value to be decorated to be at the top
+ // of the stack prior to this call. It will apply the decorators to this
+ // value, possibly replacing the value with a value returned by a decorator.
+ // [stack] VAL
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ // 1. Let decorators be elementRecord.[[Decorators]].
+ // 2. If decorators is empty, return unused.
+ // This is checked by the caller.
+ MOZ_ASSERT(!decorators->empty());
+
+ // 3. Let key be elementRecord.[[Key]].
+ // 4. Let kind be elementRecord.[[Kind]].
+ // 5. For each element decorator of decorators, do
+ for (ParseNode* decorator : decorators->contents()) {
+ // 5.a. Let decorationState be the Record { [[Finished]]: false }.
+ if (!emitDecorationState()) {
+ return false;
+ }
+
+ if (!emitCallDecorator(kind, key, isStatic, decorator)) {
+ return false;
+ }
+
+ // 5.i. Set decorationState.[[Finished]] to true.
+ if (!emitUpdateDecorationState()) {
+ return false;
+ }
+
+ // We need to check if the decorator returned undefined, a callable value,
+ // or any other value.
+ IfEmitter ie(bce_);
+ if (!ie.emitIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ if (!emitCheckIsUndefined()) {
+ // [stack] VAL RETVAL ISUNDEFINED
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] VAL RETVAL
+ return false;
+ }
+
+ // Pop the undefined RETVAL from the stack, leaving the original value in
+ // place.
+ if (!bce_->emitPopN(1)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!ie.emitElseIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ // 5.l.i. If IsCallable(newValue) is true, then
+ if (!emitCheckIsCallable()) {
+ // [stack] VAL RETVAL ISCALLABLE_RESULT
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] VAL RETVAL
+ return false;
+ }
+
+ // clang-format off
+ // 5.k. Else if kind is accessor, then
+ // 5.k.i. If newValue is an Object, then
+ // 5.k.i.1. Let newGetter be ? Get(newValue, "get").
+ // 5.k.i.2. If IsCallable(newGetter) is true, set elementRecord.[[Get]] to newGetter.
+ // 5.k.i.3. Else if newGetter is not undefined, throw a TypeError exception.
+ // 5.k.i.4. Let newSetter be ? Get(newValue, "set").
+ // 5.k.i.5. If IsCallable(newSetter) is true, set elementRecord.[[Set]] to newSetter.
+ // 5.k.i.6. Else if newSetter is not undefined, throw a TypeError exception.
+ // 5.k.i.7. Let initializer be ? Get(newValue, "init").
+ // 5.k.i.8. If IsCallable(initializer) is true, append initializer to elementRecord.[[Initializers]].
+ // 5.k.i.9. Else if initializer is not undefined, throw a TypeError exception.
+ // 5.k.ii. Else if newValue is not undefined, throw a TypeError exception.
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // clang-format on
+ // 5.l. Else,
+
+ // 5.l.i.1. Perform MakeMethod(newValue, homeObject).
+ // MakeMethod occurs in the caller, here we just drop the original method
+ // which was an argument to the decorator, and leave the new method
+ // returned by the decorator on the stack.
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] RETVAL VAL
+ return false;
+ }
+ if (!bce_->emitPopN(1)) {
+ // [stack] RETVAL
+ return false;
+ }
+ // 5.j.ii. Else if initializer is not undefined, throw a TypeError
+ // exception. 5.l.ii. Else if newValue is not undefined, throw a
+ // TypeError exception.
+ if (!ie.emitElse()) {
+ return false;
+ }
+
+ if (!bce_->emitPopN(1)) {
+ // [stack] RETVAL
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) {
+ return false;
+ }
+
+ if (!ie.emitEnd()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
+ ParseNode* key, ListNode* decorators, bool isStatic) {
+ // This method creates a new array to contain initializers added by decorators
+ // to the stack. start:
+ // [stack]
+ // end:
+ // [stack] ARRAY
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ // 1. Let decorators be elementRecord.[[Decorators]].
+ // 2. If decorators is empty, return unused.
+ // This is checked by the caller.
+ MOZ_ASSERT(!decorators->empty());
+
+ // If we're apply decorators to a field, we'll push a new array to the stack
+ // to hold newly created initializers.
+ if (!bce_->emitUint32Operand(JSOp::NewArray, 1)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!emitPropertyKey(key)) {
+ // [stack] ARRAY NAME
+ return false;
+ }
+
+ if (!bce_->emitUint32Operand(JSOp::InitElemArray, 0)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::One)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ // 3. Let key be elementRecord.[[Key]].
+ // 4. Let kind be elementRecord.[[Kind]].
+ // 5. For each element decorator of decorators, do
+ for (ParseNode* decorator : decorators->contents()) {
+ // 5.a. Let decorationState be the Record { [[Finished]]: false }.
+ if (!emitDecorationState()) {
+ return false;
+ }
+
+ if (!emitCallDecorator(Kind::Field, key, isStatic, decorator)) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // 5.i. Set decorationState.[[Finished]] to true.
+ if (!emitUpdateDecorationState()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // We need to check if the decorator returned undefined, a callable value,
+ // or any other value.
+ IfEmitter ie(bce_);
+ if (!ie.emitIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ if (!emitCheckIsUndefined()) {
+ // [stack] ARRAY INDEX RETVAL ISUNDEFINED
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // Pop the undefined RETVAL from the stack, leaving the original value in
+ // place.
+ if (!bce_->emitPopN(1)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ if (!ie.emitElseIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ // 5.l.i. If IsCallable(newValue) is true, then
+ if (!emitCheckIsCallable()) {
+ // [stack] ARRAY INDEX RETVAL ISCALLABLE_RESULT
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // 5.j. If kind is field, then
+ // 5.j.i. If IsCallable(initializer) is true, append initializer
+ // to elementRecord.[[Initializers]].
+ if (!bce_->emit1(JSOp::InitElemInc)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ // 5.j.ii. Else if initializer is not undefined, throw a TypeError
+ // exception. 5.l.ii. Else if newValue is not undefined, throw a
+ // TypeError exception.
+ if (!ie.emitElse()) {
+ return false;
+ }
+
+ if (!bce_->emitPopN(1)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) {
+ return false;
+ }
+
+ if (!ie.emitEnd()) {
+ return false;
+ }
+ }
+
+ // Pop INDEX
+ return bce_->emitPopN(1);
+ // [stack] ARRAY
+}
+
+bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
+ // [stack] THIS INITIALIZERS
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ //
+ // 1. Assert: elementRecord.[[Kind]] is field or accessor.
+ // 2. If elementRecord.[[BackingStorageKey]] is present, let fieldName be
+ // elementRecord.[[BackingStorageKey]].
+ // 3. Else, let fieldName be elementRecord.[[Key]].
+ // We've stored the fieldname in the first element of the initializers array.
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS INITIALIZERS INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Zero)) {
+ // [stack] THIS INITIALIZERS INITIALIZERS INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS INITIALIZERS FIELDNAME
+ return false;
+ }
+
+ // Retrieve initial value of the field
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME
+ return false;
+ }
+
+ if (!bce_->emitDupAt(3)) {
+ // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME THIS
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] THIS INITIALIZERS FIELDNAME THIS FIELDNAME
+ return false;
+ }
+
+ // 4. Let initValue be undefined.
+ // TODO: (See Bug 1817993) At the moment, we're applying the initialization
+ // logic in two steps. The pre-decorator initialization code runs, stores
+ // the initial value, and then we retrieve it here and apply the initializers
+ // added by decorators. We should unify these two steps.
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS INITIALIZERS FIELDNAME VALUE
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::Pick, 2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS
+ return false;
+ }
+
+ // Retrieve the length of the initializers array.
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::length())) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::One)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ // 5. For each element initializer of elementRecord.[[Initializers]], do
+ WhileEmitter wh(bce_);
+ // At this point, we have no context to determine offsets in the
+ // code for this while statement. Ideally, it would correspond to
+ // the field we're initializing.
+ if (!wh.emitCond(0, 0, 0)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX
+ return false;
+ }
+
+ if (!bce_->emitDupAt(2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX
+ // LENGTH
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Lt)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX BOOL
+ return false;
+ }
+
+ // a. Set initValue to ? Call(initializer, receiver, « initValue »).
+ if (!wh.emitBody()) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emitDupAt(2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ // INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emitDupAt(1)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ // INITIALIZERS INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC
+ return false;
+ }
+
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!bce_->emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC
+ // THIS
+ return false;
+ }
+
+ // Pass value in as argument to the initializer
+ if (!bce_->emit2(JSOp::Pick, 5)) {
+ // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX FUNC THIS
+ // VALUE
+ return false;
+ }
+
+ // Callee is always internal function.
+ if (!bce_->emitCall(JSOp::Call, 1)) {
+ // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX RVAL
+ return false;
+ }
+
+ // Store returned value for next iteration
+ if (!bce_->emit2(JSOp::Unpick, 3)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Inc)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!wh.emitEnd()) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ // 6. If fieldName is a Private Name, then
+ // 6.a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
+ // 7. Else,
+ // 6.a. Assert: IsPropertyKey(fieldName) is true.
+ // 6.b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName,
+ // initValue).
+ // TODO: (See Bug 1817993) Because the field already exists, we just store the
+ // updated value here.
+ if (!bce_->emitPopN(3)) {
+ // [stack] THIS FIELDNAME VALUE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::InitElem)) {
+ // [stack] THIS
+ return false;
+ }
+
+ // 8. Return unused.
+ return bce_->emitPopN(1);
+ // [stack]
+}
+
+bool DecoratorEmitter::emitPropertyKey(ParseNode* key) {
+ if (key->is<NameNode>()) {
+ NameNode* keyAsNameNode = &key->as<NameNode>();
+ if (keyAsNameNode->privateNameKind() == PrivateNameKind::None) {
+ if (!bce_->emitStringOp(JSOp::String, keyAsNameNode->atom())) {
+ // [stack] NAME
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(keyAsNameNode->privateNameKind() == PrivateNameKind::Field);
+ if (!bce_->emitGetPrivateName(keyAsNameNode)) {
+ // [stack] NAME
+ return false;
+ }
+ }
+ } else if (key->isKind(ParseNodeKind::NumberExpr)) {
+ if (!bce_->emitNumberOp(key->as<NumericLiteral>().value())) {
+ // [stack] NAME
+ return false;
+ }
+ } else {
+ // Otherwise this is a computed property name. BigInt keys are parsed
+ // as (synthetic) computed property names, too.
+ MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+ if (!bce_->emitComputedPropertyName(&key->as<UnaryNode>())) {
+ // [stack] NAME
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool DecoratorEmitter::emitDecorationState() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ return true;
+}
+
+bool DecoratorEmitter::emitUpdateDecorationState() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ return true;
+}
+
+[[nodiscard]] bool DecoratorEmitter::emitCallDecorator(Kind kind,
+ ParseNode* key,
+ bool isStatic,
+ ParseNode* decorator) {
+ // Prepare to call decorator
+ CallOrNewEmitter cone(bce_, JSOp::Call,
+ CallOrNewEmitter::ArgumentsKind::Other,
+ ValueUsage::WantValue);
+
+ // DecoratorMemberExpression: IdentifierReference e.g. @dec
+ if (decorator->is<NameNode>()) {
+ if (!cone.emitNameCallee(decorator->as<NameNode>().name())) {
+ // [stack] VAL? CALLEE THIS?
+ return false;
+ }
+ } else if (decorator->is<ListNode>()) {
+ // DecoratorMemberExpression: DecoratorMemberExpression . IdentifierName
+ // e.g. @decorators.nested.dec
+ PropOpEmitter& poe = cone.prepareForPropCallee(false);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+
+ ListNode* ln = &decorator->as<ListNode>();
+ bool first = true;
+ for (ParseNode* node : ln->contentsTo(ln->last())) {
+ // We should have only placed NameNode instances in this list while
+ // parsing.
+ MOZ_ASSERT(node->is<NameNode>());
+
+ if (first) {
+ NameNode* obj = &node->as<NameNode>();
+ if (!bce_->emitGetName(obj)) {
+ return false;
+ }
+ first = false;
+ } else {
+ NameNode* prop = &node->as<NameNode>();
+ GCThingIndex propAtomIndex;
+
+ if (!bce_->makeAtomIndex(prop->atom(), ParserAtom::Atomize::Yes,
+ &propAtomIndex)) {
+ return false;
+ }
+
+ if (!bce_->emitAtomOp(JSOp::GetProp, propAtomIndex)) {
+ return false;
+ }
+ }
+ }
+
+ NameNode* prop = &ln->last()->as<NameNode>();
+ if (!poe.emitGet(prop->atom())) {
+ return false;
+ }
+ } else {
+ // DecoratorCallExpression | DecoratorParenthesizedExpression,
+ // e.g. @dec('argument') | @((value, context) => value)
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+
+ if (!bce_->emitTree(decorator)) {
+ return false;
+ }
+ }
+
+ if (!cone.emitThis()) {
+ // [stack] VAL? CALLEE THIS
+ return false;
+ }
+
+ if (!cone.prepareForNonSpreadArguments()) {
+ return false;
+ }
+
+ if (kind == Kind::Field) {
+ // 5.c. Let value be undefined.
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL? CALLEE THIS undefined
+ return false;
+ }
+ } else if (kind == Kind::Getter || kind == Kind::Method ||
+ kind == Kind::Setter) {
+ // 5.d. If kind is method, set value to elementRecord.[[Value]].
+ // 5.e. Else if kind is getter, set value to elementRecord.[[Get]].
+ // 5.f. Else if kind is setter, set value to elementRecord.[[Set]].
+ // The DecoratorEmitter expects the method to already be on the stack.
+ // We dup the value here so we can use it as an argument to the decorator.
+ if (!bce_->emitDupAt(2)) {
+ // [stack] VAL CALLEE THIS VAL
+ return false;
+ }
+ }
+ // 5.g. Else if kind is accessor, then
+ // 5.g.i. Set value to OrdinaryObjectCreate(%Object.prototype%).
+ // 5.g.ii. Perform ! CreateDataPropertyOrThrow(accessor, "get",
+ // elementRecord.[[Get]]). 5.g.iii. Perform
+ // ! CreateDataPropertyOrThrow(accessor, "set",
+ // elementRecord.[[Set]]).
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // 5.b. Let context be CreateDecoratorContextObject(kind, key,
+ // extraInitializers, decorationState, isStatic).
+ if (!emitCreateDecoratorContextObject(kind, key, isStatic,
+ decorator->pn_pos)) {
+ // [stack] VAL? CALLEE THIS VAL
+ // context
+ return false;
+ }
+
+ // 5.h. Let newValue be ? Call(decorator, undefined, « value, context
+ // »).
+ return cone.emitEnd(2, decorator->pn_pos.begin);
+ // [stack] VAL? RETVAL
+}
+
+bool DecoratorEmitter::emitCreateDecoratorAccessObject() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800725.
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(0)) {
+ return false;
+ }
+ return oe.emitEnd();
+}
+
+bool DecoratorEmitter::emitCheckIsUndefined() {
+ // This emits code to check if the value at the top of the stack is
+ // undefined. The value is left on the stack.
+ // [stack] VAL
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] VAL VAL
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL VAL undefined
+ return false;
+ }
+ return bce_->emit1(JSOp::Eq);
+ // [stack] VAL ISUNDEFINED
+}
+
+bool DecoratorEmitter::emitCheckIsCallable() {
+ // This emits code to check if the value at the top of the stack is
+ // callable. The value is left on the stack.
+ // [stack] VAL
+ if (!bce_->emitAtomOp(JSOp::GetIntrinsic,
+ TaggedParserAtomIndex::WellKnown::IsCallable())) {
+ // [stack] VAL ISCALLABLE
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL ISCALLABLE UNDEFINED
+ return false;
+ }
+ if (!bce_->emitDupAt(2)) {
+ // [stack] VAL ISCALLABLE UNDEFINED VAL
+ return false;
+ }
+ return bce_->emitCall(JSOp::Call, 1);
+ // [stack] VAL ISCALLABLE_RESULT
+}
+
+bool DecoratorEmitter::emitCreateAddInitializerFunction() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(0)) {
+ return false;
+ }
+ return oe.emitEnd();
+}
+
+bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
+ ParseNode* key,
+ bool isStatic,
+ TokenPos pos) {
+ // 1. Let contextObj be OrdinaryObjectCreate(%Object.prototype%).
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(/* propertyCount */ 6)) {
+ // [stack] context
+ return false;
+ }
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+
+ if (kind == Kind::Method) {
+ // 2. If kind is method, let kindStr be "method".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::method())) {
+ // [stack] context "method"
+ return false;
+ }
+ } else if (kind == Kind::Getter) {
+ // 3. Else if kind is getter, let kindStr be "getter".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::getter())) {
+ // [stack] context "getter"
+ return false;
+ }
+ } else if (kind == Kind::Setter) {
+ // 4. Else if kind is setter, let kindStr be "setter".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::setter())) {
+ // [stack] context "setter"
+ return false;
+ }
+ } else if (kind == Kind::Field) {
+ // 6. Else if kind is field, let kindStr be "field".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::field())) {
+ // [stack] context "field"
+ return false;
+ }
+ } else {
+ // clang-format off
+
+
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793960.
+ // 5. Else if kind is accessor, let kindStr be "accessor".
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // 7. Else,
+ // a. Assert: kind is class.
+ // b. Let kindStr be "class".
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963.
+ // clang-format on
+ return false;
+ }
+
+ // 8. Perform ! CreateDataPropertyOrThrow(contextObj, "kind", kindStr).
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::kind())) {
+ // [stack] context
+ return false;
+ }
+ // 9. If kind is not class, then
+ if (kind != Kind::Class) {
+ // 9.a. Perform ! CreateDataPropertyOrThrow(contextObj, "access",
+ // CreateDecoratorAccessObject(kind, name)).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!emitCreateDecoratorAccessObject()) {
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::access())) {
+ // [stack] context
+ return false;
+ }
+ // 9.b. If isStatic is present, perform
+ // ! CreateDataPropertyOrThrow(contextObj, "static", isStatic).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!bce_->emit1(isStatic ? JSOp::True : JSOp::False)) {
+ // [stack] context isStatic
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::static_())) {
+ // [stack] context
+ return false;
+ }
+ // 9.c. If name is a Private Name, then
+ // 9.c.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private",
+ // true).
+ // 9.d. Else,
+ // 9.d.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private",
+ // false).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!bce_->emit1(key->isKind(ParseNodeKind::PrivateName) ? JSOp::True
+ : JSOp::False)) {
+ // [stack] context private
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::private_())) {
+ // [stack] context
+ return false;
+ }
+ // 9.c.ii. Perform ! CreateDataPropertyOrThrow(contextObj, "name",
+ // name.[[Description]]).
+ //
+ // 9.d.ii. Perform
+ // ! CreateDataPropertyOrThrow(contextObj, "name", name).)
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (key->is<NameNode>()) {
+ if (!bce_->emitStringOp(JSOp::String, key->as<NameNode>().atom())) {
+ return false;
+ }
+ } else {
+ if (!emitPropertyKey(key)) {
+ return false;
+ }
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::name())) {
+ // [stack] context
+ return false;
+ }
+ } else {
+ // 10. Else,
+ // 10.a. Perform ! CreateDataPropertyOrThrow(contextObj, "name", name).
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963
+ return false;
+ }
+ // 11. Let addInitializer be CreateAddInitializerFunction(initializers,
+ // decorationState).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!emitCreateAddInitializerFunction()) {
+ // [stack] context addInitializer
+ return false;
+ }
+ // 12. Perform ! CreateDataPropertyOrThrow(contextObj, "addInitializer",
+ // addInitializer).
+ if (!oe.emitInit(
+ frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::addInitializer())) {
+ // [stack] context
+ return false;
+ }
+ // 13. Return contextObj.
+ return oe.emitEnd();
+}