Skip to content

DoubleJavacVisitor walks two javac ASTs in lockstep#7429

Open
avenger2597 wants to merge 69 commits intotypetools:masterfrom
avenger2597:ajava-javac-visitor-base
Open

DoubleJavacVisitor walks two javac ASTs in lockstep#7429
avenger2597 wants to merge 69 commits intotypetools:masterfrom
avenger2597:ajava-javac-visitor-base

Conversation

@avenger2597
Copy link

This PR adds the initial DoubleJavacVisitor that walks two javac ASTs in lockstep. It includes the basic traversal logic and helpers needed to keep the two trees aligned, similar to what the existing JavaParser-based visitor does.

This is infrastructure-only and sets up later work to move ajava processing from JavaParser to javac.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

Adds a new public abstract class org.checkerframework.framework.ajava.DoubleJavacVisitor (extends SimpleTreeVisitor<Void, Tree>) with a protected no‑arg constructor, an abstract protected method defaultAction(Tree, Tree), and final scan(Tree, Tree) and scanList(List<? extends Tree>, List<? extends Tree>) methods. The class drives synchronized traversal of two Javac ASTs, asserts structural parity, and uses reflection to handle newer Javac node kinds. Adds TreeUtilsAfterJava11.ClassTreeUtils with getPermitsClause(ClassTree) and getRecordComponents(ClassTree) reflective helpers. Updates the contributors list.

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a80dcf and c9487d2.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🔇 Additional comments (4)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (4)

1-83: Well-structured infrastructure for synchronized AST traversal.

The class design is clean with clear separation of concerns:

  • Abstract defaultPairAction provides a focused extension point
  • Comprehensive documentation explains usage patterns and javac-specific considerations (wrapper nodes)
  • The visitor pattern delegation through defaultAction is appropriate

103-235: Solid traversal and normalization implementation.

The core scanning methods are well-implemented with:

  • Explicit null handling and alignment validation
  • Clear error messages including class name and context
  • Proper normalization to handle javac wrapper nodes

The normalization loop correctly handles nested wrappers by iterating until stable.

Consider whether additional wrapper node types should be normalized. The current implementation handles ParenthesizedTree and ExpressionStatementTree. Are there other javac wrapper constructs that could cause alignment issues between the two ASTs?


244-288: Clarify the intent of final modifiers on visitor methods.

The methods visitCompilationUnit, visitPackage, and visitImport are marked final, while other visitor methods (e.g., visitClass, visitMethod) are not.

If this pattern is intentional—locking down top-level structural traversal while allowing flexibility in content nodes—consider documenting this design decision in the class javadoc to guide subclass implementers.


244-611: The implemented visitor method pattern is intentional and well-designed. The class is documented to work by having subclasses override defaultPairAction() to handle all tree types uniformly, then override specific visit* methods only for nodes requiring recursive traversal. Missing visitor methods like visitIf, visitWhile, visitLambda, etc., intentionally fall through to defaultAction(), which calls defaultPairAction() without automatic recursion. This allows subclasses to centralize comparison logic in defaultPairAction() and selectively add recursion where needed.

The final modifier on visitCompilationUnit, visitPackage, and visitImport is intentional—these top-level structural nodes lock down the traversal backbone, while non-final content nodes (Class, Method, Variable) remain open for override flexibility.

Copy link
Contributor

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

This looks pretty good to me as a start! I had a few (mostly minor) comments.

As to whether to land this code separately without any usages or tests, I'm not sure about that. Perhaps @mernst could comment. But I like being able to look over the code in a PR that is not huge.

@mernst
Copy link
Member

mernst commented Dec 29, 2025

Let's land it separately, but not until I have had time to do a code review.

1 similar comment
@mernst
Copy link
Member

mernst commented Dec 29, 2025

Let's land it separately, but not until I have had time to do a code review.

Copy link
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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9487d2 and 91baf9b.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🔇 Additional comments (11)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (11)

1-51: LGTM!

The imports are appropriate for the tree types being visited, and the class documentation clearly explains the dual-tree traversal pattern and usage instructions.


53-80: LGTM!

The protected visibility for defaultPairAction is appropriate for a subclass hook method. The defaultAction override correctly delegates to defaultPairAction, ensuring all tree pairs (including those without explicit visitXxx overrides) go through the hook.

Note: The AI-generated summary incorrectly states this is a "public abstract method" when it's actually protected.


110-112: AI summary mentions normalization methods that don't exist.

The AI-generated summary lists normalize(Tree), unwrapParentheses(Tree), and unwrapExpressionStatement(Tree) as public methods, but these are not present in the code. The comment here indicates normalization may be re-introduced later. This is just a summary discrepancy, not a code issue.


133-144: The null-check duplication is acceptable for better error messages.

While scanOpt duplicates the null-checking logic from scan, having a distinct error message mentioning "scanOpt" aids debugging. If you prefer DRY, you could extract a helper that takes the method name, but the current approach is clear and maintainable.


155-157: LGTM!

The lack of @Nullable on scanExpr parameters is appropriate—this method is for call sites where expressions are known to be non-null. The Javadoc clearly documents its purpose as an intent-documenting wrapper.


168-178: LGTM!

The size check before iteration prevents subtle misalignment bugs. The error message includes both lists and their sizes, which aids debugging.


180-254: LGTM!

The compilation unit, package, import, and class visitors correctly traverse all structural children. The final modifier on the first three prevents subclass interference with foundational traversal, while visitClass remains overridable.


256-301: LGTM!

visitMethod comprehensively scans all relevant children including the less obvious ones (receiver parameter, default value for annotation methods). visitVariable correctly handles the common subset; the Javadoc appropriately notes that record component details are handled elsewhere.


303-402: LGTM!

Type-related visitors (visitModifiers through visitPrimitiveType) correctly traverse their child structures. Leaf nodes like visitPrimitiveType appropriately have no recursive scan calls.


404-465: LGTM!

visitTypeParameter, visitWildcard, visitIdentifier, and visitMemberSelect correctly handle their respective structures. Using scanOpt for the optional wildcard bound is appropriate.


467-552: LGTM!

Statement visitors (visitBlock through visitTry) correctly traverse their children. visitTry comprehensively handles resources, body block, catch clauses, and optional finally block.

@avenger2597 avenger2597 force-pushed the ajava-javac-visitor-base branch from 91baf9b to 1cdd455 Compare January 2, 2026 23:02
Copy link
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

♻️ Duplicate comments (1)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (1)

98-121: Consider using IllegalStateException instead of Error.

Error is conventionally reserved for serious JVM-level problems. For application-level invariant violations like mismatched tree structures, IllegalStateException is more appropriate. This applies to lines 104, 114, 138, and 170.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91baf9b and 1cdd455.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: typetools.checker-framework (misc_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part1_jdk25)
  • GitHub Check: typetools.checker-framework (nonjunit_jdk25)
  • GitHub Check: typetools.checker-framework (junit_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part1_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part2_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part2_jdk25)
🔇 Additional comments (12)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (12)

1-28: LGTM!

Package declaration and imports are appropriate for the visitor implementation.


48-51: LGTM!

Class declaration and constructor are well-structured for an abstract visitor base.


53-80: LGTM!

The delegation pattern from defaultAction to defaultPairAction cleanly bridges the SimpleTreeVisitor API to the paired-traversal abstraction.


146-157: LGTM!

scanExpr provides clear intent at call sites while delegating to scan.


159-178: LGTM!

The size validation and lockstep iteration correctly enforce list alignment between the two ASTs.


187-198: LGTM!

Comprehensive traversal of compilation unit children with appropriate use of scanOpt for optional module/package and scanList for imports and type declarations.


207-231: LGTM!

Package and import visitors correctly traverse their child nodes.


264-317: LGTM!

Method, variable, and modifiers visitors comprehensively traverse their child nodes, with appropriate use of scanOpt for optional elements like receiver parameters and initializers.


326-435: LGTM!

Type-related visitors (annotation, annotated type, parameterized type, array type, primitive type, type parameter, wildcard) correctly traverse their respective child nodes with consistent patterns.


444-553: LGTM!

Expression and statement visitors correctly traverse their child nodes. The visitTry implementation comprehensively handles resources, block, catches, and finally block.

Note: Many common AST node types (if, for, while, method invocation, lambda, etc.) don't have explicit visit methods yet. They'll fall back to defaultAction, which calls defaultPairAction but doesn't recurse into children. This is acceptable for an initial infrastructure implementation—subclasses can add visitor overrides as needed.


133-144: Redundant null-checking: scan already handles these cases.

The scan method already handles both-null (returns) and one-null (throws). scanOpt duplicates this logic before delegating to scan. Consider simplifying:

🔎 Proposed refactor
   public final void scanOpt(@Nullable Tree tree1, @Nullable Tree tree2) {
-    if (tree1 == null && tree2 == null) {
-      return;
-    }
-    if (tree1 == null || tree2 == null) {
-      throw new Error(
-          String.format(
-              "%s.scanOpt: one tree is null: tree1=%s tree2=%s",
-              this.getClass().getCanonicalName(), tree1, tree2));
-    }
     scan(tree1, tree2);
   }

Alternatively, if distinct error messages from scanOpt are valuable for debugging, keep the current implementation.

Likely an incorrect or invalid review comment.


241-254: The review comment is incorrect. ClassTree in the Tree API does not have a getRecordComponents() method. Record components in the javac Tree API are represented as VariableTree nodes within ClassTree.getMembers(), which this visitor already scans. The current implementation correctly handles record components through the scanList(tree1.getMembers(), tree2cls.getMembers()) call. No changes are needed.

Likely an incorrect or invalid review comment.

@msridhar
Copy link
Contributor

msridhar commented Jan 4, 2026

I think @mernst should look this over next so I've assigned him.

@mernst mernst assigned avenger2597 and unassigned mernst Jan 5, 2026
@avenger2597 avenger2597 requested a review from mernst January 6, 2026 01:42
Copy link
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

Fix all issues with AI Agents 🤖
In
@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java:
- Around line 475-493: The Javadoc for visitExpressionStatement incorrectly
references a removed normalization step; update the comment to remove or
rephrase the "may be redundant when normalize unwraps expression statements"
part and instead state that the method scans the expression and supports direct
invocation on ExpressionStatementTree nodes (mention visitExpressionStatement,
ExpressionStatementTree, scanExpr) so the comment accurately reflects current
behavior.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2cf81 and 5f0beae.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🔇 Additional comments (1)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (1)

30-39: Clarify the class javadoc per maintainer feedback.

The phrase "except for differences in annotations between the Java file and its corresponding .ajava file" (lines 34-35) is ambiguous. Do you mean the trees are structurally identical except that annotation nodes may differ (be present, absent, or have different values)?

Based on past review comments, the maintainer requested clarification of this phrasing.

Copy link
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: 3

🤖 Fix all issues with AI Agents
In
@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java:
- Around line 148-167: The method DoubleJavacVisitor.scanList throws
java.lang.Error on a list-size mismatch; change the thrown type to
java.lang.IllegalStateException to reflect an application-level invariant
violation: replace the throw new Error(...) with throw new
IllegalStateException(...) while preserving the existing formatted message
(String.format(...)) so callers get the same diagnostic text.
- Around line 90-110: Replace the two throws of java.lang.Error in
DoubleJavacVisitor.scan with java.lang.IllegalStateException to reflect
application-level invariant violations; in the scan(@Nullable Tree tree1,
@Nullable Tree tree2) method change both "throw new Error(String.format(...))"
instances (the null-tree check and the mismatched-kind check) to "throw new
IllegalStateException(String.format(...))" while preserving the existing
formatted messages and the use of this.getClass().getCanonicalName(), tree1,
tree2, and tree1.getKind()/tree2.getKind().
- Around line 122-133: Replace the thrown Error in DoubleJavacVisitor.scanOpt
with an IllegalStateException: locate the scanOpt method and change the throw
new Error(...) call to throw new IllegalStateException(...) so it matches the
exception type used in scan and follows proper exception hierarchy and
consistency.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f0beae and a03e0a4.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:40:48.819Z
Learnt from: kelloggm
Repo: typetools/checker-framework PR: 7166
File: checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java:470-515
Timestamp: 2025-10-22T20:40:48.819Z
Learning: In MustCallVisitor.java (Checker Framework), prefer using Name.equals(...) or Objects.equals(...) for com.sun.source.util.Name comparisons (instead of ==/!=). This should be the default unless the Interning Checker is explicitly used to guarantee reference equality.

Applied to files:

  • framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java
🔇 Additional comments (1)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (1)

177-220: Clarify or fix inconsistent final modifiers on visit methods.

visitCompilationUnit (line 177), visitPackage (line 197), and visitImport (line 214) are declared final, while all other visitXxx methods are not. The class Javadoc states that "subclasses are expected to override visitXxx methods for the tree kinds they care about" (lines 61-62), which suggests visit methods should be overridable.

Is there a specific reason these three methods must not be overridden, or is this an inconsistency that should be resolved?

If these methods should be overridable like the others, remove the final modifier. If they must remain final, consider documenting why in the Javadoc to clarify the design intent.

