From e77c3c7b50b31e21756c10a5ac76ef32f95b4e2d Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 28 Oct 2025 16:27:37 -0400 Subject: [PATCH 01/11] Fixes eisop#433 --- docs/CHANGELOG.md | 2 +- .../type/AbstractViewpointAdapter.java | 13 +++++++++++++ .../tests/viewpointtest/IntersectionTypes.java | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 framework/tests/viewpointtest/IntersectionTypes.java diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3b0ab018c221..958d2367211f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -40,7 +40,7 @@ The Nullness Checker now recognizes references to private, final fields with zer **Closed issues:** -eisop#1247, eisop#1263, eisop#1310, typetools#7096. +typetool#247, eisop#433, eisop#1247, eisop#1263, eisop#1310, typetools#7096. Version 3.49.5 (June 30, 2025) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index a2121ad4bf03..0e3ceb79daf3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -367,6 +367,19 @@ protected AnnotatedTypeMirror combineAnnotationWithType( receiverAnnotation, extractAnnotationMirror(ant)); ant.replaceAnnotation(resultAnnotation); return ant; + } else if (declared.getKind() == TypeKind.INTERSECTION) { + AnnotatedTypeMirror.AnnotatedIntersectionType intersection = + (AnnotatedTypeMirror.AnnotatedIntersectionType) declared.shallowCopy(true); + List listBounds = intersection.getBounds(); + List listBoundsCopy = new ArrayList<>(listBounds); + for (int i = 0; i < listBoundsCopy.size(); i++) { + AnnotatedTypeMirror bound = listBoundsCopy.get(i); + AnnotatedTypeMirror combinedBound = + combineAnnotationWithType(receiverAnnotation, bound); + listBoundsCopy.set(i, combinedBound); + } + intersection.setBounds(listBoundsCopy); + return intersection; } else { throw new BugInCF( "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java new file mode 100644 index 000000000000..f34f0ed3d900 --- /dev/null +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -0,0 +1,18 @@ +// Test case for Issue 247 & EISOP Issue 433: +// https://github.com/typetools/checker-framework/issues/247 +// https://github.com/eisop/checker-framework/issues/433 + +interface Foo {} + +interface Bar {} + +class Baz implements Foo, Bar {} + +public class IntersectionTypes { + void foo() { + Baz baz = new Baz(); + call(baz); + } + + void call(T p) {} +} From faa762edbbe15e81e7035676547d73ab972b6304 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 28 Oct 2025 16:36:27 -0400 Subject: [PATCH 02/11] Remove typetool link --- docs/CHANGELOG.md | 2 +- framework/tests/viewpointtest/IntersectionTypes.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 958d2367211f..feeb235a30b6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -40,7 +40,7 @@ The Nullness Checker now recognizes references to private, final fields with zer **Closed issues:** -typetool#247, eisop#433, eisop#1247, eisop#1263, eisop#1310, typetools#7096. +eisop#433, eisop#1247, eisop#1263, eisop#1310, typetools#7096. Version 3.49.5 (June 30, 2025) diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index f34f0ed3d900..fdc036b7b34f 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -1,5 +1,4 @@ -// Test case for Issue 247 & EISOP Issue 433: -// https://github.com/typetools/checker-framework/issues/247 +// Test case for EISOP Issue 433: // https://github.com/eisop/checker-framework/issues/433 interface Foo {} From 61bad4fdd131111ffef1bfb3803a277f53487cdc Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 28 Oct 2025 17:05:03 -0400 Subject: [PATCH 03/11] Don't create top --- framework/tests/viewpointtest/IntersectionTypes.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index fdc036b7b34f..2dae62087e88 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -1,6 +1,8 @@ // Test case for EISOP Issue 433: // https://github.com/eisop/checker-framework/issues/433 +import viewpointtest.quals.*; + interface Foo {} interface Bar {} @@ -9,7 +11,7 @@ class Baz implements Foo, Bar {} public class IntersectionTypes { void foo() { - Baz baz = new Baz(); + Baz baz = new @A Baz(); call(baz); } From e6ed628acf30f777b23fe73f4e05f23a046c7fc2 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 28 Oct 2025 20:07:37 -0400 Subject: [PATCH 04/11] Add warning --- framework/tests/viewpointtest/IntersectionTypes.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index 2dae62087e88..a70a027e05e5 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -11,6 +11,7 @@ class Baz implements Foo, Bar {} public class IntersectionTypes { void foo() { + // :: warning: (cast.unsafe.constructor.invocation) Baz baz = new @A Baz(); call(baz); } From 5aac11f5a19f09834812b819a950ccbc47cea3fd Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Wed, 29 Oct 2025 13:47:41 -0400 Subject: [PATCH 05/11] Also fix for recursive bound --- .../type/AbstractViewpointAdapter.java | 12 ++++++++++ .../viewpointtest/IntersectionTypes.java | 23 +++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 0e3ceb79daf3..71eb95e325a8 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -464,6 +464,18 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy rhs = AnnotatedTypeCopierWithReplacement.replace(aat, mappings); } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { // nothing to do for primitive types and the null type + } else if (rhs.getKind() == TypeKind.INTERSECTION) { + AnnotatedTypeMirror.AnnotatedIntersectionType intersection = + (AnnotatedTypeMirror.AnnotatedIntersectionType) rhs.shallowCopy(true); + List listBounds = intersection.getBounds(); + List listBoundsCopy = new ArrayList<>(listBounds); + for (int i = 0; i < listBoundsCopy.size(); i++) { + AnnotatedTypeMirror bound = listBoundsCopy.get(i); + AnnotatedTypeMirror substBound = substituteTVars(lhs, bound); + listBoundsCopy.set(i, substBound); + } + intersection.setBounds(listBoundsCopy); + rhs = intersection; } else { throw new BugInCF( "ViewpointAdapter::substituteTVars: Cannot handle rhs: " diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index a70a027e05e5..983cf1bea99b 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -3,18 +3,31 @@ import viewpointtest.quals.*; -interface Foo {} +public class IntersectionTypes { -interface Bar {} + interface Foo {} -class Baz implements Foo, Bar {} + interface Bar {} + + class Baz implements Foo, Bar {} + + void call(T p) {} -public class IntersectionTypes { void foo() { // :: warning: (cast.unsafe.constructor.invocation) Baz baz = new @A Baz(); call(baz); } - void call(T p) {} + interface B {} + + interface C {} + + abstract class D & C> {} + + class BC implements B, C {} + + class E extends D {} + + & C> void call(T p) {} } From b9aef560c869042aabd4a9b3b61a4adee352e3b6 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Sun, 24 May 2026 11:43:02 -0400 Subject: [PATCH 06/11] Avoid mutating intersection bounds during adaptation --- .../type/AbstractViewpointAdapter.java | 31 ++++++++++++------- .../viewpointtest/IntersectionTypes.java | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 901867784b1a..87866da9de7a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -12,6 +12,7 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; +import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; @@ -364,17 +365,20 @@ protected AnnotatedTypeMirror combineAnnotationWithType( ant.replaceAnnotation(resultAnnotation); return ant; } else if (declared.getKind() == TypeKind.INTERSECTION) { + AnnotatedTypeMirror.AnnotatedIntersectionType declaredIntersection = + (AnnotatedTypeMirror.AnnotatedIntersectionType) declared; AnnotatedTypeMirror.AnnotatedIntersectionType intersection = - (AnnotatedTypeMirror.AnnotatedIntersectionType) declared.shallowCopy(true); - List listBounds = intersection.getBounds(); - List listBoundsCopy = new ArrayList<>(listBounds); - for (int i = 0; i < listBoundsCopy.size(); i++) { - AnnotatedTypeMirror bound = listBoundsCopy.get(i); + declaredIntersection.shallowCopy(/* copyAnnotations= */ false); + List listBounds = declaredIntersection.getBounds(); + List listBoundsCopy = new ArrayList<>(listBounds.size()); + for (AnnotatedTypeMirror bound : listBounds) { AnnotatedTypeMirror combinedBound = combineAnnotationWithType(receiverAnnotation, bound); - listBoundsCopy.set(i, combinedBound); + listBoundsCopy.add(combinedBound); } intersection.setBounds(listBoundsCopy); + intersection.clearAnnotations(); + intersection.copyIntersectionBoundAnnotations(); return intersection; } else { throw new BugInCF( @@ -461,16 +465,19 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { // nothing to do for primitive types and the null type } else if (rhs.getKind() == TypeKind.INTERSECTION) { + AnnotatedTypeMirror.AnnotatedIntersectionType rhsIntersection = + (AnnotatedTypeMirror.AnnotatedIntersectionType) rhs; AnnotatedTypeMirror.AnnotatedIntersectionType intersection = - (AnnotatedTypeMirror.AnnotatedIntersectionType) rhs.shallowCopy(true); - List listBounds = intersection.getBounds(); - List listBoundsCopy = new ArrayList<>(listBounds); - for (int i = 0; i < listBoundsCopy.size(); i++) { - AnnotatedTypeMirror bound = listBoundsCopy.get(i); + rhsIntersection.shallowCopy(/* copyAnnotations= */ false); + List listBounds = rhsIntersection.getBounds(); + List listBoundsCopy = new ArrayList<>(listBounds.size()); + for (AnnotatedTypeMirror bound : listBounds) { AnnotatedTypeMirror substBound = substituteTVars(lhs, bound); - listBoundsCopy.set(i, substBound); + listBoundsCopy.add(substBound); } intersection.setBounds(listBoundsCopy); + intersection.clearAnnotations(); + intersection.copyIntersectionBoundAnnotations(); rhs = intersection; } else { throw new BugInCF( diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index 983cf1bea99b..5df65a92869c 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -29,5 +29,5 @@ class BC implements B, C {} class E extends D {} - & C> void call(T p) {} + & C> void callBC(T p) {} } From 85fd8cf3373a13cfe88bdf555fbe883f2567149d Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Sun, 24 May 2026 19:08:37 -0400 Subject: [PATCH 07/11] Address intersection viewpoint adaptation reviews --- .../type/AbstractViewpointAdapter.java | 61 ++++++++++--------- .../all-systems/IntersectionTypeNoCrash.java | 21 +++++++ .../viewpointtest/IntersectionTypes.java | 54 +++++++++++++--- 3 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 framework/tests/all-systems/IntersectionTypeNoCrash.java diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 87866da9de7a..45523d68eaa6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -4,6 +4,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -15,6 +16,7 @@ import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; +import java.util.function.Function; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -365,21 +367,9 @@ protected AnnotatedTypeMirror combineAnnotationWithType( ant.replaceAnnotation(resultAnnotation); return ant; } else if (declared.getKind() == TypeKind.INTERSECTION) { - AnnotatedTypeMirror.AnnotatedIntersectionType declaredIntersection = - (AnnotatedTypeMirror.AnnotatedIntersectionType) declared; - AnnotatedTypeMirror.AnnotatedIntersectionType intersection = - declaredIntersection.shallowCopy(/* copyAnnotations= */ false); - List listBounds = declaredIntersection.getBounds(); - List listBoundsCopy = new ArrayList<>(listBounds.size()); - for (AnnotatedTypeMirror bound : listBounds) { - AnnotatedTypeMirror combinedBound = - combineAnnotationWithType(receiverAnnotation, bound); - listBoundsCopy.add(combinedBound); - } - intersection.setBounds(listBoundsCopy); - intersection.clearAnnotations(); - intersection.copyIntersectionBoundAnnotations(); - return intersection; + return adaptIntersectionBounds( + (AnnotatedIntersectionType) declared, + bound -> combineAnnotationWithType(receiverAnnotation, bound)); } else { throw new BugInCF( "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " @@ -465,20 +455,9 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { // nothing to do for primitive types and the null type } else if (rhs.getKind() == TypeKind.INTERSECTION) { - AnnotatedTypeMirror.AnnotatedIntersectionType rhsIntersection = - (AnnotatedTypeMirror.AnnotatedIntersectionType) rhs; - AnnotatedTypeMirror.AnnotatedIntersectionType intersection = - rhsIntersection.shallowCopy(/* copyAnnotations= */ false); - List listBounds = rhsIntersection.getBounds(); - List listBoundsCopy = new ArrayList<>(listBounds.size()); - for (AnnotatedTypeMirror bound : listBounds) { - AnnotatedTypeMirror substBound = substituteTVars(lhs, bound); - listBoundsCopy.add(substBound); - } - intersection.setBounds(listBoundsCopy); - intersection.clearAnnotations(); - intersection.copyIntersectionBoundAnnotations(); - rhs = intersection; + rhs = + adaptIntersectionBounds( + (AnnotatedIntersectionType) rhs, bound -> substituteTVars(lhs, bound)); } else { throw new BugInCF( "ViewpointAdapter::substituteTVars: Cannot handle rhs: " @@ -490,6 +469,30 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy return rhs; } + /** + * Returns a copy of {@code source} whose bounds have been adapted by {@code adaptBound}. + * + * @param source intersection type whose bounds are adapted + * @param adaptBound function that adapts one bound + * @return a copy of {@code source} with adapted bounds + */ + private AnnotatedIntersectionType adaptIntersectionBounds( + AnnotatedIntersectionType source, + Function adaptBound) { + AnnotatedIntersectionType intersection = source.shallowCopy(/* copyAnnotations= */ false); + List bounds = source.getBounds(); + List adaptedBounds = new ArrayList<>(bounds.size()); + for (AnnotatedTypeMirror bound : bounds) { + adaptedBounds.add(adaptBound.apply(bound)); + } + intersection.setBounds(adaptedBounds); + // setBounds replaces the shared bounds from shallowCopy, so recompute primary annotations + // from the adapted bounds. + intersection.clearAnnotations(); + intersection.copyIntersectionBoundAnnotations(); + return intersection; + } + /** * Return actual type argument for formal type parameter "var" from "type" * diff --git a/framework/tests/all-systems/IntersectionTypeNoCrash.java b/framework/tests/all-systems/IntersectionTypeNoCrash.java new file mode 100644 index 000000000000..051213799c86 --- /dev/null +++ b/framework/tests/all-systems/IntersectionTypeNoCrash.java @@ -0,0 +1,21 @@ +// Test case for EISOP Issue 433: +// https://github.com/eisop/checker-framework/issues/433 + +public class IntersectionTypeNoCrash { + + interface Foo {} + + interface Bar {} + + class Baz implements Foo, Bar {} + + void call(T p) {} + + void test() { + call(new Baz()); + } + + void testCast(Object obj) { + Foo fooAndBar = (Foo & Bar) obj; + } +} diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index 5df65a92869c..6b2fbccc099f 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -9,25 +9,65 @@ interface Foo {} interface Bar {} - class Baz implements Foo, Bar {} + interface Accessor { + @ReceiverDependentQual Object get(); + + void set(@ReceiverDependentQual Object o); + } + + class Baz implements Foo, Bar, Accessor { + @Override + public @ReceiverDependentQual Object get() { + return null; + } + + @Override + public void set(@ReceiverDependentQual Object o) {} + } void call(T p) {} - void foo() { + // TODO: Re-enable after deciding the expected behavior for mixed explicit annotations on + // intersection bounds. See https://github.com/eisop/checker-framework/issues/1735. + // void callAnnotatedBounds(T p) {} + + void callAccessor(T p) {} + + void foo(@A Object aObj, @B Object bObj) { // :: warning: (cast.unsafe.constructor.invocation) Baz baz = new @A Baz(); call(baz); + // callAnnotatedBounds(baz); + callAccessor(baz); + + @A Object adapted = baz.get(); + // :: error: (assignment.type.incompatible) + @B Object notAdaptedToB = baz.get(); + + baz.set(aObj); + // :: error: (argument.type.incompatible) + baz.set(bObj); + } + + void intersectionCasts(Object obj) { + Foo fooAndBar = (Foo & Bar) obj; + Accessor fooAndAccessor = (Foo & Accessor) obj; + } + + void annotatedIntersectionCast(@B Object obj) { + // :: warning: (cast.unsafe) + Accessor fooAndAccessor = (@A Foo & Accessor) obj; } - interface B {} + interface BType {} - interface C {} + interface CType {} - abstract class D & C> {} + abstract class D & CType> {} - class BC implements B, C {} + class BC implements BType, CType {} class E extends D {} - & C> void callBC(T p) {} + & CType> void callBC(T p) {} } From a1b4841a9ede3cc53bc8a0d093de6ed519c530ef Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Sun, 24 May 2026 19:15:05 -0400 Subject: [PATCH 08/11] Clarify intersection annotation recomputation --- .../framework/type/AbstractViewpointAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 45523d68eaa6..b329c79eda54 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -485,9 +485,9 @@ private AnnotatedIntersectionType adaptIntersectionBounds( for (AnnotatedTypeMirror bound : bounds) { adaptedBounds.add(adaptBound.apply(bound)); } + // First replace the bounds copied by shallowCopy with the adapted bounds. Then clear the + // shallow copy's stale primary annotations and recompute them from the adapted bounds. intersection.setBounds(adaptedBounds); - // setBounds replaces the shared bounds from shallowCopy, so recompute primary annotations - // from the adapted bounds. intersection.clearAnnotations(); intersection.copyIntersectionBoundAnnotations(); return intersection; From c7758c3df8ef36aae2ed42e226d8bbae4c358d13 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Wed, 10 Jun 2026 23:06:12 -0400 Subject: [PATCH 09/11] Update framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java Co-authored-by: Werner Dietl --- .../framework/type/AbstractViewpointAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index b329c79eda54..e26c28cff8aa 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -472,7 +472,7 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy /** * Returns a copy of {@code source} whose bounds have been adapted by {@code adaptBound}. * - * @param source intersection type whose bounds are adapted + * @param source intersection type whose bounds should be adapted * @param adaptBound function that adapts one bound * @return a copy of {@code source} with adapted bounds */ From 0611ca656247768b634eacff83ccc0eb43fb9ac1 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Thu, 11 Jun 2026 00:09:38 -0400 Subject: [PATCH 10/11] Test viewpoint adaptation of intersection bounds --- .../viewpointtest/IntersectionTypes.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index 6b2fbccc099f..2e12d0b253ac 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -10,7 +10,8 @@ interface Foo {} interface Bar {} interface Accessor { - @ReceiverDependentQual Object get(); + @ReceiverDependentQual + Object get(); void set(@ReceiverDependentQual Object o); } @@ -27,17 +28,24 @@ public void set(@ReceiverDependentQual Object o) {} void call(T p) {} - // TODO: Re-enable after deciding the expected behavior for mixed explicit annotations on - // intersection bounds. See https://github.com/eisop/checker-framework/issues/1735. - // void callAnnotatedBounds(T p) {} - void callAccessor(T p) {} + class ViewpointAdaptedIntersectionBound { + void viewpointAdaptedIntersectionBound(@A T p, @A Object aObj, @B Object bObj) { + @A Object adapted = p.get(); + // :: error: (assignment.type.incompatible) + @B Object notAdaptedToB = p.get(); + + p.set(aObj); + // :: error: (argument.type.incompatible) + p.set(bObj); + } + } + void foo(@A Object aObj, @B Object bObj) { // :: warning: (cast.unsafe.constructor.invocation) Baz baz = new @A Baz(); call(baz); - // callAnnotatedBounds(baz); callAccessor(baz); @A Object adapted = baz.get(); @@ -70,4 +78,10 @@ class BC implements BType, CType {} class E extends D {} & CType> void callBC(T p) {} + + // Documents the current decision for https://github.com/eisop/checker-framework/issues/1735: + // when multiple bounds in an intersection type have explicit qualifiers in the same hierarchy, + // the later qualifier is ignored. + // :: warning: (explicit.annotation.ignored) + void callAnnotatedBounds(T p) {} } From 4d7087d3eb72a82f51c21808d5ef42299b3f2126 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Thu, 11 Jun 2026 00:22:42 -0400 Subject: [PATCH 11/11] Fix viewpoint intersection test formatting --- framework/tests/viewpointtest/IntersectionTypes.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/tests/viewpointtest/IntersectionTypes.java b/framework/tests/viewpointtest/IntersectionTypes.java index 2e12d0b253ac..f95391477a1e 100644 --- a/framework/tests/viewpointtest/IntersectionTypes.java +++ b/framework/tests/viewpointtest/IntersectionTypes.java @@ -10,8 +10,7 @@ interface Foo {} interface Bar {} interface Accessor { - @ReceiverDependentQual - Object get(); + @ReceiverDependentQual Object get(); void set(@ReceiverDependentQual Object o); }