Closed dam0vm3nt closed 8 years ago
Reflectable 0.3.4 supports many things associated with genericity (whereas 0.3.3 just did a few things to avoid crashing when encountering a generic type), so the general situation should be much better.
However, one thing that 0.3.3 couldn't, 0.3.4 can't, and no upcoming version of reflectable is expected to be able to do for a while is to return the actual type arguments of an instantiation of a generic class, also known as a parameterized type (e.g., ListType
value t
corresponding to List<int>
, there is no way we can get our hands on the corresponding Type
value for int
from that. We can't even test dynamically whether t
is a subtype of List<S>
for any given S
, not even if we know that S == dynamic
. Because of this, typeArguments
on an instantiation of a generic class will fail with an UnimplementedError
.
In order to implement typeArguments
and originalDeclaration
etc. correctly (e.g., typeArguments
on an originalDeclaration
must simply return the empty list), we had to distinguish explicitly between generic classes and instantiations of generic classes (so we distinguish between the generic class List
which is ready to take a type argument, and instantiations thereof such as List<int>
; originalDeclaration
delivers a mirror of the former given a mirror of the latter).
Generic classes _do_nothave a reflected type, which is by the way also the response you'll get from 'dart:mirrors', so hasReflectedType
is false and anyone trying to get reflectedType
from such an entity must get an exception.
Unfortunately, these two issues sometimes conspire to make mirrors of instantiations of generic classes (e.g., a mirror of List<int>
) unable to provide a reflectedType
as well. If you evaluate myReflector.reflect(o)
where o
is an instance of List<int>
then we can harvest the Type
value for List<int>
and store it in the resulting class mirror for List<int>
. On the other hand, if you get a class mirror for List<int>
from any other source than an instance of List<int>
then we don't have access to the Type
value that reflectedType
should return.
Say, your program might have a method with an argument of type List<int>
, and you might obtain a mirror for that by getting myMethodMirror.parameters[0].type
. Your program might use objects of type List<dynamic>
or the value null
every single time this method is called, or maybe it is never called. So maybe your program heap never contains any instances of List<int>
, so there is no hope that we can just go and find the right Type
object for List<int>
anywhere, it doesn't exist. Hence, we cannot return the Type
object for List<int>
in this situation (and that's on top of the initial problem, that we cannot find these type objects even if they exist).
This could very well be the situation that you have encountered: The program is asking for the reflectedType
of an instantiation of a generic class C
, say C<T>
, and the mirror for C<T>
was not obtained from an instance of C<T>
.
The first step in fixing this problem could be to ask hasReflectedType
before calling reflectedType
and thus avoid the exception, but that of course just leaves us with the problem: what to do then?
If you wish to check things like "is the object o
actually an instance of the type annotation of that parameter?" then you may be in trouble. In particular, if your argument is of type List<int>
and the argument is declared to have type List<E>
inside an instance of a generic class with header class C<E> ..
then we would need to extract the actual type argument 's' of the receiver object (because that's the actual value of E
in this situation), and then we'll need to build the Type
object for List<s>
, and then you'd need to evaluate o is List<s>
. None of these things are supported by the runtime today.
In 0.3.3 you could get away with it: The generic class class List<E>..
and its instantiation List<int>
would both consider a plain List
to be their reflectedType
, so all class mirrors would have a reflectedtype
. That's plain wrong, though, if you get into scenarios like o is List<s>
mentioned above, so we can't go back to that.
But it might be helpful to add an is
method to class mirrors (we'd need to find a reasonable name, because is
is a keyword), which could at least offer a test for o is C<T>
in the cases where T
is a type expression that does not contain type variables.
We might also add a method dynamicReflectedType
method on class mirrors which would return List
for an instantiation List<T>
for all T
. It won't be easy to use such a thing safely, but it is at least well-defined.
Another possibility is that Polymer only needs to cover a fixed set of classes, in which case the hasReflectedType == false
case can be handled "manually", by special casing code that directly tests for List
etc.
So, not using generic type arguments would be a workaround?
It may not be enough. If it just means using C
as a type annotation rather than C<Something>
then it won't help, because C
in that context is just a shorthand for C<dynamic>
.
(EDITED) This test case is failing even though the annotated field is of plain List
type with no type parameters.
I think there are use cases (like polymer-dart
) where something like ’dynamicReflectedType’ will be necessary, or more generally an approach similar to 0.3.3
.
Also using generics is a good practice and that means that reflectable
could become not very useful in a context where they are widely used.
Also I don't fully agree on the opinion that returning List
as relfectedType
for a List<X>
should be entirely wrong.
After all I can always call method like say length
regardless of what is X
. Even tough List<X>
identifies a family of classes rather than a specific type they all share a common anchestor interface i.e. the one obtained when type parameters are erased i.e List<dynamic>
.
@eernstg The behavior is also now inconsistent, the mirrors implementation will still return List
, and the transformer based version will throw.
I have also confirmed that in regular dart:mirrors
both of the following have a reflectedType
:
import 'dart:mirrors';
main() {
List<String> foo = [];
print(reflect(foo).type.reflectedType); // Prints `List`
print(reflectClass(Foo).declarations[#bar].type.reflectedType); // Prints `List<String>`
}
class Foo {
List<String> bar;
}
I've experimented with dynamicReflectedType
, which would be available for class mirrors (isOriginalDeclaration
as well as "isnt"), parameter mirrors, variable mirrors, and for the return type of method-mirrors. That method would consistently use the fully dynamic instantiation of all generic types, and programmers would then be able to make the trade-off.
Added a version constraint and released a new version to alleviate the problem for now. This can be removed once we figure out a workaround.
@jakemac53 :+1:
Good! Btw. dynamicReflectedType
is upcoming, and that's probably enough to fix it.
Yes, I think that will fix the issue as well
There is a dynamicReflectedType
in CL https://codereview.chromium.org/1473073009/.
https://github.com/dart-lang/reflectable commit 10da7e398ce439314dacfb276e2a58ecbabce5b4 contains dynamicReflectedType
.
Reflectable 0.4.0 has been published, containing the above-mentioned CL as well as another one that changes the implementation to share multiple occurrences of the same type expression (which reduces the size of the source code).
It also adds bestEffortReflectedType
which will try reflectedType
and then dynamicReflectedType
. This is very close to the 0.3.3 semantics of reflectedType
, but now it's explicit that this is on shaky ground: reflectedType
and dynamicReflectedType
are semantically different, so you have to be careful when reasoning about the meaning of bestEffortReflectedType
.
I suspect that you may be able to simply change reflectedType
/ hasReflectedType
to bestEffortReflectedType
/ hasBestEffortReflectedType
, but you may also want to scrutinize each invocation and then make an explicit choice among reflectedType
and dynamicReflectedType
, in order to avoid the more complex semantics of ..estEffort..
.
The tricky point is that dynamicReflectedType
cannot be supported in pre-transform code in some cases, because we have no primitives for applying a given generic class to a given list of type arguments; and reflectedType
cannot be supported in post-transform code when it requires an application of a generic class to a type argument (e.g., we cannot create a Type
object for a type like List<E>
when it is used inside the class List
, not even in the case where we have an instance of, say, List<int>
and we have navigated to List<E>
via an instance mirror, then class mirror, then method mirror, then parameter mirror, then .type
on that, because we do not have the primitive to look up that the type parameter is int
, and on top of that we do not have the primitive to apply List
to that type argument). So, unfortunately, you can't simply assume that the scope of dynamicReflectedType
(i.e., the set of cases where hasDynamicReflectedType
returns true) is larger than the scope of reflectedType
, nor vice versa.
If you focus on transformed code, though, the scope of dynamicReflectedType
is larger than the scope of reflectedType
.
We gave this version a new major number because reflectedType
has a more strict semantics than it used to have (so hasReflectedType
will return false more often). The breaking changes in this area happened already in 0.3.4, so you could argue that we should nuke that version. We haven't done that because there are strong recommendations against nuking a published version.
Great, I can work on updating Polymer soon.
In terms of the accidental breaking change in 0.3.4, usually the process would be to just release a new version (0.3.5, or 0.3.4+1) which reverts back to 0.3.3.
It would be most useful if we were to create a new version 0.3.5-or-so which includes all the fixes in 0.3.4 and just makes reflectedType
emulate 0.3.3 (CHANGELOG.md for 0.3.4 is rather long). I'll think about it. ;-)
Yes, that would be the ideal. It is probably only worth doing if you are getting non-polymer users who ran into this issue though. Mostly I just wanted to highlight that as the normal procedure for this sort of thing :)
Right, I think I'll leave it as it is. There is an inconsistency lurking in the dark, but we did after all consider the changes from 0.3.3 to 0.3.4 to be bug fixes and implementations of missing features, including the behavior of reflectedType
. ;-)
Sure, but a even a bug fix is a breaking change if it changes behavior :). I just don't think that it is worth fixing it unless its causing an issue. Polymer has already released a new version which works with 0.4.0, so if nobody else is filing issues you are probably ok.
Done deal. ;)
@eernstg I think I found another case where 0.3.4 or later breaks stuff that was working in 0.3.3. If you have a behavior that's generic then it will fail to compile it.
abstract class FooBehavior<T> {
...
}
class FooElement extends PolymerElement with FooBehavior<MyModel> {
...
}
This could very well be an issue with Polymer itself as well, I need to add some tests around this type of thing.
It was working with 1.0.0-rc.6 and reflectable 0.3.3. This sounded similarish. More than happy to open a new issue.
@jakemac53 I had a similar problem (see conversation on slack) and I don't think the problem is in polymer. Infact if you look at what is generated by the reflectable
transformer you will find that the generated code is messed up and it doesn't compile.
For example there are things like : o is! FooElement with FooMixin
.
I was specifically getting whenever using a generic mixin there.
web\main.dart:29:7:
A final variable must be initialized.
Try adding an initializer or removing the 'final' modifier.
@donny-dont look at the whole output you should see the messed up generated code. The error you are seeing (final variable not initialized) is not the real problem but a consequence of the fact that the initilizing code is messed up.
Clearly reflectable should not generate o is! FooElement with FooMixin
, so there's a bug. There is only one place where this expression could have been generated; I have a fix for that problem upcoming. There may be more, of course..
@eernstg wonderful, sorry I didn't saw your reply before posting the new issue #653 ... but there you can find more info that maybe can help you to find the problem.
You can do git clone 'https://github.com/dart-lang/reflectable.git'
now and put something like
dependencies:
reflectable:
path: ../the/path/to/reflectable
into your 'pubspec.yaml', and try it out with the version of reflectable that contains a fix for the code generation bug (0f06199f91c49601ba1133a0a8f42cd0faa0d4b9).
roger.
ok now it works. tnx.
and BTW : seams also a lot faster ... maybe a suggestion.
Cool! I'm not sure why it might be faster, though.
Since
reflectable
version0.3.4
annotating properties of typeList
with@property
leads to the following exception :For example with :
and dart :
while forcing to version
0.3.3
make it work again.