forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathr3_factory.ts
More file actions
331 lines (291 loc) · 11 KB
/
r3_factory.ts
File metadata and controls
331 lines (291 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {InjectFlags} from '../core';
import * as o from '../output/output_ast';
import {Identifiers as R3} from '../render3/r3_identifiers';
import {R3CompiledExpression, R3Reference, typeWithParameters} from './util';
/**
* Metadata required by the factory generator to generate a `factory` function for a type.
*/
export interface R3ConstructorFactoryMetadata {
/**
* String name of the type being generated (used to name the factory function).
*/
name: string;
/**
* An expression representing the interface type being constructed.
*/
type: R3Reference;
/** Number of arguments for the `type`. */
typeArgumentCount: number;
/**
* Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it
* may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata`
* for those parameters. If this is `null`, then the type's constructor is nonexistent and will
* be inherited from `fnOrClass` which is interpreted as the current type. If this is `'invalid'`,
* then one or more of the parameters wasn't resolvable and any attempt to use these deps will
* result in a runtime error.
*/
deps: R3DependencyMetadata[] | 'invalid' | null;
/**
* Type of the target being created by the factory.
*/
target: FactoryTarget;
}
export enum R3FactoryDelegateType {
Class = 0,
Function = 1,
}
export interface R3DelegatedFnOrClassMetadata extends R3ConstructorFactoryMetadata {
delegate: o.Expression;
delegateType: R3FactoryDelegateType;
delegateDeps: R3DependencyMetadata[];
}
export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadata {
expression: o.Expression;
}
export type R3FactoryMetadata =
| R3ConstructorFactoryMetadata
| R3DelegatedFnOrClassMetadata
| R3ExpressionFactoryMetadata;
export enum FactoryTarget {
Directive = 0,
Component = 1,
Injectable = 2,
Pipe = 3,
NgModule = 4,
}
export interface R3DependencyMetadata {
/**
* An expression representing the token or value to be injected.
* Or `null` if the dependency could not be resolved - making it invalid.
*/
token: o.Expression | null;
/**
* If an @Attribute decorator is present, this is the literal type of the attribute name, or
* the unknown type if no literal type is available (e.g. the attribute name is an expression).
* Otherwise it is null;
*/
attributeNameType: o.Expression | null;
/**
* Whether the dependency has an @Host qualifier.
*/
host: boolean;
/**
* Whether the dependency has an @Optional qualifier.
*/
optional: boolean;
/**
* Whether the dependency has an @Self qualifier.
*/
self: boolean;
/**
* Whether the dependency has an @SkipSelf qualifier.
*/
skipSelf: boolean;
}
/**
* Construct a factory function expression for the given `R3FactoryMetadata`.
*/
export function compileFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpression {
const t = o.variable('__ngFactoryType__');
let baseFactoryVar: o.ReadVarExpr | null = null;
// The type to instantiate via constructor invocation. If there is no delegated factory, meaning
// this type is always created by constructor invocation, then this is the type-to-create
// parameter provided by the user (t) if specified, or the current type if not. If there is a
// delegated factory (which is used to create the current type) then this is only the type-to-
// create parameter (t).
const typeForCtor = !isDelegatedFactoryMetadata(meta)
? new o.BinaryOperatorExpr(o.BinaryOperator.Or, t, meta.type.value)
: t;
let ctorExpr: o.Expression | null = null;
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.target));
}
} else {
// There is no constructor, use the base class' factory to construct typeForCtor.
baseFactoryVar = o.variable(`ɵ${meta.name}_BaseFactory`);
ctorExpr = baseFactoryVar.callFn([typeForCtor]);
}
const body: o.Statement[] = [];
let retExpr: o.Expression | null = null;
function makeConditionalFactory(nonCtorExpr: o.Expression): o.ReadVarExpr {
const r = o.variable('__ngConditionalFactory__');
body.push(r.set(o.NULL_EXPR).toDeclStmt());
const ctorStmt =
ctorExpr !== null
? r.set(ctorExpr).toStmt()
: o.importExpr(R3.invalidFactory).callFn([]).toStmt();
body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()]));
return r;
}
if (isDelegatedFactoryMetadata(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.target);
// Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType.
const factoryExpr = new (
meta.delegateType === R3FactoryDelegateType.Class ? o.InstantiateExpr : o.InvokeFunctionExpr
)(meta.delegate, delegateArgs);
retExpr = makeConditionalFactory(factoryExpr);
} else if (isExpressionFactoryMetadata(meta)) {
// TODO(alxhub): decide whether to lower the value here or in the caller
retExpr = makeConditionalFactory(meta.expression);
} else {
retExpr = ctorExpr;
}
if (retExpr === null) {
// The expression cannot be formed so render an `ɵɵinvalidFactory()` call.
body.push(o.importExpr(R3.invalidFactory).callFn([]).toStmt());
} else if (baseFactoryVar !== null) {
// This factory uses a base factory, so call `ɵɵgetInheritedFactory()` to compute it.
const getInheritedFactoryCall = o.importExpr(R3.getInheritedFactory).callFn([meta.type.value]);
// Memoize the base factoryFn: `baseFactory || (baseFactory = ɵɵgetInheritedFactory(...))`
const baseFactory = new o.BinaryOperatorExpr(
o.BinaryOperator.Or,
baseFactoryVar,
baseFactoryVar.set(getInheritedFactoryCall),
);
body.push(new o.ReturnStatement(baseFactory.callFn([typeForCtor])));
} else {
// This is straightforward factory, just return it.
body.push(new o.ReturnStatement(retExpr));
}
let factoryFn: o.Expression = o.fn(
[new o.FnParam(t.name, o.DYNAMIC_TYPE)],
body,
o.INFERRED_TYPE,
undefined,
`${meta.name}_Factory`,
);
if (baseFactoryVar !== null) {
// There is a base factory variable so wrap its declaration along with the factory function into
// an IIFE.
factoryFn = o
.arrowFn([], [new o.DeclareVarStmt(baseFactoryVar.name!), new o.ReturnStatement(factoryFn)])
.callFn([], /* sourceSpan */ undefined, /* pure */ true);
}
return {
expression: factoryFn,
statements: [],
type: createFactoryType(meta),
};
}
export function createFactoryType(meta: R3FactoryMetadata) {
const ctorDepsType =
meta.deps !== null && meta.deps !== 'invalid' ? createCtorDepsType(meta.deps) : o.NONE_TYPE;
return o.expressionType(
o.importExpr(R3.FactoryDeclaration, [
typeWithParameters(meta.type.type, meta.typeArgumentCount),
ctorDepsType,
]),
);
}
function injectDependencies(deps: R3DependencyMetadata[], target: FactoryTarget): o.Expression[] {
return deps.map((dep, index) => compileInjectDependency(dep, target, index));
}
function compileInjectDependency(
dep: R3DependencyMetadata,
target: FactoryTarget,
index: number,
): o.Expression {
// Interpret the dependency according to its resolved type.
if (dep.token === null) {
return o.importExpr(R3.invalidFactoryDep).callFn([o.literal(index)]);
} else if (dep.attributeNameType === null) {
// Build up the injection flags according to the metadata.
const flags =
InjectFlags.Default |
(dep.self ? InjectFlags.Self : 0) |
(dep.skipSelf ? InjectFlags.SkipSelf : 0) |
(dep.host ? InjectFlags.Host : 0) |
(dep.optional ? InjectFlags.Optional : 0) |
(target === FactoryTarget.Pipe ? InjectFlags.ForPipe : 0);
// If this dependency is optional or otherwise has non-default flags, then additional
// parameters describing how to inject the dependency must be passed to the inject function
// that's being used.
let flagsParam: o.LiteralExpr | null =
flags !== InjectFlags.Default || dep.optional ? o.literal(flags) : null;
// Build up the arguments to the injectFn call.
const injectArgs = [dep.token];
if (flagsParam) {
injectArgs.push(flagsParam);
}
const injectFn = getInjectFn(target);
return o.importExpr(injectFn).callFn(injectArgs);
} else {
// The `dep.attributeTypeName` value is defined, which indicates that this is an `@Attribute()`
// type dependency. For the generated JS we still want to use the `dep.token` value in case the
// name given for the attribute is not a string literal. For example given `@Attribute(foo())`,
// we want to generate `ɵɵinjectAttribute(foo())`.
//
// The `dep.attributeTypeName` is only actually used (in `createCtorDepType()`) to generate
// typings.
return o.importExpr(R3.injectAttribute).callFn([dep.token]);
}
}
function createCtorDepsType(deps: R3DependencyMetadata[]): o.Type {
let hasTypes = false;
const attributeTypes = deps.map((dep) => {
const type = createCtorDepType(dep);
if (type !== null) {
hasTypes = true;
return type;
} else {
return o.literal(null);
}
});
if (hasTypes) {
return o.expressionType(o.literalArr(attributeTypes));
} else {
return o.NONE_TYPE;
}
}
function createCtorDepType(dep: R3DependencyMetadata): o.LiteralMapExpr | null {
const entries: {key: string; quoted: boolean; value: o.Expression}[] = [];
if (dep.attributeNameType !== null) {
entries.push({key: 'attribute', value: dep.attributeNameType, quoted: false});
}
if (dep.optional) {
entries.push({key: 'optional', value: o.literal(true), quoted: false});
}
if (dep.host) {
entries.push({key: 'host', value: o.literal(true), quoted: false});
}
if (dep.self) {
entries.push({key: 'self', value: o.literal(true), quoted: false});
}
if (dep.skipSelf) {
entries.push({key: 'skipSelf', value: o.literal(true), quoted: false});
}
return entries.length > 0 ? o.literalMap(entries) : null;
}
export function isDelegatedFactoryMetadata(
meta: R3FactoryMetadata,
): meta is R3DelegatedFnOrClassMetadata {
return (meta as any).delegateType !== undefined;
}
export function isExpressionFactoryMetadata(
meta: R3FactoryMetadata,
): meta is R3ExpressionFactoryMetadata {
return (meta as any).expression !== undefined;
}
function getInjectFn(target: FactoryTarget): o.ExternalReference {
switch (target) {
case FactoryTarget.Component:
case FactoryTarget.Directive:
case FactoryTarget.Pipe:
return R3.directiveInject;
case FactoryTarget.NgModule:
case FactoryTarget.Injectable:
default:
return R3.inject;
}
}