PROPOSAL: Simplified Varargs Method Invocation
Reinier Zwitserloot
reinier at zwitserloot.com
Thu Mar 5 21:39:06 PST 2009
Heh, that was the next proposal I was going to write.
(Apologies for a long and technically complicated post. I've rewritten
it several times in an attempt to get to the point faster, but this is
the best I can do).
It really pains me to rain on your parade. I was doing some
experimenting and found an admittedly very unlikely scenario that
isn't backwards compatible. Then I realized, that this is actually
(arguably) a bug, or at least a severe misfeature in javac right now!
Therefore, this proposal should fix this problem as well. Of all
proposals so far, I rate this one the highest, because it causes so
much confusion and is closer to a bug in javac than a language
feature, so I would be very happy if this proposal can be fully ironed
out.
Complete code showing the problem - copy and paste into Main.java,
compile, and run:
------
import java.util.*;
class Foo<T> {
public void foo(T... t) {}
}
class Bar extends Foo<String> {
//de-varargsing? Why is this legal?
public void foo(String[] t) {
System.out.println(t.getClass().getComponentType());
}
}
public class Main {
public static void main(String[] args) {
Foo<String> f = new Bar();
List<String> l = Arrays.asList("a", "b", "c");
bar(f, l);
}
public static <T> void bar(Foo<T> f, List<T> l) {
f.foo(l.get(0), l.get(1), l.get(2));
}
}
-----
The result is an error:
Exception in thread "main" java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
Right now, you get the usual varargs warning, but nothing else at
compile time. At runtime, you get the above error.
The real bug here is the ability for any subclass to de-varargs a
parameter. This bit has nothing to do with the proposal, other than
that it so happens to deal with varargs. De-varargsing a parameter is
somewhat similar to a subclass tightening the type of an overriden
parameter, which isn't legal / gives you a different method altogether
instead of overriding.
If you really want to pass varargs into such a method, then the only
solution is to cast the object to a less specific type, and then all
of a sudden it works. In other words, this can happen, right now, in
java 1.5/1.6:
SubType s = new SubType();
s.foo(a, b, c); //method not found.
SuperType st = s;
st.foo(a, b, c); //no problem.
I say that's a bug.
The more serious problem, and one directly related to the proposal, is
trying to reify a superclass's array type. (the subclass in the
example above turning Foo's T into a reified 'String'). This action is
fundamentally broken, and yet there's no way to avoid it. You can't
effectively override a method of a superclass containing a generics
type as an array's component type if you reified that generics
parameter. You can try, but many callers simply can't call your method
anymore. Right now, in java 1.5/1.6, you do get that generics warning
Bob Lee's proposal is trying to move to the callee site, though it's
not very informative and there's absolutely nothing a caller can do
whatsoever. If Bob Lee's proposal is implemented, the warning just as
useless, and arguably skipped even faster than it is now.
Specifically, you can't:
A: widen the type of the parameter in the overriding method's
signature to Object[] or Object..., as javac will refuse to compile it
due to two different signatures erasing to the same signature.
B: add a bogus <T extends String> type and use that T. Javac will
compile it, but the signature of that method is then foo(String[]),
and thus its a completely separate method, that doesn't override
parent's foo method.
Therefore, addendum #1 to this proposal:
Generate more warnings.
A. If you are overriding a method where in your signature an array is
not varargsed, but in any of your supertypes' signatures, it is
varargsed. This warning should be very severe as well and suggest in
no uncertain terms that you NEED to replace your [] with "...";
optimally this is a compile time error, but fixing this now wouldn't
be migration compatible. This warning should not show up if using -
source 1.4 or lower, for obvious reasons.
B. If you are overriding a method where in your signature an array
type's component type has been reified compared to any of your
supertypes' signatures for this method. The warning should explain
that there's no work-around, but that anybody treating your instance
as a type of (X) will likely get a runtime error if they tried to call
this method. The only fix I can think of is to rejigger the signatures
somehow: The method should signature as an Object[] same as parent,
but javac should NOT consider this a separate method that erases to
the same signature - it IS intended as the same method. If this is
done, the warning on the method should be translated to something
along the lines of Bob Lee's proposed warning (the actual component
type of the array, if asked for at reification, isn't neccessarily
correct, and your string array might all of a sudden give you integers
if the caller handed you a generics-tainted array).
A second addendum I would strongly urge is to use escape detection to
eliminate the warning on the var-args-using methods. The following
operations are all entirely safe, and they are also the only ones used
by every single varargs method I've ever laid my eyes on, except
Arrays.asList:
Safe #1: Ask for the vararg array's length. Obviously safe - length
has no relation to the array's type.
Safe #2: Member lookup (array[index]). The type of a member lookup is
entirely dependent on the compile-time type. The caller needs to worry
about that compile-time type too, so can't stuff mistyped things in
that varargs array without triggering a generics warning of their own.
Once somebody somewhere gets a generics warning, any resulting
ClassCastExceptions are acceptable (that's the status quo now, after
all). Non-generified lookups such as the example above can't even run,
because the method invocation itself will already blow up. This is
bad, but it happens now too.
Safe #3: foreaching through a varargs parameter. foreaching a
combination of .length and member lookups, so by induction is
obviously as safe as #1 and #2.
Anything else, -anything- at all, including using it in any expression
(other than the safe forms above), storing it into another variable or
field, using it as a parameter to another method, or calling any other
property on it, including toString() (there's no point in calling
toString() on an array anyway, so why bother declaring that safe?) -
is unsafe, and would generate the warning. The warning is then not
generated on the var-args-accepting-method's signature, but instead on
the first node in that method's AST that does something that isn't
safe to the varargs parameter.
The only borderline cases where the above preconditions are broken is
Arrays.asList, which is a unique exception, and varargs methods that
pass the entire varargs parameter to a helper method. It might be
possible to declare passing varargs parameters to another method that
also takes varargs as safe, but I'm not 100% certain that's always
safe (as safe as generics are going to be in a non-reified world, that
is).
Note that for most of these cases, there are mixed-version problems.
New code that calls into old code means nobody gets any warnings. I'm
not sure if Bob's proposal includes this, but the compiler should
continue to generate warnings on the site of the caller if the class
file format of the targeted method goes with javac v1.6 or below.
If a code-base starts mixing generated class files, things get even
worse; methods that, during compile-time, weren't overriding anything,
are suddenly supposed to override a generics or varargs based
superclass because just the superclass has been recompiled with
changes, for example. The only argument I can give in favour of this
proposal is that doing that sort of thing already breaks a number of
apparent invariants and can give you NoSuchMethodErrors and the like.
--Reinier Zwitserloot
On Mar 6, 2009, at 02:30, Bob Lee wrote:
> Simplified Varargs Method Invocation
> AUTHOR: Bob Lee
>
> *OVERVIEW
> *
> FEATURE SUMMARY: When a programmer tries to invoke a *varargs*
> (variable
> arity) method with a non-reifiable varargs type, the compiler
> currently
> generates an "unsafe operation" warning. This proposal moves the
> warning
> from the call site to the method declaration.
>
> MAJOR ADVANTAGE: Safely and significantly reduces the total number of
> warnings reported to and suppressed by programmers. Reduces programmer
> confusion. Enables API designers to use varargs.
>
> MAJOR BENEFIT: Plugs a leaky abstraction. Creating an array when you
> call a
> varargs method is an implementation detail that we needn't expose to
> users.
> Addresses a well known "gotcha" encountered when you mix varargs and
> generics. Most programmers are surprised to find out you can only
> clear this
> warning by suppressing it (especially considering we introduced
> varargs and
> generics in the same language update); they waste time looking for
> alternatives that don't exist. Google Code Search finds almost 90k
> callers
> of Arrays.asList() (http://tinyurl.com/dept4d). We could safely
> suppress the
> warning once and for all on asList()'s declaration instead of
> unnecessarily
> warning every caller that uses a non-reifiable type.
>
> MAJOR DISADVANTAGE: The compiler will generate a warning for a method
> declaration whether or not someone actually calls the method with a
> non-reifiable type. Allows loss of type safety if the varargs method
> suppresses the warning and uses the varargs array unsafely.
>
> ALTERNATIVES:
> a) Don't mix varargs with generics. Use the more verbose and less
> straightforward but warning-free builder pattern. Most API designers
> choose
> this route.
> b) Improve the warning message. Current message: "uses unchecked or
> unsafe
> operations"
> c) Reify generics.
> d) Introduce a second varargs syntax (perhaps using "...." instead of
> "...") that uses List<T> instead of T[].
> e) Defile the type system.
>
> Note: This proposal doesn't preclude any of these other approaches.
>
> *EXAMPLES
> *
> Before this change:
>
> static <T> List<T> asList(T... elements) { ... }
>
> static List<Callable<String>> stringFactories() {
> Callable<String> a, b, c;
> ...
> *// Warning: **"uses unchecked or unsafe operations"*
> return asList(a, b, c);
> }
>
> After this change:
>
> *// Warning: **"enables unsafe generic array creation"*
> static <T> List<T> asList(T... elements) { ... }
>
> static List<Callable<String>> stringFactories() {
> Callable<String> a, b, c;
> ...
> return asList(a, b, c);
> }
>
> If asList() prohibits storing elements that aren't of type T in the
> elements
> array, we can safely suppress the warning:
>
> *@SuppressWarnings("generic-varargs")
> // Ensures only values of type T can be stored in elements.
> * static <T> List<T> asList(T... elements) { ... }
>
> *DETAILS
> *
> SPECIFICATION: When compiling code that calls a varargs method with a
> non-reifiable varargs element type, if the target method was compiled
> targeting Java 7 or later, the compiler needn't generate a warning
> for the
> caller. When compiling a varargs method that could accept a non-
> reifiable
> varargs type, the compiler should generate a warning on the varargs
> method
> declaration. A varargs type is non-reifiable if it contains a type
> variable
> anywhere in its signature. For a varargs argument of type T, the
> programmer
> can safely suppress the warning using @SuppressWarnings("generic-
> varargs")
> so long as the varargs method ensures that only elements of type T
> can be
> stored in the varargs array.
>
> COMPILATION: Tools should no longer generate a warning for varargs
> method
> callers. Instead, they should generate a warning for varargs method
> declarations that support non-reifiable types.
>
> TESTING: Compile test programs and ensure that the compiler
> generates the
> expected warnings.
>
> LIBRARY SUPPORT: Suppress warnings on the following varargs methods
> in the
> JDK:
> - Arrays.asList(T... a)
> - Collections.addAll(Collection<? super T> c, T... elements)
> - EnumSet.of(E first, E... rest)
>
> REFLECTIVE APIS: n/a
>
> OTHER CHANGES: n/a
>
> MIGRATION: Existing callers may be able to remove
> @SuppressWarnings("unchecked") from their code. Existing libraries
> should
> add @SuppressWarnings("generic-varargs") to methods with signatures
> containing non-reifiable varargs types.
>
> *COMPATIBILITY
> *
> BREAKING CHANGES: None
>
> EXISTING PROGRAMS: If you recompile an existing program with "-
> target 7",
> the compiler will generate warnings for method declarations containing
> non-reifiable varargs types.
>
> *REFERENCES
> *
> EXISTING BUGS: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6227971
>
> JLS Section 15.12.4.2 "Evaluate Arguments" (
> http://java.sun.com/docs/books/jls/third_edition/html/
> expressions.html)
>
> "If the method being invoked is a variable arity method
> (§8.4.1)<http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38698
> >m,
> it necessarily has n>0 formal parameters. The final formal parameter
> of m
> necessarily has type T[] for some T, and m is necessarily being
> invoked with
> k0 actual argument expressions.
>
> If m is being invoked with kn actual argument expressions, or, if m
> is being
> invoked with k=n actual argument expressions and the type of the kth
> argument expression is not assignment compatible with T[], then the
> argument
> list (e1, ... , en-1, en, ...ek) is evaluated as if it were written
> as (e1,
> ..., en-1, new T[]{en, ..., ek}).
>
> The argument expressions (possibly rewritten as described above) are
> now
> evaluated to yield argument values. Each argument value corresponds to
> exactly one of the method's n formal parameters."
>
> Angelika Langer's Java Generics FAQ, "Why does the compiler
> sometimes issue
> an unchecked warning when I invoke a 'varargs' method?" (
> http://tinyurl.com/8w2dk)
>
> Josh Bloch's "Effective Java" 2nd Edition, page 120 (
> http://tinyurl.com/chtgbd)
>
> Alex Miller's blog, "Generics puzzler - array construction" (
> http://tech.puredanger.com/2007/02/27/generics-array-construction/)
>
> "Java Generic and Collections", page 95 (http://tinyurl.com/c53pnu)
>
More information about the coin-dev
mailing list