diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 4c5f024ca1..377a71c875 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -702,6 +702,14 @@ export type InstructionValue = value: Place; // the collection loc: SourceLocation; } + // Represents a value that is unused but which we want to ensure is not dropped + // Examples include logical or ternary expressions that appear as expression statements, + // that appear only for their side effects + | { + kind: "ExpressionStatement"; + value: Place; + loc: SourceLocation; + } /** * Catch-all for statements such as type imports, nested class declarations, etc * which are not directly represented, but included for completeness and to allow diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index d7d9856057..ed37e23be6 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -474,6 +474,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string { value = `NextIterableOf ${printPlace(instrValue.value)}`; break; } + case "ExpressionStatement": { + value = `ExpressionStatement ${printPlace(instrValue.value)}`; + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 8164be1342..87a8bbacd1 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -180,6 +180,10 @@ export function* eachInstructionValueOperand( yield instrValue.value; break; } + case "ExpressionStatement": { + yield instrValue.value; + break; + } case "RegExpLiteral": case "LoadGlobal": case "UnsupportedNode": @@ -443,6 +447,10 @@ export function mapInstructionOperands( instrValue.value = fn(instrValue.value); break; } + case "ExpressionStatement": { + instrValue.value = fn(instrValue.value); + break; + } case "RegExpLiteral": case "LoadGlobal": case "UnsupportedNode": diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index da168a013c..d70c335c9f 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -807,6 +807,7 @@ function inferBlock( state.alias(lvalue, instrValue.value); continue; } + case "ExpressionStatement": case "TypeCastExpression": { // A type cast expression has no effect at runtime, so it's equivalent to a raw // identifier: diff --git a/compiler/forget/src/Optimization/DeadCodeElimination.ts b/compiler/forget/src/Optimization/DeadCodeElimination.ts index 5268e40517..cbc6025361 100644 --- a/compiler/forget/src/Optimization/DeadCodeElimination.ts +++ b/compiler/forget/src/Optimization/DeadCodeElimination.ts @@ -190,6 +190,11 @@ function pruneableValue(value: InstructionValue, state: State): boolean { } return true; } + case "ExpressionStatement": { + // We create ExpressionStatements specifically for expressions that would otherwise + // be dropped but which we don't want to eliminate. + return false; + } case "Await": case "CallExpression": case "ComputedDelete": diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index e00340025e..3be220d796 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -996,6 +996,10 @@ function codegenInstructionValue( value = codegenPlace(cx, instrValue.value); break; } + case "ExpressionStatement": { + value = codegenPlace(cx, instrValue.value); + break; + } case "DeclareLocal": case "Destructure": case "StoreLocal": { diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts index 986708c012..09d530f553 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -234,7 +234,8 @@ function mayAllocate(value: InstructionValue): boolean { case "UnaryExpression": case "TemplateLiteral": case "Primitive": - case "NextIterableOf": { + case "NextIterableOf": + case "ExpressionStatement": { return false; } case "RegExpLiteral": diff --git a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts index 5bce281193..3a0db37932 100644 --- a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -436,7 +436,8 @@ function computeMemoizationInputs( } case "Await": case "TypeCastExpression": - case "NextIterableOf": { + case "NextIterableOf": + case "ExpressionStatement": { return { // Indirection for the inner value, memoized if the value is lvalues: diff --git a/compiler/forget/src/TypeInference/InferTypes.ts b/compiler/forget/src/TypeInference/InferTypes.ts index aea2656ea2..4b4cbe67f9 100644 --- a/compiler/forget/src/TypeInference/InferTypes.ts +++ b/compiler/forget/src/TypeInference/InferTypes.ts @@ -210,6 +210,7 @@ function* generateInstructionTypes( case "TemplateLiteral": case "Await": case "NextIterableOf": + case "ExpressionStatement": case "UnsupportedNode": break; default: