Skip to content

Commit 4c89379

Browse files
committed
[compiler][wip] Improve diagnostic infra
Work in progress, i'm experimenting with revamping our diagnostic infra. Starting with a better format for representing errors, with an ability to point ot multiple locations, along with better printing of errors. Of course, Babel still controls the printing in the majority case so this still needs more work.
1 parent 21be5d2 commit 4c89379

File tree

305 files changed

+3349
-481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

305 files changed

+3349
-481
lines changed

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

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

8+
import {codeFrameColumns} from '@babel/code-frame';
89
import type {SourceLocation} from './HIR';
910
import {Err, Ok, Result} from './Utils/Result';
1011
import {assertExhaustive} from './Utils/utils';
@@ -44,6 +45,40 @@ export enum ErrorSeverity {
4445
Invariant = 'Invariant',
4546
}
4647

48+
export type CompilerDiagnosticOptions = {
49+
severity: ErrorSeverity;
50+
category: string;
51+
description: string;
52+
details: Array<CompilerDiagnosticDetail>;
53+
suggestions?: Array<CompilerSuggestion> | null | undefined;
54+
};
55+
56+
export type CompilerDiagnosticDetail =
57+
/**
58+
* Additional information not coupled to a specific location,
59+
* generally linking to documentation.
60+
*/
61+
| {
62+
kind: 'info';
63+
message: string;
64+
}
65+
/**
66+
* The (a) source of the error
67+
*/
68+
| {
69+
kind: 'error';
70+
loc: SourceLocation;
71+
message: string;
72+
}
73+
/**
74+
* A related part of the source code that does not directly contribute to the error
75+
*/
76+
| {
77+
kind: 'related';
78+
loc: SourceLocation;
79+
message: string;
80+
};
81+
4782
export enum CompilerSuggestionOperation {
4883
InsertBefore,
4984
InsertAfter,
@@ -74,6 +109,73 @@ export type CompilerErrorDetailOptions = {
74109
suggestions?: Array<CompilerSuggestion> | null | undefined;
75110
};
76111

112+
export class CompilerDiagnostic {
113+
options: CompilerDiagnosticOptions;
114+
115+
constructor(options: CompilerDiagnosticOptions) {
116+
this.options = options;
117+
}
118+
119+
get category(): CompilerDiagnosticOptions['category'] {
120+
return this.options.category;
121+
}
122+
get description(): CompilerDiagnosticOptions['description'] {
123+
return this.options.description;
124+
}
125+
get severity(): CompilerDiagnosticOptions['severity'] {
126+
return this.options.severity;
127+
}
128+
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
129+
return this.options.suggestions;
130+
}
131+
132+
printErrorMessage(source: string): string {
133+
const buffer = [`${this.severity}: ${this.category}\n\n`, this.description];
134+
for (const detail of this.options.details) {
135+
switch (detail.kind) {
136+
case 'error':
137+
case 'related': {
138+
const loc = detail.loc;
139+
if (typeof loc === 'symbol') {
140+
continue;
141+
}
142+
let codeFrame: string;
143+
try {
144+
codeFrame = codeFrameColumns(
145+
source,
146+
{
147+
start: {
148+
line: loc.start.line,
149+
column: loc.start.column + 1,
150+
},
151+
end: {
152+
line: loc.end.line,
153+
column: loc.end.column + 1,
154+
},
155+
},
156+
{
157+
message: detail.message,
158+
},
159+
);
160+
} catch (e) {
161+
codeFrame = detail.message;
162+
}
163+
buffer.push(
164+
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
165+
);
166+
buffer.push(codeFrame);
167+
}
168+
}
169+
}
170+
return buffer.join('');
171+
}
172+
173+
toString(): string {
174+
const buffer = [`${this.severity}: ${this.category}\n\n`, this.description];
175+
return buffer.join('');
176+
}
177+
}
178+
77179
/*
78180
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
79181
* aggregated into a single {@link CompilerError} later.
@@ -101,24 +203,58 @@ export class CompilerErrorDetail {
101203
return this.options.suggestions;
102204
}
103205

104-
printErrorMessage(): string {
206+
printErrorMessage(source: string): string {
105207
const buffer = [`${this.severity}: ${this.reason}`];
106208
if (this.description != null) {
107-
buffer.push(`. ${this.description}`);
209+
buffer.push(`\n\n${this.description}.`);
108210
}
109-
if (this.loc != null && typeof this.loc !== 'symbol') {
110-
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
211+
const loc = this.loc;
212+
if (loc != null && typeof loc !== 'symbol') {
213+
let codeFrame: string;
214+
try {
215+
codeFrame = codeFrameColumns(
216+
source,
217+
{
218+
start: {
219+
line: loc.start.line,
220+
column: loc.start.column + 1,
221+
},
222+
end: {
223+
line: loc.end.line,
224+
column: loc.end.column + 1,
225+
},
226+
},
227+
{
228+
message: this.reason,
229+
},
230+
);
231+
} catch (e) {
232+
codeFrame = '';
233+
}
234+
buffer.push(
235+
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
236+
);
237+
buffer.push(codeFrame);
238+
buffer.push('\n\n');
111239
}
112240
return buffer.join('');
113241
}
114242

115243
toString(): string {
116-
return this.printErrorMessage();
244+
const buffer = [`${this.severity}: ${this.reason}`];
245+
if (this.description != null) {
246+
buffer.push(`. ${this.description}.`);
247+
}
248+
const loc = this.loc;
249+
if (loc != null && typeof loc !== 'symbol') {
250+
buffer.push(` (${loc.start.line}:${loc.start.column})`);
251+
}
252+
return buffer.join('');
117253
}
118254
}
119255

120256
export class CompilerError extends Error {
121-
details: Array<CompilerErrorDetail> = [];
257+
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
122258

123259
static invariant(
124260
condition: unknown,
@@ -136,6 +272,12 @@ export class CompilerError extends Error {
136272
}
137273
}
138274

275+
static throwDiagnostic(options: CompilerDiagnosticOptions): never {
276+
const errors = new CompilerError();
277+
errors.pushDiagnostic(new CompilerDiagnostic(options));
278+
throw errors;
279+
}
280+
139281
static throwTodo(
140282
options: Omit<CompilerErrorDetailOptions, 'severity'>,
141283
): never {
@@ -210,6 +352,21 @@ export class CompilerError extends Error {
210352
return this.name;
211353
}
212354

355+
printErrorMessage(source: string): string {
356+
return (
357+
`Found ${this.details.length} errors:\n` +
358+
this.details.map(detail => detail.printErrorMessage(source)).join('\n')
359+
);
360+
}
361+
362+
merge(other: CompilerError): void {
363+
this.details.push(...other.details);
364+
}
365+
366+
pushDiagnostic(diagnostic: CompilerDiagnostic): void {
367+
this.details.push(diagnostic);
368+
}
369+
213370
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
214371
const detail = new CompilerErrorDetail({
215372
reason: options.reason,

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import * as t from '@babel/types';
99
import {z} from 'zod';
10-
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
10+
import {
11+
CompilerDiagnosticOptions,
12+
CompilerError,
13+
CompilerErrorDetailOptions,
14+
} from '../CompilerError';
1115
import {
1216
EnvironmentConfig,
1317
ExternalFunction,
@@ -224,7 +228,7 @@ export type LoggerEvent =
224228
export type CompileErrorEvent = {
225229
kind: 'CompileError';
226230
fnLoc: t.SourceLocation | null;
227-
detail: CompilerErrorDetailOptions;
231+
detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions;
228232
};
229233
export type CompileDiagnosticEvent = {
230234
kind: 'CompileDiagnostic';

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,27 @@
88
import {NodePath} from '@babel/core';
99
import * as t from '@babel/types';
1010

11-
import {
12-
CompilerError,
13-
CompilerErrorDetailOptions,
14-
EnvironmentConfig,
15-
ErrorSeverity,
16-
Logger,
17-
} from '..';
11+
import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..';
1812
import {getOrInsertWith} from '../Utils/utils';
19-
import {Environment} from '../HIR';
13+
import {Environment, GeneratedSource} from '../HIR';
2014
import {DEFAULT_EXPORT} from '../HIR/Environment';
2115
import {CompileProgramMetadata} from './Program';
16+
import {CompilerDiagnosticOptions} from '../CompilerError';
2217

2318
function throwInvalidReact(
24-
options: Omit<CompilerErrorDetailOptions, 'severity'>,
19+
options: Omit<CompilerDiagnosticOptions, 'severity'>,
2520
{logger, filename}: TraversalState,
2621
): never {
27-
const detail: CompilerErrorDetailOptions = {
28-
...options,
22+
const detail: CompilerDiagnosticOptions = {
2923
severity: ErrorSeverity.InvalidReact,
24+
...options,
3025
};
3126
logger?.logEvent(filename, {
3227
kind: 'CompileError',
3328
fnLoc: null,
3429
detail,
3530
});
36-
CompilerError.throw(detail);
31+
CompilerError.throwDiagnostic(detail);
3732
}
3833
function assertValidEffectImportReference(
3934
numArgs: number,
@@ -65,14 +60,18 @@ function assertValidEffectImportReference(
6560
*/
6661
throwInvalidReact(
6762
{
68-
reason:
69-
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' +
70-
'This will break your build! ' +
71-
'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
72-
description: maybeErrorDiagnostic
73-
? `(Bailout reason: ${maybeErrorDiagnostic})`
74-
: null,
75-
loc: parent.node.loc ?? null,
63+
category:
64+
'Cannot infer dependencies of this effect. This will break your build!',
65+
description:
66+
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
67+
(maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''),
68+
details: [
69+
{
70+
kind: 'error',
71+
message: 'Cannot infer dependencies',
72+
loc: parent.node.loc ?? GeneratedSource,
73+
},
74+
],
7675
},
7776
context,
7877
);
@@ -92,13 +91,20 @@ function assertValidFireImportReference(
9291
);
9392
throwInvalidReact(
9493
{
95-
reason:
96-
'[Fire] Untransformed reference to compiler-required feature. ' +
97-
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
98-
description: maybeErrorDiagnostic
99-
? `(Bailout reason: ${maybeErrorDiagnostic})`
100-
: null,
101-
loc: paths[0].node.loc ?? null,
94+
category:
95+
'[Fire] Untransformed reference to compiler-required feature.',
96+
description:
97+
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
98+
maybeErrorDiagnostic
99+
? ` ${maybeErrorDiagnostic}`
100+
: '',
101+
details: [
102+
{
103+
kind: 'error',
104+
message: 'Untransformed `fire` call',
105+
loc: paths[0].node.loc ?? GeneratedSource,
106+
},
107+
],
102108
},
103109
context,
104110
);

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,11 +2271,17 @@ function lowerExpression(
22712271
});
22722272
for (const [name, locations] of Object.entries(fbtLocations)) {
22732273
if (locations.length > 1) {
2274-
CompilerError.throwTodo({
2275-
reason: `Support <${tagName}> tags with multiple <${tagName}:${name}> values`,
2276-
loc: locations.at(-1) ?? GeneratedSource,
2277-
description: null,
2278-
suggestions: null,
2274+
CompilerError.throwDiagnostic({
2275+
severity: ErrorSeverity.Todo,
2276+
category: 'Support duplicate fbt tags',
2277+
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
2278+
details: locations.map(loc => {
2279+
return {
2280+
kind: 'error',
2281+
message: `Multiple \`<${tagName}:${name}>\` tags found`,
2282+
loc,
2283+
};
2284+
}),
22792285
});
22802286
}
22812287
}
@@ -3501,9 +3507,8 @@ function lowerFunction(
35013507
);
35023508
let loweredFunc: HIRFunction;
35033509
if (lowering.isErr()) {
3504-
lowering
3505-
.unwrapErr()
3506-
.details.forEach(detail => builder.errors.pushErrorDetail(detail));
3510+
const functionErrors = lowering.unwrapErr();
3511+
builder.errors.merge(functionErrors);
35073512
return null;
35083513
}
35093514
loweredFunc = lowering.unwrap();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ export class Environment {
779779
for (const error of errors.unwrapErr().details) {
780780
this.logger.logEvent(this.filename, {
781781
kind: 'CompileError',
782-
detail: error,
782+
detail: error.options,
783783
fnLoc: null,
784784
});
785785
}

0 commit comments

Comments
 (0)