@@ -56,94 +56,38 @@ object JavaNullInterop {
56
56
assert(ctx.explicitNulls)
57
57
assert(sym.is(JavaDefined ), " can only nullify java-defined members" )
58
58
59
- // A list of "policies" that special-case certain members.
60
- // The policies should be disjoint: we use the first one that is applicable.
61
- val whitelist : Seq [NullifyPolicy ] = Seq (
62
- // The `TYPE` field in every class: don't nullify.
63
- NoOpPolicy (_.name == nme.TYPE_ ),
64
- // The `toString` method: don't nullify the return type.
65
- paramsOnlyPolicy(_.name == nme.toString_),
66
- // Constructors: params are nullified, but the result type isn't.
67
- paramsOnlyPolicy(_.isConstructor),
68
- // Java enum instances: don't nullify.
69
- NoOpPolicy (_.isAllOf(Flags .JavaEnumValue ))
70
- )
71
-
72
- whitelist.find(_.isApplicable(sym)) match {
73
- case Some (pol) => pol(tp)
74
- case None => nullifyType(tp) // default case: nullify everything
75
- }
59
+ // Some special cases when nullifying the type
60
+ if (sym.name == nme.TYPE_ || sym.isAllOf(Flags .JavaEnumValue ))
61
+ // Don't nullify the `TYPE` field in every class and Java enum instances
62
+ tp
63
+ else if (sym.name == nme.toString_ || sym.isConstructor)
64
+ // Don't nullify the return type of the `toString` method and constructors
65
+ nullifyParamsOnly(tp)
66
+ else
67
+ // Otherwise, nullify everything
68
+ nullifyType(tp)
76
69
}
77
70
78
- /** A policy that special cases the handling of some symbol. */
79
- private sealed trait NullifyPolicy {
80
- /** Whether the policy applies to `sym`. */
81
- def isApplicable (sym : Symbol ): Boolean
82
- /** Nullifies `tp` according to the policy. Should call `isApplicable` first. */
83
- def apply (tp : Type ): Type
84
- }
85
-
86
- /** A policy that leaves the passed-in type unchanged. */
87
- private case class NoOpPolicy (trigger : Symbol => Boolean ) extends NullifyPolicy {
88
- override def isApplicable (sym : Symbol ): Boolean = trigger(sym)
89
-
90
- override def apply (tp : Type ): Type = tp
91
- }
92
-
93
- /** A policy for handling a method or poly.
94
- * @param trigger determines whether the policy applies to a given symbol.
95
- * @param nnParams the indices of the method parameters that should be considered "non-null" (should not be nullified).
96
- * @param nnRes whether the result type should be nullified.
97
- *
98
- * For the purposes of both `nnParams` and `nnRes`, when a parameter or return type is not nullified,
99
- * this applies only at the top level. e.g. suppose we have a Java result type `Array[String]` and `nnRes` is set.
100
- * Scala will see `Array[String|JavaNull]`; the array element type is still nullified.
101
- */
102
- private case class MethodPolicy (trigger : Symbol => Boolean ,
103
- nnParams : Seq [Int ],
104
- nnRes : Boolean )(implicit ctx : Context ) extends TypeMap with NullifyPolicy {
105
- override def isApplicable (sym : Symbol ): Boolean = trigger(sym)
106
-
107
- private def spare (tp : Type ): Type = {
108
- nullifyType(tp).stripNull
109
- }
110
-
111
- override def apply (tp : Type ): Type = {
112
- tp match {
113
- case ptp : PolyType =>
114
- derivedLambdaType(ptp)(ptp.paramInfos, this (ptp.resType))
115
- case mtp : MethodType =>
116
- val paramTpes = mtp.paramInfos.zipWithIndex.map {
117
- case (paramInfo, index) =>
118
- // TODO(abeln): the sequence lookup can be optimized, because the indices
119
- // in it appear in increasing order.
120
- if (nnParams.contains(index)) spare(paramInfo) else nullifyType(paramInfo)
121
- }
122
- val resTpe = if (nnRes) spare(mtp.resType) else nullifyType(mtp.resType)
123
- derivedLambdaType(mtp)(paramTpes, resTpe)
124
- }
125
- }
126
- }
127
-
128
- /** A policy that nullifies only method parameters (but not result types). */
129
- private def paramsOnlyPolicy (trigger : Symbol => Boolean )(implicit ctx : Context ): MethodPolicy = {
130
- MethodPolicy (trigger, nnParams = Seq .empty, nnRes = true )
131
- }
71
+ /** Only nullify method parameters (but not result types). */
72
+ private def nullifyParamsOnly (tp : Type )(implicit ctx : Context ): Type =
73
+ new JavaNullMap (alreadyNullable = false , nonNullResultType = true )(ctx)(tp)
132
74
133
75
/** Nullifies a Java type by adding `| JavaNull` in the relevant places. */
134
- private def nullifyType (tpe : Type )(implicit ctx : Context ): Type = {
135
- val nullMap = new JavaNullMap (alreadyNullable = false )
136
- nullMap(tpe)
137
- }
76
+ private def nullifyType (tp : Type )(implicit ctx : Context ): Type =
77
+ new JavaNullMap (alreadyNullable = false , nonNullResultType = false )(ctx)(tp)
78
+
138
79
139
80
/** A type map that adds `| JavaNull`.
140
81
* @param alreadyNullable whether the type being mapped is already nullable (at the outermost level).
141
82
* This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`,
142
83
* instead of `(A|JavaNull | B|JavaNull) | JavaNull`.
84
+ * @param nonNullResultType if true, then the current type is a method or generic method type,
85
+ * and we don't want to nullify its return type at the top level.
86
+ * This is useful e.g. for constructors.
143
87
*/
144
- private class JavaNullMap (var alreadyNullable : Boolean )(implicit ctx : Context ) extends TypeMap {
88
+ private class JavaNullMap (var alreadyNullable : Boolean , var nonNullResultType : Boolean )(implicit ctx : Context ) extends TypeMap {
145
89
/** Should we nullify `tp` at the outermost level? */
146
- def needsTopLevelNull (tp : Type ): Boolean = {
90
+ def needsTopLevelNull (tp : Type ): Boolean =
147
91
! alreadyNullable && (tp match {
148
92
case tp : TypeRef =>
149
93
// We don't modify value types because they're non-nullable even in Java.
@@ -158,35 +102,45 @@ object JavaNullInterop {
158
102
! tp.isRef(defn.RepeatedParamClass )
159
103
case _ => true
160
104
})
161
- }
162
105
163
106
/** Should we nullify the type arguments to the given generic `tp`?
164
107
* We only nullify the inside of Scala-defined generics.
165
108
* This is because Java classes are _all_ nullified, so both `java.util.List[String]` and
166
109
* `java.util.List[String|Null]` contain nullable elements.
167
110
*/
168
- def needsNullArgs (tp : AppliedType ): Boolean = {
169
- ! tp.classSymbol.is(JavaDefined )
170
- }
111
+ def needsNullArgs (tp : AppliedType ): Boolean = ! tp.classSymbol.is(JavaDefined )
171
112
172
113
override def apply (tp : Type ): Type = {
114
+ // Fast version of Type::toJavaNullableUnion that doesn't check whether the type
115
+ // is already a union.
116
+ def toJavaNullableUnion (tpe : Type ): Type = OrType (tpe, defn.JavaNullAliasType )
117
+
173
118
tp match {
174
- case tp : TypeRef if needsTopLevelNull(tp) => tp. toJavaNullableUnion
119
+ case tp : TypeRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp)
175
120
case appTp @ AppliedType (tycons, targs) =>
121
+ val oldAN = alreadyNullable
122
+ alreadyNullable = false
176
123
val targs2 = if (needsNullArgs(appTp)) targs map this else targs
124
+ alreadyNullable = oldAN
177
125
val appTp2 = derivedAppliedType(appTp, tycons, targs2)
178
- if (needsTopLevelNull(tycons)) appTp2.toJavaNullableUnion else appTp2
126
+ if (needsTopLevelNull(tycons)) toJavaNullableUnion(appTp2) else appTp2
127
+ case ptp : PolyType =>
128
+ derivedLambdaType(ptp)(ptp.paramInfos, this (ptp.resType))
129
+ case mtp : MethodType =>
130
+ val resTpe = if (nonNullResultType) {
131
+ nonNullResultType = false
132
+ this (mtp.resType).stripNull
133
+ }
134
+ else this (mtp.resType)
135
+ derivedLambdaType(mtp)(mtp.paramInfos map this , resTpe)
179
136
case tp : LambdaType => mapOver(tp)
180
137
case tp : TypeAlias => mapOver(tp)
181
- case tp @ AndType (tp1, tp2) =>
138
+ case tp : AndType =>
182
139
// nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add
183
140
// duplicate `JavaNull`s at the outermost level inside `A` and `B`.
184
141
alreadyNullable = true
185
- derivedAndType(tp, this (tp1), this (tp2)).toJavaNullableUnion
186
- case tp @ OrType (tp1, tp2) if ! tp.isJavaNullableUnion =>
187
- alreadyNullable = true
188
- derivedOrType(tp, this (tp1), this (tp2)).toJavaNullableUnion
189
- case tp : TypeParamRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion
142
+ toJavaNullableUnion(derivedAndType(tp, this (tp.tp1), this (tp.tp2)))
143
+ case tp : TypeParamRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp)
190
144
case _ => tp
191
145
}
192
146
}
0 commit comments