From 7ee07cd50bc73b4118b83eef4edf0408adf20cd7 Mon Sep 17 00:00:00 2001 From: Michael Kressibucher Date: Fri, 14 Nov 2025 15:41:09 +0100 Subject: [PATCH 1/3] Add nullaway plugin --- pom.xml | 35 ++++++++++++++++++++++++++++++++++- vavr/pom.xml | 7 ------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 43f3256aca..3a28cf8dab 100644 --- a/pom.xml +++ b/pom.xml @@ -37,9 +37,10 @@ UTF-8 + 1.8 3.27.6 1.0.0 - 1.8 + 2.44.0 1.37 5.13.4 3.6.2 @@ -60,6 +61,7 @@ 3.3.1 3.6.2 1.3.0.Final + 0.12.12 4.9.6 3.5.1 @@ -227,6 +229,7 @@ ${java.version} true true + true -Werror -Xlint:all @@ -234,7 +237,37 @@ -Xlint:-processing -Xlint:-options + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepDisableAllChecks -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:JSpecifyMode=true + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + com.google.errorprone + error_prone_core + ${error.prone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + ${project.groupId} + vavr-match-processor + 0.11.0-SNAPSHOT + + diff --git a/vavr/pom.xml b/vavr/pom.xml index cc0b8c45be..675b7db6b2 100644 --- a/vavr/pom.xml +++ b/vavr/pom.xml @@ -64,13 +64,6 @@ org.apache.maven.plugins maven-compiler-plugin - - - - io.vavr.match.PatternsProcessor - - - org.apache.maven.plugins From d0a488795446dd7df7775ebae8f44b605f5c164e Mon Sep 17 00:00:00 2001 From: Michael Kressibucher Date: Fri, 14 Nov 2025 23:06:58 +0100 Subject: [PATCH 2/3] Put nullaway into JDK 17+ profile --- pom.xml | 86 +++++++++++++++++++++++++++++++++++++--------------- vavr/pom.xml | 13 ++++++++ 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 3a28cf8dab..3111321bb4 100644 --- a/pom.xml +++ b/pom.xml @@ -38,11 +38,13 @@ UTF-8 1.8 + false 3.27.6 1.0.0 2.44.0 1.37 5.13.4 + 1.0.0 3.6.2 3.6.1 5.1.9 @@ -83,6 +85,11 @@ jmh-generator-annprocess ${jmh.version} + + org.jspecify + jspecify + ${jspecify.version} + @@ -229,7 +236,6 @@ ${java.version} true true - true -Werror -Xlint:all @@ -237,31 +243,8 @@ -Xlint:-processing -Xlint:-options - -XDcompilePolicy=simple - --should-stop=ifError=FLOW - -Xplugin:ErrorProne -XepDisableAllChecks -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:JSpecifyMode=true - -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED - -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED - -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED - - com.google.errorprone - error_prone_core - ${error.prone.version} - - - com.uber.nullaway - nullaway - ${nullaway.version} - ${project.groupId} vavr-match-processor @@ -443,6 +426,61 @@ + + + jspecify-mode + + [22,) + + + true + + + + nullaway + + [17,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:WARN -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:JSpecifyMode=${nullaway.jspecify.mode} + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + + com.google.errorprone + error_prone_core + ${error.prone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + + + + + + benchmark diff --git a/vavr/pom.xml b/vavr/pom.xml index 675b7db6b2..fc8a340ced 100644 --- a/vavr/pom.xml +++ b/vavr/pom.xml @@ -32,6 +32,10 @@ true + + org.jspecify + jspecify + org.junit.jupiter junit-jupiter @@ -64,6 +68,15 @@ org.apache.maven.plugins maven-compiler-plugin + + + + ${project.groupId} + vavr-match-processor + 0.11.0-SNAPSHOT + + + org.apache.maven.plugins From 165252664a90b711ac6f5109aaf6de6ad0c81938 Mon Sep 17 00:00:00 2001 From: Michael Kressibucher Date: Sun, 16 Nov 2025 02:06:24 +0100 Subject: [PATCH 3/3] Add nullness annotations to Optional --- pom.xml | 4 +- .../src/main/java/io/vavr/control/Option.java | 49 ++++++++++--------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 3111321bb4..4743f7a2cf 100644 --- a/pom.xml +++ b/pom.xml @@ -426,7 +426,7 @@ - + jspecify-mode @@ -436,7 +436,7 @@ true - + nullaway [17,) diff --git a/vavr/src/main/java/io/vavr/control/Option.java b/vavr/src/main/java/io/vavr/control/Option.java index 3b6e27d270..6816ad67f4 100644 --- a/vavr/src/main/java/io/vavr/control/Option.java +++ b/vavr/src/main/java/io/vavr/control/Option.java @@ -32,6 +32,9 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Replacement for {@link java.util.Optional}. @@ -47,7 +50,8 @@ * @param The type of the optional value. * @author Daniel Dietrich */ -public interface Option extends Value, Serializable { +@NullMarked +public interface Option extends Value, Serializable { long serialVersionUID = 1L; @@ -58,7 +62,7 @@ public interface Option extends Value, Serializable { * @param type of the value * @return {@code Some(value)} if value is not {@code null}, {@code None} otherwise */ - static Option of(T value) { + static Option of(T value) { return (value == null) ? none() : some(value); } @@ -72,7 +76,7 @@ static Option of(T value) { * @return An {@code Option} of a {@link Seq} of results * @throws NullPointerException if {@code values} is null */ - static Option> sequence(Iterable> values) { + static Option> sequence(Iterable> values) { Objects.requireNonNull(values, "values is null"); Vector vector = Vector.empty(); for (Option value : values) { @@ -95,7 +99,7 @@ static Option> sequence(Iterable> value * @return A {@code Option} of a {@link Seq} of results. * @throws NullPointerException if values or f is null. */ - static Option> traverse(Iterable values, Function> mapper) { + static Option> traverse(Iterable values, Function> mapper) { Objects.requireNonNull(values, "values is null"); Objects.requireNonNull(mapper, "mapper is null"); return sequence(Iterator.ofAll(values).map(mapper)); @@ -116,7 +120,7 @@ static Option> traverse(Iterable values, Function type of the value * @return {@code Some(value)} */ - static Option some(T value) { + static Option some(T value) { return new Some<>(value); } @@ -126,7 +130,7 @@ static Option some(T value) { * @param component type * @return the single instance of {@code None} */ - static Option none() { + static Option none() { @SuppressWarnings("unchecked") final None none = (None) None.INSTANCE; return none; @@ -142,7 +146,7 @@ static Option none() { * @return the given {@code option} instance as narrowed type {@code Option}. */ @SuppressWarnings("unchecked") - static Option narrow(Option option) { + static Option narrow(Option option) { return (Option) option; } @@ -155,7 +159,7 @@ static Option narrow(Option option) { * @return return {@code Some} of supplier's value if condition is true, or {@code None} in other case * @throws NullPointerException if the given {@code supplier} is null */ - static Option when(boolean condition, Supplier supplier) { + static Option when(boolean condition, Supplier supplier) { Objects.requireNonNull(supplier, "supplier is null"); return condition ? some(supplier.get()) : none(); } @@ -168,7 +172,7 @@ static Option when(boolean condition, Supplier supplier) { * @param value An optional value, may be {@code null} * @return return {@code Some} of value if condition is true, or {@code None} in other case */ - static Option when(boolean condition, T value) { + static Option when(boolean condition, T value) { return condition ? some(value) : none(); } @@ -180,7 +184,7 @@ static Option when(boolean condition, T value) { * @return {@code Some(optional.get())} if value is Java {@code Optional} is present, {@code None} otherwise */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - static Option ofOptional(Optional optional) { + static Option ofOptional(Optional optional) { Objects.requireNonNull(optional, "optional is null"); return optional.>map(Option::of).orElseGet(Option::none); } @@ -204,7 +208,7 @@ static Option ofOptional(Optional optional) { * @return A new {@code Option} instance containing value of type {@code R} * @throws NullPointerException if {@code partialFunction} is null */ - default Option collect(PartialFunction partialFunction) { + default Option collect(PartialFunction partialFunction) { Objects.requireNonNull(partialFunction, "partialFunction is null"); return flatMap(partialFunction.lift()::apply); } @@ -371,7 +375,7 @@ default Option filter(Predicate predicate) { * @return a new {@code Option} */ @SuppressWarnings("unchecked") - default Option flatMap(Function> mapper) { + default Option flatMap(Function> mapper) { Objects.requireNonNull(mapper, "mapper is null"); return isEmpty() ? none() : (Option) mapper.apply(get()); } @@ -384,16 +388,17 @@ default Option flatMap(Function> * @return a new {@code Some} containing the mapped value if this Option is defined, otherwise {@code None}, if this is empty. */ @Override - default Option map(Function mapper) { + default Option map(Function mapper) { Objects.requireNonNull(mapper, "mapper is null"); return isEmpty() ? none() : some(mapper.apply(get())); } @Override - default Option mapTo(U value) { + default Option mapTo(U value) { return map(ignored -> value); } + @NullUnmarked // TODO: Void needs to be marked @NonNull in Value interface, because lambda maps to null @Override default Option mapToVoid() { return map(ignored -> null); @@ -408,7 +413,7 @@ default Option mapToVoid() { * @return a {@code Try} * @throws NullPointerException if {@code mapper} is null */ - default Try mapTry(CheckedFunction1 mapper) { + default Try mapTry(CheckedFunction1 mapper) { return toTry().mapTry(mapper); } @@ -420,7 +425,7 @@ default Try mapTry(CheckedFunction1 mapper) { * @param type of the folded value * @return A value of type U */ - default U fold(Supplier ifNone, Function f) { + default U fold(Supplier ifNone, Function f) { return this.map(f).getOrElse(ifNone); } @@ -447,7 +452,7 @@ default Option peek(Consumer action) { * @return An instance of type {@code U} * @throws NullPointerException if {@code f} is null */ - default U transform(Function, ? extends U> f) { + default U transform(Function, ? extends U> f) { Objects.requireNonNull(f, "f is null"); return f.apply(this); } @@ -458,7 +463,7 @@ default Iterator iterator() { } @Override - boolean equals(Object o); + boolean equals(@Nullable Object o); @Override int hashCode(); @@ -474,7 +479,7 @@ default Iterator iterator() { * @param The type of the optional value. * @author Daniel Dietrich */ - final class Some implements Option, Serializable { + final class Some implements Option, Serializable { private static final long serialVersionUID = 1L; @@ -501,7 +506,7 @@ public boolean isEmpty() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return (obj == this) || (obj instanceof Some && Objects.equals(value, ((Some) obj).value)); } @@ -527,7 +532,7 @@ public String toString() { * @param The type of the optional value. * @author Daniel Dietrich */ - final class None implements Option, Serializable { + final class None implements Option, Serializable { private static final long serialVersionUID = 1L; @@ -553,7 +558,7 @@ public boolean isEmpty() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return o == this; }