Skip to content

Add @DoesNotUnrefineReceiver annotation#7640

Open
mernst wants to merge 12 commits intotypetools:masterfrom
mernst:does-not-unrefine-receiver
Open

Add @DoesNotUnrefineReceiver annotation#7640
mernst wants to merge 12 commits intotypetools:masterfrom
mernst:does-not-unrefine-receiver

Conversation

@mernst
Copy link
Copy Markdown
Member

@mernst mernst commented Apr 11, 2026

This feature is tested via yukiyang:modifiability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new @DoesNotUnrefineReceiver annotation to the dataflow qualifier framework. The annotation indicates that when a method is called, the receiver's refined type information should not be unrefined (reverted) by the flow analysis. The implementation adds the annotation definition, creates interface methods and implementations to detect it across the framework, and modifies the data flow store's method-call handling to preserve refined type information for the receiver when the annotation is present. A Javadoc fix and clarifying inline comments are also included.

Possibly related PRs

Suggested reviewers

  • smillst
🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java`:
- Around line 12-13: Fix the Javadoc typos and clarify the semantics of the
annotation element: in the Javadoc for the annotation type
DoesNotUnrefineReceiver correct "unrefinde" to "unrefined" and reword the
description of the value() element to use plural/array wording (e.g., "the
receiver's types" or "one or more receiver types" and "an array of type
qualifiers" / "the annotation elements") so it accurately describes that value()
returns an array of qualifiers rather than a singular qualifier; update the
corresponding Javadoc occurrences (including the similar text around lines
25–29) to match this corrected spelling and pluralized/array-aware phrasing.

In `@checker/src/main/java/org/checkerframework/checker/lock/LockStore.java`:
- Line 169: The comment in LockStore.java refers to the type-qualifier
"LockPosssiblyHeld" which is a typo; update the comment to use the correct name
"LockPossiblyHeld" for consistency with the actual annotation/type and related
code (e.g., LockAnnotatedTypeFactory and the LockPossiblyHeld type hierarchy).
Locate the comment mentioning LockPosssiblyHeld and replace it with
LockPossiblyHeld so documentation matches the code symbols.

In `@docs/CHANGELOG.md`:
- Around line 12-13: Changelog entry is inconsistent: update the API line to
document the actual method signature added to AnnotationProvider as
hasDoesNotUnrefineReceiver(ExecutableElement methodElement) (and mention the new
method annotation DoesNotUnrefineReceiver) so the changelog reflects the real
interface contract; ensure the exact parameter type and name ExecutableElement
methodElement are shown in the entry to avoid ambiguity.

In
`@framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java`:
- Around line 272-285: The doNotUnrefineField predicate is redundant; either
remove it and pass doNotUnrefine::test into updateFieldValuesForMethodCall or
broaden updateFieldValuesForMethodCall's parameter to Predicate<? super
FieldAccess> so you can pass doNotUnrefine directly; update the call site in
CFAbstractStore (replace doNotUnrefineField) and adjust
updateFieldValuesForMethodCall's signature and usages to accept Predicate<?
super FieldAccess> if you choose the signature-change option.
- Around line 388-390: The Javadoc for CFAbstractStore (parameter doNotUnrefine)
contains grammar errors; update the comment to read clearly and grammatically
(e.g., "if true for a field access, don't unrefine it" and "This predicate
indicates exceptions: fields that are not updated by this method") so that the
parameter description and the following sentence are correct and consistent;
edit the Javadoc block that documents the doNotUnrefine parameter in
CFAbstractStore to apply these fixes.

In
`@framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java`:
- Around line 5868-5880: The method hasDoesNotUnrefineReceiver currently treats
an empty `@DoesNotUnrefineReceiver` value as non-matching; update the logic in
hasDoesNotUnrefineReceiver (after obtaining typeSystems via
AnnotationUtils.getElementValueArray on doesNotUnrefineReceiverValueElement) to
treat an empty typeSystems list as applying to all checkers by returning true
when typeSystems.isEmpty(); otherwise keep the existing loop over
checker.getSuppressWarningsPrefixes() to check membership and then return false
if none match. Use the existing helpers getDeclAnnotations and areSameByClass to
locate the correct annotation handling block.

In
`@javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java`:
- Around line 61-62: The Javadoc for the method in AnnotationProvider contains
duplicated wording "does not does not"; update the method's Javadoc comment to
remove the duplicate so the sentence reads "Returns true if the given method may
have side effects but does not unrefine its receiver." Locate the Javadoc above
the method declaration in class AnnotationProvider and edit the comment text
accordingly, leaving the rest of the docblock intact.

In
`@javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java`:
- Around line 90-92: Update the Javadoc in BasicAnnotationProvider that
documents the `@DoesNotUnrefineReceiver` annotation: change the incorrect element
name "values" to the correct "value" (i.e., refer to the annotation element as
"value") so the comment matches the actual annotation API used by methods like
those in BasicAnnotationProvider that check for `@DoesNotUnrefineReceiver`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4deb6027-a139-4c0d-91f8-04817984db32

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6c616 and 0c665a5.

📒 Files selected for processing (8)
  • checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java
  • checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java
  • checker/src/main/java/org/checkerframework/checker/lock/LockStore.java
  • docs/CHANGELOG.md
  • framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java
  • framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java
  • javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java
  • javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java (1)

25-29: ⚠️ Potential issue | 🟡 Minor

Pluralize the @return Javadoc to match String[] value().

Line 28 still describes a singular checker name, but value() returns an array. Please use plural wording for consistency with Line 25 and the method signature.

📝 Suggested doc patch
-   * `@return` the name of the checker that this annotation affects
+   * `@return` the names of the checkers that this annotation affects
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java`
around lines 25 - 29, Update the Javadoc for the annotation's value() method in
DoesNotUnrefineReceiver.java to use plural wording: change the `@return` text from
a singular "the name of the checker that this annotation affects" to a plural
form such as "the names of the checkers that this annotation affects" (or "the
checkers affected by this annotation") so it matches the String[] return type
and the class-level wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java`:
- Around line 267-269: The code clears thisValue whenever
sideEffectsUnrefineAliases is true except when receiverJe is a ThisReference,
but it also needs to preserve thisValue for SuperReference (since annotated
super.method() uses the same backing store); update the conditional in
CFAbstractStore around sideEffectsUnrefineAliases to exempt both ThisReference
and SuperReference (i.e., check receiverJe instanceof ThisReference ||
receiverJe instanceof SuperReference) so thisValue is not nulled for super
calls.

In
`@framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java`:
- Around line 1391-1392: The unlabeled boolean arguments in the second
analyze(...) call should be replaced with the same labeled-argument style used
earlier to improve readability; update the analyze(...) invocation (the one
currently passing true, true) to use /* isInitializationCode= */ true and /*
updateInitializationStore= */ true so it matches the first analyze(...) call in
GenericAnnotatedTypeFactory and makes the purpose of each flag explicit.

---

Duplicate comments:
In
`@checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java`:
- Around line 25-29: Update the Javadoc for the annotation's value() method in
DoesNotUnrefineReceiver.java to use plural wording: change the `@return` text from
a singular "the name of the checker that this annotation affects" to a plural
form such as "the names of the checkers that this annotation affects" (or "the
checkers affected by this annotation") so it matches the String[] return type
and the class-level wording.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a73dccf-9ac6-4be3-8934-ee85b09e7afc

📥 Commits

Reviewing files that changed from the base of the PR and between 0c665a5 and be6d64a.

📒 Files selected for processing (5)
  • checker-qual/src/main/java/org/checkerframework/dataflow/qual/DoesNotUnrefineReceiver.java
  • framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java
  • framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java
  • javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java
  • javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java

…AbstractStore.java

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java`:
- Around line 247-254: The current doNotUnrefine predicate preserves any
receiver expression that equals receiverJe, which unsafely keeps unstable
receivers (ArrayAccess, MethodCall, FieldAccess chains) and can leave stale
facts; modify the logic that computes receiverJe and doNotUnrefine to only grant
the exemption when the receiver is a stable form: create or call
isStableReceiverExpression(Node receiver) and set receiverJe =
hasDoesNotUnrefineReceiver && isStableReceiverExpression(receiver) ?
JavaExpression.fromNode(receiver) : null, and keep doNotUnrefine = receiverJe !=
null ? je -> je.equals(receiverJe) : je -> false; ensure
isStableReceiverExpression rejects ArrayAccess and MethodCall (and any
lookup-varying forms) so those receivers fall back to the existing invalidation
logic; apply the same change to the other doNotUnrefine sites in this class (the
other occurrences noted).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 101a627d-91a9-49b2-ae7d-34103a97bc59

📥 Commits

Reviewing files that changed from the base of the PR and between be6d64a and a4994ad.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java (1)

1399-1408: 🧹 Nitpick | 🔵 Trivial

Apply the same labeled-argument style in the sibling analyze(...) call.

true, true in this call is still unlabeled, so readability is inconsistent within the same method.

♻️ Suggested consistency update
               ControlFlowGraph cfg =
                   analyze(
                       classQueue,
                       lambdaQueue,
                       new CFGStatement(b, ct),
                       fieldValues,
                       null,
-                      true,
-                      true,
+                      /* isInitializationCode= */ true,
+                      /* updateInitializationStore= */ true,
                       b.isStatic(),
                       capturedStore);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java`
around lines 1399 - 1408, The analyze(...) call that starts with new
CFGStatement(b, ct) passes two unlabeled boolean literals ("true, true"), which
breaks consistency with the sibling call; update that invocation to use the same
labeled-argument style used by the other analyze(...) call by replacing those
two positional booleans with the corresponding named parameters from analyze's
signature (use the exact parameter names used in the sibling analyze call so the
call reads like analyze(classQueue, lambdaQueue, new CFGStatement(b, ct),
fieldValues, null, <paramName1>: true, <paramName2>: true, b.isStatic(),
capturedStore)).
framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java (1)

5865-5881: ⚠️ Potential issue | 🟠 Major

Handle empty @DoesNotUnrefineReceiver value as “applies to all checkers.”

On Lines 5869-5877, an empty typeSystems list currently falls through to return false. This rejects @DoesNotUnrefineReceiver({}), which should apply universally.

💡 Proposed fix
   `@Override`
   public boolean hasDoesNotUnrefineReceiver(ExecutableElement methodElement) {
     for (AnnotationMirror am : getDeclAnnotations(methodElement)) {
       if (areSameByClass(am, org.checkerframework.dataflow.qual.DoesNotUnrefineReceiver.class)) {
         List<String> typeSystems =
             AnnotationUtils.getElementValueArray(
                 am, doesNotUnrefineReceiverValueElement, String.class);
+        if (typeSystems.isEmpty()) {
+          return true;
+        }
         for (String prefix : checker.getSuppressWarningsPrefixes()) {
           if (typeSystems.contains(prefix)) {
             return true;
           }
         }
         return false;
       }
     }
     return false;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java`
around lines 5865 - 5881, The method hasDoesNotUnrefineReceiver currently treats
an empty List returned from AnnotationUtils.getElementValueArray (from the
`@DoesNotUnrefineReceiver` annotation) as “does not apply”, but an empty value
should mean “applies to all checkers”; in hasDoesNotUnrefineReceiver, after
obtaining typeSystems from AnnotationUtils.getElementValueArray (and before
iterating checker.getSuppressWarningsPrefixes()), add a check if
typeSystems.isEmpty() and return true to indicate universal applicability; keep
the rest of the logic (matching prefixes via
checker.getSuppressWarningsPrefixes() and returning false only when a non-empty
list does not contain any prefix).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java`:
- Around line 5865-5881: The method hasDoesNotUnrefineReceiver currently treats
an empty List returned from AnnotationUtils.getElementValueArray (from the
`@DoesNotUnrefineReceiver` annotation) as “does not apply”, but an empty value
should mean “applies to all checkers”; in hasDoesNotUnrefineReceiver, after
obtaining typeSystems from AnnotationUtils.getElementValueArray (and before
iterating checker.getSuppressWarningsPrefixes()), add a check if
typeSystems.isEmpty() and return true to indicate universal applicability; keep
the rest of the logic (matching prefixes via
checker.getSuppressWarningsPrefixes() and returning false only when a non-empty
list does not contain any prefix).

In
`@framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java`:
- Around line 1399-1408: The analyze(...) call that starts with new
CFGStatement(b, ct) passes two unlabeled boolean literals ("true, true"), which
breaks consistency with the sibling call; update that invocation to use the same
labeled-argument style used by the other analyze(...) call by replacing those
two positional booleans with the corresponding named parameters from analyze's
signature (use the exact parameter names used in the sibling analyze call so the
call reads like analyze(classQueue, lambdaQueue, new CFGStatement(b, ct),
fieldValues, null, <paramName1>: true, <paramName2>: true, b.isStatic(),
capturedStore)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28cdfd76-0ea5-4160-94b8-b2229b736381

📥 Commits

Reviewing files that changed from the base of the PR and between a4994ad and 45c19f9.

📒 Files selected for processing (3)
  • framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java
  • framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java
  • framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants