| Index: pkg/smoke/lib/mirrors.dart | 
| diff --git a/pkg/smoke/lib/mirrors.dart b/pkg/smoke/lib/mirrors.dart | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..839cdd0d7a0a8de5d0e051072db4ed7f4c9b6461 | 
| --- /dev/null | 
| +++ b/pkg/smoke/lib/mirrors.dart | 
| @@ -0,0 +1,314 @@ | 
| +// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file | 
| +// for details. All rights reserved. Use of this source code is governed by a | 
| +// BSD-style license that can be found in the LICENSE file. | 
| + | 
| +/// Implementation of the smoke services using mirrors. | 
| +library smoke.mirrors; | 
| + | 
| +import 'dart:mirrors'; | 
| +import 'package:smoke/smoke.dart'; | 
| +import 'package:logging/logging.dart'; | 
| +import 'src/common.dart'; | 
| + | 
| +/// Set up the smoke package to use a mirror-based implementation. To tune what | 
| +/// is preserved by `dart:mirrors`, use a @MirrorsUsed annotation and include | 
| +/// 'smoke.mirrors' in your override arguments. | 
| +useMirrors() { | 
| +  configure(new ReflectiveObjectAccessorService(), | 
| +      new ReflectiveTypeInspectorService(), | 
| +      new ReflectiveSymbolConverterService()); | 
| +} | 
| + | 
| +var _logger = new Logger('smoke.mirrors'); | 
| + | 
| + | 
| +/// Implements [ObjectAccessorService] using mirrors. | 
| +class ReflectiveObjectAccessorService implements ObjectAccessorService { | 
| +  read(Object object, Symbol name) { | 
| +    var decl = getDeclaration(object.runtimeType, name); | 
| +    if (decl != null && decl.isMethod) { | 
| +      // TODO(sigmund,jmesserly): remove this once dartbug.com/13002 is fixed. | 
| +      return new _MethodClosure(object, name); | 
| +    } else { | 
| +      return reflect(object).getField(name).reflectee; | 
| +    } | 
| +  } | 
| + | 
| +  void write(Object object, Symbol name, value) { | 
| +    reflect(object).setField(name, value); | 
| +  } | 
| + | 
| +  invoke(receiver, Symbol methodName, List args, | 
| +      {Map namedArgs, bool adjust: false}) { | 
| +    var receiverMirror; | 
| +    var method; | 
| +    if (receiver is Type) { | 
| +      receiverMirror = reflectType(receiver); | 
| +      method = receiverMirror.declarations[methodName]; | 
| +    } else { | 
| +      receiverMirror = reflect(receiver); | 
| +      method = _findMethod(receiverMirror.type, methodName); | 
| +    } | 
| +    if (method != null && adjust) { | 
| +      var required = 0; | 
| +      var optional = 0; | 
| +      for (var p in method.parameters) { | 
| +        if (p.isOptional) { | 
| +          if (!p.isNamed) optional++; | 
| +        } else { | 
| +          required++; | 
| +        } | 
| +      } | 
| +      args = adjustList(args, required, required + optional); | 
| +    } | 
| +    return receiverMirror.invoke(methodName, args, namedArgs).reflectee; | 
| +  } | 
| +} | 
| + | 
| +/// Implements [TypeInspectorService] using mirrors. | 
| +class ReflectiveTypeInspectorService implements TypeInspectorService { | 
| +  bool hasGetter(Type type, Symbol name) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return false; | 
| +    while (mirror != _objectType) { | 
| +      final members = mirror.declarations; | 
| +      if (members.containsKey(name)) return true; | 
| +      mirror = _safeSuperclass(mirror); | 
| +    } | 
| +    return false; | 
| +  } | 
| + | 
| +  bool hasSetter(Type type, Symbol name) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return false; | 
| +    var setterName = _setterName(name); | 
| +    while (mirror != _objectType) { | 
| +      final members = mirror.declarations; | 
| +      var declaration = members[name]; | 
| +      if (declaration is VariableMirror && !declaration.isFinal) return true; | 
| +      if (members.containsKey(setterName)) return true; | 
| +      mirror = _safeSuperclass(mirror); | 
| +    } | 
| +    return false; | 
| +  } | 
| + | 
| +  bool hasInstanceMethod(Type type, Symbol name) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return false; | 
| +    while (mirror != _objectType) { | 
| +      final m = mirror.declarations[name]; | 
| +      if (m is MethodMirror && m.isRegularMethod && !m.isStatic) return true; | 
| +      mirror = _safeSuperclass(mirror); | 
| +    } | 
| +    return false; | 
| +  } | 
| + | 
| +  bool hasStaticMethod(Type type, Symbol name) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return false; | 
| +    final m = mirror.declarations[name]; | 
| +    return m is MethodMirror && m.isRegularMethod && m.isStatic; | 
| +  } | 
| + | 
| +  Declaration getDeclaration(Type type, Symbol name) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return null; | 
| + | 
| +    var declaration; | 
| +    while (mirror != _objectType) { | 
| +      final members = mirror.declarations; | 
| +      if (members.containsKey(name)) { | 
| +        declaration = members[name]; | 
| +        break; | 
| +      } | 
| +      mirror = _safeSuperclass(mirror); | 
| +    } | 
| +    if (declaration == null) { | 
| +      _logger.severe("declaration doesn't exists ($type.$name)."); | 
| +      return null; | 
| +    } | 
| +    return new _MirrorDeclaration(mirror, declaration); | 
| +  } | 
| + | 
| +  List<Declaration> query(Type type, QueryOptions options) { | 
| +    var mirror = reflectType(type); | 
| +    if (mirror is! ClassMirror) return null; | 
| +    return _query(mirror, options); | 
| +  } | 
| + | 
| +  List<Declaration> _query(ClassMirror cls, QueryOptions options) { | 
| +    var result = (!options.includeInherited || cls.superclass == _objectType) | 
| +        ? [] : _query(cls.superclass, options); | 
| +    for (var member in cls.declarations.values) { | 
| +      if (member is! VariableMirror && member is! MethodMirror) continue; | 
| +      if (member.isStatic || member.isPrivate) continue; | 
| +      var name = member.simpleName; | 
| +      bool isMethod = false; | 
| +      if (member is VariableMirror) { | 
| +        if (!options.includeProperties) continue; | 
| +        if (options.excludeFinal && member.isFinal) continue; | 
| +      } | 
| + | 
| +      // TODO(sigmund): what if we have a setter but no getter? | 
| +      if (member is MethodMirror && member.isSetter) continue; | 
| +      if (member is MethodMirror && member.isConstructor) continue; | 
| + | 
| +      if (member is MethodMirror && member.isGetter) { | 
| +        if (!options.includeProperties) continue; | 
| +        if (options.excludeFinal && !_hasSetter(cls, member)) continue; | 
| +      } | 
| + | 
| +      if (member is MethodMirror && member.isRegularMethod) { | 
| +        if (!options.includeMethods) continue; | 
| +        isMethod = true; | 
| +      } | 
| + | 
| +      var annotations = | 
| +          member.metadata.map((m) => m.reflectee).toList(); | 
| +      if (options.withAnnotations != null && | 
| +          !matchesAnnotation(annotations, options.withAnnotations)) { | 
| +        continue; | 
| +      } | 
| + | 
| +      // TODO(sigmund): should we cache parts of this declaration so we don't | 
| +      // compute them twice?  For example, this chould be `new Declaration(name, | 
| +      // type, ...)` and we could reuse what we computed above to implement the | 
| +      // query filtering.  Note, when I tried to eagerly compute everything, I | 
| +      // run into trouble with type (`type = _toType(member.type)`), dart2js | 
| +      // failed when the underlying types had type-arguments (see | 
| +      // dartbug.com/16925). | 
| +      result.add(new _MirrorDeclaration(cls, member)); | 
| +    } | 
| + | 
| +    return result; | 
| +  } | 
| +} | 
| + | 
| +/// Implements [SymbolConverterService] using mirrors. | 
| +class ReflectiveSymbolConverterService implements SymbolConverterService { | 
| +  String symbolToName(Symbol symbol) => MirrorSystem.getName(symbol); | 
| +  Symbol nameToSymbol(String name) => new Symbol(name); | 
| +} | 
| + | 
| + | 
| +// TODO(jmesserly): workaround for: | 
| +// https://code.google.com/p/dart/issues/detail?id=10029 | 
| +Symbol _setterName(Symbol getter) => | 
| +    new Symbol('${MirrorSystem.getName(getter)}='); | 
| + | 
| + | 
| +ClassMirror _safeSuperclass(ClassMirror type) { | 
| +  try { | 
| +    return type.superclass; | 
| +  } /*on UnsupportedError*/ catch (e) { | 
| +    // Note: dart2js throws UnsupportedError when the type is not | 
| +    // reflectable. | 
| +    // TODO(jmesserly): dart2js also throws a NoSuchMethodError if the `type` is | 
| +    // a bound generic, because they are not fully implemented. See | 
| +    // https://code.google.com/p/dart/issues/detail?id=15573 | 
| +    return _objectType; | 
| +  } | 
| +} | 
| + | 
| +MethodMirror _findMethod(ClassMirror type, Symbol name) { | 
| +  do { | 
| +    var member = type.declarations[name]; | 
| +    if (member is MethodMirror) return member; | 
| +    type = type.superclass; | 
| +  } while (type != null); | 
| +} | 
| + | 
| +// When recursively looking for symbols up the type-hierarchy it's generally a | 
| +// good idea to stop at Object, since we know it doesn't have what we want. | 
| +// TODO(jmesserly): This is also a workaround for what appears to be a V8 | 
| +// bug introduced between Chrome 31 and 32. After 32 | 
| +// JsClassMirror.declarations on Object calls | 
| +// JsClassMirror.typeVariables, which tries to get the _jsConstructor's | 
| +// .prototype["<>"]. This ends up getting the "" property instead, maybe | 
| +// because "<>" doesn't exist, and gets ";" which then blows up because | 
| +// the code later on expects a List of ints. | 
| +final _objectType = reflectClass(Object); | 
| + | 
| +bool _hasSetter(ClassMirror cls, MethodMirror getter) { | 
| +  var setterName = new Symbol('${MirrorSystem.getName(getter.simpleName)}='); | 
| +  var mirror = cls.declarations[setterName]; | 
| +  return mirror is MethodMirror && mirror.isSetter; | 
| +} | 
| + | 
| +Type _toType(TypeMirror t) { | 
| +  if (t is ClassMirror) return t.reflectedType; | 
| +  if (t == null || t.qualifiedName != #dynamic) { | 
| +    _logger.warning('unknown type ($t).'); | 
| +  } | 
| +  return dynamic; | 
| +} | 
| + | 
| +class _MirrorDeclaration implements Declaration { | 
| +  final ClassMirror _cls; | 
| +  final _original; | 
| + | 
| +  _MirrorDeclaration(this._cls, DeclarationMirror this._original); | 
| + | 
| +  Symbol get name => _original.simpleName; | 
| + | 
| +  /// Whether the symbol is a property (either this or [isMethod] is true). | 
| +  bool get isProperty => _original is VariableMirror || | 
| +      (_original is MethodMirror && !_original.isRegularMethod); | 
| + | 
| +  /// Whether the symbol is a method (either this or [isProperty] is true) | 
| +  bool get isMethod => !isProperty; | 
| + | 
| +  /// If this is a property, whether it's read only (final fields or properties | 
| +  /// with no setter). | 
| +  bool get isFinal => | 
| +      (_original is VariableMirror && _original.isFinal) || | 
| +      (_original is MethodMirror && _original.isGetter && | 
| +           !_hasSetter(_cls, _original)); | 
| + | 
| +  /// If this is a property, it's declared type (including dynamic if it's not | 
| +  /// declared). For methods, the returned type. | 
| +  Type get type { | 
| +    if (_original is MethodMirror && _original.isRegularMethod) { | 
| +      return Function; | 
| +    } | 
| +    var typeMirror = _original is VariableMirror ? _original.type | 
| +        : _original.returnType; | 
| +    return _toType(typeMirror); | 
| +  } | 
| + | 
| +  /// Whether this symbol is static. | 
| +  bool get isStatic => _original.isStatic; | 
| + | 
| +  /// List of annotations in this declaration. | 
| +  List get annotations => _original.metadata.map((a) => a.reflectee).toList(); | 
| + | 
| +  String toString() { | 
| +    return (new StringBuffer() | 
| +        ..write('[declaration ') | 
| +        ..write(name) | 
| +        ..write(isProperty ? ' (property) ' : ' (method) ') | 
| +        ..write(isFinal ? 'final ' : '') | 
| +        ..write(isStatic ? 'static ' : '') | 
| +        ..write(annotations) | 
| +        ..write(']')).toString(); | 
| +  } | 
| +} | 
| + | 
| +class _MethodClosure extends Function { | 
| +  final receiver; | 
| +  final Symbol methodName; | 
| + | 
| +  _MethodClosure(this.receiver, this.methodName); | 
| + | 
| +  // We shouldn't need to include [call] here, but we do to work around an | 
| +  // analyzer bug (see dartbug.com/16078). Interestingly, if [call] is invoked | 
| +  // with the wrong number of arguments, then noSuchMethod is anyways invoked | 
| +  // instead. | 
| +  call() => invoke(receiver, methodName, const []); | 
| + | 
| +  noSuchMethod(Invocation inv) { | 
| +    if (inv.memberName != #call) return null; | 
| +    return invoke(receiver, methodName, | 
| +        inv.positionalArguments, namedArgs: inv.namedArguments); | 
| +  } | 
| +} | 
|  |