Skip to content

Commit

Permalink
detect missing types during type inference
Browse files Browse the repository at this point in the history
+ don't let constraint with missing type fail type inference
+ prefer that candidate during overload resolution
additional bug fix:
+ fix tagging of TypeVariableBinding with HasMissingType
  - no magic for class level type parameters with missing type
+ improved errors in GenericsRegressionTest_1_8.testBug525580*() et al
  • Loading branch information
stephan-herrmann committed Jul 1, 2024
1 parent 2ff64f2 commit 404720f
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public ConstraintExceptionFormula(FunctionalExpression left, TypeBinding type) {

@Override
public Object reduce(InferenceContext18 inferenceContext) {
if ((this.right.tagBits & TagBits.HasMissingType) != 0) {
inferenceContext.hasIgnoredMissingType = true;
return TRUE;
}
// JLS 18.2.5
Scope scope = inferenceContext.scope;
if (!this.right.isFunctionalInterface(scope))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class ConstraintExpressionFormula extends ConstraintFormula {

@Override
public Object reduce(InferenceContext18 inferenceContext) throws InferenceFailureException {
if ((this.right.tagBits & TagBits.HasMissingType) != 0) {
inferenceContext.hasIgnoredMissingType = true;
return TRUE;
}

if (this.relation == POTENTIALLY_COMPATIBLE) {
/* 15.12.2.1: ... The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ private ConstraintTypeFormula(TypeBinding exprType, TypeBinding right, int relat
// return: ReductionResult or ConstraintFormula[]
@Override
public Object reduce(InferenceContext18 inferenceContext) {
if ((this.left.tagBits & TagBits.HasMissingType) != 0 || (this.right.tagBits & TagBits.HasMissingType) != 0) {
inferenceContext.hasIgnoredMissingType = true;
return TRUE;
}
switch (this.relation) {
case COMPATIBLE:
// 18.2.2:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ public class InferenceContext18 {
// the following two flags control to what degree we continue with incomplete information:
private boolean isInexactVarargsInference = false;
boolean prematureOverloadResolution = false;
// during reduction we ignore missing types but record that fact here:
boolean hasIgnoredMissingType;

public static boolean isSameSite(InvocationSite site1, InvocationSite site2) {
if (site1 == site2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ public static MethodBinding computeCompatibleMethod18(MethodBinding originalMeth
if (invocationSite instanceof Invocation && allArgumentsAreProper && (expectedType == null || expectedType.isProperType(true)))
infCtx18.forwardResults(result, (Invocation) invocationSite, methodSubstitute, expectedType);
try {
if (infCtx18.hasIgnoredMissingType) {
return new ProblemMethodBinding(originalMethod, originalMethod.selector, parameters, ProblemReasons.MissingTypeInSignature);
}
if (hasReturnProblem) { // illegally working from the provisional result?
MethodBinding problemMethod = infCtx18.getReturnProblemMethodIfNeeded(expectedType, methodSubstitute);
if (problemMethod instanceof ProblemMethodBinding) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ protected boolean connectTypeVariables(TypeParameter[] typeParameters, boolean c
} else {
typeVariable.setSuperInterfaces(new ReferenceBinding[] {superRefType});
}
typeVariable.tagBits |= superType.tagBits & TagBits.ContainsNestedTypeReferences;
typeVariable.tagBits |= superType.tagBits & (TagBits.ContainsNestedTypeReferences | TagBits.HasMissingType);
typeVariable.setFirstBound(superRefType); // first bound used to compute erasure
}
}
Expand All @@ -997,7 +997,7 @@ protected boolean connectTypeVariables(TypeParameter[] typeParameters, boolean c
typeVariable.tagBits |= TagBits.HierarchyHasProblems;
continue nextBound;
} else {
typeVariable.tagBits |= superType.tagBits & TagBits.ContainsNestedTypeReferences;
typeVariable.tagBits |= superType.tagBits & (TagBits.ContainsNestedTypeReferences | TagBits.HasMissingType);
boolean didAlreadyComplain = !typeRef.resolvedType.isValidBinding();
if (isFirstBoundTypeVariable && j == 0) {
problemReporter().noAdditionalBoundAfterTypeVariable(typeRef);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
* Copyright (c) 2000, 2024 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -43,6 +43,7 @@
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

Expand Down Expand Up @@ -344,6 +345,20 @@ public int boundsCount() {
public boolean canBeInstantiated() {
return false;
}

@Override
public List<TypeBinding> collectMissingTypes(List<TypeBinding> missingTypes) {
if ((this.tagBits & TagBits.HasMissingType) != 0) {
if (this.superclass != null) {
missingTypes = this.superclass.collectMissingTypes(missingTypes);
}
for (ReferenceBinding superIfc : this.superInterfaces) {
missingTypes = superIfc.collectMissingTypes(missingTypes);
}
}
return missingTypes;
}

/**
* Collect the substitutes into a map for certain type variables inside the receiver type
* e.g. {@code Collection<T>.collectSubstitutes(Collection<List<X>>, Map)} will populate Map with: {@code T --> List<X>}
Expand Down Expand Up @@ -805,7 +820,7 @@ ReferenceBinding resolve() {
TypeBinding oldSuperclass = this.superclass, oldFirstInterface = null;
if (this.superclass != null) {
ReferenceBinding resolveType = (ReferenceBinding) BinaryTypeBinding.resolveType(this.superclass, this.environment, true /* raw conversion */);
this.tagBits |= resolveType.tagBits & TagBits.ContainsNestedTypeReferences;
this.tagBits |= resolveType.tagBits & (TagBits.ContainsNestedTypeReferences | TagBits.HasMissingType);
long superNullTagBits = resolveType.tagBits & TagBits.AnnotationNullMASK;
if (superNullTagBits != 0L) {
if (nullTagBits == 0L) {
Expand All @@ -824,7 +839,7 @@ ReferenceBinding resolve() {
oldFirstInterface = interfaces[0];
for (int i = length; --i >= 0;) {
ReferenceBinding resolveType = (ReferenceBinding) BinaryTypeBinding.resolveType(interfaces[i], this.environment, true /* raw conversion */);
this.tagBits |= resolveType.tagBits & TagBits.ContainsNestedTypeReferences;
this.tagBits |= resolveType.tagBits & (TagBits.ContainsNestedTypeReferences | TagBits.HasMissingType);
long superNullTagBits = resolveType.tagBits & TagBits.AnnotationNullMASK;
if (superNullTagBits != 0L) {
if (nullTagBits == 0L) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
public class MultiProjectTests extends BuilderTests {

static {
TESTS_NAMES = new String[] { "test461074_error_1_8" };
// TESTS_NAMES = new String[] { "test461074_error_1_8" };
}

public MultiProjectTests(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7973,8 +7973,8 @@ public void test237() {
"----------\n" +
"2. ERROR in X.java (at line 6)\n" +
" List<String> ls = get();\n" +
" ^^^^^\n" +
"Type mismatch: cannot convert from B to List<String>\n" +
" ^^^\n" +
"The method get() from the type X<B> refers to the missing type ArrayList\n" +
"----------\n");
}
public void test238() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43243,6 +43243,11 @@ public void test1222() {
" public class X<T extends Zork & Runnable> {\n" +
" ^^^^\n" +
"Zork cannot be resolved to a type\n" +
"----------\n" +
"2. ERROR in X.java (at line 4)\n" +
" Runnable r = x2.get();\n" +
" ^^^\n" +
"The method get() from the type X<T> refers to the missing type Zork\n" +
"----------\n");
}
//https://bugs.eclipse.org/bugs/show_bug.cgi?id=211718
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9173,45 +9173,40 @@ public void testBug525580() {
"----------\n" +
"2. ERROR in org\\a\\a\\g\\d.java (at line 6)\n" +
" T t = (e) cls.newInstance();\n" +
" ^^^^^^^^^^^^^^^^^^^^^\n" +
"e cannot be resolved to a type\n" +
"----------\n" +
"3. ERROR in org\\a\\a\\g\\d.java (at line 6)\n" +
" T t = (e) cls.newInstance();\n" +
" ^\n" +
"e cannot be resolved to a type\n" +
"----------\n" +
"4. ERROR in org\\a\\a\\g\\d.java (at line 7)\n" +
"3. ERROR in org\\a\\a\\g\\d.java (at line 7)\n" +
" while (size >= 0) {\n" +
" ^^^^\n" +
"size cannot be resolved to a variable\n" +
"----------\n" +
"5. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
" T a = ((b) this.e.m.get(size)).a();\n" +
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
"Type mismatch: cannot convert from e to T\n" +
"----------\n" +
"6. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
"4. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
" T a = ((b) this.e.m.get(size)).a();\n" +
" ^\n" +
"e cannot be resolved or is not a field\n" +
"----------\n" +
"7. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
"5. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
" T a = ((b) this.e.m.get(size)).a();\n" +
" ^^^^\n" +
"size cannot be resolved to a variable\n" +
"----------\n" +
"8. ERROR in org\\a\\a\\g\\d.java (at line 15)\n" +
"6. ERROR in org\\a\\a\\g\\d.java (at line 8)\n" +
" T a = ((b) this.e.m.get(size)).a();\n" +
" ^\n" +
"The method a() from the type d.b refers to the missing type e\n" +
"----------\n" +
"7. ERROR in org\\a\\a\\g\\d.java (at line 15)\n" +
" <T extends e> T a();\n" +
" ^\n" +
"e cannot be resolved to a type\n" +
"----------\n" +
"9. ERROR in org\\a\\a\\g\\d.java (at line 17)\n" +
"8. ERROR in org\\a\\a\\g\\d.java (at line 17)\n" +
" <T extends j> T b();\n" +
" ^\n" +
"j cannot be resolved to a type\n" +
"----------\n" +
"10. WARNING in org\\a\\a\\g\\d.java (at line 17)\n" +
"9. WARNING in org\\a\\a\\g\\d.java (at line 17)\n" +
" <T extends j> T b();\n" +
" ^^^\n" +
"This method has a constructor name\n" +
Expand Down Expand Up @@ -9292,8 +9287,8 @@ public void testBug525580_comment28() {
"----------\n" +
"3. ERROR in xxxxxx\\iiibii.java (at line 9)\n" +
" return b041D041D041D041DН041DН(new xxxxxx.jjajaa(b, b2));\n" +
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
"Type mismatch: cannot convert from jajaja to jajaja\n" +
" ^^^^^^^^^^^^^^^^^^^^^^^\n" +
"The method b041D041D041D041DН041DН(jjajaa) from the type iiibii refers to the missing type jajaja\n" +
"----------\n" +
"----------\n" +
"1. ERROR in xxxxxx\\jjajaa.java (at line 3)\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9517,4 +9517,102 @@ The method m() from the type B refers to the missing type A
""";
runner.runNegativeTest();
}
public void testMissingClass_typeVariableBound() {
if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543
Runner runner = new Runner();
runner.testFiles = new String[] {
"p1/A.java",
"""
package p1;
public class A {}
""",
"p1/B.java",
"""
package p1;
import p1.A;
public class B {
public void m(Number n) {} // would match, but ...
public <T extends A> void m(T t) {} // ... don't rule out method with missing type
}
"""
};
runner.runConformTest();

// delete binary file A (i.e. simulate removing it from classpath for subsequent compile)
Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A.class"));

runner.shouldFlushOutputDirectory = false;
runner.testFiles = new String[] {
"p2/C.java",
"""
package p2;
import p1.B;
class C {
void test(B b) {
b.m(Integer.valueOf(13));
}
}
"""
};
runner.expectedCompilerLog = """
----------
1. ERROR in p2\\C.java (at line 5)
b.m(Integer.valueOf(13));
^
The method m(T) from the type B refers to the missing type A
----------
""";
runner.runNegativeTest();
}
public void testMissingClass_typeVariableBound2() {
if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // ignore different outcome below 1.8 since PR 2543
Runner runner = new Runner();
runner.testFiles = new String[] {
"p1/A.java",
"""
package p1;
public class A {}
""",
"p1/B.java",
"""
package p1;
import p1.A;
public class B<T extends A> {
public void m(T t) {}
}
"""
};
runner.runConformTest();

// delete binary file A (i.e. simulate removing it from classpath for subsequent compile)
Util.delete(new File(OUTPUT_DIR, "p1" + File.separator + "A.class"));

runner.shouldFlushOutputDirectory = false;
runner.testFiles = new String[] {
"p2/C.java",
"""
package p2;
import p1.B;
class C {
void test(B<C> b) {
b.m(this);
}
}
"""
};
runner.expectedCompilerLog = """
----------
1. ERROR in p2\\C.java (at line 1)
package p2;
^
The type p1.A cannot be resolved. It is indirectly referenced from required type p1.B
----------
2. ERROR in p2\\C.java (at line 4)
void test(B<C> b) {
^
Bound mismatch: The type C is not a valid substitute for the bounded parameter <T extends A> of the type B<T>
----------
""";
runner.runNegativeTest();
}
}

0 comments on commit 404720f

Please sign in to comment.