@@ -28,9 +28,6 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by:
2828
2929## Java Interop
3030
31- TODO(abeln): add support for recognizing nullability annotations a la
32- https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
33-
3431The problem we're trying to solve here is: if we see a Java method ` String foo(String) ` ,
3532what should that method look like to Scala?
3633 - 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:
4340 - we do this in two places: ` Namer ` (for Java sources) and ` ClassFileParser ` (for bytecode)
4441 - whenever we load a Java member, we "nullify" its argument and return types
4542
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.
6556
6657## JavaNull
6758
@@ -73,32 +64,46 @@ val s: String|JavaNull = "hello"
7364s.length // allowed, but might throw NPE
7465```
7566
76- ` JavaNull ` is defined as ` JavaNullAlias ` in ` Definitions ` .
67+ ` JavaNull ` is defined as ` JavaNullAlias ` in ` Definitions.scala ` .
7768The logic to allow member selections is defined in ` findMember ` in ` Types.scala ` :
7869 - if we're finding a member in a type union
7970 - and the union contains ` JavaNull ` on the r.h.s. after normalization (see below)
8071 - then we can continue with ` findMember ` on the l.h.s of the union (as opposed to failing)
8172
8273## Working with Nullable Unions
8374
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
8586are 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.
10198
10299## Flow Typing
103100
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