5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { NodePath, Scope } from "@babel/traverse";
8
+import { Binding, NodePath, Scope } from "@babel/traverse";
9
import * as t from "@babel/types";
10
import { Expression } from "@babel/types";
11
import invariant from "invariant";
298
}
299
case "BlockStatement": {
300
const stmt = stmtPath as NodePath<t.BlockStatement>;
301
- stmt.get("body").forEach((s) => lowerStatement(builder, s));
301
+ const statements = stmt.get("body");
302
+ const hoistableBindings: Set<Binding> = new Set();
303
+
304
+ const recordDeclaration = (lval: NodePath<t.LVal>): void => {
305
+ // TODO: support other kinds of declarations that might need to be hoisted
306
+ switch (lval.type) {
307
+ case "Identifier": {
308
+ const lv = lval as NodePath<t.Identifier>;
309
+ const binding = stmt.scope.getBinding(lv.node.name);
310
+ if (binding != null) {
311
+ hoistableBindings.delete(binding);
312
+ }
313
+ break;
314
+ }
315
+ }
316
+ };
317
+
318
+ for (const [, binding] of Object.entries(stmt.scope.bindings)) {
319
+ // TODO: support other kinds of bindings
320
+ if (binding.kind === "const") {
321
+ if (
322
+ binding.path.isVariableDeclarator() &&
323
+ binding.path.get("id").isIdentifier()
324
+ ) {
325
+ hoistableBindings.add(binding);
326
+ }
327
+ }
328
+ }
329
+
330
+ for (const s of statements) {
331
+ const hoistableIdentifiers = new Set<NodePath<t.Identifier>>();
332
+ // After visiting the declaration, hoisting is no longer required
333
+ // TODO: support other kinds of declarations
334
+ if (s.isVariableDeclaration()) {
335
+ for (const decl of s.get("declarations")) {
336
+ recordDeclaration(decl.get("id"));
337
+ }
338
+ }
339
+
340
+ // If we see a hoistable identifier before its declaration, it should be hoisted just
341
+ // before the statement that references it
342
+ s.traverse({
343
+ Identifier(id: NodePath<t.Identifier>) {
344
+ const binding = stmt.scope.getBinding(id.node.name);
345
+ if (binding != null && hoistableBindings.has(binding)) {
346
+ if (
347
+ id.parentPath.isVariableDeclarator() ||
348
+ // don't hoist MemberExpr `property`s, only their `object`
349
+ (id.parentPath.isMemberExpression() &&
350
+ id.parentPath.get("property") === id &&
351
+ id.parentPath.node.computed === false)
352
+ ) {
353
+ return;
354
+ }
355
+ hoistableIdentifiers.add(id);
356
+ }
357
+ },
358
+ });
359
+
360
+ // Hoist declarations that need it to the earliest point where they are needed
361
+ for (const id of hoistableIdentifiers) {
362
+ const binding = stmt.scope.getBinding(id.node.name);
363
+ CompilerError.invariant(binding != null, {
364
+ reason: "Expected to find binding for hoisted identifier",
365
+ description: `Could not find a binding for ${id.node.name}`,
366
+ suggestions: null,
367
+ loc: id.node.loc ?? GeneratedSource,
368
+ });
369
+ if (builder.environment.isHoistedIdentifier(binding.identifier)) {
370
+ // Already hoisted
371
+ continue;
372
+ }
373
+ if (!binding.path.isVariableDeclarator()) {
374
+ builder.errors.push({
375
+ severity: ErrorSeverity.Todo,
376
+ reason: "Unsupported declaration type for hoisting",
377
+ description: `${id.parentPath.type}`,
378
+ suggestions: null,
379
+ loc: id.parentPath.node.loc ?? GeneratedSource,
380
+ });
381
+ continue;
382
+ }
383
+ const identifier = builder.resolveIdentifier(id)!;
384
+ const place: Place = {
385
+ effect: Effect.Unknown,
386
+ identifier,
387
+ kind: "Identifier",
388
+ loc: id.node.loc ?? GeneratedSource,
389
+ };
390
+ lowerValueToTemporary(builder, {
391
+ kind: "DeclareContext",
392
+ lvalue: {
393
+ kind: InstructionKind.HoistedConst,
394
+ place,
395
+ },
396
+ loc: id.node.loc ?? GeneratedSource,
397
+ });
398
+ builder.environment.addHoistedIdentifier(binding.identifier);
399
+ }
400
+ lowerStatement(builder, s);
401
+ }
402
+
403
return;
404
}
405
case "BreakStatement": {
2995
node: lvalue.node,
2996
};
2997
}
2998
+ const isHoistedIdentifier = builder.environment.isHoistedIdentifier(
2999
+ lvalue.node
3000
+ );
3001
3002
let temporary;
3003
if (builder.isContextIdentifier(lvalue)) {
2900
- if (kind !== InstructionKind.Reassign) {
3004
+ if (kind !== InstructionKind.Reassign && !isHoistedIdentifier) {
3005
if (kind === InstructionKind.Const) {
3006
builder.errors.push({
2903
- reason: `Invalid declaration kind (const), this variable is reassigned later`,
3007
+ reason: `[lowerAssignment] Invalid declaration kind (const), this variable is reassigned later`,
3008
severity: ErrorSeverity.InvalidJS,
3009
loc: lvalue.node.loc ?? null,
3010
suggestions: null,