@msridhar msridhar assigned mernst and unassigned avenger2597 Jan 9, 2026
Copy link
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/ajava/DoubleJavacVisitor.java`:
- Around line 551-572: The visitCase method omits traversal of the CaseTree
guard (so guarded patterns from JDK 21+ are skipped); update visitCase to call
scan(TreeUtilsAfterJava11.CaseUtils.getGuard(ctree1),
TreeUtilsAfterJava11.CaseUtils.getGuard(ctree2)) at an appropriate point (e.g.,
after scanning labels and before scanning the body/statements) so the guard
subtree is visited when present; modify the method containing the visitCase
override to include this scan call, referencing visitCase,
TreeUtilsAfterJava11.CaseUtils.getGuard, and the existing scan/scanList usage to
locate where to insert it.

---

Duplicate comments:
In
`@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java`:
- Around line 135-147: The BugInCF exceptions in DoubleJavacVisitor are being
constructed with pre-formatted strings via String.format even though
BugInCF(String fmt, Object... args) does formatting itself; update the two throw
sites in scan (the null-tree check and the kind-mismatch check) to remove
String.format and instead call new BugInCF with the format string and the
corresponding arguments (use this.getClass().getCanonicalName(), tree1, kind1,
tree2, kind2) so BugInCF handles formatting; apply the same change for the other
duplicated occurrences noted in the review.
- Around line 560-570: The code only tests
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree1) and then unconditionally calls
getBody on ctree1 while calling getBody or getStatements on ctree2, which can
mix arrow-style and colon-style case clauses; change the branch in
DoubleJavacVisitor so you first check both CaseUtils.isCaseRule(ctree1) and
CaseUtils.isCaseRule(ctree2): if both are case rules, call getBody on both and
scan them; else if neither are case rules, call getStatements on both and
scanList them; otherwise treat the nodes as a kind mismatch (e.g., report the
difference or bail out) rather than calling getBody on one and getStatements on
the other.

Copy link
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.

🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 135-147: In DoubleJavacVisitor replace the redundant
String.format(...) wrapping when throwing BugInCF: for the checks that detect
null trees and mismatched kinds (the throws using "%s.scan: one tree is null:
tree1=%s [%s] tree2=%s [%s]" and "%s.scan: mismatched kinds: tree1=%s [%s]
tree2=%s [%s]") and the two similar throw sites later, call new BugInCF with the
format string and the arguments directly (e.g., new BugInCF(fmt,
this.getClass().getCanonicalName(), tree1, kind1, tree2, kind2)) instead of
passing String.format(...); update all four occurrences accordingly.

@msridhar msridhar assigned mernst and unassigned avenger2597 Feb 21, 2026
@msridhar
Copy link
Contributor

@mernst I think this is ready for another look from you

Copy link
Member

@mernst mernst left a comment

Choose a reason for hiding this comment

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

Thanks! This is looking good. I have just a few comments.

@mernst mernst assigned avenger2597 and unassigned mernst Feb 21, 2026
Copy link
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

🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 229-235: The scanList method can NPE when both lists are null
because assertSameLength allows two nulls; update scanList (method name) to
check for null after calling assertSameLength and return early if list1 (and
therefore list2) is null before iterating, so the for-loop invoking list1.size()
and scan(...) is only executed when list1 is non-null.
- Around line 133-145: The throws in DoubleJavacVisitor use explicit
String.format before constructing UserError; replace those calls so you pass the
format string and the argument list directly to the UserError constructor (e.g.,
for the null-tree and mismatched-kind checks), removing String.format and
keeping the same format string and arguments (use
this.getClass().getCanonicalName(), tree1, kind1, tree2, kind2 as before);
update all four throw sites that currently wrap String.format to call new
UserError(fmt, args...) instead.

Copy link
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.

🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 229-235: The scanList method can NPE when both lists are null:
after calling assertSameLength(list1, list2) you must handle the null case
before dereferencing list1; update scanList (the method that is called from
visitNewArray and where NewArrayTree.getInitializers() may return null) to
return immediately when list1 (and therefore list2) is null instead of
proceeding to list1.size(), so check for list1 == null (or both null) and return
early, preserving the assertSameLength call and the existing loop logic for
non-null lists.
- Around line 133-145: The code in DoubleJavacVisitor (in methods that call
scan) wraps error messages with String.format before constructing UserError;
instead, remove the String.format wrapper and call the UserError constructor
with the format string and the format arguments directly (e.g., change throw new
UserError(String.format(fmt, a,b,c)) to throw new UserError(fmt, a,b,c)) for the
null-tree and mismatched-kind checks and the other two analogous throw sites;
update the four occurrences to pass the format string plus the tree/kind
variables to UserError so the `@FormatMethod` constructor handles formatting.

Copy link
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/ajava/DoubleJavacVisitor.java`:
- Around line 163-167: The format string in the UserError thrown in
DoubleJavacVisitor (throw new UserError(...)) is missing a closing parenthesis;
update the format pattern to close the parenthesis so the message becomes
balanced (e.g., change "%s: different size lists: (%s [size %d], %s [size %d]"
to include the final ")" so it reads "%s: different size lists: (%s [size %d],
%s [size %d])").

