Skip to content

Commit 9f1f07d

Browse files
committed
feat: Removing implicit map dereference in expressions
Closes dart-archive#915 Before this PR a.b would have match either a.b or a['b']. The reason is that the context was a Map (before dart-archive#914) and we don't want to write ctrl['foo'] but ctrl.foo. It was also not possible to access Map properties, ie map.length would have returned map['length']. Now "a.b" return a.b and "a['b']" return a['b']
1 parent 983452a commit 9f1f07d

18 files changed

+449
-423
lines changed

lib/change_detection/dirty_checking_change_detector.dart

+9-14
Original file line numberDiff line numberDiff line change
@@ -460,22 +460,17 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
460460
return;
461461
}
462462

463-
if (object is Map) {
464-
_mode = _MODE_MAP_FIELD_;
465-
_getter = null;
466-
} else {
467-
while (object is ContextLocals) {
468-
var ctx = object as ContextLocals;
469-
if (ctx.hasProperty(field)) {
470-
_mode = _MODE_MAP_FIELD_;
471-
_getter = null;
472-
return;
473-
}
474-
object = ctx.parentContext;
463+
while (object is ContextLocals) {
464+
var ctx = object as ContextLocals;
465+
if (ctx.hasProperty(field)) {
466+
_mode = _MODE_MAP_FIELD_;
467+
_getter = null;
468+
return;
475469
}
476-
_mode = _MODE_GETTER_OR_METHOD_CLOSURE_;
477-
_getter = _fieldGetterFactory.getter(object, field);
470+
object = ctx.parentContext;
478471
}
472+
_mode = _MODE_GETTER_OR_METHOD_CLOSURE_;
473+
_getter = _fieldGetterFactory.getter(object, field);
479474
}
480475

481476
bool check() {

lib/core/parser/eval_access.dart

-3
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,13 @@ class AccessKeyed extends syntax.AccessKeyed {
3939
* use to do the access the field.
4040
*/
4141
// todo(vicb) - parser should not depend on ContextLocals
42-
// todo(vicb) - Map should not be a special case so that we can access the props
4342
abstract class AccessFast {
4443
String get name;
4544
Getter get getter;
4645
Setter get setter;
4746

4847
dynamic _eval(holder) {
4948
if (holder == null) return null;
50-
if (holder is Map) return holder[name];
5149
return getter(holder);
5250
}
5351

@@ -56,7 +54,6 @@ abstract class AccessFast {
5654
_assignToNonExisting(scope, value);
5755
return value;
5856
} else {
59-
if (holder is Map) return holder[name] = value;
6057
return setter(holder, value);
6158
}
6259
}

lib/core/parser/parser_dynamic.dart

+7-26
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,14 @@ class DynamicClosureMap implements ClosureMap {
88
final Map<String, Symbol> symbols = {};
99
Getter lookupGetter(String name) {
1010
var symbol = new Symbol(name);
11-
return (o) {
12-
if (o is Map) {
13-
return o[name];
14-
} else {
15-
return reflect(o).getField(symbol).reflectee;
16-
}
17-
};
11+
return (o) => reflect(o).getField(symbol).reflectee;
1812
}
1913

2014
Setter lookupSetter(String name) {
2115
var symbol = new Symbol(name);
2216
return (o, value) {
23-
if (o is Map) {
24-
return o[name] = value;
25-
} else {
26-
reflect(o).setField(symbol, value);
27-
return value;
28-
}
17+
reflect(o).setField(symbol, value);
18+
return value;
2919
};
3020
}
3121

@@ -37,19 +27,10 @@ class DynamicClosureMap implements ClosureMap {
3727
var symbol = symbols.putIfAbsent(name, () => new Symbol(name));
3828
sNamedArgs[symbol] = value;
3929
});
40-
if (o is Map) {
41-
var fn = o[name];
42-
if (fn is Function) {
43-
return Function.apply(fn, posArgs, sNamedArgs);
44-
} else {
45-
throw "Property '$name' is not of type function.";
46-
}
47-
} else {
48-
try {
49-
return reflect(o).invoke(symbol, posArgs, sNamedArgs).reflectee;
50-
} on NoSuchMethodError catch (e) {
51-
throw 'Undefined function $name';
52-
}
30+
try {
31+
return reflect(o).invoke(symbol, posArgs, sNamedArgs).reflectee;
32+
} on NoSuchMethodError catch (e) {
33+
throw 'Undefined function $name';
5334
}
5435
};
5536
}

lib/core/parser/parser_static.dart

+1-10
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,7 @@ class StaticClosureMap extends ClosureMap {
2727
return (o, posArgs, namedArgs) {
2828
var sNamedArgs = {};
2929
namedArgs.forEach((name, value) => sNamedArgs[symbols[name]] = value);
30-
if (o is Map) {
31-
var fn = o[name];
32-
if (fn is Function) {
33-
return Function.apply(fn, posArgs, sNamedArgs);
34-
} else {
35-
throw "Property '$name' is not of type function.";
36-
}
37-
} else {
38-
return Function.apply(fn(o), posArgs, sNamedArgs);
39-
}
30+
return Function.apply(fn(o), posArgs, sNamedArgs);
4031
};
4132
}
4233

lib/core/scope.dart

+7-7
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class ScopeDigestTTL {
9696
* a `scope` setter:
9797
*
9898
* @Component(...)
99-
* class MyComponent {
99+
* class MyComponent implements ScopeAware {
100100
* Watch watch;
101101
*
102102
* MyComponent(Dependency myDep) {
@@ -111,7 +111,7 @@ class ScopeDigestTTL {
111111
* }
112112
*/
113113
abstract class ScopeAware {
114-
void set context(ctx);
114+
void set scope(Scope scope);
115115
}
116116

117117
/**
@@ -128,7 +128,7 @@ class Scope {
128128
/**
129129
* The default execution context for [watch]es [observe]ers, and [eval]uation.
130130
*/
131-
final Object context;
131+
final context;
132132

133133
/**
134134
* The [RootScope] of the application.
@@ -165,20 +165,20 @@ class Scope {
165165
// TODO(misko): WatchGroup should be private.
166166
// Instead we should expose performance stats about the watches
167167
// such as # of watches, checks/1ms, field checks, function checks, etc
168-
WatchGroup _readWriteGroup;
169-
WatchGroup _readOnlyGroup;
168+
final WatchGroup _readWriteGroup;
169+
final WatchGroup _readOnlyGroup;
170170

171171
Scope _childHead, _childTail, _next, _prev;
172172
_Streams _streams;
173173

174174
/// Do not use. Exposes internal state for testing.
175175
bool get hasOwnStreams => _streams != null && _streams._scope == this;
176176

177-
Scope(this.context, this.rootScope, this._parentScope,
177+
Scope(Object this.context, this.rootScope, this._parentScope,
178178
this._readWriteGroup, this._readOnlyGroup, this.id,
179179
this._stats)
180180
{
181-
if (context is ScopeAware) context.scope = this;
181+
if (context is ScopeAware) (context as ScopeAware).scope = this;
182182
}
183183

184184
/**

lib/formatter/filter.dart

+51-58
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
part of angular.formatter_internal;
22

3-
// Too bad you can't stick typedef's inside a class.
4-
typedef bool _Predicate(e);
5-
typedef bool _Equals(a, b);
3+
typedef bool _PredicateFn(e);
4+
typedef bool _CompareFn(a, b);
65

76
/**
87
* Selects a subset of items from the provided [List] and returns it as a new
@@ -17,37 +16,37 @@ typedef bool _Equals(a, b);
1716
*
1817
* The expression can be of the following types:
1918
*
20-
* - [String], [bool] and [num] Only items in the list that directly
19+
* - [String], [bool] and [num]: Only items in the list that directly
2120
* match this expression, items that are Maps with any value matching this
2221
* item, and items that are lists containing a matching items are returned.
2322
*
24-
* - [Map]:  This defines a pattern map.  Filters specific properties on objects
23+
* - [Map]:  This defines a pattern map. Filters specific properties on objects
2524
* contained in the input list.  For example `{name:"M", phone:"1"}` predicate
2625
* will return a list of items which have property `name` containing "M" and
2726
* property `phone` containing "1".  A special property name, `$`, can be used
2827
* (as in `{$: "text"}`) to accept a match against any property of the object.
2928
* That's equivalent to the simple substring match with a `String` as
3029
* described above.
3130
*
32-
* - [Function] This allows you to supply a custom function to filter the
33-
* List.  The function is called for each element of the List.  The returned
31+
* - [Function]: This allows you to supply a custom function to filter the
32+
* List.  The function is called for each element of the List. The returned
3433
* List contains exactly those elements for which this function returned
3534
* `true`.
3635
*
3736
*
3837
* The comparator is optional and can be one of the following:
3938
*
40-
* - `bool comparator(expected, actual)` The function will be called with the
39+
* - `bool comparator(expected, actual)`: The function will be called with the
4140
* object value and the predicate value to compare and should return true if
4241
* the item should be included in filtered result.
4342
*
4443
* - `true`:  Specifies that only identical objects matching the expression
45-
* exactly should be considered matches.  Two strings are considered identical
46-
* if they are equal.  Two numbers are considered identical if they are either
47-
* equal or both are `NaN` All other objects are identical iff
44+
* exactly should be considered matches. Two strings are considered identical
45+
* if they are equal. Two numbers are considered identical if they are either
46+
* equal or both are `NaN`. All other objects are identical iff
4847
* identical(expected, actual) is true.
4948
*
50-
* - `false|null`:  Specifies case insensitive substring matching.
49+
* - `false|null`: Specifies case insensitive substring matching.
5150
*
5251
*
5352
* # Example ([view in plunker](http://plnkr.co/edit/6Mxz6r?p=info)):
@@ -105,27 +104,30 @@ typedef bool _Equals(a, b);
105104
@Formatter(name: 'filter')
106105
class Filter implements Function {
107106
Parser _parser;
108-
_Equals _comparator;
109-
_Equals _stringComparator;
107+
_CompareFn _comparator;
108+
_CompareFn _stringComparator;
110109

111110
static _nop(e) => e;
111+
112112
static _ensureBool(val) => (val is bool && val);
113+
113114
static _isSubstringCaseInsensitive(String a, String b) =>
114115
a != null && b != null && a.toLowerCase().contains(b.toLowerCase());
116+
115117
static _identical(a, b) => identical(a, b) ||
116118
(a is String && b is String && a == b) ||
117119
(a is num && b is num && a.isNaN && b.isNaN);
118120

119121
Filter(this._parser);
120122

121-
void _configureComparator(var comparatorExpression) {
123+
void _configureComparator(comparatorExpression) {
122124
if (comparatorExpression == null || comparatorExpression == false) {
123125
_stringComparator = _isSubstringCaseInsensitive;
124126
_comparator = _defaultComparator;
125127
} else if (comparatorExpression == true) {
126128
_stringComparator = _identical;
127129
_comparator = _defaultComparator;
128-
} else if (comparatorExpression is _Equals) {
130+
} else if (comparatorExpression is _CompareFn) {
129131
_comparator = (a, b) => _ensureBool(comparatorExpression(a, b));
130132
} else {
131133
_comparator = null;
@@ -135,67 +137,58 @@ class Filter implements Function {
135137
// Preconditions
136138
// - what: NOT a Map
137139
// - item: neither a Map nor a List
138-
bool _defaultComparator(var item, var what) {
139-
if (what == null) {
140-
return false;
141-
} else if (item == null) {
142-
return what == '';
143-
} else if (what is String && what.startsWith('!')) {
144-
return !_search(item, what.substring(1));
145-
} else if (item is String) {
146-
return (what is String) && _stringComparator(item, what);
147-
} else if (item is bool) {
140+
bool _defaultComparator(item, what) {
141+
if (what == null) return false;
142+
if (item == null) return what == '';
143+
if (what is String && what.startsWith('!')) return !_search(item, what.substring(1));
144+
if (item is String) return (what is String) && _stringComparator(item, what);
145+
if (item is bool) {
148146
if (what is bool) {
149147
return item == what;
150148
} else if (what is String) {
151149
what = (what as String).toLowerCase();
152150
return item ? (what == "true" || what == "yes" || what == "on")
153151
: (what == "false" || what == "no" || what == "off");
154-
} else {
155-
return false;
156152
}
157-
} else if (item is num) {
158-
if (what is num) {
159-
return item == what || (item.isNaN && what.isNaN);
160-
} else {
161-
return what is String && _stringComparator('$item', what);
162-
}
163-
} else {
164-
return false; // Unsupported item type.
153+
return false;
165154
}
155+
if (item is num) {
156+
return what is num ?
157+
item == what || (item.isNaN && what.isNaN) :
158+
what is String && _stringComparator('$item', what);
159+
}
160+
return false; // Unsupported item type.
166161
}
167162

168-
bool _search(var item, var what) {
163+
bool _search(item, what) {
169164
if (what is Map) {
170165
return what.keys.every((key) => _search(
171166
(key == r'$') ? item : _parser(key).eval(item), what[key]));
172-
} else if (item is Map) {
173-
return item.keys.any((k) => !k.startsWith(r'$') && _search(item[k], what));
174-
} else if (item is List) {
175-
return item.any((i) => _search(i, what));
176-
} else {
177-
return _comparator(item, what);
178167
}
168+
if (item is Map) return item.keys.any((k) => !k.startsWith(r'$') && _search(item[k], what));
169+
if (item is List) return item.any((i) => _search(i, what));
170+
return _comparator(item, what);
179171
}
180172

181-
_Predicate _toPredicate(var expression) {
182-
if (expression is _Predicate) {
183-
return (item) => _ensureBool(expression(item));
184-
} else if (_comparator == null) {
185-
return (item) => false; // Bad comparator → no items for you!
186-
} else {
187-
return (item) => _search(item, expression);
188-
}
173+
_PredicateFn _toPredicate(var expression) {
174+
if (expression is _PredicateFn) return (item) => _ensureBool(expression(item));
175+
if (_comparator == null) return (_) => false; // Bad comparator → no items for you!
176+
return (item) => _search(item, expression);
189177
}
190178

191-
List call(List items, var expression, [var comparator]) {
192-
if (expression == null) {
193-
return items.toList(growable: false); // Missing expression → passthrough.
194-
} else if (expression is! Map && expression is! Function &&
195-
expression is! String && expression is! bool &&
196-
expression is! num) {
197-
return const []; // Bad expression → no items for you!
179+
List call(List items, expression, [comparator]) {
180+
// Missing expression → passthrough.
181+
if (expression == null) return items.toList(growable: false);
182+
183+
// Bad expression → no items for you!
184+
if (expression is! Map &&
185+
expression is! Function &&
186+
expression is! String &&
187+
expression is! bool &&
188+
expression is! num) {
189+
return const [];
198190
}
191+
199192
_configureComparator(comparator);
200193
List results = items.where(_toPredicate(expression)).toList(growable: false);
201194
_comparator = null;

0 commit comments

Comments
 (0)