Bug 1793961 - Implement code generation for accessor decorators; r=arai

Differential Revision: https://phabricator.services.mozilla.com/D182084
This commit is contained in:
Dan Minor 2023-07-12 08:28:20 +00:00
Родитель 89384c4be2
Коммит 20a0efd9ae
10 изменённых файлов: 923 добавлений и 166 удалений

Просмотреть файл

@ -9678,14 +9678,31 @@ static FunctionNode* GetInitializer(ParseNode* node, bool isStaticContext) {
: node->as<StaticClassBlock>().function();
}
static bool IsPrivateInstanceAccessor(const ClassMethod* classMethod) {
return !classMethod->isStatic() &&
classMethod->name().isKind(ParseNodeKind::PrivateName) &&
classMethod->accessorType() != AccessorType::None;
}
bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce,
ListNode* obj,
FieldPlacement placement) {
// FieldPlacement::Instance
// [stack] HOMEOBJ HERITAGE?
FieldPlacement placement
#ifdef ENABLE_DECORATORS
,
bool hasHeritage
#endif
) {
// FieldPlacement::Instance, hasHeritage == false
// [stack] HOMEOBJ
//
// FieldPlacement::Instance, hasHeritage == true
// [stack] HOMEOBJ HERITAGE
//
// FieldPlacement::Static
// [stack] CTOR HOMEOBJ
#ifdef ENABLE_DECORATORS
MOZ_ASSERT_IF(placement == FieldPlacement::Static, !hasHeritage);
#endif
mozilla::Maybe<MemberInitializers> memberInitializers =
setupMemberInitializers(obj, placement);
if (!memberInitializers) {
@ -9769,18 +9786,252 @@ bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce,
}
if (field->decorators() && !field->decorators()->empty()) {
DecoratorEmitter de(this);
if (!de.emitApplyDecoratorsToFieldDefinition(
&field->name(), field->decorators(), field->isStatic())) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITIALIZERS
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITIALIZERS
return false;
}
if (!field->hasAccessor()) {
if (!de.emitApplyDecoratorsToFieldDefinition(
&field->name(), field->decorators(), field->isStatic())) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS
return false;
}
} else {
ClassMethod* accessorGetterNode = field->accessorGetterNode();
auto accessorGetterKeyAtom =
accessorGetterNode->left()->as<NameNode>().atom();
ClassMethod* accessorSetterNode = field->accessorSetterNode();
auto accessorSetterKeyAtom =
accessorSetterNode->left()->as<NameNode>().atom();
if (!IsPrivateInstanceAccessor(accessorGetterNode)) {
if (!emitTree(&accessorGetterNode->method())) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX GETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX GETTER
return false;
}
if (!emitTree(&accessorSetterNode->method())) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX GETTER SETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX GETTER SETTER
return false;
}
} else {
MOZ_ASSERT(IsPrivateInstanceAccessor(accessorSetterNode));
auto getAccessor = [this](
ClassMethod* classMethod,
TaggedParserAtomIndex& updatedAtom) -> bool {
// [stack]
// Synthesize a name for the lexical variable that will store the
// private method body.
TaggedParserAtomIndex name =
classMethod->name().as<NameNode>().atom();
AccessorType accessorType = classMethod->accessorType();
StringBuffer storedMethodName(fc);
if (!storedMethodName.append(parserAtoms(), name)) {
return false;
}
if (!storedMethodName.append(accessorType == AccessorType::Getter
? ".getter"
: ".setter")) {
return false;
}
updatedAtom = storedMethodName.finishParserAtom(parserAtoms(), fc);
if (!updatedAtom) {
return false;
}
return emitGetName(updatedAtom);
// [stack] ACCESSOR
};
if (!getAccessor(accessorGetterNode, accessorGetterKeyAtom)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX GETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX GETTER
return false;
};
if (!getAccessor(accessorSetterNode, accessorSetterKeyAtom)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX GETTER SETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX GETTER SETTER
return false;
};
}
if (!de.emitApplyDecoratorsToAccessorDefinition(
&field->name(), field->decorators(), field->isStatic())) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX GETTER SETTER INITS
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX GETTER SETTER INITS
return false;
}
if (!emitUnpickN(2)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS GETTER SETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS GETTER SETTER
return false;
}
if (!IsPrivateInstanceAccessor(accessorGetterNode)) {
if (!isStatic) {
if (!emitPickN(hasHeritage ? 6 : 5)) {
// [stack] HERITAGE? ARRAY INDEX INITS GETTER SETTER HOMEOBJ
return false;
}
} else {
if (!emitPickN(6)) {
// [stack] HOMEOBJ ARRAY INDEX INITS GETTER SETTER CTOR
return false;
}
if (!emitPickN(6)) {
// [stack] ARRAY INDEX INITS GETTER SETTER CTOR HOMEOBJ
return false;
}
}
PropertyEmitter::Kind kind = field->isStatic()
? PropertyEmitter::Kind::Static
: PropertyEmitter::Kind::Prototype;
if (!accessorGetterNode->name().isKind(ParseNodeKind::PrivateName)) {
MOZ_ASSERT(
!accessorSetterNode->name().isKind(ParseNodeKind::PrivateName));
if (!ce.prepareForPropValue(propdef->pn_pos.begin, kind)) {
// [stack] HERITAGE? ARRAY INDEX INITS GETTER SETTER HOMEOBJ
// or:
// [stack] ARRAY INDEX INITS GETTER SETTER CTOR HOMEOBJ CTOR
return false;
}
if (!emitPickN(isStatic ? 3 : 1)) {
// [stack] HERITAGE? ARRAY INDEX INITS GETTER HOMEOBJ SETTER
// or:
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ CTOR SETTER
return false;
}
if (!ce.emitInit(AccessorType::Setter, accessorSetterKeyAtom)) {
// [stack] HERITAGE? ARRAY INDEX INITS GETTER HOMEOBJ
// or:
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ
return false;
}
if (!ce.prepareForPropValue(propdef->pn_pos.begin, kind)) {
// [stack] HERITAGE? ARRAY INDEX INITS GETTER HOMEOBJ
// or:
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ CTOR
return false;
}
if (!emitPickN(isStatic ? 3 : 1)) {
// [stack] HERITAGE? ARRAY INDEX INITS HOMEOBJ GETTER
// or:
// [stack] ARRAY INDEX INITS CTOR HOMEOBJ CTOR GETTER
return false;
}
if (!ce.emitInit(AccessorType::Getter, accessorGetterKeyAtom)) {
// [stack] HERITAGE? ARRAY INDEX INITS HOMEOBJ
// or:
// [stack] ARRAY INDEX INITS CTOR HOMEOBJ
return false;
}
} else {
MOZ_ASSERT(isStatic);
// The getter and setter share the same name.
if (!emitNewPrivateName(accessorSetterKeyAtom,
accessorSetterKeyAtom)) {
return false;
}
if (!ce.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) {
// [stack] ARRAY INDEX INITS GETTER SETTER CTOR HOMEOBJ CTOR
return false;
}
if (!emitGetPrivateName(
&accessorSetterNode->name().as<NameNode>())) {
// [stack] ARRAY INDEX INITS GETTER SETTER CTOR HOMEOBJ CTOR
// KEY
return false;
}
if (!emitPickN(4)) {
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ CTOR KEY
// SETTER
return false;
}
if (!ce.emitPrivateStaticMethod(AccessorType::Setter)) {
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ
return false;
}
if (!ce.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) {
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ CTOR
return false;
}
if (!emitGetPrivateName(
&accessorGetterNode->name().as<NameNode>())) {
// [stack] ARRAY INDEX INITS GETTER CTOR HOMEOBJ CTOR KEY
return false;
}
if (!emitPickN(4)) {
// [stack] ARRAY INDEX INITS CTOR HOMEOBJ CTOR KEY GETTER
return false;
}
if (!ce.emitPrivateStaticMethod(AccessorType::Getter)) {
// [stack] ARRAY INDEX INITS CTOR HOMEOBJ
return false;
}
}
if (!isStatic) {
if (!emitUnpickN(hasHeritage ? 4 : 3)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS
return false;
}
} else {
if (!emitUnpickN(4)) {
// [stack] HOMEOBJ ARRAY INDEX INITS CTOR
return false;
}
if (!emitUnpickN(4)) {
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS
return false;
}
}
} else {
MOZ_ASSERT(IsPrivateInstanceAccessor(accessorSetterNode));
if (!emitLexicalInitialization(accessorSetterKeyAtom)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS GETTER SETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS GETTER SETTER
return false;
}
if (!emitPopN(1)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS GETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS GETTER
return false;
}
if (!emitLexicalInitialization(accessorGetterKeyAtom)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS GETTER
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS GETTER
return false;
}
if (!emitPopN(1)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITS
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX INITS
return false;
}
}
}
if (!emit1(JSOp::InitElemInc)) {
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX
// [stack] HOMEOBJ HERITAGE? ARRAY INDEX
// or:
// [stack] CTOR HOMEOBJ ARRAY INDEX
// [stack] CTOR HOMEOBJ ARRAY INDEX
return false;
}
}
@ -9788,9 +10039,9 @@ bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce,
// Pop INDEX
if (!emitPopN(1)) {
// [stack] HOMEOBJ HERITAGE? ARRAY
// [stack] HOMEOBJ HERITAGE? ARRAY
// or:
// [stack] CTOR HOMEOBJ ARRAY
// [stack] CTOR HOMEOBJ ARRAY
return false;
}
#endif
@ -9805,12 +10056,6 @@ bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce,
return true;
}
static bool IsPrivateInstanceAccessor(const ClassMethod* classMethod) {
return !classMethod->isStatic() &&
classMethod->name().isKind(ParseNodeKind::PrivateName) &&
classMethod->accessorType() != AccessorType::None;
}
bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce,
ListNode* obj) {
for (ParseNode* propdef : obj->contents()) {
@ -11345,7 +11590,12 @@ bool BytecodeEmitter::emitClass(
// Any class with field initializers will have a constructor
if (!emitCreateMemberInitializers(ce, classMembers,
FieldPlacement::Instance)) {
FieldPlacement::Instance
#ifdef ENABLE_DECORATORS
,
isDerived
#endif
)) {
return false;
}
}
@ -11383,7 +11633,12 @@ bool BytecodeEmitter::emitClass(
return false;
}
if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static)) {
if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static
#ifdef ENABLE_DECORATORS
,
false
#endif
)) {
return false;
}