---

Duplicate comments:
In
`@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java`:
- Around line 594-604: The UserError instantiation in DoubleJavacVisitor (inside
the visitCase handling) is redundantly wrapping the message with String.format;
change the call to pass the format string and the arguments directly to the
UserError constructor (i.e., remove String.format and supply the same format
string and the same five arguments: this.getClass().getCanonicalName(), ctree1,
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree1), ctree2,
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree2)) so it matches the other
assertion sites in the file.

Copy link
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: 3

🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 292-296: Replace uses of scan(...) with scanExpr(...) for child
nodes that are ExpressionTree-typed: change the call using
DeconstructionPatternUtils.getDeconstructor(...) in DoubleJavacVisitor (case
"DECONSTRUCTION_PATTERN") from scan(...) to scanExpr(...), and similarly replace
scan(MethodTree.getDefaultValue()), scan(ReturnTree.getExpression()), and
scan(ModuleTree.getName()) with scanExpr(...) so the visitor consistently
signals ExpressionTree children; leave surrounding defaultAction(...) and scan
ordering unchanged.
- Around line 460-467: The visitBlock method currently scans paired BlockTree
nodes but does not verify that their static-ness matches; add a check in
visitBlock (method visitBlock(BlockTree btree1, Tree tree2)) that compares
btree1.isStatic() and btree2.isStatic() and either assert they are equal or
throw/handle a mismatch before calling defaultAction/scanList, or alternatively
add a Javadoc comment on visitBlock explaining that static-ness is intentionally
not checked; reference BlockTree.isStatic() and the visitBlock implementation
when making the change.
- Around line 594-616: In visitCase in DoubleJavacVisitor, avoid repeated
reflective calls to TreeUtilsAfterJava11.CaseUtils.isCaseRule by storing results
in local booleans (e.g., isCase1 and isCase2) and use those variables both for
the mismatch check and the subsequent branch decision; also remove the redundant
String.format wrapper and throw UserError using the same varargs-style
constructor used elsewhere (pass the format string and arguments directly, using
isCase1/isCase2 and the ctree variables) so the error message is consistent with
other UserError throws.

Copy link
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

