Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/common/src/directives/ng_if.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ export class NgIf {
}
}

public static ngIfTypeGuard: <T>(v: T|null|undefined|false) => v is T;
/** @internal */
public static ngIfUseIfTypeGuard: void;
}

/**
Expand Down
298 changes: 298 additions & 0 deletions packages/compiler-cli/test/diagnostics/check_types_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,304 @@ describe('ng type checker', () => {
export class MainModule {}`
});
});

it('should narrow an *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow a renamed *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *my-if="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[my-if]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input('my-if')
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow a type in a nested *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Address {
street: string;
}

export interface Person {
name: string;
address?: Address;
}


@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
})
export class MainComp {
person?: Person;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow an *ngIf like directive with UseIf and &&', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Address {
street: string;
}

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *myIf="person && address"> {{person.name}} lives at {{address.street}} </div>'
})
export class MainComp {
person?: Person;
address?: Address;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow an *ngIf like directive with UseIf and !!', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *myIf="!!person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow an *ngIf like directive with UseIf and != null', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *myIf="person != null"> {{person.name}} </div>'
})
export class MainComp {
person: Person | null = null;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});

it('should narrow an *ngIf like directive with UseIf and != undefined', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';

export interface Person {
name: string;
}

@Component({
selector: 'comp',
template: '<div *myIf="person != undefined"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}

export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}

@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}

@Input()
set myIf(condition: any) {}

static myIfUseIfTypeGuard: void;
}

@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
});

describe('casting $any', () => {
Expand Down
14 changes: 12 additions & 2 deletions packages/compiler/src/aot/static_reflector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const USE_VALUE = 'useValue';
const PROVIDE = 'provide';
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
const TYPEGUARD_POSTFIX = 'TypeGuard';
const USE_IF = 'UseIf';

function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
Expand Down Expand Up @@ -296,8 +297,17 @@ export class StaticReflector implements CompileReflector {
const staticMembers = this._staticMembers(type);
const result: {[key: string]: StaticSymbol} = {};
for (let name of staticMembers) {
result[name.substr(0, name.length - TYPEGUARD_POSTFIX.length)] =
this.getStaticSymbol(type.filePath, type.name, [name]);
if (name.endsWith(TYPEGUARD_POSTFIX)) {
let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length);
let value: any;
if (property.endsWith(USE_IF)) {
property = name.substr(0, property.length - USE_IF.length);
value = USE_IF;
} else {
value = this.getStaticSymbol(type.filePath, type.name, [name]);
}
result[property] = value;
}
}
return result;
}
Expand Down
10 changes: 8 additions & 2 deletions packages/compiler/src/compiler_util/expression_converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function convertPropertyBinding(
return new ConvertPropertyBindingResult([], outputExpr);
}

stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr);
}

Expand Down Expand Up @@ -334,7 +334,13 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}

visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value));
// For literal values of null, undefined, true, or false allow type inteference
// to infer the type.
const type =
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
o.INFERRED_TYPE :
undefined;
return convertToStatementIfNeeded(mode, o.literal(ast.value, type));
}

private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); }
Expand Down
Loading