AI helps me for redaction and details but idea is mine.
Context
I have a utility class to make assertions (not null, not blank, positive, etc.) on a value see: Assert. Running mutation tests and all return this are replaced by return null. To kill all mutants, I should write dumb tests on each case to check not null returns.
JSpecify's @NullMarked annotation (on a package-info.java) makes non-null the default for every method in that package unless the return type is explicitly @Nullable. Tools like NullAway, Error Prone, the Checker Framework or IntelliJ enforce this statically at compile time: a method in a @NullMarked package that isn't declared @Nullable simply cannot return null in the compiled artifact.
Since PIT mutates already-compiled bytecode, NULL_RETURNS still inserts return null; into these methods anyway. The resulting mutant is
unkillable by design — no real call path can ever produce it, because the type-checker already rules it out before the class is built.
Existing partial solution
The NULL_RETURNS docs already say methods "annotated with NotNull will not be mutated," so PIT already has precedent for reading nullness annotations to skip this mutator. That heuristic doesn't help here though: JSpecify's whole point is package-level defaulting — under @NullMarked most methods carry no annotation at all, non-null is implicit, and only the @Nullable exceptions are marked. So the "skip if annotated NotNull" check never fires for JSpecify-style code.
AI helps me for redaction and details but idea is mine.
Context
I have a utility class to make assertions (not null, not blank, positive, etc.) on a value see: Assert. Running mutation tests and all
return thisare replaced byreturn null. To kill all mutants, I should write dumb tests on each case to check not null returns.JSpecify's
@NullMarkedannotation (on apackage-info.java) makes non-null the default for every method in that package unless the return type is explicitly@Nullable. Tools like NullAway, Error Prone, the Checker Framework or IntelliJ enforce this statically at compile time: a method in a@NullMarkedpackage that isn't declared@Nullablesimply cannot returnnullin the compiled artifact.Since PIT mutates already-compiled bytecode,
NULL_RETURNSstill insertsreturn null;into these methods anyway. The resulting mutant isunkillable by design — no real call path can ever produce it, because the type-checker already rules it out before the class is built.
Existing partial solution
The
NULL_RETURNSdocs already say methods "annotated with NotNull will not be mutated," so PIT already has precedent for reading nullness annotations to skip this mutator. That heuristic doesn't help here though: JSpecify's whole point is package-level defaulting — under@NullMarkedmost methods carry no annotation at all, non-null is implicit, and only the@Nullableexceptions are marked. So the "skip if annotated NotNull" check never fires for JSpecify-style code.