Просмотреть файл

@ -657,7 +657,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
FieldPlacement placement);
[[nodiscard]] bool emitCreateMemberInitializers(ClassEmitter& ce,
ListNode* obj,
FieldPlacement placement);
FieldPlacement placement
#ifdef ENABLE_DECORATORS
,
bool hasHeritage
#endif
);
const MemberInitializers& findMemberInitializersForCall();
[[nodiscard]] bool emitInitializeInstanceMembers(
bool isDerivedClassConstructor);

Просмотреть файл

@ -22,7 +22,7 @@ DecoratorEmitter::DecoratorEmitter(BytecodeEmitter* bce) : bce_(bce) {}
bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
DecoratorEmitter::Kind kind, ParseNode* key, ListNode* decorators,
bool isStatic) {
MOZ_ASSERT(kind != Kind::Field);
MOZ_ASSERT(kind != Kind::Field && kind != Kind::Accessor);
// 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
@ -31,16 +31,16 @@ bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
// 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.
// Step 1. Let decorators be elementRecord.[[Decorators]].
// Step 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
// Step 3. Let key be elementRecord.[[Key]].
// Step 4. Let kind be elementRecord.[[Kind]].
// Step 5. For each element decorator of decorators, do
for (ParseNode* decorator : decorators->contents()) {
// 5.a. Let decorationState be the Record { [[Finished]]: false }.
// Step 5.a. Let decorationState be the Record { [[Finished]]: false }.
if (!emitDecorationState()) {
return false;
}
@ -49,7 +49,7 @@ bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
return false;
}
// 5.i. Set decorationState.[[Finished]] to true.
// Step 5.i. Set decorationState.[[Finished]] to true.
if (!emitUpdateDecorationState()) {
return false;
}
@ -82,7 +82,7 @@ bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
return false;
}
// 5.l.i. If IsCallable(newValue) is true, then
// Step 5.l.i. If IsCallable(newValue) is true, then
if (!emitCheckIsCallable()) {
// [stack] VAL RETVAL ISCALLABLE_RESULT
return false;
@ -92,25 +92,8 @@ bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
// [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).
// Step 5.l. Else,
// Step 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.
@ -122,9 +105,10 @@ bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
// [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.
// Step 5.j.ii. Else if initializer is not undefined, throw a TypeError
// exception.
// Step 5.l.ii. Else if newValue is not undefined, throw a
// TypeError exception.
if (!ie.emitElse()) {
return false;
}
@ -157,8 +141,8 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
// 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.
// Step 1. Let decorators be elementRecord.[[Decorators]].
// Step 2. If decorators is empty, return unused.
// This is checked by the caller.
MOZ_ASSERT(!decorators->empty());
@ -184,11 +168,11 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
return false;
}
// 3. Let key be elementRecord.[[Key]].
// 4. Let kind be elementRecord.[[Kind]].
// 5. For each element decorator of decorators, do
// Step 3. Let key be elementRecord.[[Key]].
// Step 4. Let kind be elementRecord.[[Kind]].
// Step 5. For each element decorator of decorators, do
for (ParseNode* decorator : decorators->contents()) {
// 5.a. Let decorationState be the Record { [[Finished]]: false }.
// Step 5.a. Let decorationState be the Record { [[Finished]]: false }.
if (!emitDecorationState()) {
return false;
}
@ -198,7 +182,7 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
return false;
}
// 5.i. Set decorationState.[[Finished]] to true.
// Step 5.i. Set decorationState.[[Finished]] to true.
if (!emitUpdateDecorationState()) {
// [stack] ARRAY INDEX RETVAL
return false;
@ -232,7 +216,7 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
return false;
}
// 5.l.i. If IsCallable(newValue) is true, then
// Step 5.l.i. If IsCallable(newValue) is true, then
if (!emitCheckIsCallable()) {
// [stack] ARRAY INDEX RETVAL ISCALLABLE_RESULT
return false;
@ -243,17 +227,18 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
return false;
}
// 5.j. If kind is field, then
// 5.j.i. If IsCallable(initializer) is true, append initializer
// to elementRecord.[[Initializers]].
// Step 5.j. If kind is field, then
// Step 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.
// Step 5.j.ii. Else if initializer is not undefined, throw a TypeError
// exception.
// Step 5.l.ii. Else if newValue is not undefined, throw a
// TypeError exception.
if (!ie.emitElse()) {
return false;
}
@ -278,16 +263,204 @@ bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
// [stack] ARRAY
}
bool DecoratorEmitter::emitApplyDecoratorsToAccessorDefinition(
ParseNode* key, ListNode* decorators, bool isStatic) {
// This method creates a new array to contain initializers added by decorators
// to the stack. start:
// [stack] GETTER SETTER
// end:
// [stack] GETTER SETTER ARRAY
MOZ_ASSERT(key->is<NameNode>());
// Decorators Proposal
// https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
// Step 1. Let decorators be elementRecord.[[Decorators]].
// Step 2. If decorators is empty, return unused.
// This is checked by the caller.
MOZ_ASSERT(!decorators->empty());
// If we're applying 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] GETTER SETTER ARRAY
return false;
}
if (!bce_->emitGetPrivateName(&key->as<NameNode>())) {
// [stack] GETTER SETTER ARRAY NAME
return false;
}
if (!bce_->emitUint32Operand(JSOp::InitElemArray, 0)) {
// [stack] GETTER SETTER ARRAY
return false;
}
if (!bce_->emit1(JSOp::One)) {
// [stack] GETTER SETTER ARRAY INDEX
return false;
}
// Step 3. Let key be elementRecord.[[Key]].
// Step 4. Let kind be elementRecord.[[Kind]].
// Step 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;
}
// Step 5.g.i. Set value to OrdinaryObjectCreate(%Object.prototype%).
ObjectEmitter oe(bce_);
if (!oe.emitObject(2)) {
// [stack] GETTER SETTER ARRAY INDEX VALUE
return false;
}
// Step 5.g.ii. Perform ! CreateDataPropertyOrThrow(value, "get",
// elementRecord.[[Get]]).
if (!oe.prepareForPropValue(decorator->pn_pos.begin,
PropertyEmitter::Kind::Prototype)) {
return false;
}
if (!bce_->emitDupAt(4)) {
// [stack] GETTER SETTER ARRAY INDEX VALUE GETTER
return false;
}
if (!oe.emitInit(frontend::AccessorType::None,
frontend::TaggedParserAtomIndex::WellKnown::get())) {
// [stack] GETTER SETTER ARRAY INDEX VALUE
return false;
}
// Step 5.g.iii. Perform ! CreateDataPropertyOrThrow(value, "set",
// elementRecord.[[Set]]).
if (!oe.prepareForPropValue(decorator->pn_pos.begin,
PropertyEmitter::Kind::Prototype)) {
return false;
}
if (!bce_->emitDupAt(3)) {
// [stack] GETTER SETTER ARRAY INDEX VALUE SETTER
return false;
}
if (!oe.emitInit(frontend::AccessorType::None,
frontend::TaggedParserAtomIndex::WellKnown::set())) {
// [stack] GETTER SETTER ARRAY INDEX VALUE
return false;
}
if (!oe.emitEnd()) {
// [stack] GETTER SETTER ARRAY INDEX VALUE
return false;
}
// Step 5.j. Let newValue be ? Call(decorator, decoratorReceiver,
// « value, context »).
if (!emitCallDecorator(Kind::Accessor, key, isStatic, decorator)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
// Step 5.k. Set decorationState.[[Finished]] to true.
if (!emitUpdateDecorationState()) {
// [stack] GETTER SETTER 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] GETTER SETTER ARRAY INDEX RETVAL ISUNDEFINED
return false;
}
if (!ie.emitThenElse()) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
// Pop the undefined RETVAL from the stack, leaving the original values in
// place.
if (!bce_->emitPopN(1)) {
// [stack] GETTER SETTER ARRAY INDEX
return false;
}
if (!ie.emitElse()) {
return false;
}
// Step 5.k. Else if kind is accessor, then
// Step 5.k.ii. Else if newValue is not undefined, throw a TypeError
// exception. (Reordered)
if (!bce_->emit2(JSOp::CheckIsObj,
uint8_t(CheckIsObjectKind::DecoratorReturn))) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
// Step 5.k.i. If newValue is an Object, then
// Step 5.k.i.1. Let newGetter be ? Get(newValue, "get").
// Step 5.k.i.2. If IsCallable(newGetter) is true, set
// elementRecord.[[Get]] to newGetter.
// Step 5.k.i.3. Else if newGetter is not undefined, throw a
// TypeError exception.
if (!emitHandleNewValueField(
frontend::TaggedParserAtomIndex::WellKnown::get(), 5)) {
return false;
}
// Step 5.k.i.4. Let newSetter be ? Get(newValue, "set").
// Step 5.k.i.5. If IsCallable(newSetter) is true, set
// elementRecord.[[Set]] to newSetter.
// Step 5.k.i.6. Else if newSetter is not undefined, throw a
// TypeError exception.
if (!emitHandleNewValueField(
frontend::TaggedParserAtomIndex::WellKnown::set(), 4)) {
return false;
}
// Step 5.k.i.7. Let initializer be ? Get(newValue, "init").
// Step 5.k.i.8. If IsCallable(initializer) is true, append
// initializer to elementRecord.[[Initializers]].
// Step 5.k.i.9. Else if initializer is not undefined, throw a
// TypeError exception.
if (!emitHandleNewValueField(
frontend::TaggedParserAtomIndex::WellKnown::init(), 0)) {
return false;
}
// Pop RETVAL from stack
if (!bce_->emitPopN(1)) {
// [stack] GETTER SETTER ARRAY INDEX
return false;
}
if (!ie.emitEnd()) {
return false;
}
}
// Pop INDEX
return bce_->emitPopN(1);
// [stack] GETTER SETTER 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]].
// Step 1. Assert: elementRecord.[[Kind]] is field or accessor.
// Step 2. If elementRecord.[[BackingStorageKey]] is present, let fieldName be
// elementRecord.[[BackingStorageKey]].
// Step 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
@ -320,7 +493,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
return false;
}
// 4. Let initValue be undefined.
// Step 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
@ -331,7 +504,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
}
if (!bce_->emit2(JSOp::Pick, 2)) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS
// [stack] THIS FIELDNAME VALUE INITIALIZERS
return false;
}
@ -352,7 +525,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
return false;
}
// 5. For each element initializer of elementRecord.[[Initializers]], do
// Step 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
@ -369,7 +542,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
if (!bce_->emitDupAt(2)) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX
// LENGTH
// LENGTH
return false;
}
@ -378,7 +551,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
return false;
}
// a. Set initValue to ? Call(initializer, receiver, « initValue »).
// Step 5.a. Set initValue to ? Call(initializer, receiver, « initValue»).
if (!wh.emitBody()) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
return false;
@ -386,13 +559,13 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
if (!bce_->emitDupAt(2)) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
// INITIALIZERS
// INITIALIZERS
return false;
}
if (!bce_->emitDupAt(1)) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
// INITIALIZERS INDEX
// INITIALIZERS INDEX
return false;
}
@ -402,27 +575,25 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
}
if (!bce_->emitDupAt(6)) {
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC
// THIS
// [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
// [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
// [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
// [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
return false;
}
@ -436,12 +607,12 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
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).
// Step 6. If fieldName is a Private Name, then
// Step 6.a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
// Step 7. Else,
// Step 7.a. Assert: IsPropertyKey(fieldName) is true.
// Step 7.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)) {
@ -454,7 +625,7 @@ bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
return false;
}
// 8. Return unused.
// Step 8. Return unused.
return bce_->emitPopN(1);
// [stack]
}
@ -507,6 +678,11 @@ bool DecoratorEmitter::emitUpdateDecorationState() {
ParseNode* key,
bool isStatic,
ParseNode* decorator) {
// Except for fields, this method expects the value to be passed
// to the decorator to be on top of the stack. For methods, getters and
// setters this is the method itself. For accessors it is an object
// containing the getter and setter associated with the accessor.
// [stack] VAL?
// Prepare to call decorator
CallOrNewEmitter cone(bce_, JSOp::Call,
CallOrNewEmitter::ArgumentsKind::Other,
@ -580,41 +756,42 @@ bool DecoratorEmitter::emitUpdateDecorationState() {
}
if (kind == Kind::Field) {
// 5.c. Let value be undefined.
// Step 5.c. Let value be undefined.
if (!bce_->emit1(JSOp::Undefined)) {
// [stack] VAL? CALLEE THIS undefined
// [stack] 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]].
// Step 5.d. If kind is method, set value to elementRecord.[[Value]].
// Step 5.e. Else if kind is getter, set value to elementRecord.[[Get]].
// Step 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;
}
} else {
// Step 5.g. Else if kind is accessor, then
// Step 5.g.i. Set value to OrdinaryObjectCreate(%Object.prototype%).
// For accessor decorators, we've already created the value object prior
// to calling this method.
MOZ_ASSERT(kind == Kind::Accessor);
if (!bce_->emitPickN(2)) {
// [stack] 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).
// Step 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
// [stack] VAL? CALLEE THIS VAL context
return false;
}
// 5.h. Let newValue be ? Call(decorator, undefined, « value, context
// »).
// Step 5.h. Let newValue be ? Call(decorator, undefined, « value, context»).
return cone.emitEnd(2, decorator->pn_pos.begin);
// [stack] VAL? RETVAL
}
@ -678,7 +855,7 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
ParseNode* key,
bool isStatic,
TokenPos pos) {
// 1. Let contextObj be OrdinaryObjectCreate(%Object.prototype%).
// Step 1. Let contextObj be OrdinaryObjectCreate(%Object.prototype%).
ObjectEmitter oe(bce_);
if (!oe.emitObject(/* propertyCount */ 6)) {
// [stack] context
@ -689,7 +866,7 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
}
if (kind == Kind::Method) {
// 2. If kind is method, let kindStr be "method".
// Step 2. If kind is method, let kindStr be "method".
if (!bce_->emitStringOp(
JSOp::String,
frontend::TaggedParserAtomIndex::WellKnown::method())) {
@ -697,7 +874,7 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
return false;
}
} else if (kind == Kind::Getter) {
// 3. Else if kind is getter, let kindStr be "getter".
// Step 3. Else if kind is getter, let kindStr be "getter".
if (!bce_->emitStringOp(
JSOp::String,
frontend::TaggedParserAtomIndex::WellKnown::getter())) {
@ -705,15 +882,23 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
return false;
}
} else if (kind == Kind::Setter) {
// 4. Else if kind is setter, let kindStr be "setter".
// Step 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::Accessor) {
// Step 5. Else if kind is accessor, let kindStr be "accessor".
if (!bce_->emitStringOp(
JSOp::String,
frontend::TaggedParserAtomIndex::WellKnown::accessor())) {
// [stack] context "accessor"
return false;
}
} else if (kind == Kind::Field) {
// 6. Else if kind is field, let kindStr be "field".
// Step 6. Else if kind is field, let kindStr be "field".
if (!bce_->emitStringOp(
JSOp::String,
frontend::TaggedParserAtomIndex::WellKnown::field())) {
@ -722,29 +907,24 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
}
} 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".
// Step 7. Else,
// Step 7.a. Assert: kind is class.
// Step 7.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).
// Step 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
// Step 9. If kind is not class, then
if (kind != Kind::Class) {
// 9.a. Perform ! CreateDataPropertyOrThrow(contextObj, "access",
// CreateDecoratorAccessObject(kind, name)).
// Step 9.a. Perform ! CreateDataPropertyOrThrow(contextObj, "access",
// CreateDecoratorAccessObject(kind, name)).
if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
return false;
}
@ -756,8 +936,8 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
// [stack] context
return false;
}
// 9.b. If isStatic is present, perform
// ! CreateDataPropertyOrThrow(contextObj, "static", isStatic).
// Step 9.b. If isStatic is present, perform
// ! CreateDataPropertyOrThrow(contextObj, "static", isStatic).
if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
return false;
}
@ -770,12 +950,11 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
// [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).
// Step 9.c. If name is a Private Name, then
// Step 9.c.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private",
// true).
// Step 9.d. Else, Step 9.d.i. Perform
// ! CreateDataPropertyOrThrow(contextObj, "private", false).
if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
return false;
}
@ -789,11 +968,11 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
// [stack] context
return false;
}
// 9.c.ii. Perform ! CreateDataPropertyOrThrow(contextObj, "name",
// name.[[Description]]).
// Step 9.c.ii. Perform ! CreateDataPropertyOrThrow(contextObj,
// "name", name.[[Description]]).
//
// 9.d.ii. Perform
// ! CreateDataPropertyOrThrow(contextObj, "name", name).)
// Step 9.d.ii. Perform ! CreateDataPropertyOrThrow(contextObj,
// "name", name.[[Description]]).)
if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
return false;
}
@ -812,13 +991,13 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
return false;
}
} else {
// 10. Else,
// 10.a. Perform ! CreateDataPropertyOrThrow(contextObj, "name", name).
// Steo 10. Else,
// Step 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).
// Step 11. Let addInitializer be CreateAddInitializerFunction(initializers,
// decorationState).
if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
return false;
}
@ -826,14 +1005,131 @@ bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
// [stack] context addInitializer
return false;
}
// 12. Perform ! CreateDataPropertyOrThrow(contextObj, "addInitializer",
// addInitializer).
// Step 12. Perform ! CreateDataPropertyOrThrow(contextObj, "addInitializer",
// addInitializer).
if (!oe.emitInit(
frontend::AccessorType::None,
frontend::TaggedParserAtomIndex::WellKnown::addInitializer())) {
// [stack] context
return false;
}
// 13. Return contextObj.
// Step 13. Return contextObj.
return oe.emitEnd();
}
bool DecoratorEmitter::emitHandleNewValueField(TaggedParserAtomIndex atom,
int8_t offset) {
// This function handles retrieving the new value from a field in the RETVAL
// object returned by the decorator. The `atom` is the atom of the field to be
// examined. The offset is the offset of the existing value on the stack,
// which will be replaced by the new value. If the offset is zero, we're
// handling the initializer which will be added to the array of initializers
// already on the stack.
// [stack] GETTER SETTER ARRAY INDEX RETVAL
if (!bce_->emit1(JSOp::Dup)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL RETVAL
return false;
}
if (!bce_->emitStringOp(JSOp::String, atom)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL RETVAL ATOM
return false;
}
if (!bce_->emit1(JSOp::GetElem)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
// NEW_VALUE
return false;
}
IfEmitter ifCallable(bce_);
if (!ifCallable.emitIf(mozilla::Nothing())) {
return false;
}
if (!emitCheckIsUndefined()) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
// NEW_VALUE ISUNDEFINED
return false;
}
if (!ifCallable.emitThenElse()) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
// NEW_VALUE
return false;
}
// Pop the undefined getter or setter from the stack, leaving the original
// values in place.
if (!bce_->emitPopN(1)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
if (!ifCallable.emitElseIf(mozilla::Nothing())) {
return false;
}
if (!emitCheckIsCallable()) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
// NEW_VALUE ISCALLABLE_RESULT
return false;
}
if (!ifCallable.emitThenElse()) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
// NEW_VALUE
return false;
}
if (offset != 0) {
if (!bce_->emitPickN(offset)) {
// [stack] GETTER? SETTER? ARRAY INDEX RETVAL
// NEW_VALUE GETTER_OR_SETTER
return false;
}
if (!bce_->emitPopN(1)) {
// [stack] GETTER? SETTER? ARRAY INDEX RETVAL
// NEW_VALUE
return false;
}
if (!bce_->emitUnpickN(offset - 1)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
} else {
// Offset == 0 means we're retrieving the initializer, this is
// stored in the initializer array on the stack.
if (!bce_->emit1(JSOp::Swap)) {
// [stack] GETTER SETTER ARRAY INDEX NEW_VALUE RETVAL
return false;
}
if (!bce_->emitUnpickN(3)) {
// [stack] GETTER SETTER RETVAL ARRAY INDEX NEW_VALUE
return false;
}
if (!bce_->emit1(JSOp::InitElemInc)) {
// [stack] GETTER SETTER RETVAL ARRAY INDEX
return false;
}
if (!bce_->emitPickN(2)) {
// [stack] GETTER SETTER ARRAY INDEX RETVAL
return false;
}
}
if (!ifCallable.emitElse()) {
return false;
}
if (!bce_->emitPopN(1)) {
// [stack] GETTER SETTER ARRAY INDEX
return false;
}
if (!bce_->emit2(JSOp::ThrowMsg,
uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) {
return false;
}
return ifCallable.emitEnd();
}

Просмотреть файл

@ -29,6 +29,9 @@ class MOZ_STACK_CLASS DecoratorEmitter {
ListNode* decorators,
bool isStatic);
[[nodiscard]] bool emitApplyDecoratorsToAccessorDefinition(
ParseNode* key, ListNode* decorators, bool isStatic);
[[nodiscard]] bool emitInitializeFieldOrAccessor();
private:
@ -52,6 +55,9 @@ class MOZ_STACK_CLASS DecoratorEmitter {
[[nodiscard]] bool emitCreateDecoratorContextObject(Kind kind, ParseNode* key,
bool isStatic,
TokenPos pos);
[[nodiscard]] bool emitHandleNewValueField(TaggedParserAtomIndex atom,
int8_t offset);
};
} /* namespace js::frontend */

