From f474a320b09e08ddd07f315b23c00394b79e792e Mon Sep 17 00:00:00 2001 From: Stephan Herrmann Date: Sun, 16 Jun 2024 14:01:47 +0200 Subject: [PATCH] more complete coverage of varargs methods/invocations - if in doubt signal "missing type" rather than plain "ambiguous" --- .../jdt/internal/compiler/lookup/Scope.java | 12 +- .../regression/ProblemTypeAndMethodTest.java | 324 +++++++++++++++++- 2 files changed, 330 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java index 531049fc509..1b84b7c5bf8 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java @@ -5156,13 +5156,17 @@ public int parameterCompatibilityLevel(MethodBinding method, TypeBinding[] argum TypeBinding param = ((ArrayBinding) parameters[lastIndex]).elementsType(); for (int i = lastIndex; i < argLength; i++) { TypeBinding arg = (tiebreakingVarargsMethods && (i == (argLength - 1))) ? ((ArrayBinding)arguments[i]).elementsType() : arguments[i]; - if (TypeBinding.notEquals(param, arg) && parameterCompatibilityLevel(arg, param, env, tiebreakingVarargsMethods, method) == NOT_COMPATIBLE) - return NOT_COMPATIBLE; + if (TypeBinding.notEquals(param, arg)) { + level = parameterCompatibilityLevel(arg, param, env, tiebreakingVarargsMethods, method); + if (level == NOT_COMPATIBLE) + return NOT_COMPATIBLE; + } } } else if (lastIndex != argLength) { // can call foo(int i, X ... x) with foo(1) but NOT foo(); return NOT_COMPATIBLE; } - level = VARARGS_COMPATIBLE; // varargs support needed + if (level != NEEDS_MISSING_TYPE) // preserve any NEEDS_MISSING_TYPE + level = VARARGS_COMPATIBLE; // varargs support needed } } else if (paramLength != argLength) { return NOT_COMPATIBLE; @@ -5175,7 +5179,7 @@ public int parameterCompatibilityLevel(MethodBinding method, TypeBinding[] argum int newLevel = parameterCompatibilityLevel(arg, param, env, tiebreakingVarargsMethods, method); if (newLevel < COMPATIBLE) return newLevel; - if (newLevel > level) + if (newLevel > level && level != NEEDS_MISSING_TYPE) // preserve any NEEDS_MISSING_TYPE level = newLevel; } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java index 886175f5b6c..613085f3399 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java @@ -8879,7 +8879,326 @@ The method m(A) from the type B refers to the missing type A """; runner.runNegativeTest(); } -public void testMissingClassNeededForOverloadResolution_varargs1() { +public void testMissingClassNeededForOverloadResolution_varargs1a() { + // varargs arg: B vs Missing + if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 + Runner runner = new Runner(); + runner.customOptions = getCompilerOptions(); + runner.testFiles = new String[] { + "p1/A1.java", + """ + package p1; + public class A1 {} + """, + "p1/A2.java", + """ + package p1; + public class A2 {} + """, + "p1/B.java", + """ + package p1; + public class B { + public void m(Object o1, B... s) {} + public void m(Object o1, A1... a) {} + public void m(Object o1, A2... a) {} + } + """ + }; + runner.runConformTest(); + + // delete binary files A1, A2 (i.e. simulate removing it from classpath for subsequent compile) + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A1.class")); + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A2.class")); + runner.shouldFlushOutputDirectory = false; + + runner.customOptions.put(CompilerOptions.OPTION_ReportVarargsArgumentNeedCast, CompilerOptions.IGNORE); + runner.testFiles = new String[] { + "p2/C.java", + """ + package p2; + import p1.B; + public class C { + void test(B b) { + b.m(this); // simply ambiguous as we don't even look at A1 or A2 + b.m(this, null); + b.m(this, b); + b.m(this, b, b); + b.m(this, new B[0]); + } + } + """ + }; + runner.expectedCompilerLog = """ + ---------- + 1. ERROR in p2\\C.java (at line 5) + b.m(this); // simply ambiguous as we don't even look at A1 or A2 + ^ + The method m(Object, B[]) is ambiguous for the type B + ---------- + 2. ERROR in p2\\C.java (at line 6) + b.m(this, null); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 3. ERROR in p2\\C.java (at line 7) + b.m(this, b); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 4. ERROR in p2\\C.java (at line 8) + b.m(this, b, b); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 5. ERROR in p2\\C.java (at line 9) + b.m(this, new B[0]); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + """; + runner.runNegativeTest(); +} +public void testMissingClassNeededForOverloadResolution_varargs1b() { + // like testMissingClassNeededForOverloadResolution_varargs1a, but no preceding regular parameter + if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 + Runner runner = new Runner(); + runner.customOptions = getCompilerOptions(); + runner.testFiles = new String[] { + "p1/A1.java", + """ + package p1; + public class A1 {} + """, + "p1/A2.java", + """ + package p1; + public class A2 {} + """, + "p1/B.java", + """ + package p1; + public class B { + public void m(String... s) {} + public void m(A1... a) {} + public void m(A2... a) {} + } + """ + }; + runner.runConformTest(); + + // delete binary files A1, A1 (i.e. simulate removing it from classpath for subsequent compile) + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A1.class")); + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A2.class")); + runner.shouldFlushOutputDirectory = false; + + runner.customOptions.put(CompilerOptions.OPTION_ReportVarargsArgumentNeedCast, CompilerOptions.IGNORE); + runner.testFiles = new String[] { + "p2/C.java", + """ + package p2; + import p1.B; + public class C { + void test(B b) { + b.m(); // simply ambiguous as we don't even look at A1 or A2 + b.m(null); + b.m(b); + b.m(b, b); + b.m(new B[0]); + } + } + """ + }; + runner.expectedCompilerLog = """ + ---------- + 1. ERROR in p2\\C.java (at line 5) + b.m(); // simply ambiguous as we don't even look at A1 or A2 + ^ + The method m(String[]) is ambiguous for the type B + ---------- + 2. ERROR in p2\\C.java (at line 6) + b.m(null); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 3. ERROR in p2\\C.java (at line 7) + b.m(b); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 4. ERROR in p2\\C.java (at line 8) + b.m(b, b); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 5. ERROR in p2\\C.java (at line 9) + b.m(new B[0]); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + """; + runner.runNegativeTest(); +} +public void testMissingClassNeededForOverloadResolution_varargs1c() { + // varargs arg: only missing types competing + if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 + Runner runner = new Runner(); + runner.customOptions = getCompilerOptions(); + runner.testFiles = new String[] { + "p1/A1.java", + """ + package p1; + public class A1 {} + """, + "p1/A2.java", + """ + package p1; + public class A2 {} + """, + "p1/B.java", + """ + package p1; + public class B { + public void m(Object o1, A1... a) {} + public void m(Object o1, A2... a) {} + } + """ + }; + runner.runConformTest(); + + // delete binary files A1, A1 (i.e. simulate removing it from classpath for subsequent compile) + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A1.class")); + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A2.class")); + runner.shouldFlushOutputDirectory = false; + + runner.customOptions.put(CompilerOptions.OPTION_ReportVarargsArgumentNeedCast, CompilerOptions.IGNORE); + runner.testFiles = new String[] { + "p2/C.java", + """ + package p2; + import p1.B; + public class C { + void test(B b) { + b.m(this); // simply ambiguous as we don't even look at A1 or A2 + b.m(this, null); + b.m(this, b); + b.m(this, b, b); + b.m(this, new B[0]); + } + } + """ + }; + runner.expectedCompilerLog = """ + ---------- + 1. ERROR in p2\\C.java (at line 5) + b.m(this); // simply ambiguous as we don't even look at A1 or A2 + ^ + The method m(Object, A1[]) is ambiguous for the type B + ---------- + 2. ERROR in p2\\C.java (at line 6) + b.m(this, null); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 3. ERROR in p2\\C.java (at line 7) + b.m(this, b); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 4. ERROR in p2\\C.java (at line 8) + b.m(this, b, b); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + 5. ERROR in p2\\C.java (at line 9) + b.m(this, new B[0]); + ^ + The method m(Object, A1...) from the type B refers to the missing type A1 + ---------- + """; + runner.runNegativeTest(); +} +public void testMissingClassNeededForOverloadResolution_varargs1d() { + // like testMissingClassNeededForOverloadResolution_varargs1c, but no preceding regular parameter + if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 + Runner runner = new Runner(); + runner.customOptions = getCompilerOptions(); + runner.testFiles = new String[] { + "p1/A1.java", + """ + package p1; + public class A1 {} + """, + "p1/A2.java", + """ + package p1; + public class A2 {} + """, + "p1/B.java", + """ + package p1; + public class B { + public void m(A1... a) {} + public void m(A2... a) {} + } + """ + }; + runner.runConformTest(); + + // delete binary files A1, A1 (i.e. simulate removing it from classpath for subsequent compile) + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A1.class")); + Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A2.class")); + runner.shouldFlushOutputDirectory = false; + + runner.customOptions.put(CompilerOptions.OPTION_ReportVarargsArgumentNeedCast, CompilerOptions.IGNORE); + runner.testFiles = new String[] { + "p2/C.java", + """ + package p2; + import p1.B; + public class C { + void test(B b) { + b.m(); // simply ambiguous as we don't even look at A1 or A2 + b.m(null); + b.m(b); + b.m(b, b); + b.m(new B[0]); + } + } + """ + }; + runner.expectedCompilerLog = """ + ---------- + 1. ERROR in p2\\C.java (at line 5) + b.m(); // simply ambiguous as we don't even look at A1 or A2 + ^ + The method m(A1[]) is ambiguous for the type B + ---------- + 2. ERROR in p2\\C.java (at line 6) + b.m(null); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 3. ERROR in p2\\C.java (at line 7) + b.m(b); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 4. ERROR in p2\\C.java (at line 8) + b.m(b, b); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + 5. ERROR in p2\\C.java (at line 9) + b.m(new B[0]); + ^ + The method m(A1...) from the type B refers to the missing type A1 + ---------- + """; + runner.runNegativeTest(); +} +public void testMissingClassNeededForOverloadResolution_varargs2() { + // different arities if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 Runner runner = new Runner(); runner.customOptions = getCompilerOptions(); @@ -8951,7 +9270,8 @@ The method n(Object, A...) from the type B refers to the missing type A """; runner.runNegativeTest(); } -public void testMissingClassNeededForOverloadResolution_varargs2() { +public void testMissingClassNeededForOverloadResolution_varargs3() { + // missing type in non-varargs position if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543 Runner runner = new Runner(); runner.testFiles = new String[] {