feat(compiler): narrow types of expressions used in *ngIf (#20702)

Structural directives can now specify a type guard that describes
what types can be inferred for an input expression inside the
directive's template.

NgIf was modified to declare an input guard on ngIf.

After this change, `fullTemplateTypeCheck` will infer that
usage of `ngIf` expression inside it's template is truthy.

For example, if a component has a property `person?: Person`
and a template of `<div *ngIf="person"> {{person.name}} </div>`
the compiler will no longer report that `person` might be null or
undefined.

The template compiler will generate code similar to,

```
  if (NgIf.ngIfTypeGuard(instance.person)) {
    instance.person.name
  }
```

to validate the template's use of the interpolation expression.
Calling the type guard in this fashion allows TypeScript to infer
that `person` is non-null.

Fixes: #19756?

PR Close #20702
This commit is contained in:
Chuck Jazdzewski
2017-11-29 16:29:05 -08:00
committed by Jason Aden
parent e544742156
commit e7d9cb3e4c
19 changed files with 341 additions and 53 deletions

View File

@ -38,28 +38,29 @@ function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
}
function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, exportAs,
changeDetection, inputs, outputs, host, providers,
viewProviders, queries, viewQueries, entryComponents,
template, componentViewType, rendererType}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string | null,
exportAs?: string | null,
changeDetection?: ChangeDetectionStrategy | null,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
providers?: CompileProviderMetadata[] | null,
viewProviders?: CompileProviderMetadata[] | null,
queries?: CompileQueryMetadata[] | null,
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileEntryComponentMetadata[],
template?: CompileTemplateMetadata,
componentViewType?: StaticSymbol | ProxyClass | null,
rendererType?: StaticSymbol | RendererType2 | null,
}) {
function compileDirectiveMetadataCreate(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
providers, viewProviders, queries, guards, viewQueries, entryComponents, template,
componentViewType, rendererType}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string | null,
exportAs?: string | null,
changeDetection?: ChangeDetectionStrategy | null,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
providers?: CompileProviderMetadata[] | null,
viewProviders?: CompileProviderMetadata[] | null,
queries?: CompileQueryMetadata[] | null,
guards?: {[key: string]: any},
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileEntryComponentMetadata[],
template?: CompileTemplateMetadata,
componentViewType?: StaticSymbol | ProxyClass | null,
rendererType?: StaticSymbol | RendererType2 | null,
}) {
return CompileDirectiveMetadata.create({
isHost: !!isHost,
type: noUndefined(type) !,
@ -73,6 +74,7 @@ function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, ex
providers: providers || [],
viewProviders: viewProviders || [],
queries: queries || [],
guards: guards || {},
viewQueries: viewQueries || [],
entryComponents: entryComponents || [],
template: noUndefined(template) !,
@ -390,6 +392,7 @@ export function main() {
providers: [],
viewProviders: [],
queries: [],
guards: {},
viewQueries: [],
entryComponents: [],
componentViewType: null,