diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DisabledIntersectionBoundAnnosTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DisabledIntersectionBoundAnnosTest.java new file mode 100644 index 000000000000..be7a7c2e58a5 --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DisabledIntersectionBoundAnnosTest.java @@ -0,0 +1,49 @@ +package org.checkerframework.framework.test.junit; + +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.framework.testchecker.util.EvenOddChecker; +import org.junit.Ignore; +import org.junit.runners.Parameterized.Parameters; + +import java.io.File; +import java.util.List; + +/** + * Disabled regression test for per-bound annotation preservation on intersection-type upper bounds. + * + *

An intersection-type bound such as {@code } homogenizes its + * per-bound annotations: {@code AnnotatedIntersectionType.copyIntersectionBoundAnnotations} feeds + * the summarized primary annotation through {@code addAnnotations}, whose intersection override + * calls {@code fixupBoundAnnotations} and copies that primary back onto every bound. The + * unannotated bound therefore loses its default (top) qualifier and appears to carry the other + * bound's qualifier, order-dependently. The test inputs in {@code + * tests/disabled-intersection-bounds} assert the fixed, per-bound behavior. + * + *

This test is {@link Ignore}d because the narrowest fix (not writing the primary back onto the + * bounds) changes intersection-bound defaulting and subtyping semantics that the standard Nullness + * Checker tests deliberately rely on (see {@code checker/tests/nullness/Issue868.java} and {@code + * Issue3349.java}), so it is out of scope for a contained bug fix. See {@code + * cf-tasks/task-2-findings.md} for the full diagnosis and proposed patch. + */ +@Ignore("Fix changes intersection-bound semantics relied on by the Nullness Checker; see findings.") +public class DisabledIntersectionBoundAnnosTest extends CheckerFrameworkPerDirectoryTest { + + /** + * Creates a new DisabledIntersectionBoundAnnosTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public DisabledIntersectionBoundAnnosTest(List testFiles) { + super(testFiles, EvenOddChecker.class, "disabled-intersection-bounds"); + } + + /** + * Returns the directories containing test code. + * + * @return the directories containing test code + */ + @Parameters + public static String[] getTestDirs() { + return new String[] {"disabled-intersection-bounds"}; + } +} diff --git a/framework/tests/disabled-intersection-bounds/IntersectionBoundAnnos.java b/framework/tests/disabled-intersection-bounds/IntersectionBoundAnnos.java new file mode 100644 index 000000000000..d61d306a2e44 --- /dev/null +++ b/framework/tests/disabled-intersection-bounds/IntersectionBoundAnnos.java @@ -0,0 +1,37 @@ +import org.checkerframework.framework.testchecker.util.Odd; + +// DISABLED regression test: run by DisabledIntersectionBoundAnnosTest, which is +// @Ignore'd. The fix it verifies changes intersection-bound defaulting/subtyping +// semantics that the standard Nullness Checker tests deliberately rely on, so it +// is out of scope for a contained bug fix. See cf-tasks/task-2-findings.md. +// +// Regression test for per-bound annotation preservation on intersection-type +// upper bounds. The unannotated bound must retain its default (top) qualifier +// instead of being homogenized to the other bound's explicit @Odd. Both bounds +// are interfaces so the two declarations can differ only in bound order. +// See AnnotatedIntersectionType.copyIntersectionBoundAnnotations. +public class IntersectionBoundAnnos { + + interface IfaceA {} + + interface IfaceB {} + + void oddFirst(T t) { + // The IfaceA bound is @Odd; t is assignable to @Odd IfaceA. + @Odd IfaceA ok = t; + // IfaceB is unannotated, so t viewed as IfaceB is top, not @Odd. Before + // the fix this spuriously type-checked because the IfaceB bound was + // homogenized to the other bound's @Odd. + // :: error: (assignment.type.incompatible) + @Odd IfaceB bad = t; + } + + // Order-dependence guard: the @Odd bound comes second. The unannotated IfaceB + // bound must still default to top, independent of the bound order and of the + // order in which methods (and thus type parameters) are processed. + void oddSecond(T t) { + @Odd IfaceA ok = t; + // :: error: (assignment.type.incompatible) + @Odd IfaceB bad = t; + } +}