Skip to content

Commit fe07eeb

Browse files
committed
[Fix] ES2025+: GeneratorResumeAbrupt: properly handle return completions
1 parent c753fe4 commit fe07eeb

File tree

2 files changed

+52
-13
lines changed

2 files changed

+52
-13
lines changed

2025/GeneratorResumeAbrupt.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,34 @@ module.exports = function GeneratorResumeAbrupt(generator, abruptCompletion, gen
2222
var state = GeneratorValidate(generator, generatorBrand); // step 1
2323

2424
if (state === 'SUSPENDED-START') { // step 2
25-
SLOT.set(generator, '[[GeneratorState]]', 'COMPLETED'); // step 3.a
26-
SLOT.set(generator, '[[GeneratorContext]]', null); // step 3.b
27-
state = 'COMPLETED'; // step 3.c
25+
SLOT.set(generator, '[[GeneratorState]]', 'COMPLETED'); // step 2.a
26+
SLOT.set(generator, '[[GeneratorContext]]', null); // step 2.b
27+
state = 'COMPLETED'; // step 2.c
2828
}
2929

3030
var value = abruptCompletion.value();
3131

3232
if (state === 'COMPLETED') { // step 3
33-
return CreateIteratorResultObject(value, true); // steps 3.a-b
33+
if (abruptCompletion.type() === 'return') { // step 3.a
34+
return CreateIteratorResultObject(value, true); // step 3.a.i
35+
}
36+
return abruptCompletion['?'](); // step 3.b
3437
}
3538

3639
if (state !== 'SUSPENDED-YIELD') {
3740
throw new $TypeError('Assertion failed: generator state is unexpected: ' + state); // step 4
3841
}
39-
if (abruptCompletion.type() === 'return') {
40-
// due to representing `GeneratorContext` as a function, we can't safely re-invoke it, so we can't support sending it a return completion
41-
return CreateIteratorResultObject(SLOT.get(generator, '[[CloseIfAbrupt]]')(NormalCompletion(abruptCompletion.value())), true);
42-
}
4342

4443
var genContext = SLOT.get(generator, '[[GeneratorContext]]'); // step 5
4544

4645
SLOT.set(generator, '[[GeneratorState]]', 'EXECUTING'); // step 8
4746

48-
var result = genContext(value); // steps 6-7, 8-11
47+
if (abruptCompletion.type() === 'return') {
48+
// due to representing `GeneratorContext` as a function, we can't safely re-invoke it, so we can't support sending it a return completion
49+
return CreateIteratorResultObject(SLOT.get(generator, '[[CloseIfAbrupt]]')(NormalCompletion(value)), true);
50+
}
51+
52+
var result = genContext(value); // steps 6-7, 9-11
4953

5054
return result; // step 12
5155
};

test/methods/GeneratorResumeAbrupt.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,50 @@ module.exports = function (t, year, GeneratorResumeAbrupt, extras) {
8686
'generator with return completion -> done iter result'
8787
);
8888

89+
// test re-entrant return() throws TypeError
90+
var reentrantGenerator = {};
91+
SLOT.set(reentrantGenerator, '[[GeneratorState]]', 'SUSPENDED-YIELD');
92+
SLOT.set(reentrantGenerator, '[[GeneratorBrand]]', brand);
93+
SLOT.set(reentrantGenerator, '[[GeneratorContext]]', null);
94+
95+
var enterCount = 0;
96+
var reentrantCloseIfAbrupt = function () {
97+
enterCount += 1;
98+
if (enterCount > 1) {
99+
// guard against infinite recursion in case the fix is not applied
100+
throw new RangeError('re-entrant call was not prevented');
101+
}
102+
// re-entrant call while generator is executing
103+
GeneratorResumeAbrupt(reentrantGenerator, ReturnCompletion(undefined), brand);
104+
return { sentinel: true };
105+
};
106+
SLOT.set(reentrantGenerator, '[[CloseIfAbrupt]]', reentrantCloseIfAbrupt);
107+
108+
t.equal(enterCount, 0, 'closeIfAbrupt not called yet');
109+
110+
t['throws'](
111+
function () { GeneratorResumeAbrupt(reentrantGenerator, ReturnCompletion(42), brand); },
112+
TypeError,
113+
'throws TypeError when return() is called re-entrantly during [[CloseIfAbrupt]]'
114+
);
115+
116+
t.equal(enterCount, 1, 'closeIfAbrupt was entered exactly once before throwing');
117+
89118
SLOT.set(generator, '[[GeneratorState]]', 'SUSPENDED-START');
119+
t['throws'](
120+
function () { GeneratorResumeAbrupt(generator, completion, brand); },
121+
42,
122+
'SUSPENDED-START with throw completion transitions to COMPLETED then throws'
123+
);
124+
t.equal(SLOT.get(generator, '[[GeneratorState]]'), 'COMPLETED', 'state is completed');
125+
t.equal(SLOT.get(generator, '[[GeneratorContext]]'), null, 'context is unset');
126+
90127
t.deepEqual(
91-
GeneratorResumeAbrupt(generator, completion, brand),
128+
GeneratorResumeAbrupt(generator, ReturnCompletion(42), brand),
92129
{
93130
value: 42,
94131
done: true
95132
},
96-
'completed state -> done iter result'
133+
'COMPLETED state with return completion -> done iter result'
97134
);
98-
t.equal(SLOT.get(generator, '[[GeneratorState]]'), 'COMPLETED', 'state is completed');
99-
t.equal(SLOT.get(generator, '[[GeneratorContext]]'), null, 'context is unset');
100135
};

0 commit comments

Comments
 (0)