Safe Varargs

Bob Lee crazybob at crazybob.org
Fri Dec 17 12:10:37 PST 2010


I'm disappointed that we're adding another type to java.lang, especially for
an ugly workaround. As you know, this is roughly equivalent to adding a
reserved word to the language.

The original proposal (see below) didn't require this.

I'm also not sure the new proposal addresses overriding varags methods. For
example, if I override a method add(T[]) with add(String[]) (see proposal
for more complete example), the compiler should generate a warning.

On Fri, Dec 17, 2010 at 11:51 AM, Paul Benedict <pbenedict at apache.org>wrote:

> Your above explanation would be good enough for me. At least
> developers could then have some sort of an idea what was intended by
> "safe operation". Right now, it's too vague.


>From the original proposal: "For a varargs argument with a component 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."

Thanks,
Bob

---------- Forwarded message ----------
From: Bob Lee <crazybob at crazybob.org>
Date: Sun, Mar 8, 2009 at 3:29 PM
Subject: PROPOSAL: Simplified Varargs Method Invocation (Round II)
To: coin-dev at openjdk.java.net


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 with non-reifiable types.

MAJOR BENEFITS:
  - Plugs a leaky abstraction. Creating an array when you call a varargs
method is an implementation detail that we needn't expose to users.
  - Most programmers are surprised to find out they can only clear this
warning by suppressing it. They expect two language features introduced in
the same version to work well together. Programmers will no longer waste
time looking for alternatives that don't exist.
  - Google Code Search finds almost 90k callers of Arrays.asList() (
http://tinyurl.com/dept4d). We can 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. Introduces a
small risk of not reporting warnings if you compile against code compiled
with Java 7 but run against code compiled with an earlier version.

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.
  f) List literals: [a, b, c] instead of Arrays.asList(a, b, c)

Note: This proposal doesn't preclude any of these other approaches.

EXAMPLES

SIMPLE EXAMPLE:

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) { ... }

OVERRIDE VARARGS WITH ARRAY:

Before this change:

  interface Sink<T> {
    void add(T... a);
  }

  interface BrokenSink<T> extends Sink<T> {
    /** Overrides Sink.add(T...). */
    void add(T[] a); // no varargs
  }

This code compiles and runs:

    BrokenSink<String> s = ...;
    s.add("a", "b", "c"); // varargs

A BrokenSink implementation could do something unsafe to the T[], so after
this change, we generate a warning:

  interface BrokenSink<T> extends Sink<T> {
    // Warning: "Overriddes non-reifiable varargs type with array"
    void add(T[] a);
  }

To clear the warning, the programmer should use varargs instead of an array:

  interface BrokenSink<T> extends Sink<T> {
    void add(T... a);
  }

VARARGS TYPE ERASURE NARROWED BY OVERRIDE:

The following method adds a non-null element to a Sink. Before this
proposal, the call to Sink.add() generates a warning:

  static <T> void addUnlessNull(Sink<T> sink, T t) {
    if (t != null)
      // Warning: "uses unchecked or unsafe operations"
      sink.add(t);
  }

Type T is unknown, so the method creates a single-element Object[]
containing t and passes it to Sink.add().

After erasure, this String-based implementation of Sink produces a more
narrow erased varargs type, specifically String[] instead of the Object[]
accepted by Sink.add():

  class StringSink implements Sink<String> {
    public void add(String... a) { ... }
  }

If you run the following code, the bridge method StringSink.add(Object[])
will throw a ClassCastException when it tries to cast the Object[] created
in addUnlessNull() to the String[] required by StringSink.add(String[]):

    Sink<String> ss = new StringSink();
    addUnlessNull(ss, "Bob"); // ClassCastException!

After this change, the compiler warning moves to the vargs method
declaration that results in a more narrow erased array type:

  static <T> void addUnlessNull(Sink<T> sink, T t) {
    if (t != null)
      sink.add(t); // no warning
  }

  class StringSink implements Sink<String> {
    // Warning: "override generates a more specific varargs type erasure"
    public void add(String... a) {}
  }

At this point, the API designer would likely choose a different design
without an overridable varargs method. For example:

  interface Sink<T> {
    void add(T t); // no varargs
  }

  static <T> void addNonNull(Sink<T> sink, T... a) {
    for (T t : a)
      if (t != null)
        sink.add(t);
  }

DETAILS

SPECIFICATION:
  - A varargs type is reifiable if its component type is reifiable (see JLS
4.7).
  - When compiling code that calls a varargs method with a non-reifiable
varargs type, if the target method was compiled with Java 7 or later, the
compiler needn't generate a warning for the caller. (Note: This won't help
if you then run against code compiled with an earlier version, but the risk
is very low.)
  - When compiling a varargs method that could accept a non-reifiable
varargs type, the compiler should generate a warning on the varargs method
declaration.
  - If a non-varargs method overrides a varargs method with a non-reifiable
varargs type, generate a warning. The warning a) makes it clear that the
method can still be used like a varargs method, and b) warns the implementor
to prevent unsafe operations on the array.
  - If a varargs method with a non-reifiable varargs type overrides another
varargs method and the varargs parameter erases to a more-specific array
type than that of the overridden method, generate a warning. If a client
invokes the sub type implementation through the super type's interface, the
client may pass in a super type of the array type expected by the sub type's
method which will result in a ClassCastException at run time.
  - For a varargs argument with a component 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 on certain varargs method
declarations.

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

Bug #5048776: "promote varargs/non-varargs overrider diagnostics to errors"
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5048776

Bug #6227971: "generic inferrence bug in varargs"
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) 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