feat(ivy): introduce typecheck package and a type constructor generator (#26203)

This commit introduces //packages/compiler-cli/src/ngtsc/typecheck as a
container for template type-checking code, and implements an initial API:
type constructor generation.

Type constructors are static methods on component/directive types with
no runtime implementation. The methods are used during compilation to
enable inference of a component or directive's generic type parameters
from the types of expressions bound to any of their @Inputs. A type
constructor looks like:

class Directive<T> {
  someInput: T;
  static ngTypeCtor<T>(init: Partial<Pick<Directive<T>, 'someInput'>>): Directive<T>;
}

It can be used to infer a type for T based on the input:

const _dir = Directive.ngTypeCtor({someInput: 'string'}); // Directive<T>

PR Close #26203
This commit is contained in:
Alex Rickabaugh 2018-09-26 09:43:52 -07:00 committed by Jason Aden
parent 9ed4e3df60
commit b0070dfb9a
3 changed files with 151 additions and 0 deletions

View File

@ -0,0 +1,15 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "typecheck",
srcs = glob(["**/*.ts"]),
module_name = "@angular/compiler-cli/src/ngtsc/typecheck",
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/util",
],
)

View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright Google Inc. 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.io/license
*/
/**
* Metadata to generate a type constructor for a particular directive.
*/
export interface TypeCtorMetadata {
/**
* The name of the requested type constructor function.
*/
fnName: string;
/**
* Whether to generate a body for the function or not.
*/
body: boolean;
/**
* Input, output, and query field names in the type which should be included as constructor input.
*/
fields: {inputs: string[]; outputs: string[]; queries: string[];};
}

View File

@ -0,0 +1,109 @@
/**
* @license
* Copyright Google Inc. 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.io/license
*/
import * as ts from 'typescript';
import {TypeCtorMetadata} from './api';
/**
* Generate a type constructor for the given class and metadata.
*
* A type constructor is a specially shaped TypeScript static method, intended to be placed within
* a directive class itself, that permits type inference of any generic type parameters of the class
* from the types of expressions bound to inputs or outputs, and the types of elements that match
* queries performed by the directive. It also catches any errors in the types of these expressions.
* This method is never called at runtime, but is used in type-check blocks to construct directive
* types.
*
* A type constructor for NgFor looks like:
*
* static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>):
* NgForOf<T>;
*
* A typical usage would be:
*
* NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); // Infers a type of NgForOf<string>.
*
* @param node the `ts.ClassDeclaration` for which a type constructor will be generated.
* @param meta additional metadata required to generate the type constructor.
* @returns a `ts.MethodDeclaration` for the type constructor.
*/
export function generateTypeCtor(
node: ts.ClassDeclaration, meta: TypeCtorMetadata): ts.MethodDeclaration {
// Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from
// the definition without any type bounds. For example, if the class is
// `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
const rawTypeArgs = node.typeParameters !== undefined ?
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined)) :
undefined;
const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name !, rawTypeArgs);
// initType is the type of 'init', the single argument to the type constructor method.
// If the Directive has any inputs, outputs, or queries, its initType will be:
//
// Partial<Pick<rawType, 'inputField'|'outputField'|'queryField'>>
//
// Pick here is used to select only those fields from which the generic type parameters of the
// directive will be inferred. Partial is used because inputs are optional, so there may not be
// bindings for each field.
//
// In the special case there are no inputs/outputs/etc, initType is set to {}.
let initType: ts.TypeNode;
const keys: string[] = [
...meta.fields.inputs,
...meta.fields.outputs,
...meta.fields.queries,
];
if (keys.length === 0) {
// Special case - no inputs, outputs, or other fields which could influence the result type.
initType = ts.createTypeLiteralNode([]);
} else {
// Construct a union of all the field names.
const keyTypeUnion = ts.createUnionTypeNode(
keys.map(key => ts.createLiteralTypeNode(ts.createStringLiteral(key))));
// Construct the Pick<rawType, keyTypeUnion>.
const pickType = ts.createTypeReferenceNode('Pick', [rawType, keyTypeUnion]);
// Construct the Partial<pickType>.
initType = ts.createTypeReferenceNode('Partial', [pickType]);
}
// If this constructor is being generated into a .ts file, then it needs a fake body. The body
// is set to a return of `null!`. If the type constructor is being generated into a .d.ts file,
// it needs no body.
let body: ts.Block|undefined = undefined;
if (meta.body) {
body = ts.createBlock([
ts.createReturn(ts.createNonNullExpression(ts.createNull())),
]);
}
// Create the 'init' parameter itself.
const initParam = ts.createParameter(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ 'init',
/* questionToken */ undefined,
/* type */ initType,
/* initializer */ undefined, );
// Create the type constructor method declaration.
return ts.createMethod(
/* decorators */ undefined,
/* modifiers */[ts.createModifier(ts.SyntaxKind.StaticKeyword)],
/* asteriskToken */ undefined,
/* name */ meta.fnName,
/* questionToken */ undefined,
/* typeParameters */ node.typeParameters,
/* parameters */[initParam],
/* type */ rawType,
/* body */ body, );
}