@@ -11,7 +11,7 @@ import * as ts from 'typescript';
11
11
12
12
import { Reference } from '../../imports' ;
13
13
import { ClassPropertyName } from '../../metadata' ;
14
- import { ClassDeclaration } from '../../reflection' ;
14
+ import { ClassDeclaration , ReflectionHost } from '../../reflection' ;
15
15
import { TemplateId , TypeCheckableDirectiveMeta , TypeCheckBlockMetadata } from '../api' ;
16
16
17
17
import { addExpressionIdentifier , ExpressionIdentifier , markIgnoreDiagnostics } from './comments' ;
@@ -21,7 +21,8 @@ import {Environment} from './environment';
21
21
import { astToTypescript , NULL_AS_ANY } from './expression' ;
22
22
import { OutOfBandDiagnosticRecorder } from './oob' ;
23
23
import { ExpressionSemanticVisitor } from './template_semantics' ;
24
- import { tsCallMethod , tsCastToAny , tsCreateElement , tsCreateTypeQueryForCoercedInput , tsCreateVariable , tsDeclareVariable } from './ts_util' ;
24
+ import { checkIfGenericTypesAreUnbound , tsCallMethod , tsCastToAny , tsCreateElement , tsCreateTypeQueryForCoercedInput , tsCreateVariable , tsDeclareVariable } from './ts_util' ;
25
+ import { requiresInlineTypeCtor } from './type_constructor' ;
25
26
26
27
/**
27
28
* Given a `ts.ClassDeclaration` for a component, and metadata regarding that component, compose a
@@ -357,18 +358,13 @@ class TcbTextInterpolationOp extends TcbOp {
357
358
}
358
359
359
360
/**
360
- * A `TcbOp` which constructs an instance of a directive _without_ setting any of its inputs. Inputs
361
- * are later set in the `TcbDirectiveInputsOp`. Type checking was found to be faster when done in
362
- * this way as opposed to `TcbDirectiveCtorOp` which is only necessary when the directive is
363
- * generic.
364
- *
365
- * Executing this operation returns a reference to the directive instance variable with its inferred
366
- * type.
361
+ * A `TcbOp` which constructs an instance of a directive. For generic directives, generic
362
+ * parameters are set to `any` type.
367
363
*/
368
- class TcbDirectiveTypeOp extends TcbOp {
364
+ abstract class TcbDirectiveTypeOpBase extends TcbOp {
369
365
constructor (
370
- private tcb : Context , private scope : Scope , private node : TmplAstTemplate | TmplAstElement ,
371
- private dir : TypeCheckableDirectiveMeta ) {
366
+ protected tcb : Context , protected scope : Scope ,
367
+ protected node : TmplAstTemplate | TmplAstElement , protected dir : TypeCheckableDirectiveMeta ) {
372
368
super ( ) ;
373
369
}
374
370
@@ -380,16 +376,74 @@ class TcbDirectiveTypeOp extends TcbOp {
380
376
}
381
377
382
378
execute ( ) : ts . Identifier {
383
- const id = this . tcb . allocateId ( ) ;
379
+ const dirRef = this . dir . ref as Reference < ClassDeclaration < ts . ClassDeclaration > > ;
380
+
381
+ const rawType = this . tcb . env . referenceType ( this . dir . ref ) ;
382
+
383
+ let type : ts . TypeNode ;
384
+ if ( this . dir . isGeneric === false || dirRef . node . typeParameters === undefined ) {
385
+ type = rawType ;
386
+ } else {
387
+ if ( ! ts . isTypeReferenceNode ( rawType ) ) {
388
+ throw new Error (
389
+ `Expected TypeReferenceNode when referencing the type for ${ this . dir . ref . debugName } ` ) ;
390
+ }
391
+ const typeArguments = dirRef . node . typeParameters . map (
392
+ ( ) => ts . factory . createKeywordTypeNode ( ts . SyntaxKind . AnyKeyword ) ) ;
393
+ type = ts . factory . createTypeReferenceNode ( rawType . typeName , typeArguments ) ;
394
+ }
384
395
385
- const type = this . tcb . env . referenceType ( this . dir . ref ) ;
396
+ const id = this . tcb . allocateId ( ) ;
386
397
addExpressionIdentifier ( type , ExpressionIdentifier . DIRECTIVE ) ;
387
398
addParseSpanInfo ( type , this . node . startSourceSpan || this . node . sourceSpan ) ;
388
399
this . scope . addStatement ( tsDeclareVariable ( id , type ) ) ;
389
400
return id ;
390
401
}
391
402
}
392
403
404
+ /**
405
+ * A `TcbOp` which constructs an instance of a non-generic directive _without_ setting any of its
406
+ * inputs. Inputs are later set in the `TcbDirectiveInputsOp`. Type checking was found to be
407
+ * faster when done in this way as opposed to `TcbDirectiveCtorOp` which is only necessary when the
408
+ * directive is generic.
409
+ *
410
+ * Executing this operation returns a reference to the directive instance variable with its inferred
411
+ * type.
412
+ */
413
+ class TcbNonGenericDirectiveTypeOp extends TcbDirectiveTypeOpBase {
414
+ /**
415
+ * Creates a variable declaration for this op's directive of the argument type. Returns the id of
416
+ * the newly created variable.
417
+ */
418
+ execute ( ) : ts . Identifier {
419
+ const dirRef = this . dir . ref as Reference < ClassDeclaration < ts . ClassDeclaration > > ;
420
+ if ( this . dir . isGeneric ) {
421
+ throw new Error ( `Assertion Error: expected ${ dirRef . debugName } not to be generic.` ) ;
422
+ }
423
+ return super . execute ( ) ;
424
+ }
425
+ }
426
+
427
+ /**
428
+ * A `TcbOp` which constructs an instance of a generic directive with its generic parameters set
429
+ * to `any` type. This op is like `TcbDirectiveTypeOp`, except that generic parameters are set to
430
+ * `any` type. This is used for situations where we want to avoid inlining.
431
+ *
432
+ * Executing this operation returns a reference to the directive instance variable with its generic
433
+ * type parameters set to `any`.
434
+ */
435
+ class TcbGenericDirectiveTypeWithAnyParamsOp extends TcbDirectiveTypeOpBase {
436
+ execute ( ) : ts . Identifier {
437
+ const dirRef = this . dir . ref as Reference < ClassDeclaration < ts . ClassDeclaration > > ;
438
+ if ( dirRef . node . typeParameters === undefined ) {
439
+ throw new Error ( `Assertion Error: expected typeParameters when creating a declaration for ${
440
+ dirRef . debugName } `) ;
441
+ }
442
+
443
+ return super . execute ( ) ;
444
+ }
445
+ }
446
+
393
447
/**
394
448
* A `TcbOp` which creates a variable for a local ref in a template.
395
449
* The initializer for the variable is the variable expression for the directive, template, or
@@ -1383,8 +1437,27 @@ class Scope {
1383
1437
1384
1438
const dirMap = new Map < TypeCheckableDirectiveMeta , number > ( ) ;
1385
1439
for ( const dir of directives ) {
1386
- const directiveOp = dir . isGeneric ? new TcbDirectiveCtorOp ( this . tcb , this , node , dir ) :
1387
- new TcbDirectiveTypeOp ( this . tcb , this , node , dir ) ;
1440
+ let directiveOp : TcbOp ;
1441
+ const host = this . tcb . env . reflector ;
1442
+ const dirRef = dir . ref as Reference < ClassDeclaration < ts . ClassDeclaration > > ;
1443
+
1444
+ if ( ! dir . isGeneric ) {
1445
+ // The most common case is that when a directive is not generic, we use the normal
1446
+ // `TcbNonDirectiveTypeOp`.
1447
+ directiveOp = new TcbNonGenericDirectiveTypeOp ( this . tcb , this , node , dir ) ;
1448
+ } else if (
1449
+ ! requiresInlineTypeCtor ( dirRef . node , host ) ||
1450
+ this . tcb . env . config . useInlineTypeConstructors ) {
1451
+ // For generic directives, we use a type constructor to infer types. If a directive requires
1452
+ // an inline type constructor, then inlining must be available to use the
1453
+ // `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below.
1454
+ directiveOp = new TcbDirectiveCtorOp ( this . tcb , this , node , dir ) ;
1455
+ } else {
1456
+ // If inlining is not available, then we give up on infering the generic params, and use
1457
+ // `any` type for the directive's generic parameters.
1458
+ directiveOp = new TcbGenericDirectiveTypeWithAnyParamsOp ( this . tcb , this , node , dir ) ;
1459
+ }
1460
+
1388
1461
const dirIndex = this . opQueue . push ( directiveOp ) - 1 ;
1389
1462
dirMap . set ( dir , dirIndex ) ;
1390
1463
0 commit comments