I am having a null analysis issue when compiling GriefPrevention in Eclipse. For example, line 304 in PlayerEventHandler.java invokes Player#getLocation(). The API specification for this class can be found here.
The problem is that this class inherits from both the Entity and OfflinePlayer interfaces, which have conflicting annotations.
For Entity, getLocation() is defined as NonNull:
@NotNull Location getLocation(); But for OfflinePlayer, it is defined as Nullable:
/** * Gets the player's current location. * * @return the player's location, {@code null} if player hasn't ever played * before. */ @Nullable public Location getLocation(); It's my opinion that, given this situation, the least permissive annotation (Nullable) should be given precedence. However, IntelliJ disagrees. This creates a problem, because I cannot compile this code with Eclipse without disabling null annotations, which is undesirable, and the developer who uses IntelliJ insists that this is not a problem (according to his IDE).
Is either IntelliJ or Eclipse's interpretation "more correct" or is the result ambiguous given the conflicting annotation in the API specification?
When testing this kind of ambiguous inheritance, IntelliJ allows @NotNull without any warning or error, but @Nullable throws a warning:
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class Main { interface ITestNotNull { @NotNull Object ambiguous(); } interface ITestNullable { @Nullable Object ambiguous(); } static class ITest implements ITestNullable, ITestNotNull { @Override // Method annotated with @Nullable must not override @NotNull method public @Nullable Object ambiguous() { return new Object(); } } public static void main(String[] args) { ITest a = new ITest(); System.out.println(a.ambiguous()); } } The opposite is true in Eclipse, however I receive a compiler error instead:
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; public class Main { interface ITestNotNull { @NonNull Object ambiguous(); } interface ITestNullable { @Nullable Object ambiguous(); } static class ITest implements ITestNullable, ITestNotNull { @Override // The return type is incompatible with '@NonNull Object' returned from // Main.ITestNotNull.ambiguous() (mismatching null constraints) public @Nullable Object ambiguous() { return new Object(); } } public static void main(String[] args) { ITest a = new ITest(); System.out.println(a.ambiguous()); } } UPDATE
Upon further consideration and analysis of the problem, the issue I have been experience seems to be a bug in Eclipse. IntelliJ's implementation of @NonNull seems to be correct. Regardless of the ambiguity, it satisfies both possible implementations.
https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2308
My original report was under the false premise that the annotation behavior experienced when importing a jar was correct and that the native source-level implementation was wrong. The opposite is true. There is nothing wrong with the source-level implementation; this bug can only be demonstrated when importing a jar file (see the git bug report).
Here is a very simple example demonstrating the actual bug:
import java.util.Objects; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; public class Main { public static void main(String[] args) { @Nullable Object nullableObject; @NonNull Object nonNullObject; Player player = Objects.requireNonNull(Bukkit.getPlayer("")); /* Null annotation error *WRONGLY* derives the @Nullable implementation when choosing between the two conflicting interfaces */ // Unexpected inverse @Nullable implementation is derived // nonNullObject = player.getLocation(); // ^^^^^^^^^^^^^^^^^^^^ Error: Null type mismatch // This is the expected @NonNull interface implementation nonNullObject = ((Entity)player).getLocation(); // This would be a NPE if Player was actually implementing @Nullable! nullableObject = ((OfflinePlayer)player).getLocation(); } } Spigot API jar library with source:
spigot-api-1.20.4-R0.1-20240407.022953-115.jar
spigot-api-1.20.4-R0.1-20240407.022953-115-sources.jar
Eclipse External Annotations (EEA) for Spigot API
spigot-api-eea.zip
@NonNulland@Nullable. Eclipse is right (mismatching null constraints) and IntelliJ is not smart enough to detect this. IntelliJ completely misses this issue and only detects contradictions of overriding and overridden method annotations, which is different thing (but also based on the fact, that something cannot be@NonNulland@Nullable).@NonNullis used, the error disappears. Each IDE believes that one is "correct", however, it happens to be the inverse of the other.ambiguous()is not ambiguous, but that there are clearly conflicting constraints: something cannot be@NonNulland@Nullable.ITest implements ITestNullable, ITestNotNullmeans "ITest implements ITestNullable and ITestNotNull" and that it must be ensured thatITestcan be used asITestNullableas well asITestNotNull. If each IDE misses one of the two cases, then both are wrong, which is pretty much the same thing, not the opposite. A null annotation is a constraint that cannot be overwritten.