7

Looking at the type inference for generic types in the following example, I cannot say why methodAutoTypeInference works fine, but methodNotCompilable (which is almost the same) fails to compile and to manage it the compiler requires additional tricks like methodWorkaroundTypeHint or methodWorkaroundTypeCast.

What is the problem with methodNotCompilable causing the compiler to be unsure that the expression type and the method result type are compatible?

Stream<CharSequence> methodAutoTypeInference() {
  return Stream.of("a");
}

Stream<CharSequence> methodNotCompilable() {
  return Stream.of("a").distinct(); 
  //    incompatible types:  java.util.stream.Stream<java.lang.String>
  // cannot be converted to  java.util.stream.Stream<java.lang.CharSequence>
}

Stream<CharSequence> methodWorkaroundTypeHint() {
  return Stream.<CharSequence>of("a").distinct();
}

Stream<CharSequence> methodWorkaroundTypeCast() {
  return Stream.of((CharSequence) "a").distinct();
}
diziaq
  • 6,881
  • 16
  • 54
  • 96
  • 1
    Your title reminded me of this paper,: https://arxiv.org/pdf/1605.05274.pdf which was accepted at POPL 17, which is called "Java Generics are Turing Complete". It shows you how difficult it is to do some type inference on chains of generic method calls. The presentation really was funny to watch, i think it's on youtube – ghilesZ Feb 07 '21 at 16:37

3 Answers3

3

It's a compiler's feature. When you write Stream.of("a"); without return statement and generic type for method of() compiler gets the type from methods parameter "a" (Stream<String>).

When you write return Stream.of("a"); compiler gets the type from the return type (Stream<CharSequence>) because you don't set generic type. And when you write return Stream.of("a").distinct(); compiler get type from method's parameter (Stream<String> too), for calling .distinct() or .sorted()

In other words, you don't set generic type for a method of(), and the compiler got it from the method's return type when you don't have another stream's method, and got type from variable's type if you have other stream's methods.

Dmitrii B
  • 2,672
  • 3
  • 5
  • 14
3

This answer from JDK Developers themselves sort of covers the same area. Just notice that Stuart Marks says : "It might be possible for the compiler to be enhanced to cover this case in a future release". Though the case there is around lambdas, this is not very different than you have. It's just the way compiler (at the moment) works. And we are "stuck" with this.

You can sort of look under the resolution of how a compiler thinks about return Stream.of("a").distinct(); and decides what type to use, via:

javac --debug=verboseResolution=all

which in an un-documented flag. If you compile with that flag, you will see some big output:

  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: Object()
DeleteMe.java:60: Note: resolving method of in type Stream to candidate 1
        return Stream.of("a").distinct();
                     ^
  phase: BASIC
  with actuals: String
  with type-args: no arguments
  candidates:
      #0 not applicable method found: <T#1>of(T#1...)
        (cannot infer type-variable(s) T#1
          (argument mismatch; String cannot be converted to T#1[]))
      #1 applicable method found: <T#2>of(T#2)
        (partially instantiated to: (String)Stream<String>)
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>of(T#1...)
    T#2 extends Object declared in method <T#2>of(T#2)
DeleteMe.java:60: Note: Deferred instantiation of method <T>of(T)
        return Stream.of("a").distinct();
                        ^
  instantiated signature: (String)Stream<String>
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>of(T)
DeleteMe.java:60: Note: resolving method distinct in type Stream to candidate 0
        return Stream.of("a").distinct();
                             ^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: distinct()
  where T is a type-variable:
    T extends Object declared in interface Stream
DeleteMe.java:60: error: incompatible types: Stream<String> cannot be converted to Stream<CharSequence>
        return Stream.of("a").distinct();
                                      ^
1 error

I guess the most important part is this : (partially instantiated to: (String)Stream<String>)

You can see that the resolution of what type T is, is done a method call basis; not of the whole chain of calls. If it would would, btw, this would complicate compilers works quite a lot. For a simple chain like this, things might look trivial, but it gets far, far more tricky and involved when there are many. Especially, when you find out about non-denotable types, which will even further complicate this.

Eugene
  • 117,005
  • 15
  • 201
  • 306
1

You will find this behavior quite a bit. Especially when chaining Comparatorsas the compiler simply can't infer the appropriate types down the chain. Your solution below.

Stream<CharSequence> methodWorkaroundTypeHint() {
        return Stream.<CharSequence>of("a").distinct();
}

Is referred to as type witnessing and is the typical solution when dealing with these issues. Check this answer for more details on this.

WJS
  • 36,363
  • 4
  • 24
  • 39