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