Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit a38a9bf

Browse files
committed
feat(parser): Add support for member field setters
1 parent 4aec5f3 commit a38a9bf

File tree

7 files changed

+235
-24
lines changed

7 files changed

+235
-24
lines changed

lib/angular.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ part 'directives/ng_repeat.dart';
1818
part 'dom_utilities.dart';
1919
part 'exception_handler.dart';
2020
part 'http.dart';
21+
part 'interface_typing.dart';
2122
part 'interpolate.dart';
2223
part 'mirrors.dart';
2324
part 'node_cursor.dart';
@@ -55,17 +56,19 @@ typedef FnWith3Args(a0, a1, a2);
5556
typedef FnWith4Args(a0, a1, a2, a3);
5657
typedef FnWith5Args(a0, a1, a2, a3, a4);
5758

58-
_relaxFnApply(Function fn, List args) {
59+
relaxFnApply(Function fn, List args) {
60+
// Check the args.length to support functions with optional parameters.
61+
var argsLen = args.length;
5962
if (fn is Function && fn != null) {
60-
if (fn is FnWith5Args) {
63+
if (fn is FnWith5Args && argsLen > 4) {
6164
return fn(args[0], args[1], args[2], args[3], args[4]);
62-
} else if (fn is FnWith4Args) {
65+
} else if (fn is FnWith4Args && argsLen > 3) {
6366
return fn(args[0], args[1], args[2], args[3]);
64-
} else if (fn is FnWith3Args) {
67+
} else if (fn is FnWith3Args&& argsLen > 2 ) {
6568
return fn(args[0], args[1], args[2]);
66-
} else if (fn is FnWith2Args) {
69+
} else if (fn is FnWith2Args && argsLen > 1 ) {
6770
return fn(args[0], args[1]);
68-
} else if (fn is FnWith1Args) {
71+
} else if (fn is FnWith1Args && argsLen > 0) {
6972
return fn(args[0]);
7073
} else if (fn is FnWith0Args) {
7174
return fn();

lib/interface_typing.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
part of angular;
2+
3+
var OBJECT_QUAL_NAME = fastReflectClass(Object).qualifiedName;
4+
5+
_equalTypes(ClassMirror a, ClassMirror b) => a.qualifiedName == b.qualifiedName;
6+
7+
_isType(obj, type) {
8+
InstanceMirror instanceMirror = reflect(obj);
9+
ClassMirror classM = instanceMirror.type;
10+
11+
if (type is Type) {
12+
type = fastReflectClass(type);
13+
}
14+
15+
if (classM.superinterfaces.any((si) {return _equalTypes(si, type);})) return true;
16+
17+
while (classM != null) {
18+
if (_equalTypes(classM, type)) return true;
19+
if (classM.qualifiedName == OBJECT_QUAL_NAME) classM = null; // dart bug 5794
20+
else classM = classM.superclass;
21+
22+
}
23+
return false;
24+
}
25+
26+
isInterface(obj, Type type) {
27+
if (_isType(obj, type)) return true;
28+
29+
var objMembers = reflect(obj).type.members;
30+
31+
bool allPresent = true;
32+
fastReflectClass(type).members.forEach((symbol, mirror) {
33+
if (!objMembers.containsKey(symbol)) allPresent = false;
34+
var objMirror = objMembers[symbol];
35+
if (!_isType(objMirror, reflect(mirror).type)) allPresent = false;
36+
37+
// TODO(deboer): Check that the method signatures match. Waiting on dartbug 11334
38+
/*if (mirror is MethodMirror) {
39+
// are the paremeters the same?
40+
var sameParameters = true;
41+
var interfaceParams = mirror.parameters;
42+
var objParams = objMirror.parameters;
43+
44+
Map<Symbol, ParameterMirror> namedParams;
45+
int minParams = 0;
46+
int maxParams = 0;
47+
mirror.parameters.forEach((ParameterMirror param) {
48+
if (param.isNamed) namedParams[param.qualifiedName] = param;
49+
if (param.isOptional) maxParams++;
50+
else { minParams++; minParams++; }
51+
});
52+
53+
objMirror.parameters.forEach((param) {
54+
if (param.isNamed) namedParams.remove(param.qualifiedName);
55+
if (param.isOptional) maxParams--;
56+
else { minParams--; maxParams--; }
57+
});
58+
59+
if (minParams > 0) return false;
60+
if (maxParams < 0) return false;
61+
}*/
62+
});
63+
return allPresent;
64+
}

lib/parser.dart

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,13 @@ ParsedFn ZERO = new ParsedFn((_, _x) => 0);
125125

126126
class BreakException {}
127127

128-
// returns a function that calls fn with numArgs args as an array
129-
varArgs(numArgs, fn) {
130-
switch (numArgs) {
131-
case 0: return () => fn([]);
132-
case 1: return (p0) => fn([p0]);
133-
case 2: return (p0, p1) => fn([p0, p1]);
134-
case 3: return (p0, p1, p2) => fn([p0, p1, p3]);
135-
case 4: return (p0, p1, p2, p3) => fn([p0, p1, p2, p3]);
136-
case 5: return (p0, p1, p2, p3) => fn([p0, p1, p2, p3, p4]);
137-
}
138-
throw "varArgs with $numArgs is not supported.";
128+
class Setter {
129+
operator[]=(name, value){}
130+
}
131+
132+
abstract class Getter {
133+
bool containsKey(name);
134+
operator[](name);
139135
}
140136

141137
// Returns a tuple [found, value]
@@ -146,7 +142,9 @@ getterChild(value, childKey) {
146142
} else {
147143
return [false, null];
148144
}
149-
} else if (value is Map && value.containsKey(childKey)) {
145+
}
146+
147+
if (isInterface(value, Getter) && value.containsKey(childKey)) {
150148
return [true, value[childKey]];
151149
} else {
152150
InstanceMirror instanceMirror = reflect(value);
@@ -159,6 +157,7 @@ getterChild(value, childKey) {
159157
if (instanceMirror.type.members.containsKey(curSym)) {
160158
MethodMirror methodMirror = instanceMirror.type.members[curSym];
161159
return [true, _relaxFnArgs((args) {
160+
if (args == null) args = [];
162161
try {
163162
return instanceMirror.invoke(curSym, args).reflectee;
164163
} catch (e) {
@@ -195,6 +194,21 @@ getter(scope, locals, path) {
195194
return currentValue;
196195
}
197196

197+
setterChild(obj, childKey, value) {
198+
if (isInterface(obj, Setter)) {
199+
obj[childKey] = value;
200+
return value;
201+
}
202+
InstanceMirror instanceMirror = reflect(obj);
203+
Symbol curSym = new Symbol(childKey);
204+
try {
205+
// maybe it is a member field?
206+
return instanceMirror.setField(curSym, value).reflectee;
207+
} catch (e) {
208+
throw "$e \n\n${e.stacktrace}";
209+
}
210+
}
211+
198212
setter(obj, path, setValue) {
199213
var element = path.split('.');
200214
for (var i = 0; element.length > 1; i++) {
@@ -206,8 +220,7 @@ setter(obj, path, setValue) {
206220
}
207221
obj = propertyObj;
208222
}
209-
obj[element.removeAt(0)] = setValue;
210-
return setValue;
223+
return setterChild(obj, element.removeAt(0), setValue);
211224
}
212225

213226
class Parser {
@@ -698,7 +711,7 @@ class Parser {
698711
args.add(argsFn[i](self, locals));
699712
}
700713
var userFn = fn(self, locals);
701-
return _relaxFnApply(userFn, args);
714+
return relaxFnApply(userFn, args);
702715
});
703716
};
704717

lib/scope.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class Scope implements Map {
158158
};
159159

160160
var $watchCollectionAction = () {
161-
_relaxFnApply(listener, [newValue, oldValue, self]);
161+
relaxFnApply(listener, [newValue, oldValue, self]);
162162
};
163163

164164
return this.$watch($watchCollectionWatch, $watchCollectionAction);
@@ -324,7 +324,7 @@ class Scope implements Map {
324324
i = 0;
325325
for (var length = namedListeners.length; i<length; i++) {
326326
try {
327-
_relaxFnApply(namedListeners[i], listenerArgs);
327+
relaxFnApply(namedListeners[i], listenerArgs);
328328
if (event.propagationStopped) return event;
329329
} catch (e, s) {
330330
_exceptionHandler(e, s);
@@ -356,7 +356,7 @@ class Scope implements Map {
356356
if (current._listeners.containsKey(name)) {
357357
current._listeners[name].forEach((listener) {
358358
try {
359-
_relaxFnApply(listener, listenerArgs);
359+
relaxFnApply(listener, listenerArgs);
360360
} catch(e, s) {
361361
_exceptionHandler(e, s);
362362
}

test/angular_spec.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import '_specs.dart';
2+
3+
main() {
4+
describe('relaxFnApply', () {
5+
it('should work with 6 arguments', () {
6+
var sixArgs = [1, 1, 2, 3, 5, 8];
7+
expect(relaxFnApply(() => "none", sixArgs)).toEqual("none");
8+
expect(relaxFnApply((a) => a, sixArgs)).toEqual(1);
9+
expect(relaxFnApply((a, b) => a + b, sixArgs)).toEqual(2);
10+
expect(relaxFnApply((a, b, c) => a + b + c, sixArgs)).toEqual(4);
11+
expect(relaxFnApply((a, b, c, d) => a + b + c + d, sixArgs)).toEqual(7);
12+
expect(relaxFnApply((a, b, c, d, e) => a + b + c + d + e, sixArgs)).toEqual(12);
13+
});
14+
15+
it('should work with 0 arguments', () {
16+
var noArgs = [];
17+
expect(relaxFnApply(() => "none", noArgs)).toEqual("none");
18+
expect(relaxFnApply(([a]) => a, noArgs)).toEqual(null);
19+
expect(relaxFnApply(([a, b]) => b, noArgs)).toEqual(null);
20+
expect(relaxFnApply(([a, b, c]) => c, noArgs)).toEqual(null);
21+
expect(relaxFnApply(([a, b, c, d]) => d, noArgs)).toEqual(null);
22+
expect(relaxFnApply(([a, b, c, d, e]) => e, noArgs)).toEqual(null);
23+
});
24+
25+
it('should fail with not enough arguments', () {
26+
expect(() {
27+
relaxFnApply((required, alsoRequired) => "happy", [1]);
28+
}).toThrow('Unknown function type, expecting 0 to 5 args.');
29+
});
30+
});
31+
}

test/interface_typing_spec.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import '_specs.dart';
2+
import "dart:mirrors";
3+
4+
class InterfaceWithFields {
5+
int aNumber;
6+
String aString;
7+
}
8+
9+
class ClassWithFields {
10+
int aNumber;
11+
String aString;
12+
bool somethingElse;
13+
}
14+
15+
class ClassWithDifferentFields {
16+
int aDifferentNumber;
17+
}
18+
19+
class ClassWithNotFields {
20+
aNumber(){}
21+
aString(){}
22+
}
23+
24+
class InterfaceWithMethods {
25+
method({b}) {}
26+
}
27+
28+
class ClassWithMethods {
29+
method({b, c}) {}
30+
}
31+
32+
class ClassWithDifferentMethods {
33+
method({c, d}) {}
34+
}
35+
36+
main() {
37+
describe('Interface Typing', () {
38+
it('should recognize built-in objects as an object', () {
39+
var im = reflect(new Object());
40+
var cm = reflectClass(Object);
41+
expect(im.type.qualifiedName).toEqual(cm.qualifiedName);
42+
43+
expect(isInterface(new Object(), Object)).toBeTruthy();
44+
45+
expect(isInterface([], Object)).toBeTruthy();
46+
expect(isInterface({}, Object)).toBeTruthy();
47+
expect(isInterface(6, Object)).toBeTruthy();
48+
expect(isInterface('s', Object)).toBeTruthy();
49+
});
50+
51+
it('should recognize interfaces with fields', () {
52+
expect(isInterface(new ClassWithFields(), InterfaceWithFields)).toBeTruthy();
53+
expect(isInterface(new ClassWithDifferentFields(), InterfaceWithFields)).toBeFalsy();
54+
expect(isInterface(new ClassWithNotFields(), InterfaceWithFields)).toBeFalsy();
55+
});
56+
57+
// waiting on dartbug 11334
58+
xit('should recognize interfaces with methods', () {
59+
expect(isInterface(new ClassWithMethods(), InterfaceWithMethods)).toBeTruthy();
60+
expect(isInterface(new ClassWithDifferentMethods(), InterfaceWithMethods)).toBeFalsy();
61+
});
62+
});
63+
}

test/parser_spec.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import '_specs.dart';
22

3+
// Used to test getter / setter logic.
4+
class TestData {
5+
String _str = "testString";
6+
get str => _str;
7+
set str(x) => _str = x;
8+
9+
method() => "testMethod";
10+
}
11+
312
class LexerExpect extends Expect {
413
LexerExpect(actual) : super(actual);
514
toBeToken(int index, String text) {
@@ -528,6 +537,34 @@ main() {
528537
// DOES NOT WORK. bool.toString is not reflected
529538
// expect(eval('bool.toString()')).toEqual('false');
530539
});
540+
541+
it('should support map getters', () {
542+
expect(Parser.parse('a')({'a': 4})).toEqual(4);
543+
});
544+
545+
it('should support member getters', () {
546+
expect(Parser.parse('str')(new TestData())).toEqual('testString');
547+
});
548+
549+
it('should support returning member functions', () {
550+
expect(Parser.parse('method')(new TestData())()).toEqual('testMethod');
551+
});
552+
553+
it('should support calling member functions', () {
554+
expect(Parser.parse('method()')(new TestData())).toEqual('testMethod');
555+
});
556+
557+
it('should support array setters', () {
558+
var data = {'a': [1,3]};
559+
expect(Parser.parse('a[1]=2')(data)).toEqual(2);
560+
expect(data['a'][1]).toEqual(2);
561+
});
562+
563+
it('should support member field setters', () {
564+
TestData data = new TestData();
565+
expect(Parser.parse('str="bob"')(data)).toEqual('bob');
566+
expect(data.str).toEqual("bob");
567+
});
531568
});
532569

533570
describe('assignable', () {

0 commit comments

Comments
 (0)