Skip to content

TypeReference hack to ensure non-raw usage doesn't actually work. #697

@rzwitserloot

Description

@rzwitserloot

The source of TypeReference.java refers in its javadoc to specifically this comment from Neal Gafter's blogpost about Super TypeTokens:

Brian Oxley said...
Rather than check for a type parameter in the constructor, you can make it a syntax error to leave out:

public abstract TypeReference<R> implements Comparable<TypeReference<R>> {
  // ...
  
  public int compareTo(TypeReference<R> o) {
    // Need a real implementation.
    // Only saying "return 0" for illustration.
    return 0;
  }
}

Now this is legal:

new TypeReference<Object>() { };

But this is a syntax error for not implementing Comparable:

new TypeReference() { };

February 20, 2007 6:12 AM

The problem is, that comment is utter hogwash and it does absolutely nothing. And yet, jackson's TypeReference code copies this idea.

To reproduce, make a blank dir and save this to Test.java:

import java.lang.reflect.*;

abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
  protected final Type _type;
  protected TypeReference() {
    Type superClass = getClass().getGenericSuperclass();
    if (superClass instanceof Class<?>) { // sanity check, should never happen
      throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
    }
    _type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
  }

  @Override public int compareTo(TypeReference<T> o) { return 0; }

  public static void main(String[] args) {
    TypeReference raw = new TypeReference() {};
  }
}

(That is the current TypeRef code, with a main that tests the theory that the Comparable 'trick' does anything useful).

Then:

javac Test.java

and it just compiles. It shouldn't be compiling. (tried it on my system, with AdoptOpenJDK 14.0.1, and with OpenJDK8 as well. Perhaps this really did work in 6 or 7, but keeping this weirdness around for the sake of versions EOLed so long ago is perhaps not worthwhile).

SUGGESTED FIX:

  • Adjust the javadoc by removing the commentary what Comparator is for and the reference to the comment, keeping the link to the STT blogpost, though.
  • Remove implements Comparable<TypeReference<T>>.
  • Remove the compareTo method implementation.
  • Change the text of the thrown exception; remove the "Internal error: " part. Probably mention that a raw TypeReference isn't how you're supposed to use it, so that the programmer who runs into this gets a hint about what they are doing wrong.
  • Remove the // sanity check comment.

Alternatively, first investigate ways to make this code fail at compile time (which is the original intent of this non-working 'hack'), but I don't think there's any way to make it happen without preprocessors annotation processor hackery.

NB: I can provide a PR for this trivial change; the principal time effort is presumably to investigate if removing Comparable is going to cause backwards compatibility issues and if the Jackson dev team is willing to give up on the dream of compile-time-checking for an actual type vs. spending some time trying to find another inventive hack. I thought it best to kickstart discussion first.

IMPACT ANALYSIS OF BUG: Light, it just confuses folks. If you try this stunt of making a 'raw' TypeReference, then you get no compiler error (even though one is intended). However, resolving the expression new TypeReference() {} still throws that 'Internal error' exception. Someone who does that by accident is presumably confused and would be helped out a little more with better exception text, but that's all.

IMPACT ANALYSIS OF FIX: Well, anybody who decided to use a TypeReference as a Comparator for some bizarre reason is going to find that this is technically backwards incompatible, but presumably you'd be doing them a favour by breaking code that makes no sense at all. This 'fix' also means the notion that raw TypeReference STTs are turned into compiler errors no longer works (but then it never did, presumably).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions