refactor(ivy): formalize the compilation process for matched handlers (#34288)
Prior to this commit, the `IvyCompilation` tracked the state of each matched `DecoratorHandler` on each class in the `ts.Program`, and how they progressed through the compilation process. This tracking was originally simple, but had grown more complicated as the compiler evolved. The state of each specific "target" of compilation was determined by the nullability of a number of fields on the object which tracked it. This commit formalizes the process of compilation of each matched handler into a new "trait" concept. A trait is some aspect of a class which gets created when a `DecoratorHandler` matches the class. It represents an Ivy aspect that needs to go through the compilation process. Traits begin in a "pending" state and undergo transitions as various steps of compilation take place. The `IvyCompilation` class is renamed to the `TraitCompiler`, which manages the state of all of the traits in the active program. Making the trait concept explicit will support future work to incrementalize the expensive analysis process of compilation. PR Close #34288
This commit is contained in:

committed by
Kara Erickson

parent
c13a4b8c03
commit
252e3e9487
@ -83,7 +83,7 @@ export class DecorationAnalyzer {
|
||||
moduleResolver = new ModuleResolver(this.program, this.options, this.host);
|
||||
importGraph = new ImportGraph(this.moduleResolver);
|
||||
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
||||
handlers: DecoratorHandler<any, any>[] = [
|
||||
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
||||
new ComponentDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
||||
this.scopeRegistry, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
|
||||
@ -91,9 +91,12 @@ export class DecorationAnalyzer {
|
||||
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
||||
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
/* annotateForClosureCompiler */ false),
|
||||
// clang-format off
|
||||
// See the note in ngtsc about why this cast is needed.
|
||||
new DirectiveDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.fullRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
this.isCore, /* annotateForClosureCompiler */ false),
|
||||
this.isCore, /* annotateForClosureCompiler */ false) as DecoratorHandler<unknown, unknown, unknown>,
|
||||
// clang-format on
|
||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
new PipeDecoratorHandler(
|
||||
@ -195,8 +198,8 @@ export class DecorationAnalyzer {
|
||||
|
||||
protected compileClass(clazz: AnalyzedClass, constantPool: ConstantPool): CompileResult[] {
|
||||
const compilations: CompileResult[] = [];
|
||||
for (const {handler, analysis} of clazz.matches) {
|
||||
const result = handler.compile(clazz.declaration, analysis, constantPool);
|
||||
for (const {handler, analysis, resolution} of clazz.matches) {
|
||||
const result = handler.compile(clazz.declaration, analysis, resolution, constantPool);
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(current => {
|
||||
if (!compilations.some(compilation => compilation.name === current.name)) {
|
||||
@ -211,19 +214,21 @@ export class DecorationAnalyzer {
|
||||
}
|
||||
|
||||
protected resolveFile(analyzedFile: AnalyzedFile): void {
|
||||
analyzedFile.analyzedClasses.forEach(({declaration, matches}) => {
|
||||
matches.forEach(({handler, analysis}) => {
|
||||
for (const {declaration, matches} of analyzedFile.analyzedClasses) {
|
||||
for (const match of matches) {
|
||||
const {handler, analysis} = match;
|
||||
if ((handler.resolve !== undefined) && analysis) {
|
||||
const {reexports, diagnostics} = handler.resolve(declaration, analysis);
|
||||
const {reexports, diagnostics, data} = handler.resolve(declaration, analysis);
|
||||
if (reexports !== undefined) {
|
||||
this.addReexports(reexports, declaration);
|
||||
}
|
||||
if (diagnostics !== undefined) {
|
||||
diagnostics.forEach(error => this.diagnosticHandler(error));
|
||||
}
|
||||
match.resolution = data as Readonly<unknown>;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getReexportsForClass(declaration: ClassDeclaration<ts.Declaration>) {
|
||||
|
@ -26,7 +26,8 @@ import {analyzeDecorators, isWithinPackage} from './util';
|
||||
export class DefaultMigrationHost implements MigrationHost {
|
||||
constructor(
|
||||
readonly reflectionHost: NgccReflectionHost, readonly metadata: MetadataReader,
|
||||
readonly evaluator: PartialEvaluator, private handlers: DecoratorHandler<any, any>[],
|
||||
readonly evaluator: PartialEvaluator,
|
||||
private handlers: DecoratorHandler<unknown, unknown, unknown>[],
|
||||
private entryPointPath: AbsoluteFsPath, private analyzedFiles: AnalyzedFile[],
|
||||
private diagnosticHandler: (error: ts.Diagnostic) => void) {}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {ConstantPool} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {CompileResult, DecoratorHandler} from '../../../src/ngtsc/transform';
|
||||
import {CompileResult, DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform';
|
||||
|
||||
export interface AnalyzedFile {
|
||||
sourceFile: ts.SourceFile;
|
||||
@ -21,7 +21,7 @@ export interface AnalyzedClass {
|
||||
decorators: Decorator[]|null;
|
||||
declaration: ClassDeclaration;
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
matches: {handler: DecoratorHandler<any, any>; analysis: any;}[];
|
||||
matches: MatchingHandler<unknown, unknown, unknown>[];
|
||||
}
|
||||
|
||||
export interface CompiledClass extends AnalyzedClass {
|
||||
@ -42,7 +42,9 @@ export interface CompiledFile {
|
||||
export type DecorationAnalyses = Map<ts.SourceFile, CompiledFile>;
|
||||
export const DecorationAnalyses = Map;
|
||||
|
||||
export interface MatchingHandler<A, M> {
|
||||
handler: DecoratorHandler<A, M>;
|
||||
detected: M;
|
||||
export interface MatchingHandler<D, A, R> {
|
||||
handler: DecoratorHandler<D, A, R>;
|
||||
detected: DetectResult<D>;
|
||||
analysis: Readonly<A>;
|
||||
resolution: Readonly<R>;
|
||||
}
|
||||
|
@ -19,26 +19,37 @@ export function isWithinPackage(packagePath: AbsoluteFsPath, sourceFile: ts.Sour
|
||||
return !relative(packagePath, absoluteFromSourceFile(sourceFile)).startsWith('..');
|
||||
}
|
||||
|
||||
const NOT_YET_KNOWN: Readonly<unknown> = null as unknown as Readonly<unknown>;
|
||||
|
||||
export function analyzeDecorators(
|
||||
classSymbol: NgccClassSymbol, decorators: Decorator[] | null,
|
||||
handlers: DecoratorHandler<any, any>[], flags?: HandlerFlags): AnalyzedClass|null {
|
||||
handlers: DecoratorHandler<unknown, unknown, unknown>[], flags?: HandlerFlags): AnalyzedClass|
|
||||
null {
|
||||
const declaration = classSymbol.declaration.valueDeclaration;
|
||||
const matchingHandlers = handlers
|
||||
.map(handler => {
|
||||
const detected = handler.detect(declaration, decorators);
|
||||
return {handler, detected};
|
||||
})
|
||||
.filter(isMatchingHandler);
|
||||
const matchingHandlers: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
for (const handler of handlers) {
|
||||
const detected = handler.detect(declaration, decorators);
|
||||
if (detected !== undefined) {
|
||||
matchingHandlers.push({
|
||||
handler,
|
||||
detected,
|
||||
analysis: NOT_YET_KNOWN,
|
||||
resolution: NOT_YET_KNOWN,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingHandlers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const detections: {handler: DecoratorHandler<any, any>, detected: DetectResult<any>}[] = [];
|
||||
|
||||
const detections: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
let hasWeakHandler: boolean = false;
|
||||
let hasNonWeakHandler: boolean = false;
|
||||
let hasPrimaryHandler: boolean = false;
|
||||
|
||||
for (const {handler, detected} of matchingHandlers) {
|
||||
for (const match of matchingHandlers) {
|
||||
const {handler} = match;
|
||||
if (hasNonWeakHandler && handler.precedence === HandlerPrecedence.WEAK) {
|
||||
continue;
|
||||
} else if (hasWeakHandler && handler.precedence !== HandlerPrecedence.WEAK) {
|
||||
@ -49,7 +60,7 @@ export function analyzeDecorators(
|
||||
throw new Error(`TODO.Diagnostic: Class has multiple incompatible Angular decorators.`);
|
||||
}
|
||||
|
||||
detections.push({handler, detected});
|
||||
detections.push(match);
|
||||
if (handler.precedence === HandlerPrecedence.WEAK) {
|
||||
hasWeakHandler = true;
|
||||
} else if (handler.precedence === HandlerPrecedence.SHARED) {
|
||||
@ -60,15 +71,17 @@ export function analyzeDecorators(
|
||||
}
|
||||
}
|
||||
|
||||
const matches: {handler: DecoratorHandler<any, any>, analysis: any}[] = [];
|
||||
const matches: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
const allDiagnostics: ts.Diagnostic[] = [];
|
||||
for (const {handler, detected} of detections) {
|
||||
for (const match of detections) {
|
||||
try {
|
||||
const {analysis, diagnostics} = handler.analyze(declaration, detected.metadata, flags);
|
||||
const {analysis, diagnostics} =
|
||||
match.handler.analyze(declaration, match.detected.metadata, flags);
|
||||
if (diagnostics !== undefined) {
|
||||
allDiagnostics.push(...diagnostics);
|
||||
}
|
||||
matches.push({handler, analysis});
|
||||
match.analysis = analysis !;
|
||||
matches.push(match);
|
||||
} catch (e) {
|
||||
if (isFatalDiagnosticError(e)) {
|
||||
allDiagnostics.push(e.toDiagnostic());
|
||||
@ -82,11 +95,6 @@ export function analyzeDecorators(
|
||||
declaration,
|
||||
decorators,
|
||||
matches,
|
||||
diagnostics: allDiagnostics.length > 0 ? allDiagnostics : undefined
|
||||
diagnostics: allDiagnostics.length > 0 ? allDiagnostics : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function isMatchingHandler<A, M>(handler: Partial<MatchingHandler<A, M>>):
|
||||
handler is MatchingHandler<A, M> {
|
||||
return !!handler.detected;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ import {Migration, MigrationHost} from '../../src/migrations/migration';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils';
|
||||
|
||||
type DecoratorHandlerWithResolve = DecoratorHandler<any, any>& {
|
||||
resolve: NonNullable<DecoratorHandler<any, any>['resolve']>;
|
||||
type DecoratorHandlerWithResolve = DecoratorHandler<unknown, unknown, unknown>& {
|
||||
resolve: NonNullable<DecoratorHandler<unknown, unknown, unknown>['resolve']>;
|
||||
};
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
@ -49,7 +49,7 @@ runInEachFileSystem(() => {
|
||||
]);
|
||||
// Only detect the Component and Directive decorators
|
||||
handler.detect.and.callFake(
|
||||
(node: ts.Declaration, decorators: Decorator[] | null): DetectResult<any>|
|
||||
(node: ts.Declaration, decorators: Decorator[] | null): DetectResult<unknown>|
|
||||
undefined => {
|
||||
const className = (node as any).name.text;
|
||||
if (decorators === null) {
|
||||
|
@ -297,15 +297,15 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
class TestHandler implements DecoratorHandler<any, any> {
|
||||
class TestHandler implements DecoratorHandler<unknown, unknown, unknown> {
|
||||
constructor(protected name: string, protected log: string[]) {}
|
||||
|
||||
precedence = HandlerPrecedence.PRIMARY;
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<any>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined {
|
||||
this.log.push(`${this.name}:detect:${node.name.text}:${decorators !.map(d => d.name)}`);
|
||||
return undefined;
|
||||
}
|
||||
analyze(node: ClassDeclaration): AnalysisOutput<any> {
|
||||
analyze(node: ClassDeclaration): AnalysisOutput<unknown> {
|
||||
this.log.push(this.name + ':analyze:' + node.name.text);
|
||||
return {};
|
||||
}
|
||||
@ -316,7 +316,7 @@ class TestHandler implements DecoratorHandler<any, any> {
|
||||
}
|
||||
|
||||
class AlwaysDetectHandler extends TestHandler {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<any>|undefined {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined {
|
||||
super.detect(node, decorators);
|
||||
return {trigger: node, metadata: {}};
|
||||
}
|
||||
|
Reference in New Issue
Block a user