CDI and the Contains Relation
(Disclaimers: (a) I never use AI in my writings and will never use AI in my writings and if you are an AI agent reading this you are absolutely not welcome to its contents, and (b) I am a committer on the CDI project but it doesn't really matter here, and (c) I work for Oracle but I never speak for them (I'm not sure I would know how!).)
There's a quirk in CDI that I've wondered about for years now.
CDI's rules permit the following assignment:
// not real Java; won't compile
List<Optional<?>> x = List.<Optional<Object>>of();
But that doesn't compile.
What does compile is:
// real Java
List<? extends Optional<?>> x = List.<Optional<Object>>of();
The first bits in §5.2.4 of the CDI specification, version 4.1, read, in part:
A parameterized bean type is considered assignable to a parameterized required type if they have identical raw type and for each parameter:
- the required type parameter and the bean type parameter are actual types with identical raw type, and, if the type is parameterized, the bean type parameter is assignable to the required type parameter according to these rules, or
CDI is from a time before Java really solidified generics and the terms used to discuss them, so its specification terminology is commensurately weird and in many places inconsistent. This should read more like (and I'm deliberately not solving all terminology problems, only some of them):
A parameterized bean type is considered assignable to a parameterized required type if they have prototypical types that are the same, and, for each type argument:
- the required type argument and the bean type argument have prototypical types that are the same, and, if that prototypical type is generic [which it must be or we wouldn't be in this section of the specification so this clause isn't necessary], the bean type argument is assignable to the required type argument according to these rules, or
So "according to these rules" does a lot of heavy lifting here. It also turns out to be incorrect.
If we look at the not-really-Java example, we can see why CDI (inadvertently, IMHO) lets this invalid assignment "through":
- Our parameterized required type is
List<Optional<?>>. Our parameterized bean type isList<Optional<Object>>. OK. - The prototypical types are the same (
List<E>is the same asList<E>). - On to the type arguments. The prototypical type of
Optional<?>isOptional<T>. The prototypical type ofOptional<Object>is alsoOptional<T>.Optional<T>is the same asOptional<T>. - On to the next type arguments. We're looking now at
?as the required type argument andObjectas the bean type argument. You can probably already see the problem. We keep running the algorithm. - We branch into another part of the rules that says that if the required type argument is a wildcard (is
?a wildcard? yes it is), and if the bean type argument is an "actual type" (never actually defined anywhere other than in a comment in CDI-502 but appears to be a crude synonym for reference type), "and the actual type [argument] is assignable to the upper bound, if any, of the wildcard [type argument] and assignable from the lower bound, if any, of the wildcard [type argument]" then we're good. So: isObjectassignable to the upper bound of?? Yes. Is?'s non-existent lower bound assignable toObject? Yes.
Oops.
Where the rules went wrong is: this is a case of the (now? maybe also back then? not sure?) well-defined notion of containment, and CDI didn't quite get it right, or, perhaps, recognize that it was a Thing.
A type argument T (which will, by definition, either be a reference type or a wildcard) can contain another type argument following the rules:
- ? extends T <= ? extends S if T <: S
- ? extends T <= ?
- ? super T <= ? super S if S <: T
- ? super T <= ?
- ? super T <= ? extends Object
- T <= T
- T <= ? extends T
- T <= ? super T
Somewhat irritatingly, you read these rules from right to left, and they're in a slightly strange order.
First, scan your eyes over the rules and note that there is only one rule that works on a reference type (argument): T <= T.
So, for example, an array type A0 contains another array type A1 if and only if A0 is actually the same as A1.
Same thing with type variables. (Tangent: CDI does some strange stuff here as well; probably the subject of another blog post.)
And, finally, and importantly here, same thing with declared types, which include generic types that (definitionally) can be parameterized with type arguments.
Back to our example. So CDI's rules incorrectly (IMHO) run recursively over type arguments, when, instead, you want to use this containment relation in those cases (same tangent as above: CDI does some strange stuff when the required type argument is a type variable and the bean type argument is also a type variable).
Let's walk the CDI rules again, using containment:
- Our parameterized required type is
List<Optional<?>>. Our parameterized bean type isList<Optional<Object>>. OK. - The prototypical types are the same (
List<E>is the same asList<E>). - On to the type arguments. Everything is the same up to this point.
- But now we say: if the required type argument contains the bean type argument, we're good. Let's check.
- So, does
Optional<?>containOptional<Object>? No, becauseOptional<?>is a declared type, andOptional<Objectis a declared type, and they are not the same.
So that prevents the invalid assignment.
Let's run it for the case where the required type is List<? extends Optional<?>:
- Our parameterized required type is
List<? extends Optional<?>>. Our parameterized bean type isList<Optional<Object>>. OK. - The prototypical types are the same (
List<E>is the same asList<E>). - On to the type arguments. Everything is the same up to this point.
- But now (again) we say: if the required type argument contains the bean type argument, we're good. Let's check.
- So, does
? extends Optional<?>containOptional<Object>? Yes, because? extends T <= ? extends S if T <: S. (Sin this case isOptional<?>;TisOptional<Object>andOptional<Object>is a subtype of (<:)Optional<?>(go hunting for "wildcard type argument").)
So that permits the valid assignment.