diff options
author | Stephan Herrmann | 2013-12-10 15:12:18 +0000 |
---|---|---|
committer | Stephan Herrmann | 2013-12-11 00:44:25 +0000 |
commit | d72b33df7e926bdba104646a573cf60af359abd4 (patch) | |
tree | adcad7c148754ab85e9ddf6a9b8143ef93dc5d14 | |
parent | d3de41afc4bae04cfee3367369f0e9174a3b79dd (diff) | |
download | eclipse.jdt.core-d72b33df7e926bdba104646a573cf60af359abd4.tar.gz eclipse.jdt.core-d72b33df7e926bdba104646a573cf60af359abd4.tar.xz eclipse.jdt.core-d72b33df7e926bdba104646a573cf60af359abd4.zip |
Resolved Bug 419048
- revamped much of the control for piecemeal / repeated resolving
19 files changed, 360 insertions, 78 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java index 200715895b..bcd6bbdadb 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java @@ -22,7 +22,7 @@ import junit.framework.Test; public class LambdaExpressionsTest extends AbstractRegressionTest { static { - TESTS_NAMES = new String[] { "testBug419048_1"}; +// TESTS_NAMES = new String[] { "testReferenceExpressionInference3a"}; // TESTS_NUMBERS = new int[] { 50 }; // TESTS_RANGE = new int[] { 11, -1 }; } @@ -1934,7 +1934,6 @@ public void testReferenceExpressionInference3a() { } // previous test demonstrates that a solution exists, just inference doesn't find it. -// FAIL: the second error is intermittently reported twice public void testReferenceExpressionInference3b() { runNegativeTest( new String[] { @@ -1955,7 +1954,7 @@ public void testReferenceExpressionInference3b() { "1. ERROR in X.java (at line 7)\n" + " I<X,String> x2s = compose(this::bar, this::i2s);\n" + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Type mismatch: cannot convert from I<Object,Object> to I<X,String>\n" + + "Type mismatch: cannot convert from I<Object,Object> to I<X,String>\n" + "----------\n" + "2. ERROR in X.java (at line 7)\n" + " I<X,String> x2s = compose(this::bar, this::i2s);\n" + @@ -2031,6 +2030,64 @@ public void testBug419048_1() { "}\n" }); } + +public void testBug419048_2() { + runConformTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "import java.util.function.*;\n" + + "import java.util.stream.*;\n" + + "public class X {\n" + + " public void test() {\n" + + " List<Person> roster = new ArrayList<>();\n" + + " \n" + + " Map<String, Person> map = \n" + + " roster\n" + + " .stream()\n" + + " .collect(\n" + + " Collectors.toMap(\n" + + " Person::getLast,\n" + + " Function.identity()\n" + + " ));\n" + + " }\n" + + "}\n" + + "class Person {\n" + + " public String getLast() { return null; }\n" + + "}\n" + }); +} + +public void testBug419048_3() { + runConformTest( + new String[] { + "X.java", + "import java.util.*;\n" + + "import java.util.function.*;\n" + + "import java.util.stream.*;\n" + + "public class X {\n" + + " public void test() {\n" + + " List<Person> roster = new ArrayList<>();\n" + + " \n" + + " Map<String, Person> map = \n" + + " roster\n" + + " .stream()\n" + + " .collect(\n" + + " Collectors.toMap(\n" + + " new Function<Person, String>() {\n" + + " public String apply(Person p) { \n" + + " return p.getLast(); \n" + + " } \n" + + " },\n" + + " Function.identity()\n" + + " ));\n" + + " }\n" + + "}\n" + + "class Person {\n" + + " public String getLast() { return null; }\n" + + "}\n" + }); +} public static Class testClass() { return LambdaExpressionsTest.class; } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java index 8b45389090..7baa493779 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java @@ -5744,11 +5744,13 @@ public void test401939a() { "----------\n" + "2. ERROR in X.java (at line 8)\n" + " foo(()->{ if (1 == 2) throw new RuntimeException(); });\n" + - " ^^^\n" + - "The method foo(I) in the type X is not applicable for the arguments (() -> {\n" + - " if ((1 == 2))\n" + - " throw new RuntimeException();\n" + - "})\n" + + " ^^^^\n" + + "This method must return a result of type int\n" + + "----------\n" + + "3. WARNING in X.java (at line 8)\n" + + " foo(()->{ if (1 == 2) throw new RuntimeException(); });\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Dead code\n" + "----------\n"); } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=401939, [1.8][compiler] Incorrect shape analysis leads to method resolution failure . diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/OverloadResolutionTest8.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/OverloadResolutionTest8.java index 85009de74b..ff1c896a80 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/OverloadResolutionTest8.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/OverloadResolutionTest8.java @@ -152,11 +152,18 @@ public void test004() { "----------\n" + "1. ERROR in X.java (at line 11)\n" + " goo(()-> { \n" + - " ^^^\n" + - "The method goo(J) in the type X is not applicable for the arguments (() -> {\n" + - " boolean y = true;\n" + - " while (y) ;\n" + - "})\n" + + " ^^^^\n" + + "This method must return a result of type int\n" + + "----------\n" + + "2. ERROR in X.java (at line 15)\n" + + " goo(()-> { \n" + + " ^^^^\n" + + "This method must return a result of type int\n" + + "----------\n" + + "3. ERROR in X.java (at line 18)\n" + + " goo(()-> { \n" + + " ^^^^\n" + + "This method must return a result of type int\n" + "----------\n"); } public void test005() { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index f99625febd..00a329bcd2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -53,6 +53,7 @@ import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedGenericMethodBinding; import org.eclipse.jdt.internal.compiler.lookup.PolyTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; @@ -626,51 +627,96 @@ public abstract class ASTNode implements TypeConstants, TypeIds { return output; } - public static boolean resolvePolyExpressionArguments(BlockScope scope, MethodBinding methodBinding, Expression [] arguments, TypeBinding[] argumentTypes) { + /** + * After a first round of method lookup has produces 'methodBinding' but when poly expressions have been seen as arguments, + * inspect the arguments to trigger another round of resolving with improved target types from the methods parameters. + * If this resolving produces better types for any arguments, update the 'argumentTypes' array in-place and + * signal by returning null that the outer should perform another round of method lookup. + * @param invocation the outer invocation which is being resolved + * @param scope scope + * @param methodBinding the candidate method produced by the first round of lookup + * @param argumentTypes the argument types as collected from first resolving the invocation arguments and as used for + * the first round of method lookup. + * @return true signals that the caller should try another round of method lookup + */ + public static boolean resolvePolyExpressionArguments(Invocation invocation, BlockScope scope, MethodBinding methodBinding, TypeBinding[] argumentTypes) { + int problemReason = 0; MethodBinding candidateMethod; if (methodBinding.isValidBinding()) { candidateMethod = methodBinding; } else if (methodBinding instanceof ProblemMethodBinding) { + problemReason = methodBinding.problemId(); candidateMethod = ((ProblemMethodBinding) methodBinding).closestMatch; } else { candidateMethod = null; } + boolean polyExpressionsHaveErrors = false; boolean hasUpdatedInner = false; if (candidateMethod != null) { boolean variableArity = candidateMethod.isVarargs(); final TypeBinding[] parameters = candidateMethod.parameters; final int parametersLength = parameters.length; + Expression [] arguments = invocation.arguments(); for (int i = 0, length = arguments == null ? 0 : arguments.length; i < length; i++) { Expression argument = arguments[i]; TypeBinding parameterType = i < parametersLength ? parameters[i] : variableArity ? parameters[parametersLength - 1] : null; - if (argumentTypes[i] instanceof PolyTypeBinding) { + TypeBinding updatedArgumentType = null; + + if (argument instanceof Invocation) { + boolean invocationFinished = false; + Invocation innerInvocation = (Invocation) argument; + MethodBinding updatedMethod = innerInvocation.binding(); + if (innerInvocation.hasInferenceFinished() && updatedMethod != null) { + // outer invocation already included invocation type inference for this inner + invocationFinished = true; + } else { + // Inner Inference? + InferenceContext18 infCtx18 = innerInvocation.inferenceContext(); + if (infCtx18 != null) { + // Previous time around we only performed Invocation Applicability Inference, do the rest now: + updatedMethod = infCtx18.getInvocationTypeInferenceSolution(innerInvocation, parameterType); + if (updatedMethod != null) { + innerInvocation.updateBindings(updatedMethod); + invocationFinished = true; + } + } + } + if (invocationFinished) { + if (updatedMethod.isConstructor()) + updatedArgumentType = updatedMethod.declaringClass; + else + updatedArgumentType = updatedMethod.returnType; + } + } + + if (updatedArgumentType == null && argumentTypes[i] instanceof PolyTypeBinding) { argument.setExpressionContext(parameterType != null ? ExpressionContext.INVOCATION_CONTEXT: ExpressionContext.ASSIGNMENT_CONTEXT); // force the errors to surface. if (variableArity && i >= parametersLength - 1) argument.tagAsEllipsisArgument(); - argument.setExpectedType(parameterType); - TypeBinding argumentType = argument.resolveType(scope); -// if (argumentType == null || !argumentType.isValidBinding()) -// polyExpressionsHaveErrors = true; -// if (argument instanceof LambdaExpression && ((LambdaExpression) argument).hasErrors()) -// polyExpressionsHaveErrors = true; - } else if (argument instanceof Invocation) { - Invocation invocation = (Invocation) argument; - InferenceContext18 infCtx18 = invocation.inferenceContext(); - if (infCtx18 != null) { - // Previous time around we only performed Invocation Applicability Inference, do the rest now: - MethodBinding updatedMethod = infCtx18.getInvocationTypeInferenceSolution(invocation, parameterType); - if (updatedMethod != null) { - invocation.updateBindings(updatedMethod); - // update the argumentTypes array (supposed to be owned by the calling method) - // in order to give better information into a second round of method lookup: - if (updatedMethod.isConstructor()) - argumentTypes[i] = updatedMethod.declaringClass; - else - argumentTypes[i] = updatedMethod.returnType; - // signal that we need another round of method lookup: - hasUpdatedInner = true; - } + // perform the level of resolving suitable for the state of affairs at the enclosing context: + if (invocation.hasInferenceFinished()) { + argument.setExpectedType(parameterType); + updatedArgumentType = argument.resolveType(scope); + } else { + updatedArgumentType = argument.resolveTentatively(scope, parameterType); } + TypeBinding argumentType = argument.resolvedType; + if (argumentType == null || !argumentType.isValidBinding()) + polyExpressionsHaveErrors = true; + if (argument instanceof LambdaExpression && ((LambdaExpression) argument).hasErrors()) + polyExpressionsHaveErrors = true; + } + + if (!polyExpressionsHaveErrors // don't propagate typing results from polies with errors + && problemReason != ProblemReasons.Ambiguous // preserve this error + && updatedArgumentType != null // do we have a relevant update? ... + && !(updatedArgumentType instanceof PolyTypeBinding) + && TypeBinding.notEquals(updatedArgumentType, argumentTypes[i])) + { + // update the argumentTypes array (supposed to be owned by the calling method) + // in order to give better information into a second round of method lookup: + argumentTypes[i] = updatedArgumentType; + hasUpdatedInner = true; } } } 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 79a9fa6f9d..d3e65f35ab 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 @@ -446,7 +446,7 @@ public TypeBinding resolveType(BlockScope scope) { } ReferenceBinding allocationType = (ReferenceBinding) this.resolvedType; - this.binding = findConstructorBinding(scope, this, allocationType, this.arguments, argumentTypes, polyExpressionSeen); + this.binding = findConstructorBinding(scope, this, allocationType, argumentTypes, polyExpressionSeen); if (!this.binding.isValidBinding()) { if (this.binding.declaringClass == null) { @@ -622,11 +622,18 @@ public Expression[] arguments() { return this.arguments; } public int inferenceKind() { - return this.inferenceKind; + return (this.inferenceKind & InferenceContext18.INFERENCE_KIND_MASK); } public void setInferenceKind(int checkKind) { this.inferenceKind = checkKind; } +public void markInferenceFinished() { + this.inferenceKind |= InferenceContext18.CHECK_FINISHED; +} +public boolean hasInferenceFinished() { + return this.inferenceKind == 0 // only relevant if inference has been started + || (this.inferenceKind & InferenceContext18.CHECK_FINISHED) != 0; +} public TypeBinding updateBindings(MethodBinding updatedBinding) { this.binding = updatedBinding; return this.resolvedType = updatedBinding.declaringClass; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java index a66fee3ee5..a159f74459 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java @@ -423,7 +423,7 @@ public class ExplicitConstructorCall extends Statement implements Invocation, Ex if (receiverType == null) { return; } - this.binding = findConstructorBinding(scope, this, receiverType, this.arguments, argumentTypes, polyExpressionSeen); + this.binding = findConstructorBinding(scope, this, receiverType, argumentTypes, polyExpressionSeen); if (this.binding.isValidBinding()) { if ((this.binding.tagBits & TagBits.HasMissingType) != 0) { @@ -498,11 +498,18 @@ public class ExplicitConstructorCall extends Statement implements Invocation, Ex return this.inferenceContext; } public int inferenceKind() { - return this.inferenceKind; + return (this.inferenceKind & InferenceContext18.INFERENCE_KIND_MASK); } public void setInferenceKind(int checkKind) { this.inferenceKind = checkKind; } + public void markInferenceFinished() { + this.inferenceKind |= InferenceContext18.CHECK_FINISHED; + } + public boolean hasInferenceFinished() { + return this.inferenceKind == 0 // only relevant if inference has been started + || (this.inferenceKind & InferenceContext18.CHECK_FINISHED) != 0; + } public TypeBinding updateBindings(MethodBinding updatedBinding) { this.binding = updatedBinding; return TypeBinding.VOID; // not an expression diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java index c16f25fbb1..59c40f5ede 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java @@ -1053,6 +1053,25 @@ public TypeBinding resolveTypeExpecting(BlockScope scope, TypeBinding expectedTy } return expressionType; } + +/** + * If we might still be in the context of an unfinished outer inference, use this method to + * tentatively resolve this expression without leaving any undesired traces, in case we will + * come back with a better target type later. + */ +public TypeBinding resolveTentatively(BlockScope scope, TypeBinding targetType) { + return resolveType(scope); // default is to do full resolution in just this one step +} + +/** + * Once outer contexts have finalized the target type for this expression, + * perform any checks that might have been delayed previously. + * @param targetType the final target type (aka expectedType) for this expression. + */ +public void checkAgainstFinalTargetType(TypeBinding targetType) { + // nop, subclasses may choose to do real stuff here +} + /** * Returns true if the receiver is forced to be of raw type either to satisfy the contract imposed * by a super type or because it *is* raw and the current type has no control over it (i.e the rawness diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java index 08b9a39556..37c97fcefb 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java @@ -35,9 +35,10 @@ import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBindingVisitor; -public abstract class FunctionalExpression extends Expression { +public abstract class FunctionalExpression extends Expression implements PolyExpression { TypeBinding expectedType; + boolean expectedIsFinalTargetType = false; // flip to true once the expression context has determined the final target type for this expression public MethodBinding descriptor; public MethodBinding binding; // Code generation binding. May include synthetics. See getMethodBinding() protected MethodBinding actualMethodBinding; // void of synthetics. @@ -49,7 +50,8 @@ public abstract class FunctionalExpression extends Expression { public BlockScope enclosingScope; protected boolean ellipsisArgument; protected static IErrorHandlingPolicy silentErrorHandlingPolicy = DefaultErrorHandlingPolicies.ignoreAllProblems(); - + protected boolean hasInferenceFinished = false; + public FunctionalExpression(CompilationResult compilationResult) { this.compilationResult = compilationResult; } @@ -83,6 +85,14 @@ public abstract class FunctionalExpression extends Expression { public boolean isPolyExpression() { return true; // always as per introduction of part D, JSR 335 } + + public void markInferenceFinished() { + this.hasInferenceFinished = true; + } + + public boolean hasInferenceFinished() { + return this.hasInferenceFinished; + } public TypeBinding invocationTargetType() { if (this.expectedType == null) return null; @@ -135,19 +145,53 @@ public abstract class FunctionalExpression extends Expression { /** During inference: Try to find an applicable method binding without causing undesired side-effects. */ public MethodBinding findCompileTimeMethodTargeting(TypeBinding targetType, Scope scope) { - setExpectedType(targetType); + if (this.hasInferenceFinished) + return this.binding; + return (MethodBinding)internalResolveTentatively(targetType, scope)[0]; + } + + /** During inference: Try to resolve the type of this expression without causing undesired side-effects. */ + public TypeBinding resolveTentatively(BlockScope scope, TypeBinding targetType) { + if (this.hasInferenceFinished) + return this.resolvedType; + return (TypeBinding)internalResolveTentatively(targetType, scope)[1]; + } + + private Object[] internalResolveTentatively(TypeBinding targetType, Scope scope) { // TODO: convert return to Pair<MethodBinding,TypeBinding> + // FIXME: could enclosingScope still be null here?? IErrorHandlingPolicy oldPolicy = this.enclosingScope.problemReporter().switchErrorHandlingPolicy(silentErrorHandlingPolicy); + ExpressionContext previousContext = this.expressionContext; + MethodBinding previousBinding = this.binding; + MethodBinding previousDescriptor = this.descriptor; try { + setExpressionContext(INVOCATION_CONTEXT); + setExpectedType(targetType); this.binding = null; - resolveType(this.enclosingScope); - return this.binding; + TypeBinding type = resolveType(this.enclosingScope); + return new Object[] { this.binding, type }; } finally { this.enclosingScope.problemReporter().switchErrorHandlingPolicy(oldPolicy); - this.binding = null; - setExpectedType(null); + // remove *any relevant* traces of this 'inofficial' resolving: + this.binding = previousBinding; + this.descriptor = previousDescriptor; + this.hasInferenceFinished = false; + setExpressionContext(previousContext); + this.expectedType = null; // don't call setExpectedType(null), would NPE + cleanUpAfterTentativeResolve(); } } + void cleanUpAfterTentativeResolve() { + // nop. Subclasses: do your homework! + } + + public void checkAgainstFinalTargetType(TypeBinding targetType) { + if (this.expectedIsFinalTargetType) + return; // already checked + this.expectedIsFinalTargetType = true; + resolveTypeExpecting(this.enclosingScope, targetType); + } + class VisibilityInspector extends TypeBindingVisitor { private Scope scope; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java index bd0b8503d9..2dec011207 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java @@ -26,7 +26,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; * <li>Invocation Type Inference (18.5.2).</li> * </ul> */ -public interface Invocation extends InvocationSite { +public interface Invocation extends InvocationSite, PolyExpression { Expression[] arguments(); 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 a743b6959e..b43406cc87 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 @@ -330,6 +330,26 @@ public class LambdaExpression extends FunctionalExpression implements ReferenceC return this.resolvedType; } + void cleanUpAfterTentativeResolve() { + // remove traces of attempts to resolve the argument before we have the final target type: + if (this.arguments != null) + for (int i = 0; i < this.arguments.length; i++) + this.arguments[i].binding = null; + // also remove traces of references to this argument + if (this.body != null) + this.body.traverse(new ASTVisitor() { + public boolean visit(SingleNameReference singleNameReference, BlockScope blockScope) { + if (singleNameReference.binding instanceof LocalVariableBinding) { + singleNameReference.bits &= ~ASTNode.RestrictiveFlagMASK; + singleNameReference.bits |= Binding.VARIABLE; + singleNameReference.actualReceiverType = null; + singleNameReference.binding = null; + } + return true; + } + }, this.scope); + } + public boolean argumentsTypeElided() { return this.arguments.length > 0 && this.arguments[0].hasElidedType(); } 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 23fe301e39..6bc7fa0cc5 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 @@ -846,7 +846,7 @@ protected void findMethodBinding(BlockScope scope, TypeBinding[] argumentTypes, : scope.getMethod(this.actualReceiverType, this.selector, argumentTypes, this); if (polyExpressionSeen) - if (resolvePolyExpressionArguments(scope, this.binding, this.arguments, argumentTypes)) { + if (resolvePolyExpressionArguments(this, scope, this.binding, argumentTypes)) { this.binding = this.receiver.isImplicitThis() ? scope.getImplicitMethod(this.selector, argumentTypes, this) : scope.getMethod(this.actualReceiverType, this.selector, argumentTypes, this); @@ -968,7 +968,14 @@ public void setInferenceKind(int checkKind) { this.inferenceKind = checkKind; } public int inferenceKind() { - return this.inferenceKind; + return (this.inferenceKind & InferenceContext18.INFERENCE_KIND_MASK); +} +public void markInferenceFinished() { + this.inferenceKind |= InferenceContext18.CHECK_FINISHED; +} +public boolean hasInferenceFinished() { + return this.inferenceKind == 0 // only relevant if inference has been started + || (this.inferenceKind & InferenceContext18.CHECK_FINISHED) != 0; } public TypeBinding updateBindings(MethodBinding updatedBinding) { this.binding = updatedBinding; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/PolyExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/PolyExpression.java new file mode 100644 index 0000000000..6b741df020 --- /dev/null +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/PolyExpression.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2013 GK Software AG. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * This is an implementation of an early-draft specification developed under the Java + * Community Process (JCP) and is made available for testing and evaluation purposes + * only. The code is not compatible with any specification of the JCP. + * + * Contributors: + * Stephan Herrmann - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.ast; + +/** + * Abstraction of AST nodes that can be poly expressions and participate in type inference. + * (not included: ConditionalExpression). + */ +public interface PolyExpression { + + /** Mark that inference for this expression has finished. */ + void markInferenceFinished(); + + /** Answer whether type inference for this expression has finished (if needed). */ + boolean hasInferenceFinished(); +} diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java index fd36b1b44b..3301a0e063 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java @@ -476,7 +476,7 @@ public class QualifiedAllocationExpression extends AllocationExpression { receiverType = this.type.resolvedType = scope.environment().createParameterizedType(((ParameterizedTypeBinding) receiverType).genericType(), inferredTypes, ((ParameterizedTypeBinding) receiverType).enclosingType()); } ReferenceBinding allocationType = (ReferenceBinding) receiverType; - this.binding = findConstructorBinding(scope, this, allocationType, this.arguments, argumentTypes, polyExpressionSeen); + this.binding = findConstructorBinding(scope, this, allocationType, argumentTypes, polyExpressionSeen); if (this.binding.isValidBinding()) { if (isMethodUseDeprecated(this.binding, scope, true)) { @@ -544,7 +544,7 @@ public class QualifiedAllocationExpression extends AllocationExpression { } MethodBinding inheritedBinding = scope.getConstructor(anonymousSuperclass, argumentTypes, this); if (polyExpressionSeen) - resolvePolyExpressionArguments(scope, inheritedBinding, this.arguments, argumentTypes); + resolvePolyExpressionArguments(this, scope, inheritedBinding, argumentTypes); if (!inheritedBinding.isValidBinding()) { if (inheritedBinding.declaringClass == null) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java index 835213c612..dfec42dc96 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java @@ -519,6 +519,17 @@ public class ReferenceExpression extends FunctionalExpression implements Invocat return this.resolvedType; // Phew ! } + public MethodBinding findCompileTimeMethodTargeting(TypeBinding targetType, Scope scope) { + if (this.exactMethodBinding != null) { + // TODO: shouldn't extactMethodBinding already be parameterized? + if (this.exactMethodBinding.typeVariables != Binding.NO_TYPE_VARIABLES && this.resolvedTypeArguments != null) { + return scope.environment().createParameterizedGenericMethod(this.exactMethodBinding, this.resolvedTypeArguments); + } + return this.exactMethodBinding; + } + return super.findCompileTimeMethodTargeting(targetType, scope); + } + public boolean isConstructorReference() { return CharOperation.equals(this.selector, ConstantPool.Init); } @@ -610,6 +621,8 @@ public class ReferenceExpression extends FunctionalExpression implements Invocat } public boolean isCompatibleWith(TypeBinding left, Scope scope) { + if (this.hasInferenceFinished) + return this.resolvedType.isCompatibleWith(left, scope); // 15.28.2 final MethodBinding sam = left.getSingleAbstractMethod(this.enclosingScope); if (sam == null || !sam.isValidBinding()) @@ -624,6 +637,7 @@ public class ReferenceExpression extends FunctionalExpression implements Invocat this.enclosingScope.problemReporter().switchErrorHandlingPolicy(oldPolicy); isCompatible = this.binding != null && this.binding.isValidBinding(); this.binding = null; + this.hasInferenceFinished = false; setExpectedType(null); } return isCompatible; 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 423e020f71..995b68b7a8 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 @@ -334,10 +334,10 @@ public ExpressionContext getExpressionContext() { * after applicability checking (18.5.1) to include more information into the final * invocation type inference (18.5.2). */ -protected MethodBinding findConstructorBinding(BlockScope scope, InvocationSite site, ReferenceBinding receiverType, Expression[] args, TypeBinding[] argumentTypes, boolean polyExpressionSeen) { +protected MethodBinding findConstructorBinding(BlockScope scope, Invocation site, ReferenceBinding receiverType, TypeBinding[] argumentTypes, boolean polyExpressionSeen) { MethodBinding ctorBinding = scope.getConstructor(receiverType, argumentTypes, site); if (polyExpressionSeen) { - if (resolvePolyExpressionArguments(scope, ctorBinding, args, argumentTypes)) + if (resolvePolyExpressionArguments(site, scope, ctorBinding, argumentTypes)) return scope.getConstructor(receiverType, argumentTypes, site); } return ctorBinding; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java index c293e81f98..b48a3207f5 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java @@ -152,9 +152,8 @@ class ConstraintExpressionFormula extends ConstraintFormula { MethodBinding functionType = t.getSingleAbstractMethod(inferenceContext.scope); if (functionType == null) return FALSE; - // TODO: check strategy for: potentially-applicable method for the method reference when targeting T (15.28.1), - reference.resolveTypeExpecting(reference.enclosingScope, t); - MethodBinding potentiallyApplicable = reference.binding; + // potentially-applicable method for the method reference when targeting T (15.28.1), + MethodBinding potentiallyApplicable = reference.findCompileTimeMethodTargeting(t, inferenceContext.scope); if (potentiallyApplicable == null) return FALSE; if (reference.isExactMethodReference()) { @@ -165,7 +164,7 @@ class ConstraintExpressionFormula extends ConstraintFormula { int k = pPrime.length; int offset = 0; if (n == k+1) { - newConstraints.add(new ConstraintTypeFormula(p[0], reference.receiverType, COMPATIBLE)); // 2nd arg: "ReferenceType" + newConstraints.add(new ConstraintTypeFormula(p[0], reference.lhs.resolvedType, COMPATIBLE)); offset = 1; } for (int i = offset; i < n; i++) diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java index cfd8703589..0b6c6804e8 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java @@ -49,10 +49,13 @@ public class InferenceContext18 { InvocationSite currentInvocation; Expression[] invocationArguments; - // TODO make me an enum: + // Bitmask, lower two bits: public static final int CHECK_STRICT = 1; public static final int CHECK_LOOSE = 2; public static final int CHECK_VARARG = 3; + public static final int INFERENCE_KIND_MASK = 3; + // bit 3: + public static final int CHECK_FINISHED = 4; static class InvocationRecord { InvocationSite site; @@ -609,25 +612,43 @@ public class InferenceContext18 { this.invocationArguments = record.invocationArguments; } - public void rebindInnerPolies(BoundSet bounds, TypeBinding[] arguments) { + /** + * After inference has finished, iterate all inner poly expressions, that have been + * included in the inference. For each of these update some type information + * from the inference result and perhaps trigger follow-up resolving as needed. + */ + public void rebindInnerPolies(BoundSet bounds, TypeBinding[] argumentTypes) { int len = this.innerPolies.size(); for (int i = 0; i < len; i++) { Expression inner = (Expression) this.innerPolies.get(i); if (inner instanceof Invocation) { Invocation innerMessage = (Invocation) inner; MethodBinding original = innerMessage.binding().original(); + + // apply inference results onto the binding of the inner invocation: TypeBinding[] solutions = getSolutions(original.typeVariables(), innerMessage, bounds); - if (solutions == null) continue; // play safe, but shouldn't happen in a resolved context - TypeBinding returnType = innerMessage.updateBindings(this.environment.createParameterizedGenericMethod(original, solutions)); - if (returnType != TypeBinding.VOID) { - for (int j = 0; j < this.invocationArguments.length; j++) { - if (inner == this.invocationArguments[j]) { - arguments[j] = returnType; - break; - } + if (solutions == null) + continue; // play safe, but shouldn't happen in a resolved context + ParameterizedGenericMethodBinding innerBinding = this.environment.createParameterizedGenericMethod(original, solutions); + innerMessage.updateBindings(innerBinding); + innerMessage.markInferenceFinished(); // invocation type inference has already happened on the inner, too. + + // finalize resolving of arguments of the inner invocation: + TypeBinding[] innerParameters = innerBinding.parameters; + int inferenceKind = innerMessage.inferenceKind(); + TypeBinding varArgsType = inferenceKind == CHECK_VARARG ? ((ArrayBinding)innerParameters[innerParameters.length-1]).elementsType() : null; + Expression[] arguments = innerMessage.arguments(); + if (arguments != null) { + for (int j = 0; j < arguments.length; j++) { + TypeBinding param = (varArgsType == null || (j < innerParameters.length-1)) + ? innerParameters[j] + : varArgsType; + arguments[j].checkAgainstFinalTargetType(param); } } } + // inner FunctionalExpression don't seem to be included in inference. + // TODO recheck any inquires on those actually involve inference of which the results are included here. } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java index 3ea33b53f8..e53346ed62 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java @@ -22,6 +22,7 @@ package org.eclipse.jdt.internal.compiler.lookup; import org.eclipse.jdt.internal.compiler.ast.ExpressionContext; import org.eclipse.jdt.internal.compiler.ast.Invocation; +import org.eclipse.jdt.internal.compiler.ast.PolyExpression; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -88,10 +89,8 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin boolean invocationTypeInferred = false; if (expectedType != null || invocationSite.getExpressionContext() == ExpressionContext.VANILLA_CONTEXT) { result = infCtx18.inferInvocationType(result, expectedType, invocationSite, originalMethod, checkKind); - if (result != null) - invocationTypeInferred = true; - else - hasReturnProblem = true; + invocationTypeInferred = true; + hasReturnProblem |= result == null; if (hasReturnProblem) result = provisionalResult; // let's prefer a type error regarding the return type over reporting no match at all } else { @@ -110,10 +109,15 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin problemMethod.returnType = invocationSite.invocationTargetType(); return problemMethod; } - if (invocationSite instanceof Invocation) - ((Invocation)invocationSite).setInferenceKind(checkKind); - if (invocationTypeInferred) + if (invocationSite instanceof Invocation) { + Invocation invocation = (Invocation)invocationSite; + invocation.setInferenceKind(checkKind); + } + if (invocationTypeInferred) { + if (invocationSite instanceof PolyExpression) + ((PolyExpression) invocationSite).markInferenceFinished(); infCtx18.rebindInnerPolies(result, arguments); + } break computeSubstitutes; } } diff --git a/org.eclipse.jdt.core/eval/org/eclipse/jdt/internal/eval/CodeSnippetAllocationExpression.java b/org.eclipse.jdt.core/eval/org/eclipse/jdt/internal/eval/CodeSnippetAllocationExpression.java index 320d45b788..0f0b82b263 100644 --- a/org.eclipse.jdt.core/eval/org/eclipse/jdt/internal/eval/CodeSnippetAllocationExpression.java +++ b/org.eclipse.jdt.core/eval/org/eclipse/jdt/internal/eval/CodeSnippetAllocationExpression.java @@ -225,7 +225,7 @@ public TypeBinding resolveType(BlockScope scope) { } ReferenceBinding allocatedType = (ReferenceBinding) this.resolvedType; - this.binding = findConstructorBinding(scope, this, allocatedType, this.arguments, argumentTypes, polyExpressionSeen); + this.binding = findConstructorBinding(scope, this, allocatedType, argumentTypes, polyExpressionSeen); if (!this.binding.isValidBinding()) { if (this.binding instanceof ProblemMethodBinding |