fix(compiler): fix inheritance for AOT with summaries (#15583)

Allows to inherit ctor args, lifecycle hooks and statics from a class
in another compilation unit. 
Will error if trying to inherit from a class in another compilation unit 
that has an `@Component` / `@Directive` / `@Pipe` / `@NgModule`.
This commit is contained in:
Tobias Bosch
2017-03-30 14:51:29 -07:00
committed by Alex Rickabaugh
parent 28bf222a6a
commit 8ef621ad2a
13 changed files with 436 additions and 55 deletions

View File

@ -47,7 +47,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const console = new Console();
const htmlParser = new I18NHtmlParser(

View File

@ -7,7 +7,11 @@
*/
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger, ɵReflectorReader} from '@angular/core';
import {CompileSummaryKind} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver';
import {syntaxError} from '../util';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
@ -35,8 +39,11 @@ export class StaticReflector implements ɵReflectorReader {
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;
private annotationForParentClassWithSummaryKind = new Map<CompileSummaryKind, any[]>();
private annotationNames = new Map<any, string>();
constructor(
private summaryResolver: SummaryResolver<StaticSymbol>,
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
@ -47,6 +54,17 @@ export class StaticReflector implements ɵReflectorReader {
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
knownMetadataFunctions.forEach(
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
this.annotationForParentClassWithSummaryKind.set(
CompileSummaryKind.Directive, [Directive, Component]);
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [Pipe]);
this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [NgModule]);
this.annotationForParentClassWithSummaryKind.set(
CompileSummaryKind.Injectable, [Injectable, Pipe, Directive, Component, NgModule]);
this.annotationNames.set(Directive, 'Directive');
this.annotationNames.set(Component, 'Component');
this.annotationNames.set(Pipe, 'Pipe');
this.annotationNames.set(NgModule, 'NgModule');
this.annotationNames.set(Injectable, 'Injectable');
}
importUri(typeOrFunc: StaticSymbol): string {
@ -96,17 +114,33 @@ export class StaticReflector implements ɵReflectorReader {
if (!annotations) {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
if (classMetadata['extends']) {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType && (parentType instanceof StaticSymbol)) {
const parentAnnotations = this.annotations(parentType);
annotations.push(...parentAnnotations);
}
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentAnnotations = this.annotations(parentType);
annotations.push(...parentAnnotations);
}
let ownAnnotations: any[] = [];
if (classMetadata['decorators']) {
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
ownAnnotations = this.simplify(type, classMetadata['decorators']);
annotations.push(...ownAnnotations);
}
if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) &&
this.summaryResolver.isLibraryFile(parentType.filePath)) {
const summary = this.summaryResolver.resolveSummary(parentType);
if (summary && summary.type) {
const requiredAnnotationTypes =
this.annotationForParentClassWithSummaryKind.get(summary.type.summaryKind);
const typeHasRequiredAnnotation = requiredAnnotationTypes.some(
requiredType => ownAnnotations.some(ann => ann instanceof requiredType));
if (!typeHasRequiredAnnotation) {
this.reportError(
syntaxError(
`Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind]} in another compilation unit without duplicating the decorator. ` +
`Please add a ${requiredAnnotationTypes.map(type => this.annotationNames.get(type)).join(' or ')} decorator to the class.`),
type);
}
}
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
@ -117,14 +151,12 @@ export class StaticReflector implements ɵReflectorReader {
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
if (classMetadata['extends']) {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentPropMetadata = this.propMetadata(parentType);
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
}
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentPropMetadata = this.propMetadata(parentType);
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
}
const members = classMetadata['members'] || {};
@ -157,6 +189,7 @@ export class StaticReflector implements ɵReflectorReader {
let parameters = this.parameterCache.get(type);
if (!parameters) {
const classMetadata = this.getTypeMetadata(type);
const parentType = this.findParentType(type, classMetadata);
const members = classMetadata ? classMetadata['members'] : null;
const ctorData = members ? members['__ctor__'] : null;
if (ctorData) {
@ -175,11 +208,8 @@ export class StaticReflector implements ɵReflectorReader {
}
parameters.push(nestedResult);
});
} else if (classMetadata['extends']) {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
parameters = this.parameters(parentType);
}
} else if (parentType) {
parameters = this.parameters(parentType);
}
if (!parameters) {
parameters = [];
@ -198,14 +228,12 @@ export class StaticReflector implements ɵReflectorReader {
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
if (classMetadata['extends']) {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentMethodNames = this._methodNames(parentType);
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
}
const parentType = this.findParentType(type, classMetadata);
if (parentType) {
const parentMethodNames = this._methodNames(parentType);
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
}
const members = classMetadata['members'] || {};
@ -219,6 +247,13 @@ export class StaticReflector implements ɵReflectorReader {
return methodNames;
}
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|null {
const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
return parentType;
}
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
this.reportError(

View File

@ -307,6 +307,17 @@ export class StaticSymbolResolver {
private createResolvedSymbol(
sourceSymbol: StaticSymbol, topLevelPath: string, topLevelSymbolNames: Set<string>,
metadata: any): ResolvedStaticSymbol {
// For classes that don't have Angular summaries / metadata,
// we only keep their arity, but nothing else
// (e.g. their constructor parameters).
// We do this to prevent introducing deep imports
// as we didn't generate .ngfactory.ts files with proper reexports.
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && metadata &&
metadata['__symbolic'] === 'class') {
const transformedMeta = {__symbolic: 'class', arity: metadata.arity};
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
}
const self = this;
class ReferenceTransformer extends ValueTransformer {

View File

@ -53,7 +53,7 @@ export function serializeSummaries(
// (in a minimal way).
types.forEach((typeSummary) => {
serializer.addOrMergeSummary(
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
{symbol: typeSummary.type.reference, metadata: null, type: typeSummary});
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
@ -94,10 +94,21 @@ class Serializer extends ValueTransformer {
addOrMergeSummary(summary: Summary<StaticSymbol>) {
let symbolMeta = summary.metadata;
if (symbolMeta && symbolMeta.__symbolic === 'class') {
// For classes, we only keep their statics and arity, but not the metadata
// of the class itself as that has been captured already via other summaries
// (e.g. DirectiveSummary, ...).
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics, arity: symbolMeta.arity};
// For classes, we keep everything except their class decorators.
// We need to keep e.g. the ctor args, method names, method decorators
// so that the class can be extended in another compilation unit.
// We don't keep the class decorators as
// 1) they refer to data
// that should not cause a rebuild of downstream compilation units
// (e.g. inline templates of @Component, or @NgModule.declarations)
// 2) their data is already captured in TypeSummaries, e.g. DirectiveSummary.
const clone: {[key: string]: any} = {};
Object.keys(symbolMeta).forEach((propName) => {
if (propName !== 'decorators') {
clone[propName] = symbolMeta[propName];
}
});
symbolMeta = clone;
}
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);

View File

@ -94,7 +94,7 @@ export class Extractor {
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(host, symbolCache);
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(staticSymbolResolver);
const staticReflector = new StaticReflector(summaryResolver, staticSymbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config =

View File

@ -933,14 +933,12 @@ export class CompileMetadataResolver {
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
if (dirMeta && dirMeta.metadata.isComponent) {
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory};
} else {
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (dirSummary && dirSummary.isComponent) {
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
}
}
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (dirSummary && dirSummary.isComponent) {
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
}
if (throwIfNotFound) {
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
}