@@ -11,7 +11,7 @@ import * as ts from 'typescript';
1111
1212import { Reference } from '../../imports' ;
1313import { ClassPropertyName } from '../../metadata' ;
14- import { ClassDeclaration } from '../../reflection' ;
14+ import { ClassDeclaration , ReflectionHost } from '../../reflection' ;
1515import { TemplateId , TypeCheckableDirectiveMeta , TypeCheckBlockMetadata } from '../api' ;
1616
1717import { addExpressionIdentifier , ExpressionIdentifier , markIgnoreDiagnostics } from './comments' ;
@@ -21,7 +21,8 @@ import {Environment} from './environment';
2121import { astToTypescript , NULL_AS_ANY } from './expression' ;
2222import { OutOfBandDiagnosticRecorder } from './oob' ;
2323import { 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' ;
2526
2627/**
2728 * Given a `ts.ClassDeclaration` for a component, and metadata regarding that component, compose a
@@ -357,18 +358,13 @@ class TcbTextInterpolationOp extends TcbOp {
357358}
358359
359360/**
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.
367363 */
368- class TcbDirectiveTypeOp extends TcbOp {
364+ abstract class TcbDirectiveTypeOpBase extends TcbOp {
369365 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 ) {
372368 super ( ) ;
373369 }
374370
@@ -380,16 +376,74 @@ class TcbDirectiveTypeOp extends TcbOp {
380376 }
381377
382378 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+ }
384395
385- const type = this . tcb . env . referenceType ( this . dir . ref ) ;
396+ const id = this . tcb . allocateId ( ) ;
386397 addExpressionIdentifier ( type , ExpressionIdentifier . DIRECTIVE ) ;
387398 addParseSpanInfo ( type , this . node . startSourceSpan || this . node . sourceSpan ) ;
388399 this . scope . addStatement ( tsDeclareVariable ( id , type ) ) ;
389400 return id ;
390401 }
391402}
392403
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+
393447/**
394448 * A `TcbOp` which creates a variable for a local ref in a template.
395449 * The initializer for the variable is the variable expression for the directive, template, or
@@ -1383,8 +1437,27 @@ class Scope {
13831437
13841438 const dirMap = new Map < TypeCheckableDirectiveMeta , number > ( ) ;
13851439 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+
13881461 const dirIndex = this . opQueue . push ( directiveOp ) - 1 ;
13891462 dirMap . set ( dir , dirIndex ) ;
13901463
0 commit comments