Skip to content

Commit

Permalink
Fix compiling async arrows in uncompiled class fields (#14752)
Browse files Browse the repository at this point in the history
* Refactor helper-wrap-function for better type inference

* Fix compiling async arrows in uncompiled class fields
  • Loading branch information
nicolo-ribaudo committed Jul 18, 2022
1 parent b2da6e0 commit 4f278dc
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 38 deletions.
86 changes: 52 additions & 34 deletions packages/babel-helper-wrap-function/src/index.ts
Expand Up @@ -6,19 +6,39 @@ import {
callExpression,
functionExpression,
isAssignmentPattern,
isFunctionDeclaration,
isRestElement,
returnStatement,
} from "@babel/types";
import type * as t from "@babel/types";

type ExpressionWrapperBuilder<ExtraBody extends t.Node[]> = (
replacements?: Parameters<ReturnType<typeof template.expression>>[0],
) => t.CallExpression & {
callee: t.FunctionExpression & {
body: {
body: [
t.VariableDeclaration & {
declarations: [
{ init: t.FunctionExpression | t.ArrowFunctionExpression },
];
},
...ExtraBody,
];
};
};
};

const buildAnonymousExpressionWrapper = template.expression(`
(function () {
var REF = FUNCTION;
return function NAME(PARAMS) {
return REF.apply(this, arguments);
};
})()
`);
`) as ExpressionWrapperBuilder<
[t.ReturnStatement & { argument: t.FunctionExpression }]
>;

const buildNamedExpressionWrapper = template.expression(`
(function () {
Expand All @@ -28,7 +48,9 @@ const buildNamedExpressionWrapper = template.expression(`
}
return NAME;
})()
`);
`) as ExpressionWrapperBuilder<
[t.FunctionDeclaration, t.ReturnStatement & { argument: t.Identifier }]
>;

const buildDeclarationWrapper = template.statements(`
function NAME(PARAMS) { return REF.apply(this, arguments); }
Expand Down Expand Up @@ -72,28 +94,23 @@ function plainFunction(
noNewArrows: boolean,
ignoreFunctionLength: boolean,
) {
const node = path.node;
const isDeclaration = path.isFunctionDeclaration();
// @ts-expect-error id is not in ArrowFunctionExpression
const functionId = node.id;
const wrapper = isDeclaration
? buildDeclarationWrapper
: functionId
? buildNamedExpressionWrapper
: buildAnonymousExpressionWrapper;

let functionId = null;
let node;
if (path.isArrowFunctionExpression()) {
path.arrowFunctionToExpression({ noNewArrows });
path = path.arrowFunctionToExpression({ noNewArrows });
node = path.node as t.FunctionDeclaration | t.FunctionExpression;
} else {
node = path.node as t.FunctionDeclaration | t.FunctionExpression;
}
// @ts-expect-error node is FunctionDeclaration|FunctionExpression
node.id = null;

if (isDeclaration) {
node.type = "FunctionExpression";
}
const isDeclaration = isFunctionDeclaration(node);

functionId = node.id;
node.id = null;
node.type = "FunctionExpression";

const built = callExpression(callId, [
node as Exclude<t.Function, t.Method | t.FunctionDeclaration>,
node as Exclude<typeof node, t.FunctionDeclaration>,
]);

const params: t.Identifier[] = [];
Expand All @@ -104,34 +121,35 @@ function plainFunction(
params.push(path.scope.generateUidIdentifier("x"));
}

const container = wrapper({
const wrapperArgs = {
NAME: functionId || null,
REF: path.scope.generateUidIdentifier(functionId ? functionId.name : "ref"),
FUNCTION: built,
PARAMS: params,
});
};

if (isDeclaration) {
path.replaceWith((container as t.Statement[])[0]);
path.insertAfter((container as t.Statement[])[1]);
const container = buildDeclarationWrapper(wrapperArgs);
path.replaceWith(container[0]);
path.insertAfter(container[1]);
} else {
// @ts-expect-error todo(flow->ts) separate `wrapper` for `isDeclaration` and `else` branches
const retFunction = container.callee.body.body[1].argument;
if (!functionId) {
let container;

if (functionId) {
container = buildNamedExpressionWrapper(wrapperArgs);
} else {
container = buildAnonymousExpressionWrapper(wrapperArgs);

const returnFn = container.callee.body.body[1].argument;
nameFunction({
node: retFunction,
node: returnFn,
parent: path.parent,
scope: path.scope,
});
functionId = returnFn.id;
}

if (
!retFunction ||
retFunction.id ||
(!ignoreFunctionLength && params.length)
) {
// we have an inferred function id or params so we need this wrapper
// @ts-expect-error todo(flow->ts) separate `wrapper` for `isDeclaration` and `else` branches
if (functionId || (!ignoreFunctionLength && params.length)) {
path.replaceWith(container);
} else {
// we can omit this wrapper as the conditions it protects for do not apply
Expand Down
Expand Up @@ -4,4 +4,4 @@ var _this = this;
babelHelpers.asyncToGenerator(function* () {
babelHelpers.newArrowCheck(this, _this);
return 2;
});
}).bind(this);
@@ -0,0 +1,8 @@
// Angular needs to alwys compile async functions, even if
// the targets support class fields.
// https://github.com/babel/babel/issues/14749

class A {
a = async () => this;
b = async (x, y, z) => this;
}
@@ -0,0 +1,25 @@
// Angular needs to alwys compile async functions, even if
// the targets support class fields.
// https://github.com/babel/babel/issues/14749
class A {
a = (() => {
var _this = this;

return babelHelpers.asyncToGenerator(function* () {
return _this;
});
})();
b = (() => {
var _this2 = this;

return function () {
var _ref2 = babelHelpers.asyncToGenerator(function* (x, y, z) {
return _this2;
});

return function (_x, _x2, _x3) {
return _ref2.apply(this, arguments);
};
}();
})();
}
18 changes: 15 additions & 3 deletions packages/babel-traverse/src/path/conversion.ts
Expand Up @@ -135,6 +135,13 @@ export function unwrapFunctionEnvironment(this: NodePath) {
hoistFunctionEnvironment(this);
}

function setType<N extends t.Node, T extends N["type"]>(
path: NodePath<N>,
type: T,
): asserts path is NodePath<Extract<N, { type: T }>> {
path.node.type = type;
}

/**
* Convert a given arrow function into a normal ES5 function expression.
*/
Expand All @@ -151,7 +158,7 @@ export function arrowFunctionToExpression(
specCompliant?: boolean | void;
noNewArrows?: boolean;
} = {},
) {
): NodePath<Exclude<t.Function, t.Method | t.ArrowFunctionExpression>> {
if (!this.isArrowFunctionExpression()) {
throw (this as NodePath).buildCodeFrameError(
"Cannot convert non-arrow function to a function expression.",
Expand All @@ -166,7 +173,8 @@ export function arrowFunctionToExpression(

// @ts-expect-error TS requires explicit fn type annotation
fn.ensureBlock();
fn.node.type = "FunctionExpression";
setType(fn, "FunctionExpression");

if (!noNewArrows) {
const checkBinding = thisBinding
? null
Expand All @@ -178,7 +186,7 @@ export function arrowFunctionToExpression(
});
}

(fn.get("body") as NodePath<t.BlockStatement>).unshiftContainer(
fn.get("body").unshiftContainer(
"body",
expressionStatement(
callExpression(this.hub.addHelper("newArrowCheck"), [
Expand All @@ -200,7 +208,11 @@ export function arrowFunctionToExpression(
[checkBinding ? identifier(checkBinding.name) : thisExpression()],
),
);

return fn.get("callee.object");
}

return fn;
}

const getSuperCallsVisitor = mergeVisitors<{
Expand Down

0 comments on commit 4f278dc

Please sign in to comment.