JDK-8254571 : Erroneous generic type inference in a lambda expression with a checked exception
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 11,14,15,16
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2020-10-09
  • Updated: 2021-07-15
  • Resolved: 2021-06-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17 JDK 18
17 b28Fixed 18Fixed
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.8+10-LTS, mixed mode)

A DESCRIPTION OF THE PROBLEM :
A regression occurred when I migrated my code from java 8 to java 11, and javac 11 compiler report an error now systematically
 

REGRESSION : Last worked in version 8

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
compiling project with a javac 11. 

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
compiler must infer correct lambda return type and throws exception without errors
ACTUAL -
unreported exception IOException; must be caught or declared to be thrown
                .map(Lambda.function(resource -> Mapper.toString(resource),
                                                                ^
1 error

---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

class BugGenericSneakyThrowInference {
    public static void main(String[] args) {
        process(new ArrayList<>());
    }

    public static String[] process(List<Resource> resources) {

        return resources.stream()
                .map(Lambda.function(resource -> Mapper.toString(resource),
                        (res, ex) -> Lambda.sneakyThrow(ex)))
                .toArray(String[]::new);

    }
}

interface Resource {

    InputStream getInputStream() throws IOException;
}

interface Function1<T, R> extends Function<T, R> {

    interface WithException<T, R, E extends Throwable> {

        R apply(T t) throws E;

        default Function1<T, R> cast(BiFunction<T, Throwable, R> fn) {
            return t -> {
                try {
                    return apply(t);
                } catch (Throwable e) {
                    return fn.apply(t, e);
                }
            };
        }
    }
}

final class Lambda {

    public static <T, R, E extends Throwable> Function<T, R> function(Function1.WithException<T, R, E> function,
            BiFunction<T, Throwable, R> errorFn) {
        return function.cast(errorFn);
    }

    @SuppressWarnings("unchecked")
    public static <R, E extends Throwable> R sneakyThrow(Throwable e) throws E {
        throw (E) e;
    }

}

class Mapper {

    static String toString(Resource resource) throws IOException {
        return resource.getInputStream().toString();
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
public static String[] process(List<Resource> resources) {
        return resources.stream()
                .map(Lambda.function((Function1.WithException<Resource, String, IOException>)(resource -> Mapper.toString(resource)),
                        (res, ex) -> Lambda.sneakyThrow(ex)))
                .toArray(String[]::new);

    }

FREQUENCY : always



Comments
Changeset: 7e96318a Author: Vicente Romero <vromero@openjdk.org> Date: 2021-06-23 17:13:09 +0000 URL: https://git.openjdk.java.net/jdk17/commit/7e96318a1fb083d1c037c2c8d5a26f68fddf7a04
23-06-2021

the test is compiled without errors if this code is commented: @@ -194,13 +194,14 @@ public class Infer { if (!warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) { boolean shouldPropagate = shouldPropagate(mt.getReturnType(), resultInfo, inferenceContext); - InferenceContext minContext = shouldPropagate ? - inferenceContext.min(roots(mt, deferredAttrContext), true, warn) : + InferenceContext minContext = + //shouldPropagate ? + //inferenceContext.min(roots(mt, deferredAttrContext), true, warn) : inferenceContext;
17-06-2021

Minimal test (compiles in 8, not in 9+): class JDK8254571 { public static void test() { outer(nested(x -> mightThrow())); } static <A> void outer(Object o) {} static <B, C, E extends Throwable> B nested(Fun<C,E> fun) { return null; } interface Fun<X, Y extends Throwable> { void m(X t) throws Y; } static void mightThrow() throws Exception {} }
11-12-2020

Executing the test code on JDK 9 and above, throws compilation error: .\javac : C:\test\Test.java:16: error: unreported exception IOException; must be caught or declared to be thrown ================== JDK 8: Pass JDK 9 to JDK 16: Fail ==================== To fix the issue: Typecasting the parameters in Lambda.function(resource -> Mapper.toString(resource),(res, ex) -> Lambda.sneakyThrow(ex)) to Lambda.function((Function1.WithException<Resource, String, IOException>) resource -> Mapper.toString(resource), (res, ex) -> Lambda.sneakyThrow(ex)) fixes the issue.
13-10-2020