Skip to content

Commit b4b453d

Browse files
alxhubSplaktar
authored andcommitted
refactor(compiler-cli): make file/shim split 1:n instead of 1:1 (angular#38105)
Previously in the template type-checking engine, it was assumed that every input file would have an associated type-checking shim. The type check block code for all components in the input file would be generated into this shim. This is fine for whole-program type checking operations, but to support the language service's requirements for low latency, it would be ideal to be able to check a single component in isolation, especially if the component is declared along with many others in a single file. This commit removes the assumption that the file/shim mapping is 1:1, and introduces the concept of component-to-shim mapping. Any `TypeCheckingProgramStrategy` must provide such a mapping. To achieve this: * type checking record information is now split into file-level data as well as per-shim data. * components are now assigned a stable `TemplateId` which is unique to the file in which they're declared. PR Close angular#38105
1 parent 06f0855 commit b4b453d

File tree

8 files changed

+199
-88
lines changed

8 files changed

+199
-88
lines changed

packages/compiler-cli/src/ngtsc/incremental/api.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@ import {AbsoluteFsPath} from '../file_system';
1212
/**
1313
* Interface of the incremental build engine.
1414
*
15-
* `W` is a generic type representing a unit of work. This is generic to avoid a cyclic dependency
16-
* between the incremental engine API definition and its consumer(s).
17-
* `T` is a generic type representing template type-checking data for a particular file, which is
18-
* generic for the same reason.
15+
* `AnalysisT` is a generic type representing a unit of work. This is generic to avoid a cyclic
16+
* dependency between the incremental engine API definition and its consumer(s).
17+
* `FileTypeCheckDataT` is a generic type representing template type-checking data for a particular
18+
* input file, which is generic for the same reason.
1919
*/
20-
export interface IncrementalBuild<W, T> {
20+
export interface IncrementalBuild<AnalysisT, FileTypeCheckDataT> {
2121
/**
2222
* Retrieve the prior analysis work, if any, done for the given source file.
2323
*/
24-
priorWorkFor(sf: ts.SourceFile): W[]|null;
24+
priorWorkFor(sf: ts.SourceFile): AnalysisT[]|null;
2525

2626
/**
2727
* Retrieve the prior type-checking work, if any, that's been done for the given source file.
2828
*/
29-
priorTypeCheckingResultsFor(sf: ts.SourceFile): T|null;
29+
priorTypeCheckingResultsFor(fileSf: ts.SourceFile): FileTypeCheckDataT|null;
3030
}
3131

3232
/**

packages/compiler-cli/src/ngtsc/typecheck/src/api.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {AbsoluteFsPath} from '../../file_system';
1313
import {Reference} from '../../imports';
1414
import {TemplateGuardMeta} from '../../metadata';
1515
import {ClassDeclaration} from '../../reflection';
16+
import {ComponentToShimMappingStrategy} from './context';
1617

1718

1819
/**
@@ -284,7 +285,7 @@ export interface ExternalTemplateSourceMapping {
284285
* This abstraction allows both the Angular compiler itself as well as the language service to
285286
* implement efficient template type-checking using common infrastructure.
286287
*/
287-
export interface TypeCheckingProgramStrategy {
288+
export interface TypeCheckingProgramStrategy extends ComponentToShimMappingStrategy {
288289
/**
289290
* Retrieve the latest version of the program, containing all the updates made thus far.
290291
*/

packages/compiler-cli/src/ngtsc/typecheck/src/augmented_program.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99
import * as ts from 'typescript';
1010

11-
import {AbsoluteFsPath} from '../../file_system';
11+
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
1212
import {retagAllTsFiles, untagAllTsFiles} from '../../shims';
1313

1414
import {TypeCheckingProgramStrategy, UpdateMode} from './api';
1515
import {TypeCheckProgramHost} from './host';
16+
import {TypeCheckShimGenerator} from './shim';
1617

1718
/**
1819
* Implements a template type-checking program using `ts.createProgram` and TypeScript's program
@@ -78,4 +79,8 @@ export class ReusedProgramStrategy implements TypeCheckingProgramStrategy {
7879
untagAllTsFiles(this.program);
7980
untagAllTsFiles(oldProgram);
8081
}
82+
83+
shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath {
84+
return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile()));
85+
}
8186
}

packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts

+25-18
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {isShim} from '../../shims';
1616

1717
import {TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from './api';
1818
import {FileTypeCheckingData, TypeCheckContext, TypeCheckRequest} from './context';
19-
import {shouldReportDiagnostic, translateDiagnostic} from './diagnostics';
19+
import {shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
2020

2121
/**
2222
* Interface to trigger generation of type-checking code for a program given a new
@@ -49,8 +49,8 @@ export class TemplateTypeChecker {
4949
refresh(): TypeCheckRequest {
5050
this.files.clear();
5151

52-
const ctx =
53-
new TypeCheckContext(this.config, this.compilerHost, this.refEmitter, this.reflector);
52+
const ctx = new TypeCheckContext(
53+
this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector);
5454

5555
// Typecheck all the files.
5656
for (const sf of this.originalProgram.getSourceFiles()) {
@@ -86,25 +86,32 @@ export class TemplateTypeChecker {
8686
if (!this.files.has(path)) {
8787
return [];
8888
}
89-
const record = this.files.get(path)!;
89+
const fileRecord = this.files.get(path)!;
9090

9191
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
92-
const typeCheckSf = getSourceFileOrError(typeCheckProgram, record.typeCheckFile);
93-
const rawDiagnostics = [];
94-
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(typeCheckSf));
95-
if (record.hasInlines) {
92+
93+
const diagnostics: (ts.Diagnostic|null)[] = [];
94+
if (fileRecord.hasInlines) {
9695
const inlineSf = getSourceFileOrError(typeCheckProgram, path);
97-
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf));
96+
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
97+
diag => convertDiagnostic(diag, fileRecord.sourceResolver)));
98+
}
99+
for (const [shimPath, shimRecord] of fileRecord.shimData) {
100+
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
101+
102+
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
103+
diag => convertDiagnostic(diag, fileRecord.sourceResolver)));
104+
diagnostics.push(...shimRecord.genesisDiagnostics);
98105
}
99106

100-
return rawDiagnostics
101-
.map(diag => {
102-
if (!shouldReportDiagnostic(diag)) {
103-
return null;
104-
}
105-
return translateDiagnostic(diag, record.sourceResolver);
106-
})
107-
.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null)
108-
.concat(record.genesisDiagnostics);
107+
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
108+
}
109+
}
110+
111+
function convertDiagnostic(
112+
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): ts.Diagnostic|null {
113+
if (!shouldReportDiagnostic(diag)) {
114+
return null;
109115
}
116+
return translateDiagnostic(diag, sourceResolver);
110117
}

packages/compiler-cli/src/ngtsc/typecheck/src/context.ts

+105-38
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
1414
import {ClassDeclaration, ReflectionHost} from '../../reflection';
1515
import {ImportManager} from '../../translator';
1616

17-
import {TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api';
17+
import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api';
1818
import {TemplateSourceResolver} from './diagnostics';
1919
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
2020
import {Environment} from './environment';
2121
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
22-
import {TypeCheckShimGenerator} from './shim';
2322
import {TemplateSourceManager} from './source';
2423
import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block';
2524
import {TypeCheckFile} from './type_check_file';
@@ -46,7 +45,8 @@ export interface TypeCheckRequest {
4645
}
4746

4847
/**
49-
* Data for a type-checking shim which is required to support generation of diagnostics.
48+
* Data for template type-checking related to a specific input file in the user's program (which
49+
* contains components to be checked).
5050
*/
5151
export interface FileTypeCheckingData {
5252
/**
@@ -56,25 +56,39 @@ export interface FileTypeCheckingData {
5656
hasInlines: boolean;
5757

5858
/**
59-
* Source mapping information for mapping diagnostics back to the original template.
59+
* Source mapping information for mapping diagnostics from inlined type check blocks back to the
60+
* original template.
6061
*/
6162
sourceResolver: TemplateSourceResolver;
6263

6364
/**
64-
* Any `ts.Diagnostic`s which were produced during the generation of this shim.
65+
* Data for each shim generated from this input file.
6566
*
66-
* Some diagnostics are produced during creation time and are tracked here.
67+
* A single input file will generate one or more shim files that actually contain template
68+
* type-checking code.
6769
*/
68-
genesisDiagnostics: ts.Diagnostic[];
70+
shimData: Map<AbsoluteFsPath, ShimTypeCheckingData>;
71+
}
6972

73+
/**
74+
* Data specific to a single shim generated from an input file.
75+
*/
76+
export interface ShimTypeCheckingData {
7077
/**
7178
* Path to the shim file.
7279
*/
73-
typeCheckFile: AbsoluteFsPath;
80+
path: AbsoluteFsPath;
81+
82+
/**
83+
* Any `ts.Diagnostic`s which were produced during the generation of this shim.
84+
*
85+
* Some diagnostics are produced during creation time and are tracked here.
86+
*/
87+
genesisDiagnostics: ts.Diagnostic[];
7488
}
7589

7690
/**
77-
* Data for a type-checking shim which is still having its code generated.
91+
* Data for an input file which is still in the process of template type-checking code generation.
7892
*/
7993
export interface PendingFileTypeCheckingData {
8094
/**
@@ -83,10 +97,18 @@ export interface PendingFileTypeCheckingData {
8397
hasInlines: boolean;
8498

8599
/**
86-
* `TemplateSourceManager` being used to track source mapping information for this shim.
100+
* Source mapping information for mapping diagnostics from inlined type check blocks back to the
101+
* original template.
87102
*/
88103
sourceManager: TemplateSourceManager;
89104

105+
/**
106+
* Map of in-progress shim data for shims generated from this input file.
107+
*/
108+
shimData: Map<AbsoluteFsPath, PendingShimData>;
109+
}
110+
111+
export interface PendingShimData {
90112
/**
91113
* Recorder for out-of-band diagnostics which are raised during generation.
92114
*/
@@ -98,9 +120,28 @@ export interface PendingFileTypeCheckingData {
98120
domSchemaChecker: DomSchemaChecker;
99121

100122
/**
101-
* Path to the shim file.
123+
* Shim file in the process of being generated.
102124
*/
103-
typeCheckFile: TypeCheckFile;
125+
file: TypeCheckFile;
126+
}
127+
128+
/**
129+
* Abstracts the operation of determining which shim file will host a particular component's
130+
* template type-checking code.
131+
*
132+
* Different consumers of the type checking infrastructure may choose different approaches to
133+
* optimize for their specific use case (for example, the command-line compiler optimizes for
134+
* efficient `ts.Program` reuse in watch mode).
135+
*/
136+
export interface ComponentToShimMappingStrategy {
137+
/**
138+
* Given a component, determine a path to the shim file into which that component's type checking
139+
* code will be generated.
140+
*
141+
* A major constraint is that components in different input files must not share the same shim
142+
* file. The behavior of the template type-checking system is undefined if this is violated.
143+
*/
144+
shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath;
104145
}
105146

106147
/**
@@ -115,6 +156,7 @@ export class TypeCheckContext {
115156
constructor(
116157
private config: TypeCheckingConfig,
117158
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
159+
private componentMappingStrategy: ComponentToShimMappingStrategy,
118160
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {}
119161

120162
/**
@@ -160,8 +202,7 @@ export class TypeCheckContext {
160202
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping,
161203
file: ParseSourceFile): void {
162204
const fileData = this.dataForFile(ref.node.getSourceFile());
163-
164-
const id = fileData.sourceManager.captureSource(sourceMapping, file);
205+
const shimData = this.pendingShimForComponent(ref.node);
165206
// Get all of the directives used in the template and record type constructors for all of them.
166207
for (const dir of boundTarget.getUsedDirectives()) {
167208
const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;
@@ -184,15 +225,19 @@ export class TypeCheckContext {
184225
}
185226
}
186227

187-
const tcbMetadata: TypeCheckBlockMetadata = {id, boundTarget, pipes, schemas};
228+
const meta = {
229+
id: fileData.sourceManager.captureSource(ref.node, sourceMapping, file),
230+
boundTarget,
231+
pipes,
232+
schemas,
233+
};
188234
if (requiresInlineTypeCheckBlock(ref.node)) {
189235
// This class didn't meet the requirements for external type checking, so generate an inline
190236
// TCB for the class.
191-
this.addInlineTypeCheckBlock(fileData, ref, tcbMetadata);
237+
this.addInlineTypeCheckBlock(fileData, shimData, ref, meta);
192238
} else {
193239
// The class can be type-checked externally as normal.
194-
fileData.typeCheckFile.addTypeCheckBlock(
195-
ref, tcbMetadata, fileData.domSchemaChecker, fileData.oobRecorder);
240+
shimData.file.addTypeCheckBlock(ref, meta, shimData.domSchemaChecker, shimData.oobRecorder);
196241
}
197242
}
198243

@@ -278,17 +323,27 @@ export class TypeCheckContext {
278323
perFileData: new Map<AbsoluteFsPath, FileTypeCheckingData>(),
279324
};
280325

281-
for (const [sfPath, fileData] of this.fileMap.entries()) {
282-
updates.set(fileData.typeCheckFile.fileName, fileData.typeCheckFile.render());
283-
results.perFileData.set(sfPath, {
284-
genesisDiagnostics: [
285-
...fileData.domSchemaChecker.diagnostics,
286-
...fileData.oobRecorder.diagnostics,
287-
],
288-
hasInlines: fileData.hasInlines,
289-
sourceResolver: fileData.sourceManager,
290-
typeCheckFile: fileData.typeCheckFile.fileName,
291-
});
326+
// Then go through each input file that has pending code generation operations.
327+
for (const [sfPath, pendingFileData] of this.fileMap) {
328+
const fileData: FileTypeCheckingData = {
329+
hasInlines: pendingFileData.hasInlines,
330+
shimData: new Map(),
331+
sourceResolver: pendingFileData.sourceManager,
332+
};
333+
334+
// For each input file, consider generation operations for each of its shims.
335+
for (const [shimPath, pendingShimData] of pendingFileData.shimData) {
336+
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
337+
fileData.shimData.set(shimPath, {
338+
genesisDiagnostics: [
339+
...pendingShimData.domSchemaChecker.diagnostics,
340+
...pendingShimData.oobRecorder.diagnostics,
341+
],
342+
path: pendingShimData.file.fileName,
343+
});
344+
}
345+
346+
results.perFileData.set(sfPath, fileData);
292347
}
293348

294349
for (const [sfPath, fileData] of this.adoptedFiles.entries()) {
@@ -299,32 +354,44 @@ export class TypeCheckContext {
299354
}
300355

301356
private addInlineTypeCheckBlock(
302-
fileData: PendingFileTypeCheckingData, ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
357+
fileData: PendingFileTypeCheckingData, shimData: PendingShimData,
358+
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
303359
tcbMeta: TypeCheckBlockMetadata): void {
304360
const sf = ref.node.getSourceFile();
305361
if (!this.opMap.has(sf)) {
306362
this.opMap.set(sf, []);
307363
}
308364
const ops = this.opMap.get(sf)!;
309365
ops.push(new TcbOp(
310-
ref, tcbMeta, this.config, this.reflector, fileData.domSchemaChecker,
311-
fileData.oobRecorder));
366+
ref, tcbMeta, this.config, this.reflector, shimData.domSchemaChecker,
367+
shimData.oobRecorder));
312368
fileData.hasInlines = true;
313369
}
314370

371+
private pendingShimForComponent(node: ts.ClassDeclaration): PendingShimData {
372+
const fileData = this.dataForFile(node.getSourceFile());
373+
const shimPath = this.componentMappingStrategy.shimPathForComponent(node);
374+
if (!fileData.shimData.has(shimPath)) {
375+
fileData.shimData.set(shimPath, {
376+
domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager),
377+
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
378+
file: new TypeCheckFile(
379+
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
380+
});
381+
}
382+
return fileData.shimData.get(shimPath)!;
383+
}
384+
315385
private dataForFile(sf: ts.SourceFile): PendingFileTypeCheckingData {
316386
const sfPath = absoluteFromSourceFile(sf);
317387

388+
const sourceManager = new TemplateSourceManager();
389+
318390
if (!this.fileMap.has(sfPath)) {
319-
const sourceManager = new TemplateSourceManager();
320391
const data: PendingFileTypeCheckingData = {
321-
domSchemaChecker: new RegistryDomSchemaChecker(sourceManager),
322-
oobRecorder: new OutOfBandDiagnosticRecorderImpl(sourceManager),
323-
typeCheckFile: new TypeCheckFile(
324-
TypeCheckShimGenerator.shimFor(sfPath), this.config, this.refEmitter, this.reflector,
325-
this.compilerHost),
326392
hasInlines: false,
327393
sourceManager,
394+
shimData: new Map(),
328395
};
329396
this.fileMap.set(sfPath, data);
330397
}

0 commit comments

Comments
 (0)