OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /// Implementation of the smoke services using mirrors. | |
6 library smoke.mirrors; | |
7 | |
8 import 'dart:mirrors'; | |
9 import 'package:smoke/smoke.dart'; | |
10 import 'package:logging/logging.dart'; | |
11 import 'src/common.dart'; | |
12 | |
13 /// Set up the smoke package to use a mirror-based implementation. To tune what | |
14 /// is preserved by `dart:mirrors`, use a @MirrorsUsed annotation and include | |
15 /// 'smoke.mirrors' in your override arguments. | |
16 useMirrors() { | |
17 configure(new ReflectiveObjectAccessorService(), | |
18 new ReflectiveTypeInspectorService(), | |
19 new ReflectiveSymbolConverterService()); | |
20 } | |
21 | |
22 var _logger = new Logger('smoke.mirrors'); | |
23 | |
24 | |
25 /// Implements [ObjectAccessorService] using mirrors. | |
26 class ReflectiveObjectAccessorService implements ObjectAccessorService { | |
27 read(Object object, Symbol name) { | |
28 var decl = getDeclaration(object.runtimeType, name); | |
29 if (decl != null && decl.isMethod) { | |
30 // TODO(sigmund,jmesserly): remove this once dartbug.com/13002 is fixed. | |
31 return new _MethodClosure(object, name); | |
32 } else { | |
33 return reflect(object).getField(name).reflectee; | |
34 } | |
35 } | |
36 | |
37 void write(Object object, Symbol name, value) { | |
38 reflect(object).setField(name, value); | |
39 } | |
40 | |
41 invoke(receiver, Symbol methodName, List args, | |
42 {Map namedArgs, bool adjust: false}) { | |
43 var receiverMirror; | |
44 var method; | |
45 if (receiver is Type) { | |
46 receiverMirror = reflectType(receiver); | |
47 method = receiverMirror.declarations[methodName]; | |
48 } else { | |
49 receiverMirror = reflect(receiver); | |
50 method = _findMethod(receiverMirror.type, methodName); | |
51 } | |
52 if (method != null && adjust) { | |
53 var required = 0; | |
54 var optional = 0; | |
55 for (var p in method.parameters) { | |
56 if (p.isOptional) { | |
57 if (!p.isNamed) optional++; | |
58 } else { | |
59 required++; | |
60 } | |
61 } | |
62 args = adjustList(args, required, required + optional); | |
63 } | |
64 return receiverMirror.invoke(methodName, args, namedArgs).reflectee; | |
65 } | |
66 } | |
67 | |
68 /// Implements [TypeInspectorService] using mirrors. | |
69 class ReflectiveTypeInspectorService implements TypeInspectorService { | |
70 bool hasGetter(Type type, Symbol name) { | |
71 var mirror = reflectType(type); | |
72 if (mirror is! ClassMirror) return false; | |
73 while (mirror != _objectType) { | |
74 final members = mirror.declarations; | |
75 if (members.containsKey(name)) return true; | |
76 mirror = _safeSuperclass(mirror); | |
77 } | |
78 return false; | |
79 } | |
80 | |
81 bool hasSetter(Type type, Symbol name) { | |
82 var mirror = reflectType(type); | |
83 if (mirror is! ClassMirror) return false; | |
84 var setterName = _setterName(name); | |
85 while (mirror != _objectType) { | |
86 final members = mirror.declarations; | |
87 var declaration = members[name]; | |
88 if (declaration is VariableMirror && !declaration.isFinal) return true; | |
89 if (members.containsKey(setterName)) return true; | |
90 mirror = _safeSuperclass(mirror); | |
91 } | |
92 return false; | |
93 } | |
94 | |
95 bool hasInstanceMethod(Type type, Symbol name) { | |
96 var mirror = reflectType(type); | |
97 if (mirror is! ClassMirror) return false; | |
98 while (mirror != _objectType) { | |
99 final m = mirror.declarations[name]; | |
100 if (m is MethodMirror && m.isRegularMethod && !m.isStatic) return true; | |
101 mirror = _safeSuperclass(mirror); | |
102 } | |
103 return false; | |
104 } | |
105 | |
106 bool hasStaticMethod(Type type, Symbol name) { | |
107 var mirror = reflectType(type); | |
108 if (mirror is! ClassMirror) return false; | |
109 final m = mirror.declarations[name]; | |
110 return m is MethodMirror && m.isRegularMethod && m.isStatic; | |
111 } | |
112 | |
113 Declaration getDeclaration(Type type, Symbol name) { | |
114 var mirror = reflectType(type); | |
115 if (mirror is! ClassMirror) return null; | |
116 | |
117 var declaration; | |
118 while (mirror != _objectType) { | |
119 final members = mirror.declarations; | |
120 if (members.containsKey(name)) { | |
121 declaration = members[name]; | |
122 break; | |
123 } | |
124 mirror = _safeSuperclass(mirror); | |
125 } | |
126 if (declaration == null) { | |
127 _logger.severe("declaration doesn't exists ($type.$name)."); | |
128 return null; | |
129 } | |
130 return new _MirrorDeclaration(mirror, declaration); | |
131 } | |
132 | |
133 List<Declaration> query(Type type, QueryOptions options) { | |
134 var mirror = reflectType(type); | |
135 if (mirror is! ClassMirror) return null; | |
136 return _query(mirror, options); | |
137 } | |
138 | |
139 List<Declaration> _query(ClassMirror cls, QueryOptions options) { | |
140 var result = (!options.includeInherited || cls.superclass == _objectType) | |
141 ? [] : _query(cls.superclass, options); | |
142 for (var member in cls.declarations.values) { | |
143 if (member is! VariableMirror && member is! MethodMirror) continue; | |
144 if (member.isStatic || member.isPrivate) continue; | |
145 var name = member.simpleName; | |
146 bool isMethod = false; | |
147 if (member is VariableMirror) { | |
148 if (!options.includeProperties) continue; | |
149 if (options.excludeFinal && member.isFinal) continue; | |
150 } | |
151 | |
152 // TODO(sigmund): what if we have a setter but no getter? | |
153 if (member is MethodMirror && member.isSetter) continue; | |
154 if (member is MethodMirror && member.isConstructor) continue; | |
155 | |
156 if (member is MethodMirror && member.isGetter) { | |
157 if (!options.includeProperties) continue; | |
158 if (options.excludeFinal && !_hasSetter(cls, member)) continue; | |
159 } | |
160 | |
161 if (member is MethodMirror && member.isRegularMethod) { | |
162 if (!options.includeMethods) continue; | |
163 isMethod = true; | |
164 } | |
165 | |
166 var annotations = | |
167 member.metadata.map((m) => m.reflectee).toList(); | |
168 if (options.withAnnotations != null && | |
169 !matchesAnnotation(annotations, options.withAnnotations)) { | |
170 continue; | |
171 } | |
172 | |
173 // TODO(sigmund): should we cache parts of this declaration so we don't | |
174 // compute them twice? For example, this chould be `new Declaration(name, | |
175 // type, ...)` and we could reuse what we computed above to implement the | |
176 // query filtering. Note, when I tried to eagerly compute everything, I | |
177 // run into trouble with type (`type = _toType(member.type)`), dart2js | |
178 // failed when the underlying types had type-arguments (see | |
179 // dartbug.com/16925). | |
180 result.add(new _MirrorDeclaration(cls, member)); | |
181 } | |
182 | |
183 return result; | |
184 } | |
185 } | |
186 | |
187 /// Implements [SymbolConverterService] using mirrors. | |
188 class ReflectiveSymbolConverterService implements SymbolConverterService { | |
189 String symbolToName(Symbol symbol) => MirrorSystem.getName(symbol); | |
190 Symbol nameToSymbol(String name) => new Symbol(name); | |
191 } | |
192 | |
193 | |
194 // TODO(jmesserly): workaround for: | |
195 // https://code.google.com/p/dart/issues/detail?id=10029 | |
196 Symbol _setterName(Symbol getter) => | |
197 new Symbol('${MirrorSystem.getName(getter)}='); | |
198 | |
199 | |
200 ClassMirror _safeSuperclass(ClassMirror type) { | |
201 try { | |
202 return type.superclass; | |
203 } /*on UnsupportedError*/ catch (e) { | |
204 // Note: dart2js throws UnsupportedError when the type is not | |
205 // reflectable. | |
206 // TODO(jmesserly): dart2js also throws a NoSuchMethodError if the `type` is | |
207 // a bound generic, because they are not fully implemented. See | |
208 // https://code.google.com/p/dart/issues/detail?id=15573 | |
Jennifer Messerly
2014/02/20 21:24:47
fyi -- this one has been fixed, I think we can now
Siggi Cherem (dart-lang)
2014/02/20 22:19:48
cool. good to know.
| |
209 return _objectType; | |
210 } | |
211 } | |
212 | |
213 MethodMirror _findMethod(ClassMirror type, Symbol name) { | |
214 do { | |
215 var member = type.declarations[name]; | |
216 if (member is MethodMirror) return member; | |
217 type = type.superclass; | |
218 } while (type != null); | |
219 } | |
220 | |
221 // When recursively looking for symbols up the type-hierarchy it's generally a | |
222 // good idea to stop at Object, since we know it doesn't have what we want. | |
223 // TODO(jmesserly): This is also a workaround for what appears to be a V8 | |
224 // bug introduced between Chrome 31 and 32. After 32 | |
225 // JsClassMirror.declarations on Object calls | |
226 // JsClassMirror.typeVariables, which tries to get the _jsConstructor's | |
227 // .prototype["<>"]. This ends up getting the "" property instead, maybe | |
228 // because "<>" doesn't exist, and gets ";" which then blows up because | |
229 // the code later on expects a List of ints. | |
230 final _objectType = reflectClass(Object); | |
231 | |
232 bool _hasSetter(ClassMirror cls, MethodMirror getter) { | |
233 var setterName = new Symbol('${MirrorSystem.getName(getter.simpleName)}='); | |
Jennifer Messerly
2014/02/20 21:24:47
use _setterName here?
Siggi Cherem (dart-lang)
2014/02/20 22:19:48
Done.
| |
234 var mirror = cls.declarations[setterName]; | |
235 return mirror is MethodMirror && mirror.isSetter; | |
236 } | |
237 | |
238 Type _toType(TypeMirror t) { | |
239 if (t is ClassMirror) return t.reflectedType; | |
240 if (t == null || t.qualifiedName != #dynamic) { | |
241 _logger.warning('unknown type ($t).'); | |
242 } | |
243 return dynamic; | |
244 } | |
245 | |
246 class _MirrorDeclaration implements Declaration { | |
247 final ClassMirror _cls; | |
248 final _original; | |
249 | |
250 _MirrorDeclaration(this._cls, DeclarationMirror this._original); | |
251 | |
252 Symbol get name => _original.simpleName; | |
253 | |
254 /// Whether the symbol is a property (either this or [isMethod] is true). | |
255 bool get isProperty => _original is VariableMirror || | |
256 (_original is MethodMirror && !_original.isRegularMethod); | |
257 | |
258 /// Whether the symbol is a method (either this or [isProperty] is true) | |
259 bool get isMethod => !isProperty; | |
260 | |
261 /// If this is a property, whether it's read only (final fields or properties | |
262 /// with no setter). | |
263 bool get isFinal => | |
264 (_original is VariableMirror && _original.isFinal) || | |
265 (_original is MethodMirror && _original.isGetter && | |
266 !_hasSetter(_cls, _original)); | |
267 | |
268 /// If this is a property, it's declared type (including dynamic if it's not | |
269 /// declared). For methods, the returned type. | |
270 Type get type { | |
271 if (_original is MethodMirror && _original.isRegularMethod) { | |
272 return Function; | |
273 } | |
274 var typeMirror = _original is VariableMirror ? _original.type | |
275 : _original.returnType; | |
276 return _toType(typeMirror); | |
277 } | |
278 | |
279 /// Whether this symbol is static. | |
280 bool get isStatic => _original.isStatic; | |
281 | |
282 /// List of annotations in this declaration. | |
283 List get annotations => _original.metadata.map((a) => a.reflectee).toList(); | |
284 | |
285 String toString() { | |
286 return (new StringBuffer() | |
287 ..write('[declaration ') | |
288 ..write(name) | |
289 ..write(isProperty ? ' (property) ' : ' (method) ') | |
290 ..write(isFinal ? 'final ' : '') | |
291 ..write(isStatic ? 'static ' : '') | |
292 ..write(annotations) | |
293 ..write(']')).toString(); | |
294 } | |
295 } | |
296 | |
297 class _MethodClosure extends Function { | |
298 final receiver; | |
299 final Symbol methodName; | |
300 | |
301 _MethodClosure(this.receiver, this.methodName); | |
302 | |
303 // We shouldn't need to include [call] here, but we do to work around an | |
304 // analyzer bug (see dartbug.com/16078). Interestingly, if [call] is invoked | |
305 // with the wrong number of arguments, then noSuchMethod is anyways invoked | |
306 // instead. | |
307 call() => invoke(receiver, methodName, const []); | |
308 | |
309 noSuchMethod(Invocation inv) { | |
310 if (inv.memberName != #call) return null; | |
311 return invoke(receiver, methodName, | |
312 inv.positionalArguments, namedArgs: inv.namedArguments); | |
313 } | |
314 } | |
OLD | NEW |