🤖 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/ajava/DoubleJavacVisitor.java`:
- Line 415: The call currently uses scan(mtree1.getDefaultValue(),
mtree2.getDefaultValue()) but getDefaultValue() returns an ExpressionTree and by
convention this visitor must use scanExpr for ExpressionTree children; update
the invocation in DoubleJavacVisitor (where mtree1.getDefaultValue() /
mtree2.getDefaultValue() are handled) to call scanExpr(mtree1.getDefaultValue(),
mtree2.getDefaultValue()) so the ExpressionTree-specific scanning logic is used.
- Around line 133-143: The error messages in assertSameKind incorrectly
hard-code ".scan:"; update both UserError format strings in
DoubleJavacVisitor.assertSameKind to use the correct method name (e.g.,
".assertSameKind:") instead of ".scan:" so exceptions reflect the actual method,
and keep the same format arguments (this.getClass().getCanonicalName(), tree1,
kind1, tree2, kind2).

---

Duplicate comments:
In
`@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java`:
- Around line 594-616: The code repeatedly calls
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree1/ctree2) and wraps the format
string with String.format before constructing UserError; fix by caching the two
boolean results in local variables (e.g., boolean isCase1 =
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree1); boolean isCase2 =
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree2)) and use those variables in
the conditional and in the error message, and remove the String.format call so
you call new UserError("...%s...", this.getClass().getCanonicalName(), ctree1,
isCase1, ctree2, isCase2) (i.e., pass the format string and args directly to
UserError consistent with other uses in DoubleJavacVisitor).

Copy link
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.

🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 460-467: Add a short Javadoc comment on
DoubleJavacVisitor.visitBlock explaining that this method intentionally does not
check BlockTree.isStatic() (so it treats static and instance initializers the
same) and that Tree.Kind.BLOCK is shared by both static and instance initializer
blocks, so any kind-based mismatch won't be detected by scan(); reference the
method name visitBlock and the BlockTree.isStatic() check in the comment to make
the omission explicit for future readers.
- Around line 594-606: Cache the reflective results of
TreeUtilsAfterJava11.CaseUtils.isCaseRule(ctree1) and isCaseRule(ctree2) into
local boolean vars (e.g., boolean isCase1, isCase2) and use those variables in
the subsequent comparisons and condition (replace repeated reflective calls),
and change the UserError construction to pass the format string and arguments
directly (i.e., UserError(formatString, args...)) instead of wrapping
String.format(...) so the call is consistent with other UserError throws; update
the if-checks and the subsequent if (isCase1) to use the cached booleans.

Copy link
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

♻️ Duplicate comments (2)
framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java (2)

480-480: scan used for ExpressionTree-returning getDefaultValue() — already flagged in a previous review.

MethodTree.getDefaultValue() returns ExpressionTree, making this a candidate for a more specific scanExpr helper (if one is ever introduced) to signal to readers that the child is an expression. No functional defect in the current form.

🤖 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/ajava/DoubleJavacVisitor.java`
at line 480, The call uses the generic scan(...) for
MethodTree.getDefaultValue() which returns an ExpressionTree; update to a
clearer, expression-specific helper by replacing scan(mtree1.getDefaultValue(),
mtree2.getDefaultValue()) with a call to the expression-scanning helper (e.g.,
scanExpr) so readers know this child is an ExpressionTree—locate the call site
in DoubleJavacVisitor and use the MethodTree.getDefaultValue() symbols and the
scan method name to make the replacement (add scanExpr if it doesn't exist and
delegate to the same traversal logic).

525-532: visitBlock does not verify BlockTree.isStatic() matches — already flagged in a previous review.

Both Tree.Kind.BLOCK static and instance initializer blocks share the same kind, so scan()'s kind check cannot detect a mismatch. An assertion or Javadoc note would make the omission explicit.

🤖 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/ajava/DoubleJavacVisitor.java`
around lines 525 - 532, visitBlock fails to check that the two BlockTree nodes
have matching static-ness (BlockTree.isStatic()), so static initializer vs
instance initializer mismatches can pass unnoticed; update visitBlock to assert
or validate that btree1.isStatic() == btree2.isStatic() (before proceeding to
defaultAction/scanList) and throw or fail the comparison if they differ, and/or
add a Javadoc note on visitBlock explaining that callers must ensure static-ness
matches; reference the visitBlock method and BlockTree.isStatic() when making
the change.
🤖 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/ajava/DoubleJavacVisitor.java`:
- Around line 1073-1079: visitMemberSelect currently only scans the expression
and does not compare or scan the selected name
(MemberSelectTree.getIdentifier()), so member-name mismatches go unnoticed; add
a brief Javadoc note on DoubleJavacVisitor.visitMemberSelect (mirroring the
wildcard-direction note style) stating that the selected identifier is not
compared/scanned and that callers must supply trees with identical member names
(or else ensure they compare identifiers externally).

---

Duplicate comments:
In
`@framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavacVisitor.java`:
- Line 480: The call uses the generic scan(...) for MethodTree.getDefaultValue()
which returns an ExpressionTree; update to a clearer, expression-specific helper
by replacing scan(mtree1.getDefaultValue(), mtree2.getDefaultValue()) with a
call to the expression-scanning helper (e.g., scanExpr) so readers know this
child is an ExpressionTree—locate the call site in DoubleJavacVisitor and use
the MethodTree.getDefaultValue() symbols and the scan method name to make the
replacement (add scanExpr if it doesn't exist and delegate to the same traversal
logic).
- Around line 525-532: visitBlock fails to check that the two BlockTree nodes
have matching static-ness (BlockTree.isStatic()), so static initializer vs
instance initializer mismatches can pass unnoticed; update visitBlock to assert
or validate that btree1.isStatic() == btree2.isStatic() (before proceeding to
defaultAction/scanList) and throw or fail the comparison if they differ, and/or
add a Javadoc note on visitBlock explaining that callers must ensure static-ness
matches; reference the visitBlock method and BlockTree.isStatic() when making
the change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d0e243 and 5494660.

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

@avenger2597 avenger2597 requested a review from mernst February 26, 2026 20:34
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.

3 participants