The spec says the inferred type would be Seq<Throwable>, but the compiler infers Seq<Object>.
Which one is correct?
Similarly, the compiler is supposed to infer Seq<String> from the call
cons("abc",nil());
but it infers Seq<Object>.
From the rules given in the spec I understand that the compiler infers a type parameter from the calling context if it can't inferred it from the actual arguments. In the example
cons("abc",nil());
the type parameter of nil() cannot be inferred from the arguments, since the method has no arguments. Hence, the context is used for inference.
Now, what is the context in this case? It depends on the type parameter that is inferred for cons(). When does the compiler infer the type parameter of cons()? Before or after type inference for nil()? In other words, does the compiler perform inference of nested calls from outer method to the inner method? Or vice versa? Or in any arbitrary order?
Does the order of the method call arguments affect the result of type inference? Does inference work left to right? Or right to left? Or in any arbitrary order?
I do not see where the spec specifies the exact behavior. Does anybody feel capable of shedding some light on type inference?
The spec says the inferred type would be
Seq<Throwable>, but the compiler infers Seq<Object>.
Which one is correct?
The compiler. The inference spec has changed but the example
has not been updated to correspond.
Similarly, the compiler is supposed to infer
Seq<String> from the call
cons("abc",nil());
but it infers
Seq<Object>.
Ditto.
From the rules given in the spec I understand that the
compiler infers a type parameter from the calling
context if it can't inferred it from the actual
arguments. In the example
cons("abc",nil());
the type parameter of nil() cannot be inferred from
the arguments, since the method has no arguments.
Hence, the context is used for inference.
Now, what is the context in this case? It depends on
the type parameter that is inferred for cons().
Nope. An argument position is never a "context" from which
type parameters can be inferred.
Can you elaborate on this? I'm not sure I understand
it.
The spec says (p. 16): ... if the method result
occurs in a context where it will be subject to method
invocation conversion ...
Isn't that saying that use of the method result as an
argument to a method is a context?
Yes, indeed. We were considering removing that section, and removed support
for that bullet in the compiler to see who would notice that missing from the
prototype. It turns out to make very little difference but significantly
simplifies the spec. (For example, that bullet clearly must happen AFTER
overload resolution, because it postulates a particular method being called,
but overload resolution itself depends on type inference to compare two methods.
This is very confusing).
As a practical matter the only cases that are affected are calls to methods
whose type parameters don't depend on the method arguments, such as nil()
in this example. These can be disambiguated by providing explicit type
parameters, which improves documentation of the program as well. The example
should be written
Yes, indeed. We were considering removing that
section, and removed support
for that bullet in the compiler to see who would
notice that missing from the
prototype. It turns out to make very little
difference but significantly
simplifies the spec.
May I respectfully disagree? It makes quite a difference if an assignment is considered "context" for purpose of type inference and a method call is not.
Normally you can either pass the result of a method call directly as argument to another method call, like in g(f());, or you can use a helper variable to which you assign the result of the first method call and which you then pass as an argument to the next method call, like in Object tmp = f(); g(tmp);. The effect should be the same.
This is now different in generic Java. Here is an example:
Normally you can either pass the result of a method
call directly as argument to another method call, like
in g(f());, or you can use a helper variable to which
you assign the result of the first method call and
which you then pass as an argument to the next method
call, like in Object tmp = f(); g(tmp);. The effect
should be the same.
It can't be the same because of the possibility of overloading.
With an assignment there is a single unique type that is the
expected result type of the right hand side. With a method
argument, there may be many overloadings, each of which would
imply a different expected type for the argument expression.
Normally you can either pass the result of a method
call directly as argument to another method call, like
in g(f());, or you can use a helper variable to which
you assign the result of the first method call and
which you then pass as an argument to the next method
call, like in Object tmp = f(); g(tmp);. The effect
should be the same.
It can't be the same because of the possibility of overloading.
With an assignment there is a single unique type that is the
expected result type of the right hand side. With a method
argument, there may be many overloadings, each of which would
imply a different expected type for the argument expression.
Let me get that straight. You are saying that "in generic Java" g(f()); and tmp=f(); g(tmp); can't be the same. Correct?
In non-generic Java the inner method has a static return tpye which can be used for overload resolution. For this reason g(f()); and tmp=f(); g(tmp); are the same, unless tmp has a type different from the return type of f().
But in generic Java, if the return type is subject to type inference, overload resolution would overlap with type inference, and the compiler will not do any type argument inference for method f(). Or, more precisely, the compiler will infer the type arguments as ruled out in the "otherwise" bullet.
Doesn't type inference also overlap with bounds checking? In which order are they performed? Does the compiler infer types that conform to the bounds, or does it infer types (regardless of the bounds) and checks against the bounds after type inference?
The spec requires that the inferred type arguments are within bounds. See section 5.6.2. It talks of type parameters T extends U1&...Um, for which a type argument A is inferred, which is "the most specific non-null type such that ... T:=A". Which means that the inferred type argument must in within bounds. That's not what the compiler does.
Example:
publicclass Cell2 {
<A, B extends A> doS(A[] h, B i) {
h[0] = i;
}
publicstaticvoid main(String[] args) {
Cell2 c = new Cell2();
c.doS(new Integer[]{new Integer(5)}, new String("Hallo"));
}
}
yields the error message:
Test.java:8: doS<A, B extends A>(A[],B) in Test cannot be applied to
(java.lang.Integer[],java.lang.String); inferred type argument(s)
java.lang.Integer,java.lang.String do not conform to bounds of type
variable(s) A,B
doS(new Integer[] {new Integer(5)}, "hello");
^
1 error
Why does the compiler infer A:=Integer and B:=String? According to the spec it must infer A:=Object and B:=Object, because they conform to the bounds.
A possibly related issue (else I'd have started a new thread) is the apparent lack of type inference in the ternary operator. Consider the code
Number n = myBoolean ? new Double(1.0) : new Integer(1);
The error message has changed from
incompatible types
found : java.lang.Integer
required: java.lang.Double
with javac (1.4.1 Apple; also early versions of gjc) to
incompatible types for ?: neither is a subtype of the other
second operand: java.lang.Double
third operand : java.lang.Integer
in gjc 2.2. Why can't the compiler infer a type for the result of the ternary operator which is assignable from the second and third operands and to the variable forming its context?
Let me get that straight. You are saying that "in
generic Java" g(f()); and tmp=f(); g(tmp); can't
be the same. Correct?
Correct, because in the latter you have to declare the
type of the variable tmp.
In non-generic Java the inner method has a static
return tpye which can be used for overload resolution.
For this reason g(f()); and tmp=f(); g(tmp); are
the same, unless tmp has a type different from the
return type of f().
But in generic Java, if the return type is subject to
type inference, overload resolution would overlap with
type inference, and the compiler will not do any type
argument inference for method f(). Or, more
precisely, the compiler will infer the type arguments
as ruled out in the "otherwise" bullet.
Right.
Doesn't type inference also overlap with bounds
checking? In which order are they performed? Does
the compiler infer types that conform to the bounds,
or does it infer types (regardless of the bounds) and
checks against the bounds after type inference?
It infers types and then checks against the bounds.
The spec requires that the inferred type arguments are
within bounds. See section 5.6.2. It talks of type
parameters T extends U1&...Um, for which a type
argument A is inferred, which is "the most specific
non-null type such that ... T:=A". Which means that
the inferred type argument must in within bounds.
That's not what the compiler does.
...
Why does the compiler infer A:=Integer and B:=String?
According to the spec it must infer A:=Object and
d B:=Object, because they conform to the bounds.
This is a good point. I've filed 4901682 for this issue.
It might be resolved by specifying type inference
in more detail rather than changing the compiler.