Chromium Code Reviews| 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 |