diff options
author | Stephan Herrmann | 2014-03-08 20:25:56 +0000 |
---|---|---|
committer | Stephan Herrmann | 2014-03-09 14:13:30 +0000 |
commit | b1cdfe3ee438c0a38b27b1a4134346d549384d6a (patch) | |
tree | 3460cd75bf2c7d004eb9918d839d10ea61f271f8 | |
parent | 4ec3d8941998f287d44af5beff0b7f8ef9d972cc (diff) | |
download | eclipse.jdt.core-b1cdfe3ee438c0a38b27b1a4134346d549384d6a.tar.gz eclipse.jdt.core-b1cdfe3ee438c0a38b27b1a4134346d549384d6a.tar.xz eclipse.jdt.core-b1cdfe3ee438c0a38b27b1a4134346d549384d6a.zip |
Bug 429430 - [1.8] Lambdas and method reference infer wrong exception
type with generics (RuntimeException instead of IOException)
7 files changed, 139 insertions, 9 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java index a20de230ca..a8bc837761 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java @@ -17,6 +17,7 @@ package org.eclipse.jdt.core.tests.compiler.regression; import java.util.Map; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import junit.framework.Test; @@ -2587,6 +2588,7 @@ public void testBug428811() { "The method copyOf(Collection<T>) from the type MoreCollectors.ImmutableList<T> is never used locally\n" + "----------\n"); } +// all exceptions can be inferred to match public void testBug429430() { runConformTest( new String[] { @@ -2603,14 +2605,105 @@ public void testBug429430() { "\n" + " public static void main(String[] args) throws IOException {\n" + " InputStream in = new ByteArrayInputStream(\"hello\".getBytes());\n" + -// FIXME -// " close( x -> x.close(), in ); // eclipse: NO, javac: YES\n" + - " close( InputStream::close, in ); // eclipse: NO, javac: YES\n" + - " close( (Closer<InputStream, IOException>)InputStream::close, in ); // eclipse: YES, javac: YES\n" + + " close( x -> x.close(), in );\n" + + " close( InputStream::close, in );\n" + + " close( (Closer<InputStream, IOException>)InputStream::close, in );\n" + " }\n" + "}\n" }); } +// incompatible exceptions prevent suitable inference of exception type +public void testBug429430a() { + runNegativeTest( + new String[] { + "Main.java", + "import java.io.*;\n" + + "@SuppressWarnings(\"serial\") class EmptyStream extends Exception {}\n" + + "public class Main {\n" + + " public static interface Closer<T, X extends Exception> {\n" + + " void closeIt(T it) throws X;\n" + + " }\n" + + "\n" + + " public static <T, X extends Exception> void close( Closer<T, X> closer, T it ) throws X {\n" + + " closer.closeIt(it);\n" + + " }\n" + + "\n" + + " public static void main(String[] args) throws IOException, EmptyStream {\n" + + " InputStream in = new ByteArrayInputStream(\"hello\".getBytes());\n" + + " close( x -> { if (in.available() == 0) throw new EmptyStream(); x.close(); }, in );\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in Main.java (at line 14)\n" + + " close( x -> { if (in.available() == 0) throw new EmptyStream(); x.close(); }, in );\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Unhandled exception type Exception\n" + + "----------\n"); +} +// one of two incompatible exceptions is caught +// FIXME: should be possible to infer X to EmptyStream +public void _testBug429430b() { + runConformTest( + new String[] { + "Main.java", + "import java.io.*;\n" + + "@SuppressWarnings(\"serial\") class EmptyStream extends Exception {}\n" + + "public class Main {\n" + + " public static interface Closer<T, X extends Exception> {\n" + + " void closeIt(T it) throws X;\n" + + " }\n" + + "\n" + + " public static <T, X extends Exception> void close( Closer<T, X> closer, T it ) throws X {\n" + + " closer.closeIt(it);\n" + + " }\n" + + "\n" + + " public static void main(String[] args) throws EmptyStream {\n" + + " InputStream in = new ByteArrayInputStream(\"hello\".getBytes());\n" + + " close( x -> {\n" + + " try {\n" + + " x.close();\n" + + " } catch (IOException ioex) { throw new EmptyStream(); } \n" + + " }," + + " in);\n" + + " }\n" + + "}\n" + }); +} +// ensure type annotation on exception doesn't confuse the inference +public void testBug429430c() { + Map options = getCompilerOptions(); + options.put(CompilerOptions.OPTION_Store_Annotations, CompilerOptions.ENABLED); + runConformTest( + new String[] { + "Main.java", + "import java.io.*;\n" + + "import java.lang.annotation.*;\n" + + "@Target(ElementType.TYPE_USE) @interface Severe {}\n" + + "public class Main {\n" + + " public static interface Closer<T, X extends Exception> {\n" + + " void closeIt(T it) throws X;\n" + + " }\n" + + "\n" + + " public static <T, X extends Exception> void close( Closer<T, X> closer, T it ) throws X {\n" + + " closer.closeIt(it);\n" + + " }\n" + + "\n" + + " static @Severe IOException getException() { return new IOException(\"severe\"); }\n" + + " public static void main(String[] args) throws IOException {\n" + + " InputStream in = new ByteArrayInputStream(\"hello\".getBytes());\n" + + " close( x -> {\n" + + " if (in.available() > 0)\n" + + " x.close();\n" + + " else\n" + + " throw getException();\n" + + " },\n" + + " in);\n" + + " }\n" + + "}\n" + }, + options); +} public void testBug429490() { runConformTest( new String[] { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java index 4d9bbcf0b5..95337ade92 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java @@ -39,6 +39,7 @@ * Bug 426996 - [1.8][inference] try to avoid method Expression.unresolve()? * Bug 428352 - [1.8][compiler] Resolution errors don't always surface * Bug 429203 - [1.8][compiler] NPE in AllocationExpression.binding + * Bug 429430 - [1.8] Lambdas and method reference infer wrong exception type with generics (RuntimeException instead of IOException) * Jesper S Moller <jesper@selskabet.org> - Contributions for * bug 378674 - "The method can be declared as static" is wrong * Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for @@ -555,6 +556,7 @@ TypeBinding resolvePart3(ResolutionState state) { new ImplicitNullAnnotationVerifier(state.scope.environment(), compilerOptions.inheritNullAnnotations) .checkImplicitNullAnnotations(this.binding, null/*srcMethod*/, false, state.scope); } + recordExceptionsForEnclosingLambda(state.scope, this.binding.thrownExceptions); return allocationType; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java index acfed80b45..f1276bc85f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java @@ -33,6 +33,7 @@ * Bug 428294 - [1.8][compiler] Type mismatch: cannot convert from List<Object> to Collection<Object[]> * Bug 428786 - [1.8][compiler] Inference needs to compute the "ground target type" when reducing a lambda compatibility constraint * Bug 428980 - [1.8][null] simple expression as lambda body doesn't leverage null annotation on argument + * Bug 429430 - [1.8] Lambdas and method reference infer wrong exception type with generics (RuntimeException instead of IOException) * Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for * Bug 405104 - [1.8][compiler][codegen] Implement support for serializeable lambdas *******************************************************************************/ @@ -40,6 +41,8 @@ package org.eclipse.jdt.internal.compiler.ast; import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.INVOCATION_CONTEXT; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -114,6 +117,7 @@ public class LambdaExpression extends FunctionalExpression implements ReferenceC private boolean hasIgnoredMandatoryErrors = false; private ReferenceBinding classType; public int ordinal; + private Set thrownExceptions; private static final SyntheticArgumentBinding [] NO_SYNTHETIC_ARGUMENTS = new SyntheticArgumentBinding[0]; private static final Block NO_BODY = new Block(0, true); @@ -1045,6 +1049,20 @@ public class LambdaExpression extends FunctionalExpression implements ReferenceC } } + public void throwsException(TypeBinding exceptionType) { + if (this.expressionContext != INVOCATION_CONTEXT) + return; + if (this.thrownExceptions == null) + this.thrownExceptions = new HashSet<TypeBinding>(); + this.thrownExceptions.add(exceptionType); + } + + public Set<TypeBinding> getThrownExceptions() { + if (this.thrownExceptions == null) + return Collections.emptySet(); + return this.thrownExceptions; + } + public void generateCode(ClassScope classScope, ClassFile classFile) { int problemResetPC = 0; classFile.codeStream.wideMode = false; @@ -1244,4 +1262,4 @@ public class LambdaExpression extends FunctionalExpression implements ReferenceC } return this.classType = new LambdaTypeBinding(); } -}
\ No newline at end of file +} diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java index 2cd3ff7b18..d6815b7071 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java @@ -50,6 +50,7 @@ * Bug 427438 - [1.8][compiler] NPE at org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:280) * Bug 426996 - [1.8][inference] try to avoid method Expression.unresolve()? * Bug 428352 - [1.8][compiler] Resolution errors don't always surface + * Bug 429430 - [1.8] Lambdas and method reference infer wrong exception type with generics (RuntimeException instead of IOException) * Jesper S Moller - Contributions for * Bug 378674 - "The method can be declared as static" is wrong * Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for @@ -853,6 +854,7 @@ public TypeBinding resolveType(BlockScope scope) { if (this.typeArguments != null && this.binding.original().typeVariables == Binding.NO_TYPE_VARIABLES) { scope.problemReporter().unnecessaryTypeArgumentsForMethodInvocation(this.binding, this.genericTypeArguments, this.typeArguments); } + recordExceptionsForEnclosingLambda(scope, this.binding.thrownExceptions); return (this.resolvedType.tagBits & TagBits.HasMissingType) == 0 ? this.resolvedType : null; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java index dcc8349374..9f3a2da65d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java @@ -32,6 +32,7 @@ * Bug 424415 - [1.8][compiler] Eventual resolution of ReferenceExpression is not seen to be happening. * Bug 418537 - [1.8][null] Fix null type annotation analysis for poly conditional expressions * Bug 428352 - [1.8][compiler] Resolution errors don't always surface + * Bug 429430 - [1.8] Lambdas and method reference infer wrong exception type with generics (RuntimeException instead of IOException) * Andy Clement - Contributions for * Bug 383624 - [1.8][compiler] Revive code generation support for type annotations (from Olivier's work) * Bug 409250 - [1.8][compiler] Various loose ends in 308 code generation @@ -366,4 +367,16 @@ protected MethodBinding findConstructorBinding(BlockScope scope, Invocation site resolvePolyExpressionArguments(site, ctorBinding, argumentTypes, scope); return ctorBinding; } +/** + * If an exception-throwing statement is resolved within the scope of a lambda, record the exception type(s). + * It is likely wrong to do this during resolve, should probably use precise flow information. + */ +protected void recordExceptionsForEnclosingLambda(BlockScope scope, TypeBinding... thrownExceptions) { + MethodScope methodScope = scope.methodScope(); + if (methodScope != null && methodScope.referenceContext instanceof LambdaExpression) { + LambdaExpression lambda = (LambdaExpression) methodScope.referenceContext; + for (int i = 0; i < thrownExceptions.length; i++) + lambda.throwsException(thrownExceptions[i]); + } +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThrowStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThrowStatement.java index ea45c4e5e8..3e3ec22191 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThrowStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThrowStatement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -15,6 +15,7 @@ * bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * Bug 429430 - [1.8] Lambdas and method reference infer wrong exception type with generics (RuntimeException instead of IOException) *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -71,6 +72,7 @@ public StringBuffer printStatement(int indent, StringBuffer output) { public void resolve(BlockScope scope) { this.exceptionType = this.exception.resolveType(scope); + recordExceptionsForEnclosingLambda(scope, this.exceptionType); if (this.exceptionType != null && this.exceptionType.isValidBinding()) { if (this.exceptionType == TypeBinding.NULL) { if (scope.compilerOptions().complianceLevel <= ClassFileConstants.JDK1_3){ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExceptionFormula.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExceptionFormula.java index f1590d786a..3e96f09f42 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExceptionFormula.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExceptionFormula.java @@ -76,9 +76,9 @@ public class ConstraintExceptionFormula extends ConstraintFormula { e[n++] = (InferenceVariable) thrown[i]; // thrown[i] is not a proper type, since it's an exception it must be an inferenceVariable, right? TypeBinding[] ePrime = null; if (this.left instanceof LambdaExpression) { -// TODO find exceptions thrown by the lambda's body, see 18.2.5 bullet 5 -// ((LambdaExpression)this.left). -// InferenceContext18.missingImplementation("NYI"); + LambdaExpression lambda = ((LambdaExpression) this.left).getResolvedCopyForInferenceTargeting(this.right); + Set<TypeBinding> ePrimeSet = lambda.getThrownExceptions(); + ePrime = ePrimeSet.toArray(new TypeBinding[ePrimeSet.size()]); } else { ReferenceExpression referenceExpression = (ReferenceExpression)this.left; MethodBinding method = referenceExpression.findCompileTimeMethodTargeting(this.right, scope); |