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 /// Implements [ObjectAccessorService] using mirrors. | |
25 class ReflectiveObjectAccessorService implements ObjectAccessorService { | |
26 read(Object object, Symbol name) => reflect(object).getField(name).reflectee; | |
27 | |
28 void write(Object object, Symbol name, value) { | |
29 reflect(object).setField(name, value); | |
30 } | |
31 | |
32 invoke(receiver, Symbol methodName, List args, | |
33 {Map namedArgs, bool adjust: false}) { | |
34 var receiverMirror; | |
35 var method; | |
36 if (receiver is Type && methodName != #toString) { | |
37 receiverMirror = reflectType(receiver); | |
38 method = receiverMirror.declarations[methodName]; | |
39 } else { | |
40 receiverMirror = reflect(receiver); | |
41 method = _findMethod(receiverMirror.type, methodName); | |
42 } | |
43 if (method != null && adjust) { | |
44 var required = 0; | |
45 var optional = 0; | |
46 for (var p in method.parameters) { | |
47 if (p.isOptional) { | |
48 if (!p.isNamed) optional++; | |
49 } else { | |
50 required++; | |
51 } | |
52 } | |
53 args = adjustList(args, required, required + optional); | |
54 } | |
55 return receiverMirror.invoke(methodName, args, namedArgs).reflectee; | |
56 } | |
57 } | |
58 | |
59 /// Implements [TypeInspectorService] using mirrors. | |
60 class ReflectiveTypeInspectorService implements TypeInspectorService { | |
61 bool isSubclassOf(Type type, Type supertype) { | |
62 if (type == supertype || supertype == Object) return true; | |
63 // TODO(sigmund): change to mirror.isSubclassOf when it gets implemented in | |
64 // dart2js. (dartbug.com/12439) | |
65 var mirror = reflectClass(type); | |
66 var top = reflectClass(supertype); | |
67 while (mirror != _objectType) { | |
68 mirror = _safeSuperclass(mirror); | |
69 if (mirror == top) return true; | |
70 } | |
71 return false; | |
72 } | |
73 | |
74 bool hasGetter(Type type, Symbol name) { | |
75 var mirror = reflectType(type); | |
76 if (mirror is! ClassMirror) return false; | |
77 while (mirror != _objectType) { | |
78 final members = mirror.declarations; | |
79 if (members.containsKey(name)) return true; | |
80 mirror = _safeSuperclass(mirror); | |
81 } | |
82 return false; | |
83 } | |
84 | |
85 bool hasSetter(Type type, Symbol name) { | |
86 var mirror = reflectType(type); | |
87 if (mirror is! ClassMirror) return false; | |
88 var setterName = _setterName(name); | |
89 while (mirror != _objectType) { | |
90 final members = mirror.declarations; | |
91 var declaration = members[name]; | |
92 if (declaration is VariableMirror && !declaration.isFinal) return true; | |
93 if (members.containsKey(setterName)) return true; | |
94 mirror = _safeSuperclass(mirror); | |
95 } | |
96 return false; | |
97 } | |
98 | |
99 bool hasInstanceMethod(Type type, Symbol name) { | |
100 var mirror = reflectType(type); | |
101 if (mirror is! ClassMirror) return false; | |
102 while (mirror != _objectType) { | |
103 final m = mirror.declarations[name]; | |
104 if (m is MethodMirror && m.isRegularMethod && !m.isStatic) return true; | |
105 mirror = _safeSuperclass(mirror); | |
106 } | |
107 return false; | |
108 } | |
109 | |
110 bool hasStaticMethod(Type type, Symbol name) { | |
111 var mirror = reflectType(type); | |
112 if (mirror is! ClassMirror) return false; | |
113 final m = mirror.declarations[name]; | |
114 return m is MethodMirror && m.isRegularMethod && m.isStatic; | |
115 } | |
116 | |
117 Declaration getDeclaration(Type type, Symbol name) { | |
118 var mirror = reflectType(type); | |
119 if (mirror is! ClassMirror) return null; | |
120 | |
121 var declaration; | |
122 while (mirror != _objectType) { | |
123 final members = mirror.declarations; | |
124 if (members.containsKey(name)) { | |
125 declaration = members[name]; | |
126 break; | |
127 } | |
128 mirror = _safeSuperclass(mirror); | |
129 } | |
130 if (declaration == null) { | |
131 _logger.severe("declaration doesn't exists ($type.$name)."); | |
132 return null; | |
133 } | |
134 return new _MirrorDeclaration(mirror, declaration); | |
135 } | |
136 | |
137 List<Declaration> query(Type type, QueryOptions options) { | |
138 var mirror = reflectType(type); | |
139 if (mirror is! ClassMirror) return null; | |
140 return _query(mirror, options); | |
141 } | |
142 | |
143 List<Declaration> _query(ClassMirror cls, QueryOptions options) { | |
144 final visitParent = options.includeInherited && cls.superclass != null && | |
145 // TODO(sigmund): use _toType(cls.superclass) != options.includeUpTo | |
146 // when dartbug.com/16925 gets fixed (_toType fails in dart2js if | |
147 // applied to classes with type-arguments). | |
148 cls.superclass != reflectClass(options.includeUpTo); | |
149 var result = visitParent ? _query(cls.superclass, options) : []; | |
150 for (var member in cls.declarations.values) { | |
151 if (member is! VariableMirror && member is! MethodMirror) continue; | |
152 if (member.isStatic || member.isPrivate) continue; | |
153 var name = member.simpleName; | |
154 if (member is VariableMirror) { | |
155 if (!options.includeFields) continue; | |
156 if (options.excludeFinal && member.isFinal) continue; | |
157 } | |
158 | |
159 // TODO(sigmund): what if we have a setter but no getter? | |
160 if (member is MethodMirror && member.isSetter) continue; | |
161 if (member is MethodMirror && member.isConstructor) continue; | |
162 | |
163 if (member is MethodMirror && member.isGetter) { | |
164 if (!options.includeProperties) continue; | |
165 if (options.excludeFinal && !_hasSetter(cls, member)) continue; | |
166 } | |
167 | |
168 if (member is MethodMirror && member.isRegularMethod) { | |
169 if (!options.includeMethods) continue; | |
170 } | |
171 | |
172 if (options.matches != null && !options.matches(name)) continue; | |
173 | |
174 var annotations = member.metadata.map((m) => m.reflectee).toList(); | |
175 if (options.withAnnotations != null && | |
176 !matchesAnnotation(annotations, options.withAnnotations)) { | |
177 continue; | |
178 } | |
179 | |
180 var declaration = new _MirrorDeclaration(cls, member); | |
181 | |
182 if (options.excludeOverriden) { | |
183 result.retainWhere((value) => declaration.name != value.name); | |
184 } | |
185 | |
186 // TODO(sigmund): should we cache parts of this declaration so we don't | |
187 // compute them twice? For example, this chould be `new Declaration(name, | |
188 // type, ...)` and we could reuse what we computed above to implement the | |
189 // query filtering. Note, when I tried to eagerly compute everything, I | |
190 // run into trouble with type (`type = _toType(member.type)`), dart2js | |
191 // failed when the underlying types had type-arguments (see | |
192 // dartbug.com/16925). | |
193 result.add(declaration); | |
194 } | |
195 | |
196 return result; | |
197 } | |
198 } | |
199 | |
200 /// Implements [SymbolConverterService] using mirrors. | |
201 class ReflectiveSymbolConverterService implements SymbolConverterService { | |
202 String symbolToName(Symbol symbol) => MirrorSystem.getName(symbol); | |
203 Symbol nameToSymbol(String name) => new Symbol(name); | |
204 } | |
205 | |
206 // TODO(jmesserly): workaround for: | |
207 // https://code.google.com/p/dart/issues/detail?id=10029 | |
208 Symbol _setterName(Symbol getter) => | |
209 new Symbol('${MirrorSystem.getName(getter)}='); | |
210 | |
211 ClassMirror _safeSuperclass(ClassMirror type) { | |
212 try { | |
213 var t = type.superclass; | |
214 // TODO(sigmund): workaround for darbug.com/17779. | |
215 // Interceptor is leaked by dart2js. It has the same methods as Object | |
216 // (including noSuchMethod), and our code above assumes that it doesn't | |
217 // exist. Most queries exclude Object, so they should exclude Interceptor | |
218 // too. We don't check for t.simpleName == #Interceptor because depending on | |
219 // dart2js optimizations it may be #Interceptor or #num/Interceptor. | |
220 // Checking for a private library seems to reliably filter this out. | |
221 if (t != null && t.owner != null && t.owner.isPrivate) { | |
222 t = _objectType; | |
223 } | |
224 return t; | |
225 } on UnsupportedError catch (e) { | |
226 // Note: dart2js throws UnsupportedError when the type is not reflectable. | |
227 return _objectType; | |
228 } | |
229 } | |
230 | |
231 MethodMirror _findMethod(ClassMirror type, Symbol name) { | |
232 do { | |
233 var member = type.declarations[name]; | |
234 if (member is MethodMirror) return member; | |
235 type = type.superclass; | |
236 } while (type != null); | |
237 return null; | |
238 } | |
239 | |
240 // When recursively looking for symbols up the type-hierarchy it's generally a | |
241 // good idea to stop at Object, since we know it doesn't have what we want. | |
242 // TODO(jmesserly): This is also a workaround for what appears to be a V8 | |
243 // bug introduced between Chrome 31 and 32. After 32 | |
244 // JsClassMirror.declarations on Object calls | |
245 // JsClassMirror.typeVariables, which tries to get the _jsConstructor's | |
246 // .prototype["<>"]. This ends up getting the "" property instead, maybe | |
247 // because "<>" doesn't exist, and gets ";" which then blows up because | |
248 // the code later on expects a List of ints. | |
249 final _objectType = reflectClass(Object); | |
250 | |
251 bool _hasSetter(ClassMirror cls, MethodMirror getter) { | |
252 var mirror = cls.declarations[_setterName(getter.simpleName)]; | |
253 return mirror is MethodMirror && mirror.isSetter; | |
254 } | |
255 | |
256 Type _toType(TypeMirror t) { | |
257 // TODO(sigmund): this line can go away after dartbug.com/16962 | |
258 if (t == _objectType) return Object; | |
259 if (t is ClassMirror) return t.reflectedType; | |
260 if (t == null || t.qualifiedName != #dynamic) { | |
261 _logger.warning('unknown type ($t).'); | |
262 } | |
263 return dynamic; | |
264 } | |
265 | |
266 class _MirrorDeclaration implements Declaration { | |
267 final ClassMirror _cls; | |
268 final _original; | |
269 | |
270 _MirrorDeclaration(this._cls, DeclarationMirror this._original); | |
271 | |
272 Symbol get name => _original.simpleName; | |
273 | |
274 DeclarationKind get kind => isField ? FIELD : isProperty ? PROPERTY : METHOD; | |
275 | |
276 bool get isField => _original is VariableMirror; | |
277 | |
278 bool get isProperty => | |
279 _original is MethodMirror && !_original.isRegularMethod; | |
280 | |
281 bool get isMethod => !isField && !isProperty; | |
282 | |
283 /// If this is a property, whether it's read only (final fields or properties | |
284 /// with no setter). | |
285 bool get isFinal => (_original is VariableMirror && _original.isFinal) || | |
286 (_original is MethodMirror && | |
287 _original.isGetter && | |
288 !_hasSetter(_cls, _original)); | |
289 | |
290 /// If this is a property, it's declared type (including dynamic if it's not | |
291 /// declared). For methods, the returned type. | |
292 Type get type { | |
293 if (_original is MethodMirror && _original.isRegularMethod) { | |
294 return Function; | |
295 } | |
296 var typeMirror = | |
297 _original is VariableMirror ? _original.type : _original.returnType; | |
298 return _toType(typeMirror); | |
299 } | |
300 | |
301 /// Whether this symbol is static. | |
302 bool get isStatic => _original.isStatic; | |
303 | |
304 /// List of annotations in this declaration. | |
305 List get annotations => _original.metadata.map((a) => a.reflectee).toList(); | |
306 | |
307 int get hashCode => name.hashCode; | |
308 operator ==(other) => other is Declaration && | |
309 name == other.name && | |
310 kind == other.kind && | |
311 isFinal == other.isFinal && | |
312 type == other.type && | |
313 isStatic == other.isStatic && | |
314 compareLists(annotations, other.annotations); | |
315 String toString() => (new StringBuffer() | |
316 ..write('(mirror-based-declaration ') | |
317 ..write(name) | |
318 ..write( | |
319 isField ? ' (field) ' : (isProperty ? ' (property) ' : ' (method) ')) | |
320 ..write(isFinal ? 'final ' : '') | |
321 ..write(isStatic ? 'static ' : '') | |
322 ..write(annotations) | |
323 ..write(')')).toString(); | |
324 } | |
OLD | NEW |