Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions packages/compiler/src/frontend/lowering/lower-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5432,14 +5432,8 @@ const inliningPredicates = new Set<ts.Symbol>();
// A `void e` body rides the statement lowering (the value is
// discarded here, so the operand evaluates for effect alone —
// `(name) => void doThing(name)`, the fire-and-forget arrow).
let stripped: ts.Expression = bodyExpr;
while (ts.isParenthesizedExpression(stripped)) stripped = stripped.expression;
if (ts.isVoidExpression(stripped)) {
body = [L.lowerExprStatement(stripped)];
} else {
const value = L.lowerExpr(bodyExpr);
body = value.kind === "unitLit" ? [] : [{ kind: "exprStmt", expr: value, loc: locOf(node.body!) }];
}
const stmt = L.lowerExprStatement(bodyExpr);
body = stmt.kind === "block" && stmt.body.length === 0 ? [] : [stmt];
} else {
let value = L.lowerExpr(bodyExpr);
// An async concise body whose value is itself a promise
Expand Down
12 changes: 12 additions & 0 deletions packages/compiler/src/frontend/lowering/lower-stmts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4142,6 +4142,18 @@ function isEsModuleStamp(expr: ts.Expression): boolean {
// Value-position `void` keeps the syntax fence (a standalone undefined
// VALUE needs a union slot to live in).
if (ts.isVoidExpression(expr)) return lowerExprStatement(L, expr.expression);
// Statement-position conditionals evaluate the condition, then exactly
// one arm for effect and drop that arm's value. Lower them as `if`
// statements so void-valued arms do not form invalid value ternaries.
if (ts.isConditionalExpression(expr)) {
return {
kind: "if",
cond: L.lowerCondition(expr.condition),
then: [lowerExprStatement(L, expr.whenTrue)],
else_: [lowerExprStatement(L, expr.whenFalse)],
loc: locOf(expr),
};
}
if (ts.isBinaryExpression(expr)) {
const opKind = expr.operatorToken.kind;
// Statement-position comma (`({} = a, [] = a);`, `i++, j++` in a
Expand Down
11 changes: 11 additions & 0 deletions packages/compiler/src/frontend/lowering/lowerer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5805,6 +5805,17 @@ export class Lowerer {
lowerReturnStmt(node: ts.Expression, loc: SrcLoc): IrStmt {
const expected = this.ctx.returnType;
if (expected.kind === "void") {
let expr = node;
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
if (ts.isConditionalExpression(expr)) {
return {
kind: "if",
cond: this.lowerCondition(expr.condition),
then: [this.lowerReturnStmt(expr.whenTrue, loc)],
else_: [this.lowerReturnStmt(expr.whenFalse, loc)],
loc,
};
}
let e = this.lowerExpr(node);
if (this.ctx.isAsync && e.type.kind === "promise") {
e = { kind: "awaitExpr", value: e, type: e.type.inner, loc: e.loc };
Expand Down
7 changes: 7 additions & 0 deletions tests/corpus/2352-void-coercions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const box = {
box.poke();
console.log("method unit return ok");

// Concise void-returning arrows over conditional void calls lower as a
// branch, not a value ternary.
const branchVoid = (flag: boolean) => (flag ? box.poke() : fv());
branchVoid(true);
branchVoid(false);
console.log("branch-void", effects);

// Async concise body over an existing promise: resolves through.
async function inner(): Promise<number> {
return 42;
Expand Down