JDK-8219318 (???): inferred type does not conform to upper bound(s) when collecting to a HashMap
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Dec 16 18:42:02 UTC 2019
I did some more investigation and I think javac behaves as expected. No
eager instantiation occurs (which was my fear).
In reality, since _all_ the lambda expression being passed as argument
are implicit, all arguments to the toMap() call are not pertinent to
applicability.
So you have this configuration of the inference variable:
M <: Map<? extends Integer, ? extends Integer>
M <: Map<K, U>
K <: Object
U <: Object
Where K, U, M are defined in Collectors::toMap
Now,when we go ahead and resolve this, since M 'depends' on K, U, we
must instantiate those first (to Object, given we have no other
meaningful bounds). Which then leads to the error javac reports, as now
M has two incompatible bounds: Map<? extends Integer, ? extends Integer>
and Map<Object, Object>.
Note that incorporation fails to set constraints here - the only rule
that would help is this (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."
But the "not wildcards" restriction prevent any extra constraint to be
generated here.
In fact, if you define a method like this:
static void myPutAll(Map<Integer, Integer> mii) { }
And try to call it with the collect(toMap) chain, everything works as
expected. In other words, this is a limitation that is caused by an
interaction of the putAll signature, plus arguments being not pertinent
to applicability (leading to missing constraints), plus limitation in
the incorporation logic which fails to keep track of relationship
between type variables (if their bounds contain wildcards).
So, this is not an issue, in the compiler sense (and should be closed).
Whether there's room to do better in the spec (esp. in terms of
incorporation) I'll leave that to Dan.
Cheers
Maurizio
On 16/12/2019 16:59, Maurizio Cimadamore wrote:
>
> On 16/12/2019 16:40, Maurizio Cimadamore wrote:
>> Which again leaves us in a position where no useful constraint can be
>> derived from HashMap::new
>
> I think that the key in unlocking this lies deeper in the spec. If we
> look at 18.5.1 we will see sentences such as:
>
> " Otherwise, C includes, for all i (1 ≤ i ≤ k) where ei is pertinent
> to applicability, ‹ei → Fi θ›. "
>
> In the case of your example, the outermost call is putAll and the
> argument in question is call to Stream::collect. So that one is
> pertinent to applicability. But what does the constraint above reduces
> to?
>
> If we follow 18.2.1 we find the answer:
>
> "If the expression is a class instance creation expression or a method
> invocation expression, the constraint reduces to the bound set B3
> which would be used to determine the expression's compatibility with
> target type T, as defined in §18.5.2.1. (For a class instance creation
> expression, the corresponding "method" used for inference is defined
> in §15.9.3.) "
>
> So, it seems like we should compute a new bound set using the rules in
> 18.5.2.1 - using the target type which is the argument type of putAll
> - hence Map<? extends Integer, ? extends Integer>.
>
> Now, this *should* work (despite we get no constraints from
> HashMap::new), but it seems like it doesn't. Of these there could be
> two possible scenario: (1) there's a subtle javac bug or (2) under
> some situations the spec forces eager instantiation for some of its
> arguments. Now, let's examine (2) - 18.5.2.1 says the following:
>
> "Otherwise, if R θ is an inference variable α, and one of the
> following is true:
>
> T is a reference type, but is not a wildcard-parameterized type,
> and either (i) B2 contains a bound of one of the forms α = S or S <:
> α, where S is a wildcard-parameterized type, or (ii) B2 contains two
> bounds of the forms S1 <: α and S2 <: α, where S1 and S2 have
> supertypes that are two different parameterizations of the same
> generic class or interface.
>
> T is a parameterization of a generic class or interface, G, and B2
> contains a bound of one of the forms α = S or S <: α, where there
> exists no type of the form G<...> that is a supertype of S, but the
> raw type |G<...>| is a supertype of S.
>
> T is a primitive type, and one of the primitive wrapper classes
> mentioned in §5.1.7 is an instantiation, upper bound, or lower bound
> for α in B2.
>
> then α is resolved in B2, and where the capture of the resulting
> instantiation of α is U, the constraint formula ‹U → T› is reduced and
> incorporated with B2. "
>
> Now, this does ring some bells - if you look, Stream::collect does
> return a type variable:
>
> https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.stream.Collector-
>
>
> Now, the target type here is a wildcard-parameterized type (first
> bullet), so eager instantiation should not happen - but I wonder what
> happens inside javac.
>
> Maurizio
>
More information about the compiler-dev
mailing list