Does JLS tell if this program is legal?
Stephan Herrmann
stephan.herrmann at berlin.de
Tue May 21 19:49:29 UTC 2024
Hello,
I am investigating a report about a program that is accepted by javac, but
rejected by ecj, where per my current thinking I suspect the case to be
underspecified in JLS.
The program could be reduced to this:
interface Inner<S> { }
interface Super<T> { }
interface One<U> extends Super<Inner<U>> { }
interface Two<V> extends Super<Inner<V>>, One<V> { }
public class Bug {
<W extends One<?>> W getOne(W w) {
return null;
}
<X, Y extends Two<X>> void foo(Y y) {
Y one = getOne(y);
}
}
In ecj type inference fails during the incorporation phase of invocation type
inference.
At that point we have these type bounds:
W#0 :> Y
W#0 <: Y
W#0 <: One<?>
From these, type bounds 2 and 3 trigger the following sentence from §18.3.1:
"When a bound set contains a pair of bounds α <: S and α <: T, and there exists
a supertype of S of the form G<S1, ..., Sn> and a supertype of T of the form
G<T1, ..., Tn> (for some generic class or interface, G), then for all i (1 ≤ i ≤
n), if Si and Ti are types (not wildcards), the constraint formula ‹Si = Ti› is
implied."
We have:
* α = W#0
* S = Y
* T = One<?>
* Super<Inner<X>> is a supertype of S / Y (indirectly via Two<X>)
* Super<Inner<?>> is a supertype of T / One<?> (directly)
* S1 = Inner<X>
* T1 = Inner<?>
* new constraint formula created: ⟨Inner<X> = Inner<?>⟩
As both types are proper types, but not the same type, this constraint reduces
to false, causing inference to fail.
For a moment I was tempted to challenge the part "if Si and Ti are types (not
wildcards)", perhaps also Inner<?> would be a reason not to create that
constraint, but I gave up that idea when I discovered the following solution:
* One<X> is a supertype of S / Y (as the 2nd superinterface of Two<X>)
* One<?> is a supertype of T / One<?> (identity)
* S1 = X
* T1 = ?
* NO new constraint is created
* inference succeeds to instantiate W#0 = Y
In my understanding the weakness here is in "there *exists* a supertype of S ...
(for *some* generic class or interface, G)". Which seems to imply once a
compiler finds one witness for the conditions of that supertype, that's the only
thing that needs to be looked at for this sentence. In fact, the choice which
witness is found by ecj can be toggled by swapping the order of Two's
superinterfaces.
Now declaration order is probably a bad thing to decide about accept/reject. So
which criterion should be used to select G?
My current implementation draft prefers a "more specific" supertype, which
suffices to select One<X> instead of Super<Inner<X>> in this particular case.
Does javac perform an explicit selection in this situation, or does it "just
happen" to find the more helpful supertype?
From a theory point of view which supertype is the "correct" one to work with?
How to select among several candidates?
I suspect that a direct subtype check among candidates may not be the relevant
criterion, since the different effects are caused by the position / nesting
level into which the wildcard is propagated.
thanks,
Stephan
More information about the compiler-dev
mailing list