Monday, December 31, 2012

Dart Reflection

‹prev | My Chain | next›

I know that the Dart reflection system is under heavy development and will likely change sometime early in the new year. Still, it seems far enough along that it is worth getting somewhat familiar with it.

Since it is likely to change, I will explore this library with some unit tests that can tell me when things change. I start with a simple unit test skeleton that also include my yummy Cookie class for use in exploration:
import 'dart:mirrors';
import 'package:unittest/unittest.dart';

class Cookie {
  int number_of_chips;
  Cookie({this.number_of_chips:0});
}

main() {
  group('[reflection]', (){
    // tests will go here...
  });
}
I start by using the dart:mirrors' top-level method reflect() to grab a instance mirror of my cookie class:
  group('[reflection]', (){
    InstanceMirror im;
    setUp((){
      im = reflect(new Cookie(number_of_chips: 42));
    });
  });
At this point, I have reflected on an instance of the Cookie class, which gives me a mirror with which to probe the instance. My first attempt at probing uses the getField method from InstanceMirror to see if I can grab the number of chips from the original instance:
    setUp((){
      im = reflect(new Cookie(number_of_chips: 42));
    });

    test('getField', (){
      expect(im.getField('number_of_chips'), equals(42));
    });
That fails, telling me that instead of the expected value of 42, I get a Future instead:
FAIL: [reflection] getField
  Expected: <42>
       but: was <Instance of '_FutureImpl@0x36924d72'>.
That seems useful to note for the future, so I make a test to ensure that getField continues to return a Future:
    test('getField returns a future', (){
      var type = im.getField('number_of_chips').runtimeType;
      expect(type.toString(), contains('Future'));
    });
To extract the actual value out, I need to supply a then() function for the Future to invoke when it completes its thing:
    test('getField can find the original value', (){
      im.getField('number_of_chips').then((v){
        expect(v.reflectee, equals(42));
      });
    });
With that, I have two passing tests:
➜  classes git:(master) ✗ dart reflection.dart
unittest-suite-wait-for-done
PASS: [reflection] getField returns a future
PASS: [reflection] getField can find the original value

All 2 tests passed. 
There is not much else to InstanceMirror aside from the relfectee property. Were I sending mirrors across isolates, I would expect some limitation in what I can do. Since I am writing my tests all in the same isolate, I should be able to access a Cookie instance directly from the reflectee property:
    test('reflectee in same isolate', (){
      expect(im.hasReflectee, isTrue);
      expect(im.reflectee.number_of_chips, equals(42));
    });
And indeed that works:
➜  classes git:(master) ✗ dart reflection.dart
unittest-suite-wait-for-done
PASS: [reflection] getField returns a future
PASS: [reflection] getField can find the original value
PASS: [reflection] reflectee in same isolate

All 3 tests passed.
I would expect that the InstanceMirror's reflectee refers to the same object as the original rather than to a clone of the original. To be sure, I write a test, which requires a slight modification of the set() code:
    InstanceMirror im;
    Cookie cookie;
    setUp((){
      cookie = new Cookie(number_of_chips: 42);
      im = reflect(cookie);
    });

    test('reflectee refers to the same object as the original', (){
      expect(identical(cookie, im.reflectee), isTrue);
    });
And it turns out that reflectees do point to the original instance:
➜  classes git:(master) ✗ dart reflection.dart
unittest-suite-wait-for-done
PASS: [reflection] getField returns a future
PASS: [reflection] getField can find the original value
PASS: [reflection] reflectee in same isolate
PASS: [reflection] reflectee refers to the same object as the original

All 4 tests passed.
Aside from invoke(), which seems to be identical to getField except for methods instead of getters, that looks to be the extent of InstanceMirror. Before calling it a night, I take a quick look at ClassMirror. I can get a ClassMirror instance from the type property of my instance mirror.

This lets me reflect on various aspects of the class, including the methods:
    test('list instance methods', (){
      var cm = im.type,
          methods = cm.methods;
      expect(methods, equals({}));
    });

    test('list instance getters', (){
      var cm = im.type,
          getters = cm.getters;
      expect(getters, equals({}));
    });

    test('list instance members', (){
      var cm = im.type,
          members = cm.members;
      expect(members.values.map((v)=>v.simpleName), equals(['number_of_chips']));
    });
Since there are no getters or methods in my Cookie class, I expect the class mirror to contain an empty map. There is the number_of_chips property, which I am able to extract from the mirror with the members property. With that, I have a fairly respectable start on understanding the current state of Dart mirrors:
➜  classes git:(master) ✗ dart reflection.dart
unittest-suite-wait-for-done
PASS: [reflection] getField returns a future
PASS: [reflection] getField can find the original value
PASS: [reflection] reflectee in same isolate
PASS: [reflection] reflectee refers to the same object as the original
PASS: [reflection] list instance methods
PASS: [reflection] list instance getters
PASS: [reflection] list instance members

All 7 tests passed.
I will probably leave it at that for now, since mirrors in Dart are not baked yet:
➜  classes git:(master) ✗ dart_analyzer reflection.dart 
file:/home/chris/repos/csdart/Book/code/classes/reflection.dart:3:1: dart:mirrors is not fully implemented yet
     2: 
     3: import 'dart:mirrors';
        ~~~~~~~~~~~~~~~~~~~~~~
I do look forward to more on this front. It seems like quite a bit of ceremony—especially coming from a Ruby/JavaScript background. Still, I am excited to see what comes of this library.


Day #616

No comments:

Post a Comment