Skip to content

Commit ec6fe57

Browse files
committed
[compiler] rfc: Include location information in identifiers and reactive scopes for debugging
Summary: Using the change detection code to debug codebases that violate the rules of react is a lot easier when we have a source location corresponding to the value that has changed inappropriately. I didn't see an easy way to track that information in the existing data structures at the point of codegen, so this PR adds locations to identifiers and reactive scopes (the location of a reactive scope is the range of the locations of its included identifiers). I'm interested if there's a better way to do this that I missed! ghstack-source-id: aed5f7e Pull Request resolved: #29658
1 parent 522d22f commit ec6fe57

14 files changed

+72
-29
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export function lower(
124124
) {
125125
const place: Place = {
126126
kind: "Identifier",
127-
identifier: builder.makeTemporary(),
127+
identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),
128128
effect: Effect.Unknown,
129129
reactive: false,
130130
loc: param.node.loc ?? GeneratedSource,
@@ -141,7 +141,7 @@ export function lower(
141141
} else if (param.isRestElement()) {
142142
const place: Place = {
143143
kind: "Identifier",
144-
identifier: builder.makeTemporary(),
144+
identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),
145145
effect: Effect.Unknown,
146146
reactive: false,
147147
loc: param.node.loc ?? GeneratedSource,
@@ -1256,7 +1256,9 @@ function lowerStatement(
12561256
if (hasNode(handlerBindingPath)) {
12571257
const place: Place = {
12581258
kind: "Identifier",
1259-
identifier: builder.makeTemporary(),
1259+
identifier: builder.makeTemporary(
1260+
handlerBindingPath.node.loc ?? GeneratedSource
1261+
),
12601262
effect: Effect.Unknown,
12611263
reactive: false,
12621264
loc: handlerBindingPath.node.loc ?? GeneratedSource,
@@ -3301,7 +3303,7 @@ function lowerIdentifier(
33013303
function buildTemporaryPlace(builder: HIRBuilder, loc: SourceLocation): Place {
33023304
const place: Place = {
33033305
kind: "Identifier",
3304-
identifier: builder.makeTemporary(),
3306+
identifier: builder.makeTemporary(loc),
33053307
effect: Effect.Unknown,
33063308
reactive: false,
33073309
loc,

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,7 @@ export type Identifier = {
11441144
*/
11451145
scope: ReactiveScope | null;
11461146
type: Type;
1147+
loc: SourceLocation;
11471148
};
11481149

11491150
export type IdentifierName = ValidatedIdentifier | PromotedIdentifier;
@@ -1376,6 +1377,8 @@ export type ReactiveScope = {
13761377
* no longer exist due to being pruned.
13771378
*/
13781379
merged: Set<ScopeId>;
1380+
1381+
loc: SourceLocation;
13791382
};
13801383

13811384
export type ReactiveScopeDependencies = Set<ReactiveScopeDependency>;

compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
IdentifierId,
2222
Instruction,
2323
Place,
24+
SourceLocation,
2425
Terminal,
2526
VariableBinding,
2627
makeBlockId,
@@ -174,14 +175,15 @@ export default class HIRBuilder {
174175
return handler ?? null;
175176
}
176177

177-
makeTemporary(): Identifier {
178+
makeTemporary(loc: SourceLocation): Identifier {
178179
const id = this.nextIdentifierId;
179180
return {
180181
id,
181182
name: null,
182183
mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) },
183184
scope: null,
184185
type: makeType(),
186+
loc,
185187
};
186188
}
187189

@@ -320,6 +322,7 @@ export default class HIRBuilder {
320322
},
321323
scope: null,
322324
type: makeType(),
325+
loc: node.loc ?? GeneratedSource,
323326
};
324327
this.#bindings.set(name, { node, identifier });
325328
return identifier;
@@ -877,7 +880,10 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
877880
}
878881
}
879882

880-
export function createTemporaryPlace(env: Environment): Place {
883+
export function createTemporaryPlace(
884+
env: Environment,
885+
loc: SourceLocation
886+
): Place {
881887
return {
882888
kind: "Identifier",
883889
identifier: {
@@ -886,6 +892,7 @@ export function createTemporaryPlace(env: Environment): Place {
886892
name: null,
887893
scope: null,
888894
type: makeType(),
895+
loc,
889896
},
890897
reactive: false,
891898
effect: Effect.Unknown,

compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function makeManualMemoizationMarkers(
178178
return [
179179
{
180180
id: makeInstructionId(0),
181-
lvalue: createTemporaryPlace(env),
181+
lvalue: createTemporaryPlace(env, fnExpr.loc),
182182
value: {
183183
kind: "StartMemoize",
184184
manualMemoId,
@@ -193,7 +193,7 @@ function makeManualMemoizationMarkers(
193193
},
194194
{
195195
id: makeInstructionId(0),
196-
lvalue: createTemporaryPlace(env),
196+
lvalue: createTemporaryPlace(env, fnExpr.loc),
197197
value: {
198198
kind: "FinishMemoize",
199199
manualMemoId,

compiler/packages/babel-plugin-react-compiler/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ function rewriteBlock(
236236
name: null,
237237
scope: null,
238238
type: makeType(),
239+
loc: terminal.loc,
239240
},
240241
kind: "Identifier",
241242
reactive: false,
@@ -277,6 +278,7 @@ function declareTemporary(
277278
name: null,
278279
scope: null,
279280
type: makeType(),
281+
loc: result.loc,
280282
},
281283
kind: "Identifier",
282284
reactive: false,

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,10 @@ function codegenReactiveScope(
597597
cx.env.config.enableChangeDetectionForDebugging != null &&
598598
changeExpressions.length > 0
599599
) {
600+
const loc =
601+
typeof scope.loc === "symbol"
602+
? "unknown location"
603+
: `(${scope.loc.start.line}:${scope.loc.end.line})`;
600604
const detectionFunction =
601605
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
602606
const cacheLoadOldValueStatements: Array<t.Statement> = [];
@@ -626,6 +630,7 @@ function codegenReactiveScope(
626630
t.stringLiteral(name.name),
627631
t.stringLiteral(cx.fnName),
628632
t.stringLiteral("cached"),
633+
t.stringLiteral(loc),
629634
])
630635
)
631636
);
@@ -637,6 +642,7 @@ function codegenReactiveScope(
637642
t.stringLiteral(name.name),
638643
t.stringLiteral(cx.fnName),
639644
t.stringLiteral("recomputed"),
645+
t.stringLiteral(loc),
640646
])
641647
)
642648
);

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import { CompilerError } from "..";
8+
import { CompilerError, SourceLocation } from "..";
99
import { Environment } from "../HIR";
1010
import {
1111
GeneratedSource,
@@ -110,6 +110,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
110110
reassignments: new Set(),
111111
earlyReturnValue: null,
112112
merged: new Set(),
113+
loc: identifier.loc,
113114
};
114115
scopes.set(groupIdentifier, scope);
115116
} else {
@@ -119,6 +120,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
119120
scope.range.end = makeInstructionId(
120121
Math.max(scope.range.end, identifier.mutableRange.end)
121122
);
123+
scope.loc = mergeLocation(scope.loc, identifier.loc);
122124
}
123125
identifier.scope = scope;
124126
identifier.mutableRange = scope.range;
@@ -159,6 +161,25 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
159161
}
160162
}
161163

164+
function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation {
165+
if (l === GeneratedSource) {
166+
return r;
167+
} else if (r === GeneratedSource) {
168+
return l;
169+
} else {
170+
return {
171+
start: {
172+
line: Math.min(l.start.line, r.start.line),
173+
column: Math.min(l.start.column, r.start.column),
174+
},
175+
end: {
176+
line: Math.max(l.end.line, r.end.line),
177+
column: Math.max(l.end.column, r.end.column),
178+
},
179+
};
180+
}
181+
}
182+
162183
// Is the operand mutable at this given instruction
163184
export function isMutable({ id }: Instruction, place: Place): boolean {
164185
const range = place.identifier.mutableRange;

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateEarlyReturns.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ class Transform extends ReactiveFunctionTransform<State> {
153153

154154
const instructions = scopeBlock.instructions;
155155
const loc = earlyReturnValue.loc;
156-
const sentinelTemp = createTemporaryPlace(this.env);
157-
const symbolTemp = createTemporaryPlace(this.env);
158-
const forTemp = createTemporaryPlace(this.env);
159-
const argTemp = createTemporaryPlace(this.env);
156+
const sentinelTemp = createTemporaryPlace(this.env, loc);
157+
const symbolTemp = createTemporaryPlace(this.env, loc);
158+
const forTemp = createTemporaryPlace(this.env, loc);
159+
const argTemp = createTemporaryPlace(this.env, loc);
160160
scopeBlock.instructions = [
161161
{
162162
kind: "instruction",
@@ -274,7 +274,7 @@ class Transform extends ReactiveFunctionTransform<State> {
274274
if (state.earlyReturnValue !== null) {
275275
earlyReturnValue = state.earlyReturnValue;
276276
} else {
277-
const identifier = createTemporaryPlace(this.env).identifier;
277+
const identifier = createTemporaryPlace(this.env, loc).identifier;
278278
promoteTemporary(identifier);
279279
earlyReturnValue = {
280280
label: this.env.nextBlockId,

compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class SSABuilder {
8686
},
8787
scope: null, // reset along w the mutable range
8888
type: makeType(),
89+
loc: oldId.loc,
8990
};
9091
}
9192

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ function Component(props) {
2929
let condition = $[0] !== props.value;
3030
if (!condition) {
3131
let old$x = $[1];
32-
$structuralCheck(old$x, x, "x", "Component", "cached");
32+
$structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)");
3333
}
3434
$[0] = props.value;
3535
$[1] = x;
3636
if (condition) {
3737
x = [];
3838
x.push(props.value);
39-
$structuralCheck($[1], x, "x", "Component", "recomputed");
39+
$structuralCheck($[1], x, "x", "Component", "recomputed", "(3:6)");
4040
x = $[1];
4141
}
4242
}

0 commit comments

Comments
 (0)