feat(ChangeDetector): Add support for short-circuiting
This commit is contained in:
@@ -417,7 +417,14 @@ var _availableDefinitions = [
|
||||
'a.sayHi("Jim")',
|
||||
'passThrough([12])',
|
||||
'invalidFn(1)',
|
||||
'age'
|
||||
'age',
|
||||
'true ? city : zipcode',
|
||||
'false ? city : zipcode',
|
||||
'getTrue() && getTrue()',
|
||||
'getFalse() && getTrue()',
|
||||
'getFalse() || getFalse()',
|
||||
'getTrue() || getFalse()',
|
||||
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode'
|
||||
];
|
||||
|
||||
var _availableEventDefinitions = [
|
||||
@@ -427,7 +434,8 @@ var _availableEventDefinitions = [
|
||||
// '(event)="\$event=1"',
|
||||
'(event)="a=a+1; a=a+1;"',
|
||||
'(event)="false"',
|
||||
'(event)="true"'
|
||||
'(event)="true"',
|
||||
'(event)="true ? a = a + 1 : a = a + 1"',
|
||||
];
|
||||
|
||||
var _availableHostEventDefinitions = ['(host-event)="onEvent(\$event)"'];
|
||||
|
||||
@@ -111,6 +111,54 @@ export function main() {
|
||||
return val.dispatcher.log;
|
||||
}
|
||||
|
||||
describe('short-circuit', () => {
|
||||
it('should support short-circuit for the ternary operator', () => {
|
||||
var address = new Address('Sunnyvale', '94085');
|
||||
expect(_bindSimpleValue('true ? city : zipcode', address))
|
||||
.toEqual(['propName=Sunnyvale']);
|
||||
expect(address.cityGetterCalls).toEqual(1);
|
||||
expect(address.zipCodeGetterCalls).toEqual(0);
|
||||
|
||||
address = new Address('Sunnyvale', '94085');
|
||||
expect(_bindSimpleValue('false ? city : zipcode', address)).toEqual(['propName=94085']);
|
||||
expect(address.cityGetterCalls).toEqual(0);
|
||||
expect(address.zipCodeGetterCalls).toEqual(1);
|
||||
});
|
||||
|
||||
it('should support short-circuit for the && operator', () => {
|
||||
var logical = new Logical();
|
||||
expect(_bindSimpleValue('getTrue() && getTrue()', logical)).toEqual(['propName=true']);
|
||||
expect(logical.trueCalls).toEqual(2);
|
||||
|
||||
logical = new Logical();
|
||||
expect(_bindSimpleValue('getFalse() && getTrue()', logical)).toEqual(['propName=false']);
|
||||
expect(logical.falseCalls).toEqual(1);
|
||||
expect(logical.trueCalls).toEqual(0);
|
||||
});
|
||||
|
||||
it('should support short-circuit for the || operator', () => {
|
||||
var logical = new Logical();
|
||||
expect(_bindSimpleValue('getFalse() || getFalse()', logical)).toEqual(['propName=false']);
|
||||
expect(logical.falseCalls).toEqual(2);
|
||||
|
||||
logical = new Logical();
|
||||
expect(_bindSimpleValue('getTrue() || getFalse()', logical)).toEqual(['propName=true']);
|
||||
expect(logical.falseCalls).toEqual(0);
|
||||
expect(logical.trueCalls).toEqual(1);
|
||||
});
|
||||
|
||||
it('should support nested short-circuits', () => {
|
||||
var address = new Address('Sunnyvale', '94085');
|
||||
var person = new Person('Victor', address);
|
||||
expect(_bindSimpleValue(
|
||||
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode',
|
||||
person))
|
||||
.toEqual(['propName=Sunnyvale']);
|
||||
expect(address.cityGetterCalls).toEqual(1);
|
||||
expect(address.zipCodeGetterCalls).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support literals',
|
||||
() => { expect(_bindSimpleValue('10')).toEqual(['propName=10']); });
|
||||
|
||||
@@ -1299,6 +1347,13 @@ export function main() {
|
||||
res = val.changeDetector.handleEvent("event", 0, locals);
|
||||
expect(res).toBe(false);
|
||||
});
|
||||
|
||||
it('should support short-circuiting', () => {
|
||||
d.a = 0;
|
||||
var val = _createChangeDetector('(event)="true ? a = a + 1 : a = a + 1"', d, null);
|
||||
val.changeDetector.handleEvent("event", 0, locals);
|
||||
expect(d.a).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1417,11 +1472,43 @@ class Person {
|
||||
}
|
||||
|
||||
class Address {
|
||||
constructor(public city: string) {}
|
||||
cityGetterCalls: number = 0;
|
||||
zipCodeGetterCalls: number = 0;
|
||||
|
||||
constructor(public _city: string, public _zipcode = null) {}
|
||||
|
||||
get city() {
|
||||
this.cityGetterCalls++;
|
||||
return this._city;
|
||||
}
|
||||
|
||||
get zipcode() {
|
||||
this.zipCodeGetterCalls++;
|
||||
return this._zipcode;
|
||||
}
|
||||
|
||||
set city(v) { this._city = v; }
|
||||
|
||||
set zipcode(v) { this._zipcode = v; }
|
||||
|
||||
toString(): string { return isBlank(this.city) ? '-' : this.city }
|
||||
}
|
||||
|
||||
class Logical {
|
||||
trueCalls: number = 0;
|
||||
falseCalls: number = 0;
|
||||
|
||||
getTrue() {
|
||||
this.trueCalls++;
|
||||
return true;
|
||||
}
|
||||
|
||||
getFalse() {
|
||||
this.falseCalls++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class Uninitialized {
|
||||
value: any;
|
||||
}
|
||||
|
||||
@@ -16,28 +16,29 @@ import {DirectiveIndex} from 'angular2/src/core/change_detection/directive_recor
|
||||
|
||||
export function main() {
|
||||
function r(funcOrValue, args, contextIndex, selfIndex,
|
||||
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction}: {
|
||||
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, fixedArgs}: {
|
||||
lastInBinding?: any,
|
||||
mode?: any,
|
||||
name?: any,
|
||||
directiveIndex?: any,
|
||||
argumentToPureFunction?: boolean
|
||||
argumentToPureFunction?: boolean,
|
||||
fixedArgs?: any[]
|
||||
} = {}) {
|
||||
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||
if (isBlank(mode)) mode = RecordType.PropertyRead;
|
||||
if (isBlank(name)) name = "name";
|
||||
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||
if (isBlank(fixedArgs)) fixedArgs = null;
|
||||
|
||||
return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex,
|
||||
return new ProtoRecord(mode, name, funcOrValue, args, fixedArgs, contextIndex, directiveIndex,
|
||||
selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0);
|
||||
}
|
||||
|
||||
describe("change detection - coalesce", () => {
|
||||
it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); });
|
||||
|
||||
it("should remove non-terminal duplicate records" +
|
||||
" and update the context indices referencing them",
|
||||
it("should remove non-terminal duplicate records and update the context indices referencing them",
|
||||
() => {
|
||||
var rs = coalesce(
|
||||
[r("user", [], 0, 1), r("first", [], 1, 2), r("user", [], 0, 3), r("last", [], 3, 4)]);
|
||||
@@ -52,8 +53,7 @@ export function main() {
|
||||
expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]);
|
||||
});
|
||||
|
||||
it("should remove non-terminal duplicate records" +
|
||||
" and update the args indices referencing them",
|
||||
it("should remove non-terminal duplicate records and update the args indices referencing them",
|
||||
() => {
|
||||
var rs = coalesce([
|
||||
r("user1", [], 0, 1),
|
||||
@@ -132,5 +132,104 @@ export function main() {
|
||||
expect(rs)
|
||||
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||
});
|
||||
|
||||
describe('short-circuit', () => {
|
||||
it('should not use short-circuitable records', () => {
|
||||
var records = [
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||
r("a", [], 0, 2),
|
||||
r("sk", [], 0, 3, {mode: RecordType.SkipRecords, fixedArgs: [4]}),
|
||||
r("b", [], 0, 4),
|
||||
r("cond", [2, 4], 0, 5),
|
||||
r("a", [], 0, 6),
|
||||
r("b", [], 0, 7),
|
||||
];
|
||||
|
||||
expect(coalesce(records)).toEqual(records);
|
||||
});
|
||||
|
||||
it('should not use short-circuitable records from nested short-circuits', () => {
|
||||
var records = [
|
||||
r("sknot outer", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||
r("sknot inner", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("a", [], 0, 3),
|
||||
r("sk inner", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [5]}),
|
||||
r("b", [], 0, 5),
|
||||
r("cond-inner", [3, 5], 0, 6),
|
||||
r("sk outer", [], 0, 7, {mode: RecordType.SkipRecords, fixedArgs: [8]}),
|
||||
r("c", [], 0, 8),
|
||||
r("cond-outer", [6, 8], 0, 9),
|
||||
r("a", [], 0, 10),
|
||||
r("b", [], 0, 11),
|
||||
r("c", [], 0, 12),
|
||||
];
|
||||
|
||||
expect(coalesce(records)).toEqual(records);
|
||||
});
|
||||
|
||||
it('should collapse the true branch', () => {
|
||||
var rs = coalesce([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("a", [], 0, 3),
|
||||
r("sk", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("a", [], 0, 5),
|
||||
r("b", [], 5, 6),
|
||||
r("cond", [3, 6], 0, 7),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIf, fixedArgs: [3]}),
|
||||
r("b", [], 1, 3),
|
||||
r("cond", [1, 3], 0, 4),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should collapse the false branch', () => {
|
||||
var rs = coalesce([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [5]}),
|
||||
r("a", [], 0, 3),
|
||||
r("b", [], 3, 4),
|
||||
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("a", [], 0, 6),
|
||||
r("cond", [4, 6], 0, 7),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||
r("b", [], 1, 3),
|
||||
r("cond", [3, 1], 0, 4),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should optimize skips', () => {
|
||||
var rs = coalesce([
|
||||
// skipIfNot(1) + skip(N) -> skipIf(+N)
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [2]}),
|
||||
r("sk", [], 0, 2, {mode: RecordType.SkipRecords, fixedArgs: [3]}),
|
||||
r("a", [], 0, 3),
|
||||
// skipIf(1) + skip(N) -> skipIfNot(N)
|
||||
r("skif", [], 0, 4, {mode: RecordType.SkipRecordsIf, fixedArgs: [5]}),
|
||||
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("b", [], 0, 6),
|
||||
// remove empty skips
|
||||
r("sknot", [], 0, 7, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||
r("skif", [], 0, 8, {mode: RecordType.SkipRecordsIf, fixedArgs: [8]}),
|
||||
r("sk", [], 0, 9, {mode: RecordType.SkipRecords, fixedArgs: [9]}),
|
||||
r("end", [], 0, 10),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIf, fixedArgs: [2]}),
|
||||
r("a", [], 0, 2),
|
||||
r("skif", [], 0, 3, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("b", [], 0, 4),
|
||||
r("end", [], 0, 5),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user