Skip to content

Commit e1d7bcc

Browse files
committed
Add Nullable annot and parse annots in parameter position
1 parent 07883c1 commit e1d7bcc

File tree

9 files changed

+123
-5
lines changed

9 files changed

+123
-5
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,11 @@ class Definitions {
11591159
"io.reactivex.annotations.NonNull" ::
11601160
"org.jspecify.annotations.NonNull" :: Nil)
11611161

1162+
@tu lazy val NullableAnnots: List[ClassSymbol] = getClassesIfDefined(
1163+
"javax.annotation.Nullable" ::
1164+
"org.jetbrains.annotations.Nullable" ::
1165+
"org.jspecify.annotations.Nullable" :: Nil)
1166+
11621167
// convenient one-parameter method types
11631168
def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp)
11641169
def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp)

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,22 @@ object ImplicitNullInterop:
7676
val skipResultType = sym.isConstructor || hasNotNullAnnot(sym)
7777
// Don't nullify Given/implicit parameters
7878
val skipCurrentLevel = sym.isOneOf(GivenOrImplicitVal)
79+
// Use OrNull instead of flexible types if symbol is explicitly nullable
80+
val explicitlyNullable = hasNullableAnnot(sym)
7981

8082
val map = new ImplicitNullMap(
8183
javaDefined = sym.is(JavaDefined),
8284
skipResultType = skipResultType,
83-
skipCurrentLevel = skipCurrentLevel)
85+
skipCurrentLevel = skipCurrentLevel,
86+
explicitlyNullable = explicitlyNullable)
8487
map(tp)
8588

8689
private def hasNotNullAnnot(sym: Symbol)(using Context): Boolean =
8790
ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined)
8891

92+
private def hasNullableAnnot(sym: Symbol)(using Context): Boolean =
93+
ctx.definitions.NullableAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined)
94+
8995
/** A type map that implements the nullification function on types. Given a Java-sourced type or a type
9096
* coming from Scala code compiled without explicit nulls, this adds `| Null` or `FlexibleType` in the
9197
* right places to make nullability explicit in a conservative way (without forcing incomplete symbols).
@@ -98,10 +104,15 @@ object ImplicitNullInterop:
98104
private class ImplicitNullMap(
99105
val javaDefined: Boolean,
100106
var skipResultType: Boolean = false,
101-
var skipCurrentLevel: Boolean = false
107+
var skipCurrentLevel: Boolean = false,
108+
var explicitlyNullable: Boolean = false
102109
)(using Context) extends TypeMap:
103110

104-
def nullify(tp: Type): Type = if ctx.flexibleTypes then FlexibleType(tp) else OrNull(tp)
111+
def nullify(tp: Type): Type =
112+
if ctx.flexibleTypes then
113+
if explicitlyNullable then OrNull(tp) else FlexibleType(tp)
114+
else
115+
OrNull(tp)
105116

106117
/** Should we nullify `tp` at the outermost level?
107118
* The symbols are still under construction, so we don't have precise information.

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ object JavaParsers {
554554
def formalParam(): ValDef = {
555555
val start = in.offset
556556
if (in.token == FINAL) in.nextToken()
557-
annotations()
557+
val annots = annotations()
558558
var t = typ()
559559
if (in.token == DOTDOTDOT) {
560560
in.nextToken()
@@ -563,7 +563,7 @@ object JavaParsers {
563563
}
564564
}
565565
atSpan(start, in.offset) {
566-
varDecl(Modifiers(Flags.JavaDefined | Flags.Param), t, ident().toTermName)
566+
varDecl(Modifiers(Flags.JavaDefined | Flags.Param, annotations = annots), t, ident().toTermName)
567567
}
568568
}
569569

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 String q(String s) {
39+
return "h: " + s;
40+
}
41+
42+
@Nullable
43+
public <T> String[] genericf(T a) {
44+
String[] as = new String[1];
45+
as[0] = "" + a;
46+
return as;
47+
}
48+
49+
@Nullable
50+
public <T> List<T> genericg(T a) {
51+
List<T> as = new ArrayList<T>();
52+
as.add(a);
53+
return as;
54+
}
55+
}
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 qq(s: String): String | Null = (new J).q(s)
14+
def genericff(a: String): Array[String] = (new J).genericf(a) // error
15+
def genericgg(a: String): java.util.List[String] = (new J).genericg(a) // error
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package javax.annotation;
2+
import java.util.*;
3+
4+
public class J {
5+
public String p(@Nullable String nullableString) {
6+
return nullableString;
7+
}
8+
}
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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Test that Nullable annotations are working in Java files.
2+
3+
import javax.annotation.J
4+
5+
class S extends J {
6+
override def p(s: String): String = ??? // error
7+
}

0 commit comments

Comments
 (0)