0

Given a generic Type Result<T> with the following partial implementation

public class Result<T> {

    /* fields, ctor, etc... */

    public Result<T> mergeWith(Result<? extends T> other) {
        /* fiddle with this and other (acting as producer) and return */
    }

    /* other methods */

}

and the use of it in a Visitor...

Interface:

public interface Visitor<T> {

    T visitFile(FileContext ctx);

    T aggregate(T left, T right);

    /* ... */

}

Implementation:

public class SomeVisitor implements Visitor<Result<? extends Node>> {

    // this works fine, as I can return subtypes of 'Node'
    @Override
    public Result<FileNode> visitFile() { }

    /* ... */

    @Override
    public Result<? extends Node> aggregate(Result<? extends Node> left,
                                               Result<? extends Node> right) {
        // This line completely blows up.
        Result<? extends Node> tempResult = left.mergeWith(right);
        // Expected type: 'Option<? extends capture of ? extends Node>'
        // Have type:     'Option<                     ? extends Node>'
    }

Callsite of SomeVisitor:

FileContext fileCtx = someOtherService.acquireFileContext();
SomeVisitor visitor = new SomeVisitor();
Result<FileNode> fileNodeResult = visitor.visitFile(fileCtx);
// process the result

The above example fails with the given type-mismatch error messages.

I have already tried to change the signature of .mergeWith to accept the much narrower Result<T> instead of a Result<? extends T>. This leads to an expected type of Result<capture of ? extends Node> in this example. And breaking it in other places, where <? extends T> is the correct generic type, since other is a producer in this case.

The only solution that actually works, is casting down both left and right to Result<Node>, merge them and then return:

@SuppressWarnings("unchecked")
Result<Node> tempLeft   = (Result<Node>) left;
@SuppressWarnings("unchecked")
Result<Node> tempRight  = (Result<Node>) right;
Result<Node> tempResult = tempLeft.mergeWith(tempRight);

Not only is that ugly and introduces temp variables, it's also not getting prettier when I inline the casts.

I would like to know if that is just the ugly truth about Java generics and the limit thereof or if I am doing something wrong.

baeda
  • 189
  • 12

2 Answers2

0

According to Result's merge declaration you can merge a Result<S> with a Result<T> if and only if T is a subtype of S.

In your aggregate method, left's type parameter is some unknown subclass of Node, and right's type parameter is also some unknown subclass of Node. There's no guarantee that right's type parameter is a subclass of left's.

What if you try this:

public <S extends Node, T extends S> Result<? extends Node> aggregate(Result<S> left, Result<T> right) {
    Result<? extends Node> temp = left.mergeWith(right);
    /** do stuff*/
    return temp;
}

Here, we declare two types S and T and require that S is a subtype of Node and T is a subtype of S.

Evan Darke
  • 604
  • 4
  • 7
  • A pretty nifty (yet very Java-y) way of getting around this. Sadly, the interface that I am implementing does describe aggregate as `T aggregate(T left, T right);` Maybe I should augment the question with this info :) Sorry 'bout that! – baeda May 29 '17 at 18:05
  • Not sure if this is possible without casting... However, it looks like you only need to cast once. tempLeft.mergeWith(right) should be valid. – Evan Darke May 29 '17 at 18:43
0

The problem comes from the ? captures, which are independent of each other. Don't use ? captures in class definitions. Use generic type identifiers:

public class SomeVisitor<T extends Node> implements Visitor<Result<T>> {

    public Result<FileNode> visitFile() { }

    /* ... */

    protected Result<T> aggregate(Result<T> left, Result<T> right) {
        Result<T> tempResult = left.mergeWith(right);
    }
}
Gold Dragon
  • 480
  • 2
  • 9
  • `implements Visitor>` does not compile, since the bounds of a generic type identifier must be defined on the implementing class, not the interface. – baeda May 29 '17 at 19:06
  • oops. I'll fix that. I intended to declare the class as `SomeVisitor` – Gold Dragon May 29 '17 at 19:06