diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index c4724ac036..719c380a9d 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1687,6 +1687,14 @@ function lowerExpression( }; } } + case "AwaitExpression": { + let expr = exprPath as NodePath; + return { + kind: "Await", + value: lowerExpressionToTemporary(builder, expr.get("argument")), + loc: exprLoc, + }; + } case "TypeCastExpression": { let expr = exprPath as NodePath; return { diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 678c39471c..ef873fd87a 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -632,6 +632,11 @@ export type InstructionValue = quasis: Array<{ raw: string; cooked?: string }>; loc: SourceLocation; } + | { + kind: "Await"; + 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 f4d8e76fbe..8b82b77a59 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -448,6 +448,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string { value = `RegExp /${instrValue.pattern}/${instrValue.flags}`; break; } + case "Await": { + value = `Await ${printPlace(instrValue.value)}`; + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index bca0080d68..328a995466 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -170,6 +170,10 @@ export function* eachInstructionValueOperand( yield* instrValue.subexprs; break; } + case "Await": { + yield instrValue.value; + break; + } case "RegExpLiteral": case "LoadGlobal": case "UnsupportedNode": @@ -423,6 +427,10 @@ export function mapInstructionOperands( instrValue.subexprs = instrValue.subexprs.map(fn); break; } + case "Await": { + 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 fb04c74f79..c3cf9c7281 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -788,6 +788,17 @@ function inferBlock( state.define(lvalue, instrValue); continue; } + case "Await": { + state.initialize(instrValue, state.kind(instrValue.value)); + // Awaiting a value causes it to change state (go from unresolved to resolved or error) + // It also means that any side-effects which would occur as part of the promise evaluation + // will occur. + state.reference(instrValue.value, Effect.Mutate); + const lvalue = instr.lvalue; + lvalue.effect = Effect.Mutate; + state.alias(lvalue, instrValue.value); + continue; + } 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 fe7edaa888..877a2340a4 100644 --- a/compiler/forget/src/Optimization/DeadCodeElimination.ts +++ b/compiler/forget/src/Optimization/DeadCodeElimination.ts @@ -190,6 +190,7 @@ function pruneableValue(value: InstructionValue, state: State): boolean { } return true; } + case "Await": case "CallExpression": case "ComputedDelete": case "ComputedStore": diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 27c2263af1..05697c3a1b 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -907,6 +907,10 @@ function codegenInstructionValue( value = t.regExpLiteral(instrValue.pattern, instrValue.flags); break; } + case "Await": { + value = t.awaitExpression(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 888cb83499..8f519f6e04 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -219,6 +219,7 @@ function mayAllocate(value: InstructionValue): boolean { case "Destructure": { return doesPatternContainSpreadElement(value.lvalue.pattern); } + case "Await": case "DeclareLocal": case "StoreLocal": case "LoadGlobal": diff --git a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts index 51d81fe8bb..ec2ce6dbce 100644 --- a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -424,6 +424,7 @@ function computeMemoizationInputs( rvalues: [], }; } + case "Await": case "TypeCastExpression": { return { // Indirection for the inner value, memoized if the value is diff --git a/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.expect.md new file mode 100644 index 0000000000..aabe9a208a --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +async function Component(props) { + const x = []; + await populateData(props.id, x); + return x; +} + +``` + +## Code + +```javascript +async function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.id; + let x; + if (c_0) { + x = []; + await populateData(props.id, x); + $[0] = props.id; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.js b/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.js new file mode 100644 index 0000000000..8b5a45e608 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/await-side-effecting-promise.js @@ -0,0 +1,5 @@ +async function Component(props) { + const x = []; + await populateData(props.id, x); + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/await.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/await.expect.md new file mode 100644 index 0000000000..f1b76412e7 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/await.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +async function Component(props) { + const user = await load(props.id); + return
{user.name}
; +} + +``` + +## Code + +```javascript +async function Component(props) { + const $ = React.unstable_useMemoCache(4); + const c_0 = $[0] !== props.id; + let t0; + if (c_0) { + t0 = await load(props.id); + $[0] = props.id; + $[1] = t0; + } else { + t0 = $[1]; + } + const user = t0; + const c_2 = $[2] !== user.name; + let t1; + if (c_2) { + t1 =
{user.name}
; + $[2] = user.name; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/await.js b/compiler/forget/src/__tests__/fixtures/compiler/await.js new file mode 100644 index 0000000000..3d021138fe --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/await.js @@ -0,0 +1,4 @@ +async function Component(props) { + const user = await load(props.id); + return
{user.name}
; +}