Skip to content

Commit

Permalink
fix(compiler): properly implement pure pipes and change pipe syntax
Browse files Browse the repository at this point in the history
Pure pipes as well as arrays and maps are
implemented via proxy functions. This is
faster than the previous implementation
and also generates less code.

BREAKING CHANGE:
- pipes now take a variable number of arguments, and not an array that contains all arguments.
  • Loading branch information
tbosch committed Apr 25, 2016
1 parent d662630 commit 152a117
Show file tree
Hide file tree
Showing 48 changed files with 698 additions and 283 deletions.
13 changes: 3 additions & 10 deletions modules/angular2/src/common/pipes/async_pipe.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang';
import {ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async';
import {
Pipe,
Injectable,
ChangeDetectorRef,
OnDestroy,
PipeTransform,
WrappedValue
} from 'angular2/core';
import {Pipe, Injectable, ChangeDetectorRef, OnDestroy, WrappedValue} from 'angular2/core';

import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';

Expand Down Expand Up @@ -55,7 +48,7 @@ var __unused: Promise<any>; // avoid unused import when Promise union types are
*/
@Pipe({name: 'async', pure: false})
@Injectable()
export class AsyncPipe implements PipeTransform, OnDestroy {
export class AsyncPipe implements OnDestroy {
/** @internal */
_latestValue: Object = null;
/** @internal */
Expand All @@ -76,7 +69,7 @@ export class AsyncPipe implements PipeTransform, OnDestroy {
}
}

transform(obj: Observable<any>| Promise<any>| EventEmitter<any>, args?: any[]): any {
transform(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
if (isBlank(this._obj)) {
if (isPresent(obj)) {
this._subscribe(obj);
Expand Down
3 changes: 1 addition & 2 deletions modules/angular2/src/common/pipes/date_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,13 @@ export class DatePipe implements PipeTransform {
};


transform(value: any, args: any[]): string {
transform(value: any, pattern: string = 'mediumDate'): string {
if (isBlank(value)) return null;

if (!this.supports(value)) {
throw new InvalidPipeArgumentException(DatePipe, value);
}

var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate';
if (isNumber(value)) {
value = DateWrapper.fromMillis(value);
}
Expand Down
3 changes: 1 addition & 2 deletions modules/angular2/src/common/pipes/i18n_plural_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ var interpolationExp: RegExp = RegExpWrapper.create('#');
@Pipe({name: 'i18nPlural', pure: true})
@Injectable()
export class I18nPluralPipe implements PipeTransform {
transform(value: number, args: any[] = null): string {
transform(value: number, pluralMap: {[count: string]: string}): string {
var key: string;
var valueStr: string;
var pluralMap: {[count: string]: string} = <{[count: string]: string}>(args[0]);

if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
Expand Down
3 changes: 1 addition & 2 deletions modules/angular2/src/common/pipes/i18n_select_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'i18nSelect', pure: true})
@Injectable()
export class I18nSelectPipe implements PipeTransform {
transform(value: string, args: any[] = null): string {
var mapping: {[key: string]: string} = <{[count: string]: string}>(args[0]);
transform(value: string, mapping: {[key: string]: string}): string {
if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/angular2/src/common/pipes/json_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
@Pipe({name: 'json', pure: false})
@Injectable()
export class JsonPipe implements PipeTransform {
transform(value: any, args: any[] = null): string { return Json.stringify(value); }
transform(value: any): string { return Json.stringify(value); }
}
2 changes: 1 addition & 1 deletion modules/angular2/src/common/pipes/lowercase_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'lowercase'})
@Injectable()
export class LowerCasePipe implements PipeTransform {
transform(value: string, args: any[] = null): string {
transform(value: string): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(LowerCasePipe, value);
Expand Down
13 changes: 4 additions & 9 deletions modules/angular2/src/common/pipes/number_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
import {ListWrapper} from 'angular2/src/facade/collection';

import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';

Expand Down Expand Up @@ -87,8 +86,7 @@ export class NumberPipe {
@Pipe({name: 'number'})
@Injectable()
export class DecimalPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
transform(value: any, digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Decimal, digits);
}
}
Expand All @@ -113,8 +111,7 @@ export class DecimalPipe extends NumberPipe implements PipeTransform {
@Pipe({name: 'percent'})
@Injectable()
export class PercentPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
transform(value: any, digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Percent, digits);
}
}
Expand Down Expand Up @@ -143,10 +140,8 @@ export class PercentPipe extends NumberPipe implements PipeTransform {
@Pipe({name: 'currency'})
@Injectable()
export class CurrencyPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
transform(value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode,
symbolDisplay);
}
Expand Down
18 changes: 5 additions & 13 deletions modules/angular2/src/common/pipes/replace_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';

Expand Down Expand Up @@ -39,11 +38,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'replace'})
@Injectable()
export class ReplacePipe implements PipeTransform {
transform(value: any, args: any[]): any {
if (isBlank(args) || args.length !== 2) {
throw new BaseException('ReplacePipe requires two arguments');
}

transform(value: any, pattern: string | RegExp, replacement: Function | string): any {
if (isBlank(value)) {
return value;
}
Expand All @@ -53,9 +48,6 @@ export class ReplacePipe implements PipeTransform {
}

var input = value.toString();
var pattern = args[0];
var replacement = args[1];


if (!this._supportedPattern(pattern)) {
throw new InvalidPipeArgumentException(ReplacePipe, pattern);
Expand All @@ -67,16 +59,16 @@ export class ReplacePipe implements PipeTransform {
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);

if (isFunction(replacement)) {
var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern;
var rgxPattern = isString(pattern) ? RegExpWrapper.create(<string>pattern) : <RegExp>pattern;

return StringWrapper.replaceAllMapped(input, rgxPattern, replacement);
return StringWrapper.replaceAllMapped(input, rgxPattern, <Function>replacement);
}
if (pattern instanceof RegExp) {
// use the replaceAll variant
return StringWrapper.replaceAll(input, pattern, replacement);
return StringWrapper.replaceAll(input, pattern, <string>replacement);
}

return StringWrapper.replace(input, pattern, replacement);
return StringWrapper.replace(input, <string>pattern, <string>replacement);
}

private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); }
Expand Down
8 changes: 1 addition & 7 deletions modules/angular2/src/common/pipes/slice_pipe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {isBlank, isString, isArray, StringWrapper, CONST} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
Expand Down Expand Up @@ -59,16 +58,11 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'slice', pure: false})
@Injectable()
export class SlicePipe implements PipeTransform {
transform(value: any, args: any[] = null): any {
if (isBlank(args) || args.length == 0) {
throw new BaseException('Slice pipe requires one argument');
}
transform(value: any, start: number, end: number = null): any {
if (!this.supports(value)) {
throw new InvalidPipeArgumentException(SlicePipe, value);
}
if (isBlank(value)) return value;
var start: number = args[0];
var end: number = args.length > 1 ? args[1] : null;
if (isString(value)) {
return StringWrapper.slice(value, start, end);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/angular2/src/common/pipes/uppercase_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'uppercase'})
@Injectable()
export class UpperCasePipe implements PipeTransform {
transform(value: string, args: any[] = null): string {
transform(value: string): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(UpperCasePipe, value);
Expand Down
39 changes: 38 additions & 1 deletion modules/angular2/src/compiler/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ import {
ViewUtils,
flattenNestedViewRenderNodes,
interpolate,
checkBinding
checkBinding,
castByValue,
pureProxy1,
pureProxy2,
pureProxy3,
pureProxy4,
pureProxy5,
pureProxy6,
pureProxy7,
pureProxy8,
pureProxy9,
pureProxy10
} from 'angular2/src/core/linker/view_utils';
import {
uninitialized,
Expand Down Expand Up @@ -59,6 +70,7 @@ var impFlattenNestedViewRenderNodes = flattenNestedViewRenderNodes;
var impDevModeEqual = devModeEqual;
var impInterpolate = interpolate;
var impCheckBinding = checkBinding;
var impCastByValue = castByValue;

export class Identifiers {
static ViewUtils = new CompileIdentifierMetadata({
Expand Down Expand Up @@ -162,6 +174,31 @@ export class Identifiers {
{name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: impDevModeEqual});
static interpolate = new CompileIdentifierMetadata(
{name: 'interpolate', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impInterpolate});
static castByValue = new CompileIdentifierMetadata(
{name: 'castByValue', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impCastByValue});
static pureProxies = [
null,
new CompileIdentifierMetadata(
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy1}),
new CompileIdentifierMetadata(
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy2}),
new CompileIdentifierMetadata(
{name: 'pureProxy3', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy3}),
new CompileIdentifierMetadata(
{name: 'pureProxy4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy4}),
new CompileIdentifierMetadata(
{name: 'pureProxy5', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy5}),
new CompileIdentifierMetadata(
{name: 'pureProxy6', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy6}),
new CompileIdentifierMetadata(
{name: 'pureProxy7', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy7}),
new CompileIdentifierMetadata(
{name: 'pureProxy8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy8}),
new CompileIdentifierMetadata(
{name: 'pureProxy9', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy9}),
new CompileIdentifierMetadata(
{name: 'pureProxy10', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy10}),
];
}

export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
Expand Down
5 changes: 5 additions & 0 deletions modules/angular2/src/compiler/output/abstract_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
var name = expr.name;
if (isPresent(expr.builtin)) {
name = this.getBuiltinMethodName(expr.builtin);
if (isBlank(name)) {
// some builtins just mean to skip the call.
// e.g. `bind` in Dart.
return null;
}
}
ctx.print(`.${name}(`);
this.visitAllExpressions(expr.args, ctx, `,`);
Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/compiler/output/abstract_js_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe';
break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/compiler/output/dart_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ class _DartEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisito
case o.BuiltinMethod.SubscribeObservable:
name = 'listen';
break;
case o.BuiltinMethod.bind:
name = null;
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
Expand Down
3 changes: 1 addition & 2 deletions modules/angular2/src/compiler/output/interpretive_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export class InterpretiveAppViewInstanceFactory implements InstanceFactory {
class _InterpretiveAppView extends AppView<any> implements DynamicInstance {
constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>,
public methods: Map<string, Function>) {
super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9],
args[10]);
super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
}
createInternal(rootSelector: string | any): AppElement {
var m = this.methods.get('createInternal');
Expand Down
3 changes: 2 additions & 1 deletion modules/angular2/src/compiler/output/output_ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ export class WritePropExpr extends Expression {

export enum BuiltinMethod {
ConcatArray,
SubscribeObservable
SubscribeObservable,
bind
}

export class InvokeMethodExpr extends Expression {
Expand Down
9 changes: 9 additions & 0 deletions modules/angular2/src/compiler/output/output_interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
case o.BuiltinMethod.SubscribeObservable:
result = ObservableWrapper.subscribe(receiver, args[0]);
break;
case o.BuiltinMethod.bind:
if (IS_DART) {
result = receiver;
} else {
result = receiver.bind(args[0]);
}
break;
default:
throw new BaseException(`Unknown builtin method ${expr.builtin}`);
}
Expand Down Expand Up @@ -331,6 +338,8 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
result = di.props.get(ast.name);
} else if (di.getters.has(ast.name)) {
result = di.getters.get(ast.name)();
} else if (di.methods.has(ast.name)) {
result = di.methods.get(ast.name);
} else {
result = reflector.getter(ast.name)(receiver);
}
Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/compiler/output/ts_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe';
break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,22 +324,16 @@ export class CompileElement extends CompileNode {
private _getDependency(requestingProviderType: ProviderAstType,
dep: CompileDiDependencyMetadata): o.Expression {
var currElement: CompileElement = this;
var currView = currElement.view;
var result = null;
if (dep.isValue) {
result = o.literal(dep.value);
}
if (isBlank(result) && !dep.isSkipSelf) {
result = this._getLocalDependency(requestingProviderType, dep);
}
var resultViewPath = [];
// check parent elements
while (isBlank(result) && !currElement.parent.isNull()) {
currElement = currElement.parent;
while (currElement.view !== currView && currView != null) {
currView = currView.declarationElement.view;
resultViewPath.push(currView);
}
result = currElement._getLocalDependency(ProviderAstType.PublicService,
new CompileDiDependencyMetadata({token: dep.token}));
}
Expand All @@ -350,7 +344,7 @@ export class CompileElement extends CompileNode {
if (isBlank(result)) {
result = o.NULL_EXPR;
}
return getPropertyInView(result, resultViewPath);
return getPropertyInView(result, this.view, currElement.view);
}
}

Expand Down
Loading

0 comments on commit 152a117

Please sign in to comment.