Просмотреть файл

@ -624,7 +624,6 @@ bool ClassEmitter::emitDerivedClass(TaggedParserAtomIndex name,
}
bool ClassEmitter::emitInitConstructor(bool needsHomeObject) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Class ||
classState_ == ClassState::InstanceMemberInitializersEnd);

Просмотреть файл

@ -2299,6 +2299,16 @@ class ClassMethod : public BinaryNode {
class ClassField : public BinaryNode {
bool isStatic_;
#ifdef ENABLE_DECORATORS
// The accessorGetterNode_ and accessorSetterNode_ are used to store the
// getter and setter synthesized by the `accessor` keyword when they are
// decorated. Otherwise, they are null.
//
// In most cases, the accessors are not added to the class members, and the
// code generation occurs immediately prior to the decorator running. For
// non-static private methods, the accessors are added to the class members
// which causes them to be stored in lexical variables. The references here
// are used to store the names of the accessors to look up the values of these
// variables during bytecode generation.
ClassMethod* accessorGetterNode_;
ClassMethod* accessorSetterNode_;
ListNode* decorators_;
@ -2325,7 +2335,6 @@ class ClassField : public BinaryNode {
#ifdef ENABLE_DECORATORS
MOZ_ASSERT((accessorGetterNode_ == nullptr) ==
(accessorSetterNode_ == nullptr));
MOZ_ASSERT_IF(!decorators_, !accessorGetterNode_);
#endif
}

Просмотреть файл

@ -807,9 +807,13 @@ bool GeneralParser<ParseHandler, Unit>::noteDeclaredPrivateName(
PrivateNameKind kind;
switch (propType) {
case PropertyType::Field:
case PropertyType::FieldWithAccessor:
kind = PrivateNameKind::Field;
break;
case PropertyType::FieldWithAccessor:
// In this case, we create a new private field for the underlying storage,
// and use the current name for the getter and setter.
kind = PrivateNameKind::GetterSetter;
break;
case PropertyType::Method:
case PropertyType::GeneratorMethod:
case PropertyType::AsyncMethod:
@ -7849,14 +7853,21 @@ bool GeneralParser<ParseHandler, Unit>::classMember(
if (!accessorGetterNode) {
return false;
}
if (!decorators) {
// If the accessor is not decorated, add it to the class here.
// Otherwise, we'll handle this when the decorators are called.
// If the accessor is not decorated or is a non-static private field,
// add it to the class here. Otherwise, we'll handle this when the
// decorators are called. We don't need to keep a reference to the node
// after this except for non-static private accessors. Please see the
// comment in the definition of ClassField for details.
bool addAccessorImmediately =
!decorators || (!isStatic && handler_.isPrivateName(propName));
if (addAccessorImmediately) {
if (!handler_.addClassMemberDefinition(classMembers,
accessorGetterNode)) {
return false;
}
accessorGetterNode = null();
if (!handler_.isPrivateName(propName)) {
accessorGetterNode = null();
}
}
// Step 6. Let setter be MakeAutoAccessorSetter(homeObject, name,
@ -7868,14 +7879,14 @@ bool GeneralParser<ParseHandler, Unit>::classMember(
return false;
}
if (!decorators) {
// If the accessor is not decorated, add it to the class here.
// Otherwise, we'll handle this when the decorators are called.
if (addAccessorImmediately) {
if (!handler_.addClassMemberDefinition(classMembers,
accessorSetterNode)) {
return false;
}
accessorSetterNode = null();
if (!handler_.isPrivateName(propName)) {
accessorSetterNode = null();
}
}
// Step 10. Return ClassElementDefinition Record { [[Key]]: name,
@ -9116,8 +9127,16 @@ GeneralParser<ParseHandler, Unit>::synthesizeAccessor(
//
// https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter
// 2. Let setter be CreateBuiltinFunction(setterClosure, 1, "set", « »).
FunctionNodeType funNode =
synthesizeAccessorBody(propNamePos, privateStateNameAtom, syntaxKind);
StringBuffer storedMethodName(fc_);
if (!storedMethodName.append(accessorType == AccessorType::Getter ? "get"
: "set")) {
return null();
}
TaggedParserAtomIndex funNameAtom =
storedMethodName.finishParserAtom(this->parserAtoms(), fc_);
FunctionNodeType funNode = synthesizeAccessorBody(
funNameAtom, propNamePos, privateStateNameAtom, syntaxKind);
if (!funNode) {
return null();
}
@ -9136,8 +9155,8 @@ GeneralParser<ParseHandler, Unit>::synthesizeAccessor(
template <class ParseHandler, typename Unit>
typename ParseHandler::FunctionNodeType
GeneralParser<ParseHandler, Unit>::synthesizeAccessorBody(
TokenPos propNamePos, TaggedParserAtomIndex propAtom,
FunctionSyntaxKind syntaxKind) {
TaggedParserAtomIndex funNameAtom, TokenPos propNamePos,
TaggedParserAtomIndex propNameAtom, FunctionSyntaxKind syntaxKind) {
if (!abortIfSyntaxParser()) {
return null();
}
@ -9157,8 +9176,8 @@ GeneralParser<ParseHandler, Unit>::synthesizeAccessorBody(
// Create the FunctionBox and link it to the function object.
Directives directives(true);
FunctionBox* funbox =
newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags,
propNamePos.begin, directives, generatorKind, asyncKind);
newFunctionBox(funNode, funNameAtom, flags, propNamePos.begin, directives,
generatorKind, asyncKind);
if (!funbox) {
return null();
}
@ -9204,7 +9223,7 @@ GeneralParser<ParseHandler, Unit>::synthesizeAccessorBody(
return null();
}
NameNodeType privateNameNode = privateNameReference(propAtom);
NameNodeType privateNameNode = privateNameReference(propNameAtom);
if (!privateNameNode) {
return null();
}

Просмотреть файл

@ -1329,8 +1329,9 @@ class MOZ_STACK_CLASS GeneralParser : public PerHandlerParser<ParseHandler> {
FunctionSyntaxKind syntaxKind,
ClassInitializedMembers& classInitializedMembers);
FunctionNodeType synthesizeAccessorBody(TokenPos propNamePos,
TaggedParserAtomIndex atom,
FunctionNodeType synthesizeAccessorBody(TaggedParserAtomIndex funNameAtom,
TokenPos propNamePos,
TaggedParserAtomIndex propNameAtom,
FunctionSyntaxKind syntaxKind);
#endif

Просмотреть файл

@ -0,0 +1,166 @@
// |jit-test| skip-if: !getBuildConfiguration()['decorators']
load(libdir + "asserts.js");
let dec1Called = false;
// This explicitly tests the case where undefined is returned.
function dec(value, context) {
dec1Called = true;
// return undefined
}
function decorate_getter(value, context) {
return {
get: function() {
return 2 * value.get.call(this);
}
};
}
function decorate_setter(value, context) {
return {
set: function(x) {
return value.set.call(this, 2*x);
}
};
}
function decorate_initializer(value, context) {
return {
init: function(initialValue) {
return 2 * initialValue;
}
};
}
function checkDecoratorContext(kind, isPrivate, isStatic, name) {
return (value, context) => {
assertEq(typeof value, "object");
assertEq(typeof value.get, "function");
assertEq(typeof value.set, "function");
assertEq(context.kind, kind);
assertEq(typeof context.access, "object");
assertEq(context.private, isPrivate);
assertEq(context.static, isStatic);
assertEq(context.name, name);
assertEq(typeof context.addInitializer, "object");
// return undefined
}
}
class C {
@dec accessor x = 1;
@decorate_getter accessor x2 = 1;
@decorate_setter @decorate_getter accessor x3 = 1;
@decorate_initializer accessor x4 = 1;
@decorate_initializer @decorate_initializer accessor x5 = 1;
@decorate_setter @decorate_getter @decorate_initializer accessor x6 = 1;
@checkDecoratorContext("accessor", true, false, "x8 accessor storage") accessor x8 = 1;
@checkDecoratorContext("accessor", true, true, "x9 accessor storage") static accessor x9 = 1;
@checkDecoratorContext("accessor", true, false, "#x10 accessor storage") accessor #x10 = 1;
}
let c = new C();
assertEq(dec1Called, true);
assertEq(c.x, 1);
c.x = 2;
assertEq(c.x, 2);
assertEq(c.x2, 2);
assertEq(c.x3, 2);
c.x3 = 4;
assertEq(c.x3, 16);
assertEq(c.x4, 2);
assertEq(c.x5, 4);
assertEq(c.x6, 4);
c.x6 = 4;
assertEq(c.x6, 16);
class D {
@decorate_initializer accessor #x = 1;
@decorate_getter accessor #x2 = 1;
@decorate_setter @decorate_getter accessor #x3 = 1;
getX() {
return this.#x;
}
setX(v) {
this.#x = v;
}
getX2() {
return this.#x2;
}
setX2(v) {
this.#x2 = v;
}
getX3() {
return this.#x3;
}
setX3(v) {
this.#x3 = v;
}
}
let d = new D();
assertEq(d.getX(), 2);
d.setX(4);
assertEq(d.getX(), 4);
assertEq(d.getX2(), 2);
d.setX2(4);
assertEq(d.getX2(), 8);
assertEq(d.getX3(), 2);
d.setX3(4);
assertEq(d.getX3(), 16);
class E {
@decorate_getter static accessor x = 1;
}
assertEq(E.x, 2);
E.x = 2;
assertEq(E.x, 4);
class F {
@decorate_getter static accessor #x = 1;
getX() {
return F.#x;
}
setX(v) {
F.#x = v;
}
}
let f = new F();
assertEq(f.getX(), 2);
f.setX(4);
assertEq(f.getX(), 8);
assertThrowsInstanceOf(() => {
class G {
@(() => { return "hello!"; }) accessor x;
}
}, TypeError), "Returning a value other than undefined or a callable throws.";
assertThrowsInstanceOf(() => {
class G {
@(() => { return {get: "hello!"}; }) accessor x;
}
}, TypeError), "Returning a value other than undefined or a callable throws.";
assertThrowsInstanceOf(() => {
class G {
@(() => { return {set: "hello!"}; }) accessor x;
}
}, TypeError), "Returning a value other than undefined or a callable throws.";
assertThrowsInstanceOf(() => {
class G {
@(() => { return {init: "hello!"}; }) accessor x;
}
}, TypeError), "Returning a value other than undefined or a callable throws.";

Просмотреть файл

@ -285,6 +285,7 @@
MACRO_(infinity, infinity, "infinity") \
MACRO_(intersection, intersection, "intersection") \
MACRO_(Infinity, Infinity, "Infinity") \
IF_DECORATORS(MACRO_(init, init, "init")) \
MACRO_(initial, initial, "initial") \
MACRO_(InitializeCollator, InitializeCollator, "InitializeCollator") \
MACRO_(InitializeDateTimeFormat, InitializeDateTimeFormat, \