Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta
#1,146 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

  • #1147 di @timtebeek — chiusa senza merge

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
70/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
java
Ambito
tooling

Direzione di ricerca

Inizia da OptionalNotPresentToIsEmpty.java, in particolare dal MethodMatcher e dal modello di riscrittura citati alle righe 46 e 56. Riproduci la trasformazione con com.bazaarvoice.jolt.common.Optional, quindi aggiungi un test di regressione che dimostri che i receiver non risolti o non appartenenti al JDK rimangono invariati e che java.util.Optional continua a essere riscritto correttamente.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

What version of OpenRewrite are you using?

  • Gradle plugin org.openrewrite:plugin v7.35.0
  • rewrite-migrate-java v3.38.0
  • Active recipe: org.openrewrite.java.migrate.UpgradeToJava21 (which pulls in org.openrewrite.java.migrate.util.OptionalNotPresentToIsEmpty)

How are you running OpenRewrite?

Gradle plugin, applied via an init script over a large multi-module project:

rootProject {
    plugins.apply(org.openrewrite.gradle.RewritePlugin)
    dependencies {
        rewrite("org.openrewrite.recipe:rewrite-migrate-java:3.38.0")
    }
    rewrite {
        activeRecipe("org.openrewrite.java.migrate.UpgradeToJava21")
        setExportDatatables(true)
    }
}

The affected module depends on Jolt (com.bazaarvoice.jolt:jolt-core:0.1.8), which ships its own
com.bazaarvoice.jolt.common.Optional<T>. That class exposes isPresent() and get() but has no
isEmpty() method
(its full public API is empty(), of(T), get(), isPresent(), equals, toString).

The project is private, but the third-party type is public and small; the relevant snippet is below.

What is the smallest, simplest way to reproduce the problem?

The receiver is a Jolt Optional, not a JDK Optional:

import com.bazaarvoice.jolt.common.Optional; // NOT java.util.Optional

class ConvertMarkdownToHtmlRTL {
    Optional<Object> applySingle(Object input) {
        Optional<Object> baseHtmlOpt = convert(input);
        if (!baseHtmlOpt.isPresent()) {   // original, compiles fine
            return Optional.empty();
        }
        // ...
    }
}

What did you expect to see?

The code left unchanged. baseHtmlOpt is a com.bazaarvoice.jolt.common.Optional, not a
java.util.Optional, so the recipe should not apply. The recipe's MethodMatcher is in fact scoped to the
JDK type — new MethodMatcher("java.util.Optional isPresent()") with template
#{any(java.util.Optional)}.isEmpty()
(OptionalNotPresentToIsEmpty.java, lines 46 and 56 on v3.38.0) — so the intent is clearly JDK-only.

What did you see instead?

The recipe rewrote it to call isEmpty() on the Jolt type, which does not have that method, so the module
no longer compiles:

import com.bazaarvoice.jolt.common.Optional;

class ConvertMarkdownToHtmlRTL {
    Optional<Object> applySingle(Object input) {
        Optional<Object> baseHtmlOpt = convert(input);
        if (baseHtmlOpt.isEmpty()) {   // does not compile: no isEmpty() on com.bazaarvoice.jolt.common.Optional
            return Optional.empty();
        }
        // ...
    }
}

Because the JDK-scoped MethodMatcher nevertheless matched a com.bazaarvoice.jolt.common.Optional
receiver, this looks like the matcher firing when the receiver's type is unresolved/incompletely attributed
(the third-party Optional not being on the recipe's typed classpath), falling through to a name-only match
on isPresent(). A type-scoped migration recipe should not rewrite when it cannot positively confirm the
receiver is java.util.Optional — otherwise it silently emits uncompilable code with no warning or error from
the run itself.

  • Note this is a distinct failure mode from #417/#418 (a ClassCastException thrown by the sibling
    OptionalNotEmptyToIsPresent); here there is no crash — the recipe completes "successfully" and the breakage
    only surfaces at the next compileJava.

What is the full stack trace of any errors you encountered?

No stack trace — the recipe run completes without error. The failure is a downstream Java compile error:

error: cannot find symbol
        if (baseHtmlOpt.isEmpty()) {
                       ^
  symbol:   method isEmpty()
  location: variable baseHtmlOpt of type com.bazaarvoice.jolt.common.Optional<Object>

Are you interested in contributing a fix to OpenRewrite?

Open to it if you can point me at the preferred guard (e.g. requiring resolved receiver type before applying). Happy to add a recipe test that reproduces the mis-fire on a non-JDK Optional.

Lingua principale
Java
Stelle
156
Fork
129
Merge medio
7h 12m
PR unite (30g)
43

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openrewrite/rewrite-migrate-java

Tutte le issue di openrewrite/rewrite-migrate-java

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.