@@ -6,11 +6,10 @@ import dotty.tools.dotc.core.StdNames.{jnme, nme}
6
6
import dotty .tools .dotc .core .Symbols .{Symbol , defn , _ }
7
7
import dotty .tools .dotc .core .Types .{AndType , AppliedType , LambdaType , MethodType , OrType , PolyType , Type , TypeAlias , TypeMap , TypeParamRef , TypeRef }
8
8
9
- /** This module defines methods to interpret Java types , which are implicitly nullable,
9
+ /** This module defines methods to interpret types of Java symbols , which are implicitly nullable in Java ,
10
10
* as Scala types, which are explicitly nullable.
11
11
*
12
- * The transformation from Java types to Scala types is (conceptually) a function `n`
13
- * that adheres to the following rules:
12
+ * The transformation is (conceptually) a function `n` that adheres to the following rules:
14
13
* (1) n(T) = T|JavaNull if T is a reference type
15
14
* (2) n(T) = T if T is a value type
16
15
* (3) n(C[T]) = C[T]|JavaNull if C is Java-defined
@@ -20,6 +19,15 @@ import dotty.tools.dotc.core.Types.{AndType, AppliedType, LambdaType, MethodType
20
19
* (7) n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R
21
20
* (8) n(T) = T otherwise
22
21
*
22
+ * Treatment of generics (rules 3 and 4):
23
+ * - if `C` is Java-defined, then `n(C[T]) = C[T]|JavaNull`. That is, we don't recurse
24
+ * on the type argument, and only add JavaNull on the outside. This is because
25
+ * `C` itself will be nullified, and in particular so will be usages of `C`'s type argument within C's body.
26
+ * e.g. calling `get` on a `java.util.List[String]` already returns `String|Null` and not `String`, so
27
+ * we don't need to write `java.util.List[String|Null]`.
28
+ * - if `C` is Scala-defined, however, then we want `n(C[T]) = C[n(T)]|JavaNull`. This is because
29
+ * `C` won't be nullified, so we need to indicate that its type argument is nullable.
30
+ *
23
31
* Notice that since the transformation is only applied to types attached to Java symbols, it doesn't need
24
32
* to handle the full spectrum of Scala types. Additionally, some kinds of symbols like constructors and
25
33
* enum instances get special treatment.
@@ -47,30 +55,22 @@ object JavaNullInterop {
47
55
assert(ctx.explicitNulls)
48
56
assert(sym.is(JavaDefined ), " can only nullify java-defined members" )
49
57
50
- // A list of members that are special-cased.
58
+ // A list of "policies" that special-case certain members.
59
+ // The policies should be disjoint: we use the first one that is applicable.
51
60
val whitelist : Seq [NullifyPolicy ] = Seq (
52
61
// The `TYPE` field in every class: don't nullify.
53
- NoOpP (_.name == nme.TYPE_ ),
62
+ NoOpPolicy (_.name == nme.TYPE_ ),
54
63
// The `toString` method: don't nullify the return type.
55
- paramsOnlyP (_.name == nme.toString_),
64
+ paramsOnlyPolicy (_.name == nme.toString_),
56
65
// Constructors: params are nullified, but the result type isn't.
57
- paramsOnlyP (_.isConstructor),
66
+ paramsOnlyPolicy (_.isConstructor),
58
67
// Java enum instances: don't nullify.
59
- NoOpP (_.is(Flags .JavaEnumValue ))
68
+ NoOpPolicy (_.is(Flags .JavaEnumValue ))
60
69
)
61
70
62
- val (fromWhitelistTp, handled) = whitelist.foldLeft((tp, false )) {
63
- case (res@ (_, true ), _) => res
64
- case ((_, false ), pol) =>
65
- if (pol.isApplicable(sym)) (pol(tp), true )
66
- else (tp, false )
67
- }
68
-
69
- if (handled) {
70
- fromWhitelistTp
71
- } else {
72
- // Default case: nullify everything.
73
- nullifyType(tp)
71
+ whitelist.find(_.isApplicable(sym)) match {
72
+ case Some (pol) => pol(tp)
73
+ case None => nullifyType(tp) // default case: nullify everything
74
74
}
75
75
}
76
76
@@ -83,7 +83,7 @@ object JavaNullInterop {
83
83
}
84
84
85
85
/** A policy that leaves the passed-in type unchanged. */
86
- private case class NoOpP (trigger : Symbol => Boolean ) extends NullifyPolicy {
86
+ private case class NoOpPolicy (trigger : Symbol => Boolean ) extends NullifyPolicy {
87
87
override def isApplicable (sym : Symbol ): Boolean = trigger(sym)
88
88
89
89
override def apply (tp : Type ): Type = tp
@@ -98,9 +98,9 @@ object JavaNullInterop {
98
98
* this applies only at the top level. e.g. suppose we have a Java result type `Array[String]` and `nnRes` is set.
99
99
* Scala will see `Array[String|JavaNull]`; the array element type is still nullified.
100
100
*/
101
- private case class MethodP (trigger : Symbol => Boolean ,
102
- nnParams : Seq [Int ],
103
- nnRes : Boolean )(implicit ctx : Context ) extends TypeMap with NullifyPolicy {
101
+ private case class MethodPolicy (trigger : Symbol => Boolean ,
102
+ nnParams : Seq [Int ],
103
+ nnRes : Boolean )(implicit ctx : Context ) extends TypeMap with NullifyPolicy {
104
104
override def isApplicable (sym : Symbol ): Boolean = trigger(sym)
105
105
106
106
private def spare (tp : Type ): Type = {
@@ -125,8 +125,8 @@ object JavaNullInterop {
125
125
}
126
126
127
127
/** A policy that nullifies only method parameters (but not result types). */
128
- private def paramsOnlyP (trigger : Symbol => Boolean )(implicit ctx : Context ): MethodP = {
129
- MethodP (trigger, nnParams = Seq .empty, nnRes = true )
128
+ private def paramsOnlyPolicy (trigger : Symbol => Boolean )(implicit ctx : Context ): MethodPolicy = {
129
+ MethodPolicy (trigger, nnParams = Seq .empty, nnRes = true )
130
130
}
131
131
132
132
/** Nullifies a Java type by adding `| JavaNull` in the relevant places. */
@@ -140,7 +140,7 @@ object JavaNullInterop {
140
140
* This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`,
141
141
* instead of `(A|JavaNull | B|JavaNull) | JavaNull`.
142
142
*/
143
- private class JavaNullMap (alreadyNullable : Boolean )(implicit ctx : Context ) extends TypeMap {
143
+ private class JavaNullMap (var alreadyNullable : Boolean )(implicit ctx : Context ) extends TypeMap {
144
144
/** Should we nullify `tp` at the outermost level? */
145
145
def needsTopLevelNull (tp : Type ): Boolean = {
146
146
! alreadyNullable && (tp match {
@@ -158,30 +158,26 @@ object JavaNullInterop {
158
158
* `java.util.List[String|Null]` contain nullable elements.
159
159
*/
160
160
def needsNullArgs (tp : AppliedType ): Boolean = {
161
- val AppliedType (tycons, _) = tp
162
- tycons.widenDealias match {
163
- case tp : TypeRef if ! tp.symbol.is(JavaDefined ) => true
164
- case _ => false
165
- }
161
+ ! tp.classSymbol.is(JavaDefined )
166
162
}
167
163
168
164
override def apply (tp : Type ): Type = {
169
165
tp match {
166
+ case tp : TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion
167
+ case appTp @ AppliedType (tycons, targs) =>
168
+ val targs2 = if (needsNullArgs(appTp)) targs map this else targs
169
+ derivedAppliedType(appTp, tycons, targs2).toJavaNullableUnion
170
170
case tp : LambdaType => mapOver(tp)
171
171
case tp : TypeAlias => mapOver(tp)
172
- case tp@ AndType (tp1, tp2) =>
172
+ case tp @ AndType (tp1, tp2) =>
173
173
// nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add
174
174
// duplicate `JavaNull`s at the outermost level inside `A` and `B`.
175
- val newMap = new JavaNullMap (alreadyNullable = true )
176
- derivedAndType(tp, newMap(tp1), newMap(tp2)).toJavaNullableUnion
177
- case tp@ OrType (tp1, tp2) if ! tp.isJavaNullableUnion =>
178
- val newMap = new JavaNullMap (alreadyNullable = true )
179
- derivedOrType(tp, newMap(tp1), newMap(tp2)).toJavaNullableUnion
180
- case tp : TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion
175
+ alreadyNullable = true
176
+ derivedAndType(tp, this (tp1), this (tp2)).toJavaNullableUnion
177
+ case tp @ OrType (tp1, tp2) if ! tp.isJavaNullableUnion =>
178
+ alreadyNullable = true
179
+ derivedOrType(tp, this (tp1), this (tp2)).toJavaNullableUnion
181
180
case tp : TypeParamRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion
182
- case appTp@ AppliedType (tycons, targs) =>
183
- val targs2 = if (needsNullArgs(appTp)) targs map this else targs
184
- derivedAppliedType(appTp, tycons, targs2).toJavaNullableUnion
185
181
case _ => tp
186
182
}
187
183
}
0 commit comments