Bug 542489 - [1.8] inference with recursive type doesn't check the bounds correctly
Summary: [1.8] inference with recursive type doesn't check the bounds correctly
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.10   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords:
Depends on:
Blocks:
 
Reported: 2018-12-06 15:26 EST by Rémi Forax CLA
Modified: 2022-05-27 17:41 EDT (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Rémi Forax CLA 2018-12-06 15:26:05 EST
Eclipse compiles the code below but it should not (javac correctly report the issue),
it seems there is something fishy in the way the type is inferred when calling sorted() with the comparator.

import java.util.Comparator;
import java.util.stream.Stream;

public class FakeEnum<T extends Comparable<T>> {
  private static final FakeEnum<String> S = new FakeEnum<>("");
  private static final FakeEnum<Integer> I = new FakeEnum<>(42);
  
  private T t;
  
  public FakeEnum(T t) {
    this.t = t;
  }
  
  T t() {
    return t;
  }
  
  public static void main(String[] args) {
    Stream.of(S, I).sorted(Comparator.comparing(FakeEnum::t)).forEach(System.out::println);
  }
}
Comment 1 Stephan Herrmann CLA 2018-12-07 18:13:12 EST
Thanks, Rémi

Is it really a matter of bounds checking? From what javac tells me its just a problem of t() not taking any argument, right?

$ javac -Xdiags:verbose FakeEnum.java                                                                                                               
FakeEnum.java:19: error: incompatible types: cannot infer type-variable(s) T#1,U                                                                                       
    Stream.of(S, 
I).sorted(Comparator.comparing(FakeEnum::t)).forEach(System.out::println);                                                                            
                              ^                                                                                                                       
    (argument mismatch; invalid method reference                                                                                                                       
      method t in class FakeEnum<T#2> cannot be applied to given types                                                                                                 
        required: no arguments                                                                                                                                         
        found: Object                                                                                                                                                  
        reason: actual and formal argument lists differ in length)                                                                                                     
  where T#1,U,T#2 are type-variables:                                                                                                                                  
    T#1 extends Object declared in method <T#1,U>comparing(Function<? super T#1,? extends U>)                                                                          
    U extends Comparable<? super U> declared in method <T#1,U>comparing(Function<? super T#1,? extends U>)                                                             
    T#2 extends Comparable<T#2> declared in class FakeEnum                                                                                                             
FakeEnum.java:19: error: method sorted in interface Stream<T> cannot be applied to given types;                                                                        
    Stream.of(S, 
I).sorted(Comparator.comparing(FakeEnum::t)).forEach(System.out::println);                                                                            
         ^                                                                                                                                            
  required: Comparator<? super FakeEnum<? extends INT#1>>                                                                                                              
  found: <any>                                                                                                                                                         
  reason: argument mismatch; inferred type does not conform to upper bound(s)                                                                                          
      inferred: CAP#1                                                                                                                                                  
      upper bound(s): Comparable<? super CAP#1>                                                                                                                        
  where T is a type-variable:                                                                                                                                          
    T extends Object declared in interface Stream                                                                                                                      
  where CAP#1 is a fresh type-variable:                                                                                                                                
    CAP#1 extends INT#3 from capture of ? extends INT#1
  where INT#1,INT#2,INT#3 are intersection types:
    INT#1 extends Object,Serializable,Comparable<? extends INT#2>
    INT#2 extends Object,Serializable,Comparable<?>
    INT#3 extends Object,Serializable,Comparable<? extends INT#2>
2 errors


The second error seems to be secondary (after inner inference failed).
Comment 2 Stephan Herrmann CLA 2018-12-07 18:44:06 EST
Spoke to quickly, javac's answer was distracting me from the option to interpret FakeEnum::t as a Function<FakeEnum,T>

Here's how ecj infers comparing(..):

public static Comparator<FakeEnum<? extends Object & Comparable<?> & Serializable>> comparing(Function<? super FakeEnum<? extends Object & Comparable<?> & Serializable>,? extends capture#1-of ? extends Object & Comparable<?> & Serializable>) 

T = FakeEnum<? extends Object & Comparable<?> & Serializable>
U = capture#1-of ? extends Object & Comparable<?> & Serializable

In this context FakeEnum::t is resolved as
  capture#1-of ? extends Object & Comparable<?> & Serializable t()
With declaring class
  FakeEnum<capture#1-of ? extends Object & Comparable<?> & Serializable>
IOW, invoked on an instance of T, t() returns U so it should be conform to
  Function<? super T, ? extends U>
no?

At this point it is far from obvious what would be wrong.

Rémi, can you explain why exactly you expect us to reject the program?
Comment 3 Stephan Herrmann CLA 2018-12-07 19:15:08 EST
I knew I've seen an unexpected "actual and formal argument lists differ in length" before: bug 511898. Related?
Comment 4 Rémi Forax CLA 2018-12-08 07:03:39 EST
Hi Stephan,
if you run the code you have a ClassCastException because while String (resp Integer) is comparable to itself, there are not comparable to each other.

So the type system should (i hope) protect us against this kind of error,
it's may be a bug in ecj or in the JLS i down know, as you said the error message reported by javac is rather mysterious.

For me, Stream.of() returns a FakeEnum<?>, FakeEnum::t should be Object which can not be a U extends Comparable<? super U> of Comparator.comparing. So it should not compile because Object is not comparable. The issue seems to be that the '?' is captured as a Cap#1 extends Comparable<Cap#1> which it should not, the capture here is not valid because it's a recursive type.
Comment 5 Stephan Herrmann CLA 2018-12-08 09:46:22 EST
(In reply to Rémi Forax from comment #4)
> if you run the code you have a ClassCastException because while String (resp
> Integer) is comparable to itself, there are not comparable to each other.

That's a valid point.
 
> So the type system should (i hope) protect us against this kind of error,
> it's may be a bug in ecj or in the JLS i down know, as you said the error
> message reported by javac is rather mysterious.

So who's there to ask this question? 
 
> For me, Stream.of() returns a FakeEnum<?>,

I don't see any unbounded wildcard in the picture.

This is the declaration:
    <T> Stream<T> of(T... values)

javac -XDverboseResolution=all reports the following instantiation:

FakeEnum.java:20: Note: Deferred instantiation of method <T>of(T...)
Stream.of(S,I).sorted(Comparator.comparing(FakeEnum::t)).forEach(System.out::println);
         ^
  instantiated signature: (FakeEnum<? extends INT#1>[])Stream<FakeEnum<? extends INT#1>>
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>of(T...)
  where INT#1,INT#2 are intersection types:
    INT#1 extends Object,Serializable,Comparable<? extends INT#2>
    INT#2 extends Object,Serializable,Comparable<?>

> FakeEnum::t should be Object

I don't thinks so. Unfortunately javac in verbose mode still doesn't show any sign of resolving the method reference in its second form (taking an argument).

> [...] The issue seems
> to be that the '?' is captured as a Cap#1 extends Comparable<Cap#1> which it
> should not, the capture here is not valid because it's a recursive type.

Where do you see this happening?
Where in JLS are recursive types prohibited? 
(which definition of "recursive type", btw.?).
Comment 6 Stephan Herrmann CLA 2018-12-08 10:40:48 EST
Transcript of how ecj is inferring:

Resolving Stream.of(S, I):
    T = lub(FakeEnum<Integer>, FakeEnum<String>)
    T = FakeEnum<? extends Object & Comparable<?> & Serializable>
So of() returns:
    Stream<FakeEnum<? extends Object & Comparable<?> & Serializable>>

This type is then the receiver type of .sorted(..), substitution yielding this signature:
    Stream<FakeEnum<? extends Object & Comparable<?> & Serializable>>
    sorted(Comparator<? super FakeEnum<? extends Object & Comparable<?> & Serializable>>)

Hence we have the following target type for Comparator.comparing:
    Comparator<? super FakeEnum<? extends Object & Comparable<?> & Serializable>>

Invocation type inference for <T#3,U#4>comparing(..) goes into resolution with this bounds:
    Dependency U#4 <: Comparable<? super U#4>
    TypeBound  T#3 :> FakeEnum<? extends Object & Comparable<?> & Serializable>

We first resolve
    T#3 = FakeEnum<? extends Object & Comparable<?> & Serializable>
then
    U#4 = capture#1-of ? extends Object & Comparable<?> & Serializable
so comparing(..) is instantiated as
    Comparator<FakeEnum<? extends Object & Comparable<?> & Serializable>>
    comparing(
      Function<
        ? super FakeEnum<? extends Object & Comparable<?> & Serializable>,
        ? extends capture#1-of ? extends Object & Comparable<?> & Serializable
      >)

Admittedly, the capture as a wildcard bound looks funny, but I'm not aware of
any rule prohibiting this.

In this context FakeEnum::t is resolve as
    capture#1-of ? extends Object & Comparable<?> & Serializable t()
with receiver
    FakeEnum<capture#1-of ? extends Object & Comparable<?> & Serializable>
to match the function type
    capture#1-of ? extends Object & Comparable<?> & Serializable 
    apply(FakeEnum<? extends Object & Comparable<?> & Serializable>)

In all this I could not see any obvious deviation from JLS nor reason to fail inference.


At this point I'll assume a bug in JLS (not finding any reason in JLS for rejecting the program, yet it is obviously not type correct).

Feel free to point out any bug in the above transcript or discuss this among the JSR 335 EG. I'll resume investigation once I see a better reason for rejecting than what javac is reporting. Until then I see nothing we could do.
Comment 7 Eclipse Genie CLA 2022-05-27 17:41:53 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.