Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<nullaway.jspecify.mode>false</nullaway.jspecify.mode>
<assertj.core.version>3.27.6</assertj.core.version>
<eclipse.lifecycle.mapping.version>1.0.0</eclipse.lifecycle.mapping.version>
<java.version>1.8</java.version>
<error.prone.version>2.44.0</error.prone.version>
<jmh.version>1.37</jmh.version>
<junit.version>5.13.4</junit.version>
<jspecify.version>1.0.0</jspecify.version>
<maven.enforcer.version>3.6.2</maven.enforcer.version>
<maven.build-helper.version>3.6.1</maven.build-helper.version>
<maven.bundle.version>5.1.9</maven.bundle.version>
Expand All @@ -60,6 +63,7 @@
<maven.source.version>3.3.1</maven.source.version>
<maven.exec.version>3.6.2</maven.exec.version>
<moditect.version>1.3.0.Final</moditect.version>
<nullaway.version>0.12.12</nullaway.version>
<scala.maven.version>4.9.6</scala.maven.version>
<scala.version>3.5.1</scala.version>
</properties>
Expand All @@ -81,6 +85,11 @@
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>${jspecify.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
Expand Down Expand Up @@ -235,6 +244,13 @@
<!-- Enable Java 9 compilation with 1.8 compatibility -->
<arg>-Xlint:-options</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>vavr-match-processor</artifactId>
<version>0.11.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -410,6 +426,61 @@
</build>
</profile>

<!-- ErrorProne with NullAway requires JDK 17+, JSpecifyMode requires JDK 22+ -->
<profile>
<id>jspecify-mode</id>
<activation>
<jdk>[22,)</jdk>
</activation>
<properties>
<nullaway.jspecify.mode>true</nullaway.jspecify.mode>
</properties>
</profile>
<profile>
<id>nullaway</id>
<activation>
<jdk>[17,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerArgs combine.children="append">
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:WARN -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:JSpecifyMode=${nullaway.jspecify.mode}</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${error.prone.version}</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>${nullaway.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</profile>

<!-- A profile for running the benchmarks -->
<profile>
<id>benchmark</id>
Expand Down
16 changes: 11 additions & 5 deletions vavr/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down Expand Up @@ -65,11 +69,13 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>
io.vavr.match.PatternsProcessor
</annotationProcessor>
</annotationProcessors>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>vavr-match-processor</artifactId>
<version>0.11.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
Expand Down
49 changes: 27 additions & 22 deletions vavr/src/main/java/io/vavr/control/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -47,7 +50,8 @@
* @param <T> The type of the optional value.
* @author Daniel Dietrich
*/
public interface Option<T> extends Value<T>, Serializable {
@NullMarked
public interface Option<T extends @Nullable Object> extends Value<T>, Serializable {

long serialVersionUID = 1L;

Expand All @@ -58,7 +62,7 @@ public interface Option<T> extends Value<T>, Serializable {
* @param <T> type of the value
* @return {@code Some(value)} if value is not {@code null}, {@code None} otherwise
*/
static <T> Option<T> of(T value) {
static <T extends @Nullable Object> Option<T> of(T value) {
return (value == null) ? none() : some(value);
}

Expand All @@ -72,7 +76,7 @@ static <T> Option<T> of(T value) {
* @return An {@code Option} of a {@link Seq} of results
* @throws NullPointerException if {@code values} is null
*/
static <T> Option<Seq<T>> sequence(Iterable<? extends Option<? extends T>> values) {
static <T extends @Nullable Object> Option<Seq<T>> sequence(Iterable<? extends Option<? extends T>> values) {
Objects.requireNonNull(values, "values is null");
Vector<T> vector = Vector.empty();
for (Option<? extends T> value : values) {
Expand All @@ -95,7 +99,7 @@ static <T> Option<Seq<T>> sequence(Iterable<? extends Option<? extends T>> value
* @return A {@code Option} of a {@link Seq} of results.
* @throws NullPointerException if values or f is null.
*/
static <T, U> Option<Seq<U>> traverse(Iterable<? extends T> values, Function<? super T, ? extends Option<? extends U>> mapper) {
static <T extends @Nullable Object, U extends @Nullable Object> Option<Seq<U>> traverse(Iterable<? extends T> values, Function<? super T, ? extends Option<? extends U>> mapper) {
Objects.requireNonNull(values, "values is null");
Objects.requireNonNull(mapper, "mapper is null");
return sequence(Iterator.ofAll(values).map(mapper));
Expand All @@ -116,7 +120,7 @@ static <T, U> Option<Seq<U>> traverse(Iterable<? extends T> values, Function<? s
* @param <T> type of the value
* @return {@code Some(value)}
*/
static <T> Option<T> some(T value) {
static <T extends @Nullable Object> Option<T> some(T value) {
return new Some<>(value);
}

Expand All @@ -126,7 +130,7 @@ static <T> Option<T> some(T value) {
* @param <T> component type
* @return the single instance of {@code None}
*/
static <T> Option<T> none() {
static <T extends @Nullable Object> Option<T> none() {
@SuppressWarnings("unchecked")
final None<T> none = (None<T>) None.INSTANCE;
return none;
Expand All @@ -142,7 +146,7 @@ static <T> Option<T> none() {
* @return the given {@code option} instance as narrowed type {@code Option<T>}.
*/
@SuppressWarnings("unchecked")
static <T> Option<T> narrow(Option<? extends T> option) {
static <T extends @Nullable Object> Option<T> narrow(Option<? extends T> option) {
return (Option<T>) option;
}

Expand All @@ -155,7 +159,7 @@ static <T> Option<T> narrow(Option<? extends T> 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 <T> Option<T> when(boolean condition, Supplier<? extends T> supplier) {
static <T extends @Nullable Object> Option<T> when(boolean condition, Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return condition ? some(supplier.get()) : none();
}
Expand All @@ -168,7 +172,7 @@ static <T> Option<T> when(boolean condition, Supplier<? extends T> 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 <T> Option<T> when(boolean condition, T value) {
static <T extends @Nullable Object> Option<T> when(boolean condition, T value) {
return condition ? some(value) : none();
}

Expand All @@ -180,7 +184,7 @@ static <T> Option<T> when(boolean condition, T value) {
* @return {@code Some(optional.get())} if value is Java {@code Optional} is present, {@code None} otherwise
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
static <T> Option<T> ofOptional(Optional<? extends T> optional) {
static <T extends @Nullable Object> Option<T> ofOptional(Optional<? extends T> optional) {
Objects.requireNonNull(optional, "optional is null");
return optional.<Option<T>>map(Option::of).orElseGet(Option::none);
}
Expand All @@ -204,7 +208,7 @@ static <T> Option<T> ofOptional(Optional<? extends T> optional) {
* @return A new {@code Option} instance containing value of type {@code R}
* @throws NullPointerException if {@code partialFunction} is null
*/
default <R> Option<R> collect(PartialFunction<? super T, ? extends R> partialFunction) {
default <R extends @Nullable Object> Option<R> collect(PartialFunction<? super T, ? extends R> partialFunction) {
Objects.requireNonNull(partialFunction, "partialFunction is null");
return flatMap(partialFunction.lift()::apply);
}
Expand Down Expand Up @@ -371,7 +375,7 @@ default Option<T> filter(Predicate<? super T> predicate) {
* @return a new {@code Option}
*/
@SuppressWarnings("unchecked")
default <U> Option<U> flatMap(Function<? super T, ? extends Option<? extends U>> mapper) {
default <U extends @Nullable Object> Option<U> flatMap(Function<? super T, ? extends Option<? extends U>> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return isEmpty() ? none() : (Option<U>) mapper.apply(get());
}
Expand All @@ -384,16 +388,17 @@ default <U> Option<U> flatMap(Function<? super T, ? extends Option<? extends U>>
* @return a new {@code Some} containing the mapped value if this Option is defined, otherwise {@code None}, if this is empty.
*/
@Override
default <U> Option<U> map(Function<? super T, ? extends U> mapper) {
default <U extends @Nullable Object> Option<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return isEmpty() ? none() : some(mapper.apply(get()));
}

@Override
default <U> Option<U> mapTo(U value) {
default <U extends @Nullable Object> Option<U> 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<Void> mapToVoid() {
return map(ignored -> null);
Expand All @@ -408,7 +413,7 @@ default Option<Void> mapToVoid() {
* @return a {@code Try}
* @throws NullPointerException if {@code mapper} is null
*/
default <U> Try<U> mapTry(CheckedFunction1<? super T, ? extends U> mapper) {
default <U extends @Nullable Object> Try<U> mapTry(CheckedFunction1<? super T, ? extends U> mapper) {
return toTry().mapTry(mapper);
}

Expand All @@ -420,7 +425,7 @@ default <U> Try<U> mapTry(CheckedFunction1<? super T, ? extends U> mapper) {
* @param <U> type of the folded value
* @return A value of type U
*/
default <U> U fold(Supplier<? extends U> ifNone, Function<? super T, ? extends U> f) {
default <U extends @Nullable Object> U fold(Supplier<? extends U> ifNone, Function<? super T, ? extends U> f) {
return this.<U>map(f).getOrElse(ifNone);
}

Expand All @@ -447,7 +452,7 @@ default Option<T> peek(Consumer<? super T> action) {
* @return An instance of type {@code U}
* @throws NullPointerException if {@code f} is null
*/
default <U> U transform(Function<? super Option<T>, ? extends U> f) {
default <U extends @Nullable Object> U transform(Function<? super Option<T>, ? extends U> f) {
Objects.requireNonNull(f, "f is null");
return f.apply(this);
}
Expand All @@ -458,7 +463,7 @@ default Iterator<T> iterator() {
}

@Override
boolean equals(Object o);
boolean equals(@Nullable Object o);

@Override
int hashCode();
Expand All @@ -474,7 +479,7 @@ default Iterator<T> iterator() {
* @param <T> The type of the optional value.
* @author Daniel Dietrich
*/
final class Some<T> implements Option<T>, Serializable {
final class Some<T extends @Nullable Object> implements Option<T>, Serializable {

private static final long serialVersionUID = 1L;

Expand All @@ -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));
}

Expand All @@ -527,7 +532,7 @@ public String toString() {
* @param <T> The type of the optional value.
* @author Daniel Dietrich
*/
final class None<T> implements Option<T>, Serializable {
final class None<T extends @Nullable Object> implements Option<T>, Serializable {

private static final long serialVersionUID = 1L;

Expand All @@ -553,7 +558,7 @@ public boolean isEmpty() {
}

@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
return o == this;
}

Expand Down