perf(ivy): template type-check the entire program in 1 file if possible (#29698)

The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.

Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.

This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.

It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.

Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.

PR Close #29698
This commit is contained in:
Alex Rickabaugh
2019-04-02 11:25:33 -07:00
committed by Ben Lesh
parent f4c536ae36
commit 98f86de8da
17 changed files with 753 additions and 317 deletions

View File

@ -27,11 +27,11 @@ import {TypeScriptReflectionHost} from './reflection';
import {HostResourceLoader} from './resource_loader';
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, generatedFactoryTransform} from './shims';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
import {ivySwitchTransform} from './switch';
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
import {aliasTransformFactory} from './transform/src/alias';
import {TypeCheckContext, TypeCheckProgramHost, TypeCheckingConfig} from './typecheck';
import {TypeCheckContext, TypeCheckingConfig, typeCheckFilePath} from './typecheck';
import {normalizeSeparators} from './util/src/path';
import {getRootDirs, isDtsPath} from './util/src/typescript';
@ -64,6 +64,7 @@ export class NgtscProgram implements api.Program {
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
private perfTracker: PerfTracker|null = null;
private incrementalState: IncrementalState;
private typeCheckFilePath: AbsoluteFsPath;
constructor(
rootNames: ReadonlyArray<string>, private options: api.CompilerOptions,
@ -105,6 +106,10 @@ export class NgtscProgram implements api.Program {
generators.push(summaryGenerator, factoryGenerator);
}
this.typeCheckFilePath = typeCheckFilePath(this.rootDirs);
generators.push(new TypeCheckShimGenerator(this.typeCheckFilePath));
rootFiles.push(this.typeCheckFilePath);
let entryPoint: string|null = null;
if (options.flatModuleOutFile !== undefined) {
entryPoint = findFlatIndexEntryPoint(normalizedRootNames);
@ -189,18 +194,7 @@ export class NgtscProgram implements api.Program {
fileName?: string|undefined, cancellationToken?: ts.CancellationToken|
undefined): ReadonlyArray<ts.Diagnostic|api.Diagnostic> {
const compilation = this.ensureAnalyzed();
const diagnostics = [...compilation.diagnostics];
if (!!this.options.fullTemplateTypeCheck) {
const config: TypeCheckingConfig = {
applyTemplateContextGuards: true,
checkTemplateBodies: true,
checkTypeOfBindings: true,
strictSafeNavigationTypes: true,
};
const ctx = new TypeCheckContext(config, this.refEmitter !);
compilation.typeCheck(ctx);
diagnostics.push(...this.compileTypeCheckProgram(ctx));
}
const diagnostics = [...compilation.diagnostics, ...this.getTemplateDiagnostics()];
if (this.entryPoint !== null && this.exportReferenceGraph !== null) {
diagnostics.push(...checkForPrivateExports(
this.entryPoint, this.tsProgram.getTypeChecker(), this.exportReferenceGraph));
@ -344,8 +338,11 @@ export class NgtscProgram implements api.Program {
const emitSpan = this.perfRecorder.start('emit');
const emitResults: ts.EmitResult[] = [];
const typeCheckFile = this.tsProgram.getSourceFile(this.typeCheckFilePath);
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
if (targetSourceFile.isDeclarationFile) {
if (targetSourceFile.isDeclarationFile || targetSourceFile === typeCheckFile) {
continue;
}
@ -378,15 +375,47 @@ export class NgtscProgram implements api.Program {
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
}
private compileTypeCheckProgram(ctx: TypeCheckContext): ReadonlyArray<ts.Diagnostic> {
const host = new TypeCheckProgramHost(this.tsProgram, this.host, ctx);
const auxProgram = ts.createProgram({
host,
rootNames: this.tsProgram.getRootFileNames(),
oldProgram: this.tsProgram,
options: this.options,
});
return auxProgram.getSemanticDiagnostics();
private getTemplateDiagnostics(): ReadonlyArray<ts.Diagnostic> {
// Skip template type-checking unless explicitly requested.
if (this.options.fullTemplateTypeCheck !== true) {
return [];
}
const compilation = this.ensureAnalyzed();
// Run template type-checking.
// First select a type-checking configuration, based on whether full template type-checking is
// requested.
let typeCheckingConfig: TypeCheckingConfig;
if (this.options.fullTemplateTypeCheck) {
typeCheckingConfig = {
applyTemplateContextGuards: true,
checkTemplateBodies: true,
checkTypeOfBindings: true,
strictSafeNavigationTypes: true,
};
} else {
typeCheckingConfig = {
applyTemplateContextGuards: false,
checkTemplateBodies: false,
checkTypeOfBindings: false,
strictSafeNavigationTypes: false,
};
}
// Execute the typeCheck phase of each decorator in the program.
const prepSpan = this.perfRecorder.start('typeCheckPrep');
const ctx = new TypeCheckContext(typeCheckingConfig, this.refEmitter !, this.typeCheckFilePath);
compilation.typeCheck(ctx);
this.perfRecorder.stop(prepSpan);
// Get the diagnostics.
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
const diagnostics = ctx.calculateTemplateDiagnostics(this.tsProgram, this.host, this.options);
this.perfRecorder.stop(typeCheckSpan);
return diagnostics;
}
private makeCompilation(): IvyCompilation {