Skip to content

Commit 376df98

Browse files
committed
Consider nullable annotations in explicit nulls
Fixes scala#21629
1 parent 11d4295 commit 376df98

File tree

5 files changed

+96
-6
lines changed

5 files changed

+96
-6
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,11 @@ class Definitions {
11251125
"reactor.util.annotation.NonNullApi" ::
11261126
"io.reactivex.annotations.NonNull" :: Nil)
11271127

1128+
@tu lazy val NullableAnnots: List[ClassSymbol] = getClassesIfDefined(
1129+
"javax.annotation.Nullable" ::
1130+
"org.jetbrains.annotations.Nullable" ::
1131+
"org.jspecify.annotations.Nullable" :: Nil)
1132+
11281133
// convenient one-parameter method types
11291134
def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp)
11301135
def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp)

compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,26 @@ object JavaNullInterop {
6666
nullifyExceptReturnType(tp)
6767
else
6868
// Otherwise, nullify everything
69-
nullifyType(tp)
69+
nullifyType(tp, explicitlyNullable = hasNullableAnnot(sym))
7070
}
7171

7272
private def hasNotNullAnnot(sym: Symbol)(using Context): Boolean =
7373
ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined)
7474

75+
private def hasNullableAnnot(sym: Symbol)(using Context): Boolean =
76+
ctx.definitions.NullableAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined)
77+
7578
/** If tp is a MethodType, the parameters and the inside of return type are nullified,
7679
* but the result return type is not nullable.
7780
* If tp is a type of a field, the inside of the type is nullified,
7881
* but the result type is not nullable.
7982
*/
8083
private def nullifyExceptReturnType(tp: Type)(using Context): Type =
81-
new JavaNullMap(outermostLevelAlreadyNullable = true)(tp)
84+
new JavaNullMap(outermostLevelAlreadyNullable = true, explicitlyNullable = false)(tp)
8285

8386
/** Nullifies a Java type by adding `| Null` in the relevant places. */
84-
private def nullifyType(tp: Type)(using Context): Type =
85-
new JavaNullMap(outermostLevelAlreadyNullable = false)(tp)
87+
private def nullifyType(tp: Type, explicitlyNullable: Boolean = false)(using Context): Type =
88+
new JavaNullMap(outermostLevelAlreadyNullable = false, explicitlyNullable)(tp)
8689

8790
/** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| Null`
8891
* in the right places to make the nulls explicit in Scala.
@@ -95,8 +98,17 @@ object JavaNullInterop {
9598
* This is useful for e.g. constructors, and also so that `A & B` is nullified
9699
* to `(A & B) | Null`, instead of `(A | Null & B | Null) | Null`.
97100
*/
98-
private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(using Context) extends TypeMap {
99-
def nullify(tp: Type): Type = if ctx.flexibleTypes then FlexibleType(tp) else OrNull(tp)
101+
private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean, explicitlyNullable: Boolean)(using Context) extends TypeMap {
102+
def nullify(tp: Type): Type =
103+
if ctx.flexibleTypes then {
104+
if explicitlyNullable then {
105+
OrNull(tp)
106+
} else {
107+
FlexibleType(tp)
108+
}
109+
} else {
110+
OrNull(tp)
111+
}
100112

101113
/** Should we nullify `tp` at the outermost level? */
102114
def needsNull(tp: Type): Boolean =
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package javax.annotation;
2+
import java.util.*;
3+
4+
public class J {
5+
6+
private static String getK() {
7+
return "k";
8+
}
9+
10+
@Nullable
11+
public static final String k = getK();
12+
13+
@Nullable
14+
public static String l = "l";
15+
16+
@Nullable
17+
public final String m = null;
18+
19+
@Nullable
20+
public String n = "n";
21+
22+
@Nullable
23+
public static final String f(int i) {
24+
return "f: " + i;
25+
}
26+
27+
@Nullable
28+
public static String g(int i) {
29+
return "g: " + i;
30+
}
31+
32+
@Nullable
33+
public String h(int i) {
34+
return "h: " + i;
35+
}
36+
37+
@Nullable
38+
public <T> String[] genericf(T a) {
39+
String[] as = new String[1];
40+
as[0] = "" + a;
41+
return as;
42+
}
43+
44+
@Nullable
45+
public <T> List<T> genericg(T a) {
46+
List<T> as = new ArrayList<T>();
47+
as.add(a);
48+
return as;
49+
}
50+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package javax.annotation;
2+
3+
import java.lang.annotation.*;
4+
5+
// A "fake" Nullable Annotation for jsr305
6+
@Retention(value = RetentionPolicy.RUNTIME)
7+
@interface Nullable {
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Test that Nullable annotations are working in Java files.
2+
3+
import javax.annotation.J
4+
5+
class S_3 {
6+
def kk: String = J.k // error
7+
def ll: String = J.l // error
8+
def mm: String = (new J).m // error
9+
def nn: String = (new J).n // error
10+
def ff(i: Int): String = J.f(i) // error
11+
def gg(i: Int): String = J.g(i) // error
12+
def hh(i: Int): String = (new J).h(i) // error
13+
def genericff(a: String | Null): Array[String | Null] = (new J).genericf(a) // error
14+
def genericgg(a: String | Null): java.util.List[String] = (new J).genericg(a) // error
15+
}

0 commit comments

Comments
 (0)