pruneUnusedLabels

File

src/ReactiveScopes/PruneUnusedLabels.ts

Purpose

The pruneUnusedLabels pass optimizes control flow by:

  1. Flattening labeled terminals where the label is not reachable via a break or continue statement
  2. Marking labels as implicit for terminals where the label exists but is never targeted

This pass removes unnecessary labeled blocks that were introduced during compilation but serve no control flow purpose in the final output. JavaScript labeled statements are only needed when there is a corresponding break label or continue label that targets them.

Input Invariants

Output Guarantees

Algorithm

The pass uses a two-phase approach with a single traversal:

Phase 1: Collect reachable labels

if ((terminal.kind === 'break' || terminal.kind === 'continue') &&
    terminal.targetKind === 'labeled') {
  state.add(terminal.target);  // Mark this label as reachable
}

Phase 2: Transform terminals

const isReachableLabel = stmt.label !== null && state.has(stmt.label.id);

if (stmt.terminal.kind === 'label' && !isReachableLabel) {
  // Flatten: extract block contents, removing trailing unlabeled break
  const block = [...stmt.terminal.block];
  const last = block.at(-1);
  if (last?.kind === 'terminal' && last.terminal.kind === 'break' &&
      last.terminal.target === null) {
    block.pop();  // Remove trailing break
  }
  return {kind: 'replace-many', value: block};
} else {
  if (!isReachableLabel && stmt.label != null) {
    stmt.label.implicit = true;  // Mark as implicit
  }
  return {kind: 'keep'};
}

Edge Cases

Trailing Break Removal

When flattening a labeled block, if the last statement is an unlabeled break (target === null), it is removed since it would just fall through anyway.

Implicit vs Labeled Breaks

Only breaks with targetKind === 'labeled' count toward label reachability. Implicit breaks (fallthrough) and unlabeled breaks don't make a label "used".

Continue Statements

Both break and continue with labeled targets mark the label as reachable.

Non-Label Terminals with Labels

Other terminal types (like if, while, for) can also have labels. If unreachable, these labels are marked implicit but the terminal is not flattened.

TODOs

None in the source file.

Example

Fixture: unconditional-break-label.js

Input:

function foo(a) {
  let x = 0;
  bar: {
    x = 1;
    break bar;
  }
  return a + x;
}

Output (after full compilation):

function foo(a) {
  return a + 1;
}

The labeled block bar: { ... } is removed because after the pass runs, constant propagation and dead code elimination further simplify the code.

Fixture: conditional-break-labeled.js

Input:

function Component(props) {
  const a = [];
  a.push(props.a);
  label: {
    if (props.b) {
      break label;
    }
    a.push(props.c);
  }
  a.push(props.d);
  return a;
}

Output:

function Component(props) {
  const $ = _c(5);
  let a;
  if ($[0] !== props.a || $[1] !== props.b ||
      $[2] !== props.c || $[3] !== props.d) {
    a = [];
    a.push(props.a);
    bb0: {
      if (props.b) {
        break bb0;
      }
      a.push(props.c);
    }
    a.push(props.d);
    // ... cache updates
  } else {
    a = $[4];
  }
  return a;
}

The labeled block bb0: { ... } is preserved because the break bb0 inside the conditional targets this label.