Skip to content

Commit 4ddb186

Browse files
committed
Fix confusion between Promise values and awaited values in for predicates
1 parent 9bb842e commit 4ddb186

File tree

16 files changed

+64
-30
lines changed

16 files changed

+64
-30
lines changed

async-to-promises.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,15 @@ const constantStaticMethods: { readonly [name: string]: { readonly [name: string
232232
"$": constantFunctionMethods,
233233
} as const;
234234

235+
type HelperName = "_async" | "_await" | "_empty" | "_call" | "_yield" | "_continue" | "_continueIgnored" | "_catch" | "_catchInGenerator" | "_finally" | "_finallyRethrows" | "_invoke" | "_invokeIgnored" | "_switch" | "_for" | "_do" | "_forTo" | "_forOf" | "_forOwn" | "_forIn" | "_forAwaitOf" | "_rethrow" | "_promiseResolve" | "_promiseThen" | "_AsyncGenerator";
236+
235237
// Weakly stored information on nodes
236238

237239
const originalNodeMap = new WeakMap<Node, Node>();
238240
const skipNodeSet = new WeakSet<Node>();
239241
const breakIdentifierMap = new WeakMap<Node, Identifier>();
240242
const isHelperDefinitionSet = new WeakSet<Node>();
241-
const helperNameMap = new WeakMap<Identifier | MemberExpression, string>();
243+
const helperNameMap = new WeakMap<Identifier | MemberExpression, HelperName>();
242244
const nodeIsAsyncSet = new WeakSet<Node>();
243245

244246
interface ForAwaitStatement {
@@ -307,7 +309,7 @@ interface ExtractedDeclarations {
307309

308310
interface Helper {
309311
readonly value: Node;
310-
readonly dependencies: readonly string[];
312+
readonly dependencies: readonly HelperName[];
311313
}
312314
let helpers: { [name: string]: Helper } | undefined;
313315

@@ -1117,10 +1119,8 @@ export default function ({
11171119
}
11181120
args.push(directExpression);
11191121
}
1120-
let helperName = directExpression ? (callTarget ? "_call" : "_await") : callTarget ? "_invoke" : "_continue";
1121-
if (ignoreResult) {
1122-
helperName += "Ignored";
1123-
}
1122+
const baseHelper: "_call" | "_await" | "_invoke" | "_continue" = directExpression ? (callTarget ? "_call" : "_await") : callTarget ? "_invoke" : "_continue";
1123+
const helperName: HelperName = ignoreResult ? (baseHelper + "Ignored") as HelperName : baseHelper;
11241124
if (args.length === 1) {
11251125
// Handle a few cases where a helper isn't actually necessary
11261126
switch (helperName) {
@@ -1995,12 +1995,14 @@ export default function ({
19951995
callTarget = onlyArgument;
19961996
}
19971997
// Match function() { return _await(...()); } or function() { return Promise.resolve(...()); }
1998-
if (
1999-
(types.isIdentifier(callee) || types.isMemberExpression(callee)) &&
2000-
helperNameMap.get(callee) === "_await"
2001-
) {
2002-
if (types.isCallExpression(onlyArgument) && onlyArgument.arguments.length === 0) {
2003-
callTarget = onlyArgument.callee;
1998+
if (types.isIdentifier(callee) || types.isMemberExpression(callee)) {
1999+
switch (helperNameMap.get(callee)) {
2000+
case "_await":
2001+
case "_promiseResolve":
2002+
if (types.isCallExpression(onlyArgument) && onlyArgument.arguments.length === 0) {
2003+
callTarget = onlyArgument.callee;
2004+
}
2005+
break;
20042006
}
20052007
}
20062008
break;
@@ -3417,7 +3419,7 @@ export default function ({
34173419
);
34183420
}
34193421
if (parent.node.finalizer) {
3420-
let finallyName: string;
3422+
let finallyName: HelperName;
34213423
let finallyArgs: Identifier[];
34223424
let finallyBody = parent.node.finalizer.body;
34233425
if (!pathsReturnOrThrow(parent.get("finalizer")).all) {
@@ -3848,12 +3850,13 @@ export default function ({
38483850
reusingExisting.remove();
38493851
}
38503852
}
3853+
const parentNode = parent.node;
38513854
relocateTail(
38523855
state.generatorState,
38533856
awaitPath.isYieldExpression()
38543857
? yieldOnExpression(state.generatorState, awaitExpression)
38553858
: awaitExpression,
3856-
parent.isStatement() ? parent.node : types.returnStatement(parent.node),
3859+
types.isStatement(parentNode) ? parentNode : types.returnStatement(parentNode),
38573860
parent,
38583861
additionalConstantNames,
38593862
resultIdentifier,
@@ -3921,6 +3924,11 @@ export default function ({
39213924
) {
39223925
switch (helperNameMap.get(path.node.callee)) {
39233926
case "_await":
3927+
const args = path.get("arguments");
3928+
if (args.length > 0 && args[0].isExpression()) {
3929+
unpromisify(args[0], pluginState);
3930+
}
3931+
// fallthrough
39243932
case "_call": {
39253933
const args = path.get("arguments");
39263934
if (args.length > 2) {
@@ -3936,6 +3944,21 @@ export default function ({
39363944
}
39373945
break;
39383946
}
3947+
case "_promiseThen": {
3948+
const args = path.get("arguments");
3949+
if (args.length > 2) {
3950+
const firstArg = args[1];
3951+
if (types.isExpression(firstArg.node) && isContinuation(firstArg.node)) {
3952+
firstArg.traverse(unpromisifyVisitor, pluginState);
3953+
} else if (firstArg.isIdentifier()) {
3954+
const binding = firstArg.scope.getBinding(firstArg.node.name);
3955+
if (binding && binding.path.isVariableDeclarator()) {
3956+
binding.path.get("init").traverse(unpromisifyVisitor, pluginState);
3957+
}
3958+
}
3959+
}
3960+
break;
3961+
}
39393962
}
39403963
return;
39413964
}
@@ -4099,7 +4122,7 @@ export default function ({
40994122
}
41004123

41014124
// Emits a reference to a helper, inlining or importing it as necessary
4102-
function helperReference(state: PluginState, path: NodePath, name: string): Identifier {
4125+
function helperReference(state: PluginState, path: NodePath, name: HelperName): Identifier {
41034126
const file = getFile(path);
41044127
let result = file.declarations[name];
41054128
if (result) {
@@ -4216,13 +4239,15 @@ export default function ({
42164239
// Emits a reference to Promise.resolve and tags it as an _await reference
42174240
function promiseResolve() {
42184241
const result = types.memberExpression(types.identifier("Promise"), types.identifier("resolve"));
4219-
helperNameMap.set(result, "_await");
4242+
helperNameMap.set(result, "_promiseResolve");
42204243
return result;
42214244
}
42224245

42234246
// Emits a call to a target's then method
42244247
function callThenMethod(value: Expression, continuation: Expression) {
4225-
return types.callExpression(types.memberExpression(value, types.identifier("then")), [continuation]);
4248+
const thenExpression = types.memberExpression(value, types.identifier("then"));
4249+
helperNameMap.set(thenExpression, "_promiseThen");
4250+
return types.callExpression(thenExpression, [continuation]);
42264251
}
42274252

42284253
// Checks if an expression is an async call expression
@@ -4231,6 +4256,8 @@ export default function ({
42314256
switch (helperNameMap.get(path.node.callee)) {
42324257
case "_await":
42334258
case "_call":
4259+
case "_promiseResolve":
4260+
case "_promiseThen":
42344261
return path.node.arguments.length < 3;
42354262
}
42364263
}
@@ -4581,6 +4608,7 @@ export default function ({
45814608
const firstArgument = callArgs[0];
45824609
if (types.isExpression(firstArgument)) {
45834610
switch (helperNameMap.get(argument.node.callee)) {
4611+
case "_promiseResolve":
45844612
case "_await":
45854613
argument.replaceWith(firstArgument);
45864614
break;

tests/Asynchronous TypeScript Iteration/hoisted.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Asynchronous TypeScript Iteration/inlined.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"supportedBabels": ["babel 7"]
3+
}

tests/Asynchronous TypeScript Iteration/output.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/async iteration simple/hoisted.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/babel for await of rewriting compatibility/hoisted.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)