Closed eliasvasylenko closed 9 years ago
Yes. This seems to be calling for a generic type inferrer. Basically, it can be defined as:
Given formal types A, B, C, and actual types x, y, z, resolve the type context under which [x, y, z] can be assigned to [A, B, C].
That is, if A, B and C are type expressions with type variables, which have bounds, resolve those type variables.
A few concrete examples:
Can FooEnum be assigned to Enum<?> ?
Yes.
Can FooEnum and BarEnum be assigned to Enum<A> and Enum<B>?
Yes, if A=FooEnum and B=BarEnum
Can FooEnum and BarEnum be assigned to Enum<A> and Enum<? extends Enum<A>>?
No
I've contemplated about implementing this because it seemed useful and powerful to me. But I haven't been able to justify the complexity (it's non-trivial) with good use cases. Ideally if we could reuse the javac type inferer, or if javac can reuse this utility, that would be easier to justify.
Hi, thanks for the quick response.
I'm not necessarily talking about type inference - this would be nice to take it a step further, but as you say, lots of complexity for a feature without that many use cases. Mainly I'm just talking about explicit parametrisation with given types.
In other words, rather than automagically resolving the type variables in the type expressions [A, B, C] for some [x, y, z], simply check whether a given exact substitution for those type variables is valid wrt their bounds.
It's perhaps still non-trivial to verify the requested parametrisation is valid, but I had thought that it would be the same task as where
already performs in TypeToken... Looking at it though, it seems you don't actually perform any checking on the validity of the parametrisation here, since you can just rely on getting it free from the compiler. (Which is very cleverly done, btw!)
Looks like, as you say, the only way to achieve this atm is to pull in a dependency on javac or JDT or something... and faff around with syntax trees and stuff most likely. Fun!
If you already have the mappings of all TypeVariables, I believe you could use TypeResolver
on the return type alone, just disregarding all the method parameters.
For example, in method:
Multimap<K, V> makeMap(Iterable<K> keys, Iterable<V> values);
You can just do:
TypeResolver resolver = new TypeResolver();
for (...) {
resolver.where(typeVar, actualType);
}
resolver.resolveType(method.getGenericReturnType());
With regard to type checking however, I'm afraid the complexity of doing it right may not be much easier than full inference. Consider this:
<T1, T2 extends Iterable<T1>, T3 extends Map<T1, T3>>
void foo(T2 t2, T3 t3) {}
If you only have mappings for T2 and T3, you'll still need to infer T1.
It's easier if the mapping of T1 is also available. But in that case, you could likely use TypeResolver to first resolve all the type bounds, and then use isAssignableFrom
on the resolved types?
Thank you, this makes sense and has given me a starting point. I will do as you describe and try to use the TypeResolver and isAssignableFrom to implement something along these lines. I will come back to reopen this issue, or to open another one, if I am successful in a way which might be useful for other people as a contribution.
Cheers,
Eli
FWIW, for anyone still watching this, I've started implementing the type inference rules myself according to the spec for Java 8. It doesn't actually seem that difficult now I've gotten a little way into it, just a fair bit of work.
I'm working using your reflection stuff without pulling in any other dependencies, and I've tried to design the API in a way which I think fits neatly with what you currently have. Assuming I do actually finish this and it's up to your standards, is there any chance you'd consider merging it in?
Hi, I discussed with others on the team. The general sentiment is so far reserved on adding any of these features (inference, parsing, or newParamterizedType() etc).
I do think if you could write a complete type inferrer, it'd make a nice project by itself. And then using the type inferrer, a type safe parser is a good first use case.
Fwiw, I created a tiny spin-off project with public TypeParser and also Types.
It doesn't do type bounds checking yet but it can definitely use a type inferrer to properly check bounds.
Great, TypeParser and Types will come in handy for me, thanks. And I'll let you know when the type inferrer is done all the same, even if it will just live as a stand-alone project.
Hi, if you're interested the type inferrer is pretty much done now. There were too many edge cases and new types involved with inference which weren't compatible with your library (infinite types, intersection types, type variables with upper and lower bounds, etc.), so I just reimplemented the features I needed and got rid of the dependency. It wasn't that much more work really, as the type inference algorithm effectively gives you a lot of it for free (though likely many times slower in some cases... it's not quick... thankfully this isn't an issue for me).
Anyway, the library lives here https://github.com/StrangeSkies/uk.co.strangeskies under the uk.co.strangeskies.reflection subproject. If you want to take a look, TypeLiteral and Invokable provide most of the useful functionality.
TypeToken and Invokable currently have great support for reflective examination of generic types, but I think there are some blind spots when it comes to generic methods which fall neatly within the scope of the library.
Consider the (fairly pointless looking) method:
Querying the return type and getting a TypeToken over a java.lang.reflect.TypeVariable isn't that useful in most contexts. A client isn't likely to know what to do with type 'T' without doing some further manual investigation.
What would be nice is a rough analogue to the
<X> TypeToken<T> where(TypeParameter<X> typeParam, TypeToken<X> typeArg)
method from the TypeToken API. Obviously the design would have to be a little different, since an Invokable can't be parametrised over the generic method type parameters so we can't magically capture them in a TypeParameter in the same way we do there.But essentially it'd be nice if we could do something like this:
to get a reference to the parametrised method with the parametrized signature (if that is a concept which makes sense):
Or maybe even just:
giving an invokable over the parametrized signature:
This way we could do a few more useful type checking tasks in serialisation libraries and the like out of the box.
Of course my ideal wish list feature would be:
to give an Invokable over:
though I suppose this would require an implementation of the entire Java method type inference rules, which is far from trivial, and probably asking too much when this is a bit of a niche feature!
Cheers,
Eli