JSpecify mode: an override of a null-unmarked method is reported for mismatched type argument nullability

Open
#2,078 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
backend, testing

Research direction

Start by reading the JSpecify override checks and the existing tests around #2052 and #2074; the issue gives a CompilationTestHelper reproducer with four failing cases. Add coverage for the unmarked interfaces and verify those overrides produce no warnings, while the issue notes that explicitly annotated types in unmarked methods should remain checked. The work is done when the reproducer passes and the stated @NullMarked interface cases still report warnings.

Written by the indexing model from the issue text.

Description

In JSpecify mode, a class in @NullMarked code that implements a null-unmarked interface and declares List<@Nullable String> where the interface method has List<String> is reported with mismatched type parameter nullability. This happens for the return type and for a parameter, in a named class, in an anonymous class, and for an explicitly typed lambda parameter. The type argument String in the unmarked interface has unspecified nullness, so under the JSpecify "some world" rules List<@Nullable String> is a valid override and nothing should be reported.

Version: master at 2fd31d501 (0.14.4-SNAPSHOT), JDK 21.0.9.

Options: -XepOpt:NullAway:AnnotatedPackages=com.uber, plus the JSpecify mode arguments from JSpecifyJavacConfig.withJSpecifyModeArgs.

Reproducer (a CompilationTestHelper test extending NullAwayTestsBase):

// UnmarkedListSupplier.java
package com.example.unmarked;
import java.util.List;
public interface UnmarkedListSupplier {
  List<String> get();
}

// UnmarkedListConsumer.java
package com.example.unmarked;
import java.util.List;
public interface UnmarkedListConsumer {
  void accept(List<String> l);
}

// Test.java
package com.uber;
import com.example.unmarked.UnmarkedListConsumer;
import com.example.unmarked.UnmarkedListSupplier;
import java.util.List;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
class Test {
  static class NamedSupplier implements UnmarkedListSupplier {
    @Override
    public List<@Nullable String> get() { throw new RuntimeException(); }
  }
  static class NamedConsumer implements UnmarkedListConsumer {
    @Override
    public void accept(List<@Nullable String> l) {}
  }
  void anonymous() {
    UnmarkedListSupplier s = new UnmarkedListSupplier() {
      @Override
      public List<@Nullable String> get() { throw new RuntimeException(); }
    };
  }
  void lambdaParam() {
    UnmarkedListConsumer c = (List<@Nullable String> l) -> {};
  }
}

Actual: four warnings:

/Test.java:11: warning: [NullAway] Method returns List<@Nullable String>, but overridden method returns List<String>, which has mismatched type parameter nullability
/Test.java:15: warning: [NullAway] Parameter has type List<@Nullable String>, but overridden method has parameter type List<String>, which has mismatched type parameter nullability
/Test.java:20: warning: [NullAway] Method returns List<@Nullable String>, but overridden method returns List<String>, which has mismatched type parameter nullability
/Test.java:24: warning: [NullAway] Parameter has type List<@Nullable String>, but overridden method has parameter type List<String>, which has mismatched type parameter nullability

Expected: no warnings in Test.java. With the interfaces annotated @NullMarked, all four lines should still be reported.

Type arguments of an unmarked method are already treated as unspecified elsewhere: arguments of a call to an unmarked method are not checked, a method reference passed for an unmarked functional interface is not checked since #2074, and #2052 skips the check for lambda returns. This came up in the review of #2052: https://github.com/uber/NullAway/pull/2052#discussion_r4368571361.

As with #2052, skipping the comparison would also leave explicit annotations in the unmarked method, such as List<@NonNull String>, unchecked.

Dominant language
Java
Stars
4.1k
Forks
381
Avg merge
1d 2h
Merged PRs (30d)
106

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from uber/NullAway

All issues in uber/NullAway

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.