@@ -28,9 +28,6 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by:
28
28
29
29
## Java Interop
30
30
31
- TODO(abeln): add support for recognizing nullability annotations a la
32
- https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
33
-
34
31
The problem we're trying to solve here is: if we see a Java method ` String foo(String) ` ,
35
32
what should that method look like to Scala?
36
33
- since we should be able to pass ` null ` into Java methods, the argument type should be ` String|JavaNull `
@@ -43,25 +40,19 @@ At a high-level:
43
40
- we do this in two places: ` Namer ` (for Java sources) and ` ClassFileParser ` (for bytecode)
44
41
- whenever we load a Java member, we "nullify" its argument and return types
45
42
46
- The nullification logic lives in ` JavaNullInterop.scala ` , a new file.
47
-
48
- The entry point is the function ` def nullifyMember(sym: Symbol, tp: Type)(implicit ctx: Context): Type `
49
- which, given a symbol and its "regular" type, produces what the type of the symbol should be in the
50
- explicit nulls world.
51
-
52
- In order to nullify a member, we first pass it through a "whitelist" of symbols that need
53
- special handling (e.g. ` constructors ` , which never return ` null ` ). If none of the "policies" in the
54
- whitelist apply, we then process the symbol with a ` TypeMap ` that implements the following nullification
55
- function ` n ` :
56
- 1 . n(T) = T|JavaNull if T is a reference type
57
- 2 . n(T) = T if T is a value type
58
- 3 . n(T) = T|JavaNull if T is a type parameter
59
- 4 . n(C[ T] ) = C[ T] |JavaNull if C is Java-defined
60
- 5 . n(C[ T] ) = C[ n(T)] |JavaNull if C is Scala-defined
61
- 6 . n(A|B) = n(A)|n(B)|JavaNull
62
- 7 . n(A&B) = (n(A)&n(B))|JavaNull
63
- 8 . n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R
64
- 9 . n(T) = T otherwise
43
+ The nullification logic lives in ` compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala ` .
44
+
45
+ The entry point is the function
46
+ ` def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(implicit ctx: Context): Type `
47
+ which, given a symbol, its "regular" type, and a boolean whether it is a Enum value definition,
48
+ produces what the type of the symbol should be in the explicit nulls world.
49
+
50
+ 1 . If the symbol is a Enum value definition or a ` TYPE_ ` field, we don't nullify the type
51
+ 2 . If it is ` toString() ` method or the constructor, or it has a ` @NotNull ` annotation,
52
+ we nullify the type, without a ` JavaNull ` at the outmost level.
53
+ 3 . Otherwise, we nullify the type in regular way.
54
+
55
+ See ` JavaNullMap ` in ` JavaNullInterop.scala ` for more details about how we nullify different types.
65
56
66
57
## JavaNull
67
58
@@ -73,32 +64,46 @@ val s: String|JavaNull = "hello"
73
64
s.length // allowed, but might throw NPE
74
65
```
75
66
76
- ` JavaNull ` is defined as ` JavaNullAlias ` in ` Definitions ` .
67
+ ` JavaNull ` is defined as ` JavaNullAlias ` in ` Definitions.scala ` .
77
68
The logic to allow member selections is defined in ` findMember ` in ` Types.scala ` :
78
69
- if we're finding a member in a type union
79
70
- and the union contains ` JavaNull ` on the r.h.s. after normalization (see below)
80
71
- then we can continue with ` findMember ` on the l.h.s of the union (as opposed to failing)
81
72
82
73
## Working with Nullable Unions
83
74
84
- Within ` Types.scala ` , we defined a few utility methods to work with nullable unions. All of these
75
+ Within ` Types.scala ` , we defined some extractors to work with nullable unions:
76
+ ` OrNull ` and ` OrJavaNull ` .
77
+
78
+ ``` scala
79
+ (tp : Type ) match {
80
+ case OrNull (tp1) => // if tp is a nullable union: tp1 | Null
81
+ case _ => // otherwise
82
+ }
83
+ ```
84
+
85
+ These extractor will call utility methods in ` NullOpsDecorator.scala ` . All of these
85
86
are methods of the ` Type ` class, so call them with ` this ` as a receiver:
86
- - ` isNullableUnion ` determines whether ` this ` is a nullable union. Here, what constitutes
87
- a nullable union is determined purely syntactically:
88
- 1. first we "normalize" ` this ` (see below)
89
- 2. if the result is of the form ` T | Null ` , then the type is considered a nullable union.
90
- Otherwise, it isn't.
91
- - ` isJavaNullableUnion ` determines whether ` this ` is syntactically a union of the form ` T|JavaNull `
92
- - ` normNullableUnion ` normalizes ` this ` as follows:
93
- 1 . if ` this ` is not a nullable union, it's returned unchanged.
94
- 2 . if ` this ` is a union, then it's re-arranged so that all the ` Null ` s are to the right of all
95
- the non-` Null ` s.
96
- - ` stripNull ` syntactically strips nullability from ` this ` : e.g. ` String|Null => String ` . Notice this
97
- works only at the "top level": e.g. if we have an ` Array[String|Null]|Null ` and we call ` stripNull `
98
- we'll get ` Array[String|Null] ` (only the outermost nullable union was removed).
99
- - ` stripAllJavaNull ` is like ` stripNull ` but removes _ all_ nullable unions in the type (and only works
100
- for ` JavaNull ` ). This is needed when we want to "revert" the Java nullification function.
87
+
88
+ - ` normNullableUnion ` normalizes unions so that the ` Null ` type (or aliases to ` Null ` )
89
+ appears to the right of all other types.
90
+
91
+ - ` isNullableUnion ` determines whether ` this ` is a nullable union.
92
+ - ` isJavaNullableUnion ` determines whether ` this ` is syntactically a union of the form
93
+ ` T|JavaNull `
94
+ - ` stripNull ` syntactically strips all ` Null ` types in the union:
95
+ e.g. ` String|Null => String ` .
96
+ - ` stripAllJavaNull ` is like ` stripNull ` but only removes ` JavaNull ` from the union.
97
+ This is needed when we want to "revert" the Java nullification function.
101
98
102
99
## Flow Typing
103
100
104
- TODO
101
+ ` NotNullInfo ` s are collected as we typing each statements, see ` Nullables.scala ` for more
102
+ details about how we compute ` NotNullInfo ` s.
103
+
104
+ When we type an identity or a select tree (in ` typedIdent ` and ` typedSelect ` ), we will
105
+ call ` toNotNullTermRef ` on the tree before reture the result. If the tree ` x ` has nullable
106
+ type ` T|Null ` and it is known to be not null according to the ` NotNullInfo ` and it is not
107
+ on the lhs of assignment, then we cast it to ` x.type & T ` using ` defn.Any_typeCast ` . The
108
+ reason to have a ` TermRef(x) ` in the ` AndType ` is that we can track the new result as well and
109
+ use it as a path.
0 commit comments