Skip to content

Don't rewrite non-JDK Optional in OptionalNot{Present,Empty} recipes#1147

Closed
timtebeek wants to merge 1 commit into
mainfrom
tim/issue-1146
Closed

Don't rewrite non-JDK Optional in OptionalNot{Present,Empty} recipes#1147
timtebeek wants to merge 1 commit into
mainfrom
tim/issue-1146

Conversation

@timtebeek

@timtebeek timtebeek commented Jun 25, 2026

Copy link
Copy Markdown
Member

What's changed?

OptionalNotPresentToIsEmpty and OptionalNotEmptyToIsPresent now skip a source file when the simple name Optional is bound to an explicitly imported type other than java.util.Optional.

Why?

When a third-party Optional (e.g. com.bazaarvoice.jolt.common.Optional, which has isPresent() but no isEmpty()) is off the recipe's typed classpath, javac can misattribute bar.isPresent() to java.util.Optional — for instance when a java.util.* import is in scope. The MethodMatcher (java.util.Optional isPresent()) then matches and the recipe rewrites the call to isEmpty(), which does not exist on the third-party type, so the module no longer compiles.

Because the type information is uniformly (mis)attributed to java.util.Optional in this scenario, a type-based guard on the receiver can't distinguish the cases. The reliable signal is the source itself: if the file explicitly imports a non-JDK type named Optional, the bare Optional name cannot be java.util.Optional, so the recipe conservatively leaves the file untouched.

The sibling OptionalNotEmptyToIsPresent has the identical structure and the same failure mode, so it gets the same guard.

Tests

Added doNotChangeNonJdkOptionalMisattributedToJdk to both recipe test classes. Both reproduce the misfire (they fail on main and pass with this change). A test covering a fully-resolved third-party Optional is also added.

When a third-party Optional (e.g. com.bazaarvoice.jolt.common.Optional) is
off the recipe classpath, javac can misattribute its isPresent()/isEmpty()
calls to java.util.Optional, fooling the MethodMatcher into emitting an
uncompilable isEmpty()/isPresent() call. Skip the file when the simple name
Optional is bound to some other explicitly imported type.

Fixes #1146
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Jun 25, 2026
@timtebeek timtebeek closed this Jun 25, 2026
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Jun 25, 2026
@timtebeek timtebeek deleted the tim/issue-1146 branch June 25, 2026 09:03
@timtebeek

Copy link
Copy Markdown
Member Author

This should be fixed in the type information upstream.

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

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

OptionalNotPresentToIsEmpty rewrites !isPresent() on a non-java.util.Optional type, producing uncompilable isEmpty()

1 participant