JSpecify mode: a diamond passed to `<U> U id(U u)` is inferred as `Box<String>` whatever the target type
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 56/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- java
- Domain
- testing-qa
Research direction
Start with GenericDiamondTests.genericMethodTypeVarParamWithDiamondArg in nullaway/src/test/java/com/uber/nullaway/ and read the JSpecify inference handling for diamond expressions passed to generic methods. Compare the bare type-variable parameter in id with the nested type-variable parameter in idBox, using the provided CompilationTestHelper sample and flags. Done means the nullable-target cases produce no diagnostic and the non-null target reports the null argument, matching the stated expectations.
Written by the indexing model from the issue text.
Description
With JSpecify mode on, a diamond new Box<>(…) passed to a generic method whose parameter is the bare type variable, <U> U id(U u), is inferred as Box<String> no matter what the result is assigned to. Box<@Nullable String> b = id(new Box<>(null)) is reported as an incompatible assignment, and Box<String> b = id(new Box<>(null)), which passes null to the constructor of a Box<String>, is not reported. The same calls through <U> Box<U> idBox(Box<U> b), where the parameter mentions the type variable inside a type, are handled correctly since #1708 and #1544 (0.14.0).
Versions: NullAway master at e91ea4bcf and the 0.14.2 release give the same result. Error Prone 2.50.0, JDK 21.0.9. Flags: -XepOpt:NullAway:AnnotatedPackages=com.uber -XepOpt:NullAway:JSpecifyMode=true -XDaddTypeAnnotationsToSymbol=true, and the same with -XepOpt:NullAway:JSpecifyExperimental=true added.
Code sample. I ran it as a CompilationTestHelper test in nullaway/src/test/java/com/uber/nullaway/ with makeTestHelperWithArgs, without // BUG: markers, so the helper lists every diagnostic.
package com.uber;
import org.jspecify.annotations.*;
@NullMarked
public class Test {
static class Box<T extends @Nullable Object> {
Box(T value) {}
}
static <U extends @Nullable Object> U id(U u) { return u; }
static <U extends @Nullable Object> Box<U> idBox(Box<U> b) { return b; }
static @Nullable String nullableString() { return null; }
static void takeNullableBox(Box<@Nullable String> b) {}
void assign() {
Box<@Nullable String> b = id(new Box<>(null));
}
void arg() {
takeNullableBox(id(new Box<>(null)));
}
Box<@Nullable String> ret() {
return id(new Box<>(nullableString()));
}
void positive() {
Box<String> b = id(new Box<>(null));
}
void controlAssign() {
Box<@Nullable String> b = idBox(new Box<>(null));
}
void controlPositive() {
Box<String> b = idBox(new Box<>(null));
}
}
Expected: no diagnostic on lines 13, 16, and 19, as on line 25, where idBox gets the same arguments. On line 22, passing @Nullable parameter 'null' where @NonNull is required, as on line 28. The idBox lines are NullAway's own behavior for the same diamond, and the parameter shape is the only difference.
Actual (both versions):
/Test.java:13: warning: [NullAway] incompatible types: Box<String> cannot be converted to Box<@Nullable String>
/Test.java:16: warning: [NullAway] incompatible types: Box<String> cannot be converted to Box<@Nullable String>
/Test.java:19: warning: [NullAway] incompatible types: Box<String> cannot be converted to Box<@Nullable String>
/Test.java:28: warning: [NullAway] passing @Nullable parameter 'null' where @NonNull is required
With JSpecifyExperimental=true the same four, plus inference failure: type variable T constrained to be both @NonNull and @Nullable on lines 19 and 28. Line 22 gets no diagnostic in either configuration.
GenericDiamondTests.genericMethodTypeVarParamWithDiamondArg uses this shape but only checks that there is no stack overflow. #1825 (constructor-declared type variables in a diamond call) is a different shape. Found while triaging #1470.
- Dominant language
- Java
- Stars
- 4.1k
- Forks
- 379
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 106
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from uber/NullAway
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
uber/NullAway#2145 · 1 reaction ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
uber/NullAway#2058 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
uber/NullAway#1899 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 15/100
uber/NullAway#2160 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
johanhaleby/occurrent#1505 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FasterXML/jackson-databind#6443 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
finos/fluxnova-bpm-platform#387 ·
Maintainers usually reply within 4 days
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100