Skip to content

Commit d4df9e7

Browse files
authored
Merge pull request scala#29 from noti0na1/dotty-explicit-nulls-small
Inline Nullify Policies in JavaNullInterop; combine two TypeMap
2 parents 19925a5 + 28552f5 commit d4df9e7

File tree

1 file changed

+43
-89
lines changed

1 file changed

+43
-89
lines changed

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

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -56,94 +56,38 @@ object JavaNullInterop {
5656
assert(ctx.explicitNulls)
5757
assert(sym.is(JavaDefined), "can only nullify java-defined members")
5858

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)
7669
}
7770

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)
13274

13375
/** 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+
13879

13980
/** A type map that adds `| JavaNull`.
14081
* @param alreadyNullable whether the type being mapped is already nullable (at the outermost level).
14182
* This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`,
14283
* 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.
14387
*/
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 {
14589
/** Should we nullify `tp` at the outermost level? */
146-
def needsTopLevelNull(tp: Type): Boolean = {
90+
def needsTopLevelNull(tp: Type): Boolean =
14791
!alreadyNullable && (tp match {
14892
case tp: TypeRef =>
14993
// We don't modify value types because they're non-nullable even in Java.
@@ -158,35 +102,45 @@ object JavaNullInterop {
158102
!tp.isRef(defn.RepeatedParamClass)
159103
case _ => true
160104
})
161-
}
162105

163106
/** Should we nullify the type arguments to the given generic `tp`?
164107
* We only nullify the inside of Scala-defined generics.
165108
* This is because Java classes are _all_ nullified, so both `java.util.List[String]` and
166109
* `java.util.List[String|Null]` contain nullable elements.
167110
*/
168-
def needsNullArgs(tp: AppliedType): Boolean = {
169-
!tp.classSymbol.is(JavaDefined)
170-
}
111+
def needsNullArgs(tp: AppliedType): Boolean = !tp.classSymbol.is(JavaDefined)
171112

172113
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+
173118
tp match {
174-
case tp: TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion
119+
case tp: TypeRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp)
175120
case appTp @ AppliedType(tycons, targs) =>
121+
val oldAN = alreadyNullable
122+
alreadyNullable = false
176123
val targs2 = if (needsNullArgs(appTp)) targs map this else targs
124+
alreadyNullable = oldAN
177125
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)
179136
case tp: LambdaType => mapOver(tp)
180137
case tp: TypeAlias => mapOver(tp)
181-
case tp @ AndType(tp1, tp2) =>
138+
case tp: AndType =>
182139
// nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add
183140
// duplicate `JavaNull`s at the outermost level inside `A` and `B`.
184141
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)
190144
case _ => tp
191145
}
192146
}

0 commit comments

Comments
 (0)