fix(ivy): allow abstract directives to have an invalid constructor (#32987)
For abstract directives, i.e. directives without a selector, it may happen that their constructor is called explicitly from a subclass, hence its parameters are not required to be valid for Angular's DI purposes. Prior to this commit however, having an abstract directive with a constructor that has parameters that are not eligible for Angular's DI would produce a compilation error. A similar scenario may occur for `@Injectable`s, where an explicit `use*` definition allows for the constructor to be irrelevant. For example, the situation where `useFactory` is specified allows for the constructor to be called explicitly with any value, so its constructor parameters are not required to be valid. For `@Injectable`s this is handled by generating a DI factory function that throws. This commit implements the same solution for abstract directives, such that a compilation error is avoided while still producing an error at runtime if the type is instantiated implicitly by Angular's DI mechanism. Fixes #32981 PR Close #32987
This commit is contained in:
@ -96,7 +96,7 @@ export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent
|
||||
export * from './render3/view/t2_api';
|
||||
export * from './render3/view/t2_binder';
|
||||
export {Identifiers as R3Identifiers} from './render3/r3_identifiers';
|
||||
export {R3DependencyMetadata, R3FactoryDefMetadata, R3ResolvedDependencyType, compileFactoryFromMetadata, R3FactoryMetadata} from './render3/r3_factory';
|
||||
export {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, R3FactoryMetadata, R3FactoryTarget} from './render3/r3_factory';
|
||||
export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler';
|
||||
export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||
export {makeBindingParser, parseTemplate, ParseTemplateOptions} from './render3/view/template';
|
||||
|
@ -45,6 +45,7 @@ export interface CompilerFacade {
|
||||
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
||||
|
||||
R3ResolvedDependencyType: typeof R3ResolvedDependencyType;
|
||||
R3FactoryTarget: typeof R3FactoryTarget;
|
||||
ResourceLoader: {new (): ResourceLoader};
|
||||
}
|
||||
|
||||
@ -70,6 +71,14 @@ export enum R3ResolvedDependencyType {
|
||||
ChangeDetectorRef = 2,
|
||||
}
|
||||
|
||||
export enum R3FactoryTarget {
|
||||
Directive = 0,
|
||||
Component = 1,
|
||||
Injectable = 2,
|
||||
Pipe = 3,
|
||||
NgModule = 4,
|
||||
}
|
||||
|
||||
export interface R3DependencyMetadataFacade {
|
||||
token: any;
|
||||
resolved: R3ResolvedDependencyType;
|
||||
@ -167,7 +176,7 @@ export interface R3FactoryDefMetadataFacade {
|
||||
typeArgumentCount: number;
|
||||
deps: R3DependencyMetadataFacade[]|null;
|
||||
injectFn: 'directiveInject'|'inject';
|
||||
isPipe: boolean;
|
||||
target: R3FactoryTarget;
|
||||
}
|
||||
|
||||
export type ViewEncapsulation = number;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Identifiers} from './identifiers';
|
||||
import * as o from './output/output_ast';
|
||||
import {R3DependencyMetadata, R3FactoryDelegateType, compileFactoryFunction} from './render3/r3_factory';
|
||||
import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryTarget, compileFactoryFunction} from './render3/r3_factory';
|
||||
import {mapToMapExpression, typeWithParameters} from './render3/util';
|
||||
|
||||
export interface InjectableDef {
|
||||
@ -38,6 +38,7 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||
typeArgumentCount: meta.typeArgumentCount,
|
||||
deps: [],
|
||||
injectFn: Identifiers.inject,
|
||||
target: R3FactoryTarget.Injectable,
|
||||
};
|
||||
|
||||
if (meta.useClass !== undefined) {
|
||||
|
@ -16,7 +16,7 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/int
|
||||
import {DeclareVarStmt, Expression, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast';
|
||||
import {JitEvaluator} from './output/output_jit';
|
||||
import {ParseError, ParseSourceSpan, r3JitTypeSourceSpan} from './parse_util';
|
||||
import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFromMetadata} from './render3/r3_factory';
|
||||
import {R3DependencyMetadata, R3FactoryTarget, R3ResolvedDependencyType, compileFactoryFunction} from './render3/r3_factory';
|
||||
import {R3JitReflector} from './render3/r3_jit';
|
||||
import {R3InjectorMetadata, R3NgModuleMetadata, compileInjector, compileNgModule} from './render3/r3_module_compiler';
|
||||
import {compilePipeFromMetadata} from './render3/r3_pipe_compiler';
|
||||
@ -29,6 +29,7 @@ import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
|
||||
|
||||
export class CompilerFacadeImpl implements CompilerFacade {
|
||||
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
||||
R3FactoryTarget = R3FactoryTarget as any;
|
||||
ResourceLoader = ResourceLoader;
|
||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
|
||||
@ -155,14 +156,14 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||
|
||||
compileFactory(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade) {
|
||||
const factoryRes = compileFactoryFromMetadata({
|
||||
const factoryRes = compileFactoryFunction({
|
||||
name: meta.name,
|
||||
type: new WrappedNodeExpr(meta.type),
|
||||
typeArgumentCount: meta.typeArgumentCount,
|
||||
deps: convertR3DependencyMetadataArray(meta.deps),
|
||||
injectFn: meta.injectFn === 'directiveInject' ? Identifiers.directiveInject :
|
||||
Identifiers.inject,
|
||||
isPipe: meta.isPipe
|
||||
target: meta.target,
|
||||
});
|
||||
return this.jitExpression(
|
||||
factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements);
|
||||
|
@ -56,6 +56,11 @@ export interface R3ConstructorFactoryMetadata {
|
||||
* function could be different, and other options control how it will be invoked.
|
||||
*/
|
||||
injectFn: o.ExternalReference;
|
||||
|
||||
/**
|
||||
* Type of the target being created by the factory.
|
||||
*/
|
||||
target: R3FactoryTarget;
|
||||
}
|
||||
|
||||
export enum R3FactoryDelegateType {
|
||||
@ -82,13 +87,12 @@ export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadat
|
||||
export type R3FactoryMetadata = R3ConstructorFactoryMetadata | R3DelegatedFactoryMetadata |
|
||||
R3DelegatedFnOrClassMetadata | R3ExpressionFactoryMetadata;
|
||||
|
||||
export interface R3FactoryDefMetadata {
|
||||
name: string;
|
||||
type: o.Expression;
|
||||
typeArgumentCount: number;
|
||||
deps: R3DependencyMetadata[]|'invalid'|null;
|
||||
injectFn: o.ExternalReference;
|
||||
isPipe?: boolean;
|
||||
export enum R3FactoryTarget {
|
||||
Directive = 0,
|
||||
Component = 1,
|
||||
Injectable = 2,
|
||||
Pipe = 3,
|
||||
NgModule = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,7 +167,7 @@ export interface R3FactoryFn {
|
||||
/**
|
||||
* Construct a factory function expression for the given `R3FactoryMetadata`.
|
||||
*/
|
||||
export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): R3FactoryFn {
|
||||
export function compileFactoryFunction(meta: R3FactoryMetadata): R3FactoryFn {
|
||||
const t = o.variable('t');
|
||||
const statements: o.Statement[] = [];
|
||||
|
||||
@ -179,8 +183,9 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false):
|
||||
if (meta.deps !== null) {
|
||||
// There is a constructor (either explicitly or implicitly defined).
|
||||
if (meta.deps !== 'invalid') {
|
||||
ctorExpr =
|
||||
new o.InstantiateExpr(typeForCtor, injectDependencies(meta.deps, meta.injectFn, isPipe));
|
||||
ctorExpr = new o.InstantiateExpr(
|
||||
typeForCtor,
|
||||
injectDependencies(meta.deps, meta.injectFn, meta.target === R3FactoryTarget.Pipe));
|
||||
}
|
||||
} else {
|
||||
const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`);
|
||||
@ -206,7 +211,7 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false):
|
||||
if (ctorExprFinal !== null) {
|
||||
ctorStmt = r.set(ctorExprFinal).toStmt();
|
||||
} else {
|
||||
ctorStmt = makeErrorStmt(meta.name);
|
||||
ctorStmt = o.importExpr(R3.invalidFactory).callFn([]).toStmt();
|
||||
}
|
||||
body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()]));
|
||||
return r;
|
||||
@ -228,8 +233,9 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false):
|
||||
} else if (isDelegatedMetadata(meta)) {
|
||||
// This type is created with a delegated factory. If a type parameter is not specified, call
|
||||
// the factory instead.
|
||||
const delegateArgs = injectDependencies(meta.delegateDeps, meta.injectFn, isPipe);
|
||||
// Either call `new delegate(...)` or `delegate(...)` depending on meta.useNewForDelegate.
|
||||
const delegateArgs =
|
||||
injectDependencies(meta.delegateDeps, meta.injectFn, meta.target === R3FactoryTarget.Pipe);
|
||||
// Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType.
|
||||
const factoryExpr = new (
|
||||
meta.delegateType === R3FactoryDelegateType.Class ?
|
||||
o.InstantiateExpr :
|
||||
@ -245,7 +251,7 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false):
|
||||
if (retExpr !== null) {
|
||||
body.push(new o.ReturnStatement(retExpr));
|
||||
} else {
|
||||
body.push(makeErrorStmt(meta.name));
|
||||
body.push(o.importExpr(R3.invalidFactory).callFn([]).toStmt());
|
||||
}
|
||||
|
||||
return {
|
||||
@ -258,21 +264,6 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false):
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the factory def (`ɵfac`) from directive/component/pipe metadata.
|
||||
*/
|
||||
export function compileFactoryFromMetadata(meta: R3FactoryDefMetadata): R3FactoryFn {
|
||||
return compileFactoryFunction(
|
||||
{
|
||||
name: meta.name,
|
||||
type: meta.type,
|
||||
deps: meta.deps,
|
||||
typeArgumentCount: meta.typeArgumentCount,
|
||||
injectFn: meta.injectFn,
|
||||
},
|
||||
meta.isPipe);
|
||||
}
|
||||
|
||||
function injectDependencies(
|
||||
deps: R3DependencyMetadata[], injectFn: o.ExternalReference, isPipe: boolean): o.Expression[] {
|
||||
return deps.map(dep => compileInjectDependency(dep, injectFn, isPipe));
|
||||
@ -358,13 +349,6 @@ export function dependenciesFromGlobalMetadata(
|
||||
return deps;
|
||||
}
|
||||
|
||||
function makeErrorStmt(name: string): o.Statement {
|
||||
return new o.ThrowStmt(new o.InstantiateExpr(new o.ReadVarExpr('Error'), [
|
||||
o.literal(
|
||||
`${name} has a constructor which is not compatible with Dependency Injection. It should probably not be @Injectable().`)
|
||||
]));
|
||||
}
|
||||
|
||||
function isDelegatedMetadata(meta: R3FactoryMetadata): meta is R3DelegatedFactoryMetadata|
|
||||
R3DelegatedFnOrClassMetadata {
|
||||
return (meta as any).delegateType !== undefined;
|
||||
|
@ -211,6 +211,7 @@ export class Identifiers {
|
||||
o.ExternalReference = {name: 'ɵɵinjectPipeChangeDetectorRef', moduleName: CORE};
|
||||
|
||||
static directiveInject: o.ExternalReference = {name: 'ɵɵdirectiveInject', moduleName: CORE};
|
||||
static invalidFactory: o.ExternalReference = {name: 'ɵɵinvalidFactory', moduleName: CORE};
|
||||
|
||||
static templateRefExtractor:
|
||||
o.ExternalReference = {name: 'ɵɵtemplateRefExtractor', moduleName: CORE};
|
||||
|
@ -12,7 +12,7 @@ import {mapLiteral} from '../output/map_util';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
import {R3DependencyMetadata, compileFactoryFunction} from './r3_factory';
|
||||
import {R3DependencyMetadata, R3FactoryTarget, compileFactoryFunction} from './r3_factory';
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {R3Reference, convertMetaToOutput, mapToMapExpression} from './util';
|
||||
|
||||
@ -210,6 +210,7 @@ export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef {
|
||||
typeArgumentCount: 0,
|
||||
deps: meta.deps,
|
||||
injectFn: R3.inject,
|
||||
target: R3FactoryTarget.NgModule,
|
||||
});
|
||||
const definitionMap = {
|
||||
factory: result.factory,
|
||||
|
@ -12,7 +12,7 @@ import {DefinitionKind} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {R3DependencyMetadata, compileFactoryFromMetadata, compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory';
|
||||
import {R3DependencyMetadata, R3FactoryTarget, compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory';
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {typeWithParameters} from './util';
|
||||
|
||||
@ -89,8 +89,8 @@ export function compilePipeFromRender2(
|
||||
pure: pipe.pure,
|
||||
};
|
||||
const res = compilePipeFromMetadata(metadata);
|
||||
const factoryRes =
|
||||
compileFactoryFromMetadata({...metadata, injectFn: R3.directiveInject, isPipe: true});
|
||||
const factoryRes = compileFactoryFunction(
|
||||
{...metadata, injectFn: R3.directiveInject, target: R3FactoryTarget.Pipe});
|
||||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe);
|
||||
const ngFactoryDefStatement = new o.ClassStmt(
|
||||
/* name */ name,
|
||||
|
@ -41,7 +41,7 @@ export interface R3DirectiveMetadata {
|
||||
/**
|
||||
* Dependencies of the directive's constructor.
|
||||
*/
|
||||
deps: R3DependencyMetadata[]|null;
|
||||
deps: R3DependencyMetadata[]|'invalid'|null;
|
||||
|
||||
/**
|
||||
* Unparsed selector of the directive, or `null` if there was no selector.
|
||||
|
@ -12,17 +12,17 @@ import {CompileReflector} from '../../compile_reflector';
|
||||
import {BindingForm, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, Interpolation, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast';
|
||||
import {AST, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseError, ParseSourceSpan, typeSourceSpan} from '../../parse_util';
|
||||
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {ShadowCss} from '../../shadow_css';
|
||||
import {CONTENT_ATTR, HOST_ATTR} from '../../style_compiler';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
import {BoundEvent} from '../r3_ast';
|
||||
import {compileFactoryFromMetadata, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory';
|
||||
import {R3FactoryTarget, compileFactoryFunction} from '../r3_factory';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
||||
@ -330,7 +330,8 @@ export function compileDirectiveFromRender2(
|
||||
|
||||
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
|
||||
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||
const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject});
|
||||
const factoryRes = compileFactoryFunction(
|
||||
{...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive});
|
||||
const ngFactoryDefStatement = new o.ClassStmt(
|
||||
name, null,
|
||||
[new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [],
|
||||
@ -382,7 +383,8 @@ export function compileComponentFromRender2(
|
||||
i18nUseExternalIds: true,
|
||||
};
|
||||
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||
const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject});
|
||||
const factoryRes = compileFactoryFunction(
|
||||
{...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive});
|
||||
const ngFactoryDefStatement = new o.ClassStmt(
|
||||
name, null,
|
||||
[new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [],
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import * as core from '../../core/src/compiler/compiler_facade_interface';
|
||||
import {R3ResolvedDependencyType} from '../public_api';
|
||||
import {R3FactoryTarget, R3ResolvedDependencyType} from '../public_api';
|
||||
import * as compiler from '../src/compiler_facade_interface';
|
||||
|
||||
/**
|
||||
@ -60,6 +60,15 @@ const coreR3ResolvedDependencyType3: core.R3ResolvedDependencyType =
|
||||
const compilerR3ResolvedDependencyType3: compiler.R3ResolvedDependencyType =
|
||||
null !as R3ResolvedDependencyType;
|
||||
|
||||
const coreR3FactoryTarget: core.R3FactoryTarget = null !as compiler.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget: compiler.R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
|
||||
const coreR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
const compilerR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget;
|
||||
|
||||
const coreR3FactoryTarget3: core.R3FactoryTarget = null !as R3FactoryTarget;
|
||||
const compilerR3FactoryTarget3: compiler.R3FactoryTarget = null !as R3FactoryTarget;
|
||||
|
||||
const coreR3DependencyMetadataFacade: core.R3DependencyMetadataFacade =
|
||||
null !as compiler.R3DependencyMetadataFacade;
|
||||
const compilerR3DependencyMetadataFacade: compiler.R3DependencyMetadataFacade =
|
||||
|
Reference in New Issue
Block a user