Tuesday, October 8, 2013

Dart's reflection details

Since I needed to manipulate reflection library to develop reflective json_mapper, I investigated some fine details of library. And found strange behavior.
So I asked to dart-misc mailing list.
Fortunately these question was answered by Gilad Bracha(main designer of Dart), so it became clear for what I should expect from Dart now.

Following style will return a mirror for generic class 'declaration'.

ClassMirror cmirror = reflectClass(new List<A>().runtimeType) 
(ClassMirror cmirror = reflectClass(List<A>)) // not supported yet

the above cmirror correspond to the generic class 'declaration', which is equivalent to none applied generic type, so the type including type variables in the generic class still contain type variables when it is inspected by reflection library. This is called a generic declaration.

So the strange things are even List<A> are applied generic class(type variable are substituted by A), but the  reflectClass returns List<T> class which does not corresponding to any runtime type of an object. I don't know why this is so designed, but this is confusing, and quite useless return result for most of the applications.

But as long as there are some way to return Type of applied generic class, it will be OK.
in fact, there is such a way, but it would be difficult to know without having told the difference!

This will return a mirror for applied generic class. (but not complete yet.. since there is a bug now)

ClassMirror cmirror = reflect(new List<A>()).type;
(ClassMirror cmirror = reflectType(List<A>)) // not supported yet

This cmirror is corresponding to applied generic class, and if we do print  cmirror.reflectedType, it prints List<A>.
So it seems OK, but in fact, I already knew this was not OK.

For json_mapper, we need to get field's type in order to process sub json string.

but if we run following code, the field types are none applied generic types. namely including type variables, not substituted by applied types.

ClassMirror cmirr = reflect(new List()).type;
    print(">>0@@ cmirr.reflectedType: ${cmirr.reflectedType}");
    cmirr.getters.forEach((k, MethodMirror md){
      Type tm = null;
      if (md.returnType is ClassMirror) {

        tm = (md.returnType as ClassMirror).reflectedType;
        print(">>1@@ k: ${k}, tm: ${tm}");
      } else if (md.returnType is TypeVariableMirror) {
        TypeVariableMirror tvmirr = md.returnType;
        print(">>2@@ tvmirr: ${tvmirr.runtimeType}");
      }
    });
output:

>> type: ChouchDBFetchData
>>0@@ cmirr.reflectedType: List
>>1@@ k: Symbol("length"), tm: int
>>1@@ k: Symbol("_capacity"), tm: int
>>2@@ tvmirr: _LocalTypeVariableMirrorImpl
>>2@@ tvmirr: _LocalTypeVariableMirrorImpl
>>2@@ tvmirr: _LocalTypeVariableMirrorImpl
>>1@@ k: Symbol("isEmpty"), tm: bool
>>1@@ k: Symbol("isNotEmpty"), tm: bool
>>1@@ k: Symbol("reversed"), tm: Iterable
>>1@@ k: Symbol("iterator"), tm: Iterator

So essentially right now, there is no way to handle generic class properly.
Fortunately, Gilad agreed this is bug, and google teams is now working on fixing this problem. Actually this is a big bug.

So maybe we will get better mirror lib soon.
Other related issues seems also addressed later release.

like reflectClass(typedef List<A>)(here typedef keyword is 'required' to avoid parsing issue..)
and 'invariant'  reflectType, which probablly it returns applied generic class.

here is his answers:
I've been meaning to update the mirror tutorial on our site, but because significant changes are coming soon, I'm delaying that just a tad longer. That said, those changes have nothing to do with this issue, so I'll try and answer your question here. Bear with me, as it is slightly non-obvious and requires some background.

(A)  ClassMirrors reflect both the classes of objects and class declarations. What's the difference between the class of an object and a class declaration?  Well, in a world without generics you can leave the distinction to philosophers, but once you have generics, you want to distinguish between the one declaration of the generic (i.e, the declaration in the source code) and its many invocations (such as List<String>, List<bool> etc.), aka parameterized types.  All the invocations share the same declaration.

This matters when you ask a class mirror for its methods, and then look up a method and ask for its return type. If the mirror is on List<int>, then the return type of the method #first will be int; if it is List<Element> it will be Element etc.  If ask the same question on a declaration, the return type is E, which is a type variable (and no actual object has a type variable as its type; the type variable is always bound to some specific type).

There is never an object whose type is a generic declaration. Hence there is no Type object that corresponds to a generic declaration, and so a mirror on generic declaration has no Type object associated with it. Which is why hasReflectedType on such a mirror returns false. If you still try and get the reflectedType, it throws. 

Hence the behavior you are seeing. 

Now you might ask why.  We could have reflectClass give you a mirror on the specific Type you passed in, and then the invariant you expected would hold. However, then we'd have a problem if you wanted to get at the declaration. If you wrote reflectClass(List) you might well expect the result to be a mirror on the declaration, but you'd be disappointed. Let's say we have a class 

class G<T> ...

In Dart, the identifier G, used without type arguments, always denotes G<dynamic>.  So you would not get a mirror on the declaration of G (or of List) but a mirror on a specific parameterized type (G<dynamic> or List<dynamic> as the case might be).

We decided to keep reflectClass for the declaration. If you want a mirror on a specific parameterized type, you can get it by reflecting on an instance and asking the instance mirror for its type

reflect(new List<A>()).type

we are planning on adding a function reflectType() that would always return a mirror on the specific Type object passed to it. The the invariant

reflectType(t).reflectedType == t

No comments:

Post a Comment