feat(ChangeDetector): Add support for short-circuiting

This commit is contained in:
Victor Berchet
2015-10-16 10:55:21 -07:00
parent b91351469f
commit 7e92d2e6b7
10 changed files with 582 additions and 113 deletions
@@ -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),
]);
});
});
});
}