diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 59e2f0c89f..91f2fb8c7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -124,7 +124,7 @@ export function lower( ) { const place: Place = { kind: "Identifier", - identifier: builder.makeTemporary(), + identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource), effect: Effect.Unknown, reactive: false, loc: param.node.loc ?? GeneratedSource, @@ -141,7 +141,7 @@ export function lower( } else if (param.isRestElement()) { const place: Place = { kind: "Identifier", - identifier: builder.makeTemporary(), + identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource), effect: Effect.Unknown, reactive: false, loc: param.node.loc ?? GeneratedSource, @@ -1256,7 +1256,9 @@ function lowerStatement( if (hasNode(handlerBindingPath)) { const place: Place = { kind: "Identifier", - identifier: builder.makeTemporary(), + identifier: builder.makeTemporary( + handlerBindingPath.node.loc ?? GeneratedSource + ), effect: Effect.Unknown, reactive: false, loc: handlerBindingPath.node.loc ?? GeneratedSource, @@ -3301,7 +3303,7 @@ function lowerIdentifier( function buildTemporaryPlace(builder: HIRBuilder, loc: SourceLocation): Place { const place: Place = { kind: "Identifier", - identifier: builder.makeTemporary(), + identifier: builder.makeTemporary(loc), effect: Effect.Unknown, reactive: false, loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index da900c275c..f9dfea52f3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1144,6 +1144,7 @@ export type Identifier = { */ scope: ReactiveScope | null; type: Type; + loc: SourceLocation; }; export type IdentifierName = ValidatedIdentifier | PromotedIdentifier; @@ -1376,6 +1377,8 @@ export type ReactiveScope = { * no longer exist due to being pruned. */ merged: Set; + + loc: SourceLocation; }; export type ReactiveScopeDependencies = Set; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 970e4ba51d..0342d57ea3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -21,6 +21,7 @@ import { IdentifierId, Instruction, Place, + SourceLocation, Terminal, VariableBinding, makeBlockId, @@ -174,7 +175,7 @@ export default class HIRBuilder { return handler ?? null; } - makeTemporary(): Identifier { + makeTemporary(loc: SourceLocation): Identifier { const id = this.nextIdentifierId; return { id, @@ -182,6 +183,7 @@ export default class HIRBuilder { mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) }, scope: null, type: makeType(), + loc, }; } @@ -320,6 +322,7 @@ export default class HIRBuilder { }, scope: null, type: makeType(), + loc: node.loc ?? GeneratedSource, }; this.#bindings.set(name, { node, identifier }); return identifier; @@ -877,7 +880,10 @@ export function removeUnnecessaryTryCatch(fn: HIR): void { } } -export function createTemporaryPlace(env: Environment): Place { +export function createTemporaryPlace( + env: Environment, + loc: SourceLocation +): Place { return { kind: "Identifier", identifier: { @@ -886,6 +892,7 @@ export function createTemporaryPlace(env: Environment): Place { name: null, scope: null, type: makeType(), + loc, }, reactive: false, effect: Effect.Unknown, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 5aaf7989fe..932cb4cc80 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -178,7 +178,7 @@ function makeManualMemoizationMarkers( return [ { id: makeInstructionId(0), - lvalue: createTemporaryPlace(env), + lvalue: createTemporaryPlace(env, fnExpr.loc), value: { kind: "StartMemoize", manualMemoId, @@ -193,7 +193,7 @@ function makeManualMemoizationMarkers( }, { id: makeInstructionId(0), - lvalue: createTemporaryPlace(env), + lvalue: createTemporaryPlace(env, fnExpr.loc), value: { kind: "FinishMemoize", manualMemoId, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts index 5da6fcd4fe..c64ed19d18 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts @@ -236,6 +236,7 @@ function rewriteBlock( name: null, scope: null, type: makeType(), + loc: terminal.loc, }, kind: "Identifier", reactive: false, @@ -277,6 +278,7 @@ function declareTemporary( name: null, scope: null, type: makeType(), + loc: result.loc, }, kind: "Identifier", reactive: false, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index ba04d827c7..f43cae0831 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -597,6 +597,10 @@ function codegenReactiveScope( cx.env.config.enableChangeDetectionForDebugging != null && changeExpressions.length > 0 ) { + const loc = + typeof scope.loc === "symbol" + ? "unknown location" + : `(${scope.loc.start.line}:${scope.loc.end.line})`; const detectionFunction = cx.env.config.enableChangeDetectionForDebugging.importSpecifierName; const cacheLoadOldValueStatements: Array = []; @@ -626,6 +630,7 @@ function codegenReactiveScope( t.stringLiteral(name.name), t.stringLiteral(cx.fnName), t.stringLiteral("cached"), + t.stringLiteral(loc), ]) ) ); @@ -637,6 +642,7 @@ function codegenReactiveScope( t.stringLiteral(name.name), t.stringLiteral(cx.fnName), t.stringLiteral("recomputed"), + t.stringLiteral(loc), ]) ) ); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index a8142c8720..833b784f0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { CompilerError } from ".."; +import { CompilerError, SourceLocation } from ".."; import { Environment } from "../HIR"; import { GeneratedSource, @@ -110,6 +110,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { reassignments: new Set(), earlyReturnValue: null, merged: new Set(), + loc: identifier.loc, }; scopes.set(groupIdentifier, scope); } else { @@ -119,6 +120,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { scope.range.end = makeInstructionId( Math.max(scope.range.end, identifier.mutableRange.end) ); + scope.loc = mergeLocation(scope.loc, identifier.loc); } identifier.scope = scope; identifier.mutableRange = scope.range; @@ -159,6 +161,25 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void { } } +function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation { + if (l === GeneratedSource) { + return r; + } else if (r === GeneratedSource) { + return l; + } else { + return { + start: { + line: Math.min(l.start.line, r.start.line), + column: Math.min(l.start.column, r.start.column), + }, + end: { + line: Math.max(l.end.line, r.end.line), + column: Math.max(l.end.column, r.end.column), + }, + }; + } +} + // Is the operand mutable at this given instruction export function isMutable({ id }: Instruction, place: Place): boolean { const range = place.identifier.mutableRange; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts index ee25a123fb..ef2c217e25 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts @@ -153,10 +153,10 @@ class Transform extends ReactiveFunctionTransform { const instructions = scopeBlock.instructions; const loc = earlyReturnValue.loc; - const sentinelTemp = createTemporaryPlace(this.env); - const symbolTemp = createTemporaryPlace(this.env); - const forTemp = createTemporaryPlace(this.env); - const argTemp = createTemporaryPlace(this.env); + const sentinelTemp = createTemporaryPlace(this.env, loc); + const symbolTemp = createTemporaryPlace(this.env, loc); + const forTemp = createTemporaryPlace(this.env, loc); + const argTemp = createTemporaryPlace(this.env, loc); scopeBlock.instructions = [ { kind: "instruction", @@ -274,7 +274,7 @@ class Transform extends ReactiveFunctionTransform { if (state.earlyReturnValue !== null) { earlyReturnValue = state.earlyReturnValue; } else { - const identifier = createTemporaryPlace(this.env).identifier; + const identifier = createTemporaryPlace(this.env, loc).identifier; promoteTemporary(identifier); earlyReturnValue = { label: this.env.nextBlockId, diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index e39b54aaca..8f5b78cc77 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -86,6 +86,7 @@ class SSABuilder { }, scope: null, // reset along w the mutable range type: makeType(), + loc: oldId.loc, }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md index 19a6710f8f..099faadced 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md @@ -29,14 +29,14 @@ function Component(props) { let condition = $[0] !== props.value; if (!condition) { let old$x = $[1]; - $structuralCheck(old$x, x, "x", "Component", "cached"); + $structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)"); } $[0] = props.value; $[1] = x; if (condition) { x = []; x.push(props.value); - $structuralCheck($[1], x, "x", "Component", "recomputed"); + $structuralCheck($[1], x, "x", "Component", "recomputed", "(3:6)"); x = $[1]; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md index 0950681960..63203246d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-and-other-hook-unpruned-dependency.expect.md @@ -46,13 +46,13 @@ function Component(props) { let condition = $[0] !== props.x; if (!condition) { let old$t0 = $[1]; - $structuralCheck(old$t0, t0, "t0", "Component", "cached"); + $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(8:8)"); } $[0] = props.x; $[1] = t0; if (condition) { t0 = f(props.x); - $structuralCheck($[1], t0, "t0", "Component", "recomputed"); + $structuralCheck($[1], t0, "t0", "Component", "recomputed", "(8:8)"); t0 = $[1]; } } @@ -65,13 +65,13 @@ function Component(props) { let condition = $[2] !== x; if (!condition) { let old$t1 = $[3]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached"); + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(11:11)"); } $[2] = x; $[3] = t1; if (condition) { t1 =
{x}
; - $structuralCheck($[3], t1, "t1", "Component", "recomputed"); + $structuralCheck($[3], t1, "t1", "Component", "recomputed", "(11:11)"); t1 = $[3]; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md index 3b89bfbd3f..4ae84cfdf2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-pruned-dependency-change-detect.expect.md @@ -35,13 +35,13 @@ function Component(props) { let condition = $[1] !== x; if (!condition) { let old$t1 = $[2]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached"); + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(6:6)"); } $[1] = x; $[2] = t1; if (condition) { t1 =
{x}
; - $structuralCheck($[2], t1, "t1", "Component", "recomputed"); + $structuralCheck($[2], t1, "t1", "Component", "recomputed", "(6:6)"); t1 = $[2]; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index e99b826315..8ca0d23ba8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -42,13 +42,13 @@ function Component(props) { let condition = $[0] !== props.x; if (!condition) { let old$t0 = $[1]; - $structuralCheck(old$t0, t0, "t0", "Component", "cached"); + $structuralCheck(old$t0, t0, "t0", "Component", "cached", "(4:4)"); } $[0] = props.x; $[1] = t0; if (condition) { t0 = f(props.x); - $structuralCheck($[1], t0, "t0", "Component", "recomputed"); + $structuralCheck($[1], t0, "t0", "Component", "recomputed", "(4:4)"); t0 = $[1]; } } @@ -65,7 +65,7 @@ function Component(props) { let condition = $[2] !== x || $[3] !== w; if (!condition) { let old$t1 = $[4]; - $structuralCheck(old$t1, t1, "t1", "Component", "cached"); + $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } $[2] = x; $[3] = w; @@ -77,7 +77,7 @@ function Component(props) { {w} ); - $structuralCheck($[4], t1, "t1", "Component", "recomputed"); + $structuralCheck($[4], t1, "t1", "Component", "recomputed", "(7:10)"); t1 = $[4]; } } diff --git a/compiler/packages/react-compiler-runtime/src/index.ts b/compiler/packages/react-compiler-runtime/src/index.ts index f758319811..6975c19411 100644 --- a/compiler/packages/react-compiler-runtime/src/index.ts +++ b/compiler/packages/react-compiler-runtime/src/index.ts @@ -259,10 +259,11 @@ export function $structuralCheck( newValue: any, variableName: string, fnName: string, - kind: string + kind: string, + loc: string ): void { function error(l: string, r: string, path: string, depth: number) { - const str = `${fnName}: [${kind}] ${variableName}${path} changed from ${l} to ${r} at depth ${depth}`; + const str = `${fnName}:${loc} [${kind}] ${variableName}${path} changed from ${l} to ${r} at depth ${depth}`; if (seenErrors.has(str)) { return; } @@ -283,7 +284,7 @@ export function $structuralCheck( if (oldValue === null && newValue !== null) { error("null", `type ${typeof newValue}`, path, depth); } else if (newValue === null) { - error(`type ${typeof oldValue}`, null, path, depth); + error(`type ${typeof oldValue}`, "null", path, depth); } else if (oldValue instanceof Map) { if (!(newValue instanceof Map)) { error(`Map instance`, `other value`, path, depth);