immutables / immutables

Annotation processor to create immutable objects and builders. Feels like Guava's immutable collections but for regular value objects. JSON, Jackson, Gson, JAX-RS integrations included
http://immutables.org
Apache License 2.0
3.42k stars 272 forks source link

Custom Nullable annotations produce compilation errors #1500

Open ascopes opened 8 months ago

ascopes commented 8 months ago

It would appear that the current implementation is not fully compatible with custom nullable annotations such as JSpecify when specifying @Style(fallbackNullableAnnotation = Nullable.class). The following code produces a malformed Java source file in the generated source outputs:

package org.example;

import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Style;
import org.jspecify.annotations.Nullable;

@Immutable
@Style(fallbackNullableAnnotation = Nullable.class, jdkOnly = true)
public interface User {
  String getId();
  String getUserName();
  @Nullable String getNickName();
}
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project immutables-bug: Compilation failure
[ERROR] /home/ashley/code/junk/immutables-bug/target/generated-sources/annotations/org/example/ImmutableUser.java:[180,33] scoping construct cannot be annotated with type-use annotation: @org.jspecify.annotations.Nullable

The generated code around the erroneous content is as follows:

package org.example;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.immutables.value.Generated;
import org.jspecify.annotations.Nullable;

/**
 * Immutable implementation of {@link User}.
 * <p>
 * Use the builder to create immutable instances:
 * {@code ImmutableUser.builder()}.
 */
@Generated(from = "User", generator = "Immutables")
@SuppressWarnings({"all"})
@javax.annotation.processing.Generated("org.immutables.processor.ProxyProcessor")
public final class ImmutableUser implements User {
  private final String id;
  private final String userName;
  private final java.lang.@Nullable String nickName;

  ...

  @Generated(from = "User", generator = "Immutables")
  public static final class Builder {
    private static final long INIT_BIT_ID = 0x1L;
    private static final long INIT_BIT_USER_NAME = 0x2L;
    private long initBits = 0x3L;

    private @Nullable String id;
    private @Nullable String userName;
    private @Nullable java.lang.@Nullable String nickName;

    ...    

Where the erroneous line is private @Nullable java.lang.@Nullable String nickName;.

In this case, I would expect the library to have generated this instead:

@Nullable
private java.lang.String nickName;

Use case

Without fallbackNullableAnnotation = Nullable.class, internal @Nullable annotations are not applied as expected, even if I specify @Style(nullableAnnotation = "org.jspecify.annotations.Nullable") directly, meaning nullness checks may fail.


Versions used to reproduce the issue:

  <dependencies>
    <dependency>
      <groupId>org.jspecify</groupId>
      <artifactId>jspecify</artifactId>
      <version>0.3.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.immutables</groupId>
      <artifactId>value</artifactId>
      <version>2.10.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

The issue appears to be that the annotation processor is trying to stack

elucash commented 7 months ago

These annotations (with troubles) are TYPE_USE. so correct syntax for type use annotations (when fully qualified name involved) is java.lang.@Nullable String nickName, so private @Nullable java.lang.@Nullable String nickName; is wrong, yes. I have to look into this seems like we have only partial support for the type use annotations.