Skip to content

Fix #7759: Rename JavaNull -> UncheckedNull #7782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -355,20 +355,20 @@ class Definitions {
@tu lazy val RuntimeNullModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Null")

/** An alias for null values that originate in Java code.
* This type gets special treatment in the Typer. Specifically, `JavaNull` can be selected through:
* This type gets special treatment in the Typer. Specifically, `UncheckedNull` can be selected through:
* e.g.
* ```
* // x: String|Null
* x.length // error: `Null` has no `length` field
* // x2: String|JavaNull
* // x2: String|UncheckedNull
* x2.length // allowed by the Typer, but unsound (might throw NPE)
* ```
*/
lazy val JavaNullAlias: TypeSymbol = {
lazy val UncheckedNullAlias: TypeSymbol = {
assert(ctx.explicitNulls)
enterAliasType(tpnme.JavaNull, NullType)
enterAliasType(tpnme.UncheckedNull, NullType)
}
def JavaNullAliasType: TypeRef = JavaNullAlias.typeRef
def UncheckedNullAliasType: TypeRef = UncheckedNullAlias.typeRef

@tu lazy val ImplicitScrutineeTypeSym =
newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered
Expand Down Expand Up @@ -1371,7 +1371,7 @@ class Definitions {
NothingClass,
SingletonClass)

if (ctx.explicitNulls) synth :+ JavaNullAlias else synth
if (ctx.explicitNulls) synth :+ UncheckedNullAlias else synth
}

@tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List(
Expand Down
42 changes: 21 additions & 21 deletions compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import NullOpsDecorator._
* as Scala types, which are explicitly nullable.
*
* The transformation is (conceptually) a function `n` that adheres to the following rules:
* (1) n(T) = T|JavaNull if T is a reference type
* (1) n(T) = T|UncheckedNull if T is a reference type
* (2) n(T) = T if T is a value type
* (3) n(C[T]) = C[T]|JavaNull if C is Java-defined
* (4) n(C[T]) = C[n(T)]|JavaNull if C is Scala-defined
* (5) n(A|B) = n(A)|n(B)|JavaNull
* (3) n(C[T]) = C[T]|UncheckedNull if C is Java-defined
* (4) n(C[T]) = C[n(T)]|UncheckedNull if C is Scala-defined
* (5) n(A|B) = n(A)|n(B)|UncheckedNull
* (6) n(A&B) = n(A) & n(B)
* (7) n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R
* (8) n(T) = T otherwise
*
* Treatment of generics (rules 3 and 4):
* - if `C` is Java-defined, then `n(C[T]) = C[T]|JavaNull`. That is, we don't recurse
* on the type argument, and only add JavaNull on the outside. This is because
* - if `C` is Java-defined, then `n(C[T]) = C[T]|UncheckedNull`. That is, we don't recurse
* on the type argument, and only add UncheckedNull on the outside. This is because
* `C` itself will be nullified, and in particular so will be usages of `C`'s type argument within C's body.
* e.g. calling `get` on a `java.util.List[String]` already returns `String|Null` and not `String`, so
* we don't need to write `java.util.List[String|Null]`.
* - if `C` is Scala-defined, however, then we want `n(C[T]) = C[n(T)]|JavaNull`. This is because
* - if `C` is Scala-defined, however, then we want `n(C[T]) = C[n(T)]|UncheckedNull`. This is because
* `C` won't be nullified, so we need to indicate that its type argument is nullable.
*
* Notice that since the transformation is only applied to types attached to Java symbols, it doesn't need
Expand All @@ -43,9 +43,9 @@ object JavaNullInterop {
*
* After calling `nullifyMember`, Scala will see the method as
*
* def foo(arg: String|JavaNull): String|JavaNull
* def foo(arg: String|UncheckedNull): String|UncheckedNull
*
* This nullability function uses `JavaNull` instead of vanilla `Null`, for usability.
* This nullability function uses `UncheckedNull` instead of vanilla `Null`, for usability.
* This means that we can select on the return of `foo`:
*
* val len = foo("hello").length
Expand Down Expand Up @@ -81,20 +81,20 @@ object JavaNullInterop {
private def nullifyExceptReturnType(tp: Type)(implicit ctx: Context): Type =
new JavaNullMap(true)(ctx)(tp)

/** Nullifies a Java type by adding `| JavaNull` in the relevant places. */
/** Nullifies a Java type by adding `| UncheckedNull` in the relevant places. */
private def nullifyType(tp: Type)(implicit ctx: Context): Type =
new JavaNullMap(false)(ctx)(tp)

/** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| JavaNull`
/** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| UncheckedNull`
* in the right places to make the nulls explicit in Scala.
*
* @param outermostLevelAlreadyNullable whether this type is already nullable at the outermost level.
* For example, `Array[String]|JavaNull` is already nullable at the
* outermost level, but `Array[String|JavaNull]` isn't.
* For example, `Array[String]|UncheckedNull` is already nullable at the
* outermost level, but `Array[String|UncheckedNull]` isn't.
* If this parameter is set to true, then the types of fields, and the return
* types of methods will not be nullified.
* This is useful for e.g. constructors, and also so that `A & B` is nullified
* to `(A & B) | JavaNull`, instead of `(A|JavaNull & B|JavaNull) | JavaNull`.
* to `(A & B) | UncheckedNull`, instead of `(A|UncheckedNull & B|UncheckedNull) | UncheckedNull`.
*/
private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap {
/** Should we nullify `tp` at the outermost level? */
Expand All @@ -107,15 +107,15 @@ object JavaNullInterop {
!tp.isRef(defn.AnyClass) &&
// We don't nullify Java varargs at the top level.
// Example: if `setNames` is a Java method with signature `void setNames(String... names)`,
// then its Scala signature will be `def setNames(names: (String|JavaNull)*): Unit`.
// then its Scala signature will be `def setNames(names: (String|UncheckedNull)*): Unit`.
// This is because `setNames(null)` passes as argument a single-element array containing the value `null`,
// and not a `null` array.
!tp.isRef(defn.RepeatedParamClass)
case _ => true
})

override def apply(tp: Type): Type = tp match {
case tp: TypeRef if needsNull(tp) => OrJavaNull(tp)
case tp: TypeRef if needsNull(tp) => OrUncheckedNull(tp)
case appTp @ AppliedType(tycon, targs) =>
val oldOutermostNullable = outermostLevelAlreadyNullable
// We don't make the outmost levels of type arguements nullable if tycon is Java-defined.
Expand All @@ -125,7 +125,7 @@ object JavaNullInterop {
val targs2 = targs map this
outermostLevelAlreadyNullable = oldOutermostNullable
val appTp2 = derivedAppliedType(appTp, tycon, targs2)
if (needsNull(tycon)) OrJavaNull(appTp2) else appTp2
if (needsNull(tycon)) OrUncheckedNull(appTp2) else appTp2
case ptp: PolyType =>
derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType))
case mtp: MethodType =>
Expand All @@ -136,11 +136,11 @@ object JavaNullInterop {
derivedLambdaType(mtp)(paramInfos2, this(mtp.resType))
case tp: TypeAlias => mapOver(tp)
case tp: AndType =>
// nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add
// duplicate `JavaNull`s at the outermost level inside `A` and `B`.
// nullify(A & B) = (nullify(A) & nullify(B)) | UncheckedNull, but take care not to add
// duplicate `UncheckedNull`s at the outermost level inside `A` and `B`.
outermostLevelAlreadyNullable = true
OrJavaNull(derivedAndType(tp, this(tp.tp1), this(tp.tp2)))
case tp: TypeParamRef if needsNull(tp) => OrJavaNull(tp)
OrUncheckedNull(derivedAndType(tp, this(tp.tp1), this(tp.tp2)))
case tp: TypeParamRef if needsNull(tp) => OrUncheckedNull(tp)
// In all other cases, return the type unchanged.
// In particular, if the type is a ConstantType, then we don't nullify it because it is the
// type of a final non-nullable field.
Expand Down
36 changes: 18 additions & 18 deletions compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ import dotty.tools.dotc.core.Types._
object NullOpsDecorator {

implicit class NullOps(val self: Type) {
/** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */
def isJavaNullType(implicit ctx: Context): Boolean = {
/** Is this type exactly `UncheckedNull` (no vars, aliases, refinements etc allowed)? */
def isUncheckedNullType(implicit ctx: Context): Boolean = {
assert(ctx.explicitNulls)
// We can't do `self == defn.JavaNull` because when trees are unpickled new references
// to `JavaNull` could be created that are different from `defn.JavaNull`.
// We can't do `self == defn.UncheckedNull` because when trees are unpickled new references
// to `UncheckedNull` could be created that are different from `defn.UncheckedNull`.
// Instead, we compare the symbol.
self.isDirectRef(defn.JavaNullAlias)
self.isDirectRef(defn.UncheckedNullAlias)
}

/** Syntactically strips the nullability from this type.
* If the type is `T1 | ... | Tn`, and `Ti` references to `Null` (or `JavaNull`),
* If the type is `T1 | ... | Tn`, and `Ti` references to `Null` (or `UncheckedNull`),
* then return `T1 | ... | Ti-1 | Ti+1 | ... | Tn`.
* If this type isn't (syntactically) nullable, then returns the type unchanged.
*
* @param onlyJavaNull whether we only remove `JavaNull`, the default value is false
* @param onlyUncheckedNull whether we only remove `UncheckedNull`, the default value is false
*/
def stripNull(onlyJavaNull: Boolean = false)(implicit ctx: Context): Type = {
def stripNull(onlyUncheckedNull: Boolean = false)(implicit ctx: Context): Type = {
assert(ctx.explicitNulls)

def isNull(tp: Type) =
if (onlyJavaNull) tp.isJavaNullType
if (onlyUncheckedNull) tp.isUncheckedNullType
else tp.isNullType

def strip(tp: Type): Type = tp match {
Expand All @@ -55,15 +55,15 @@ object NullOpsDecorator {
if (stripped ne self1) stripped else self
}

/** Like `stripNull`, but removes only the `JavaNull`s. */
def stripJavaNull(implicit ctx: Context): Type = self.stripNull(true)
/** Like `stripNull`, but removes only the `UncheckedNull`s. */
def stripUncheckedNull(implicit ctx: Context): Type = self.stripNull(true)

/** Collapses all `JavaNull` unions within this type, and not just the outermost ones (as `stripJavaNull` does).
* e.g. (Array[String|JavaNull]|JavaNull).stripJavaNull => Array[String|JavaNull]
* (Array[String|JavaNull]|JavaNull).stripAllJavaNull => Array[String]
* If no `JavaNull` unions are found within the type, then returns the input type unchanged.
/** Collapses all `UncheckedNull` unions within this type, and not just the outermost ones (as `stripUncheckedNull` does).
* e.g. (Array[String|UncheckedNull]|UncheckedNull).stripUncheckedNull => Array[String|UncheckedNull]
* (Array[String|UncheckedNull]|UncheckedNull).stripAllUncheckedNull => Array[String]
* If no `UncheckedNull` unions are found within the type, then returns the input type unchanged.
*/
def stripAllJavaNull(implicit ctx: Context): Type = {
def stripAllUncheckedNull(implicit ctx: Context): Type = {
object RemoveNulls extends TypeMap {
override def apply(tp: Type): Type = mapOver(tp.stripNull(true))
}
Expand All @@ -77,8 +77,8 @@ object NullOpsDecorator {
stripped ne self
}

/** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */
def isJavaNullableUnion(implicit ctx: Context): Boolean = {
/** Is self (after widening and dealiasing) a type of the form `T | UncheckedNull`? */
def isUncheckedNullableUnion(implicit ctx: Context): Boolean = {
val stripped = self.stripNull(true)
stripped ne self
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ object StdNames {
final val Nothing: N = "Nothing"
final val NotNull: N = "NotNull"
final val Null: N = "Null"
final val JavaNull: N = "JavaNull"
final val UncheckedNull: N = "UncheckedNull"
final val Object: N = "Object"
final val Product: N = "Product"
final val PartialFunction: N = "PartialFunction"
Expand Down
16 changes: 8 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -712,12 +712,12 @@ object Types {
go(l) & (go(r), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name))

def goOr(tp: OrType) = tp match {
case OrJavaNull(tp1) =>
// Selecting `name` from a type `T|JavaNull` is like selecting `name` from `T`.
case OrUncheckedNull(tp1) =>
// Selecting `name` from a type `T|UncheckedNull` is like selecting `name` from `T`.
// This can throw at runtime, but we trade soundness for usability.
// We need to strip `JavaNull` from both the type and the prefix so that
// We need to strip `UncheckedNull` from both the type and the prefix so that
// `pre <: tp` continues to hold.
tp1.findMember(name, pre.stripJavaNull, required, excluded)
tp1.findMember(name, pre.stripUncheckedNull, required, excluded)
case _ =>
// we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix`
// achieved that by narrowing `pre` to each alternative, but it led to merge errors in
Expand Down Expand Up @@ -2963,15 +2963,15 @@ object Types {
* e.g.
*
* (tp: Type) match
* case OrJavaNull(tp1) => // tp had the form `tp1 | JavaNull`
* case OrUncheckedNull(tp1) => // tp had the form `tp1 | UncheckedNull`
* case _ => // tp was not a Java-nullable union
*/
object OrJavaNull {
object OrUncheckedNull {
def apply(tp: Type)(given Context) =
OrType(tp, defn.JavaNullAliasType)
OrType(tp, defn.UncheckedNullAliasType)
def unapply(tp: Type)(given ctx: Context): Option[Type] =
if (ctx.explicitNulls) {
val tp1 = tp.stripJavaNull
val tp1 = tp.stripUncheckedNull
if tp1 ne tp then Some(tp1) else None
}
else None
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/FirstTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
tree match {
case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists =>
val qualTpe = if (ctx.explicitNulls) {
// `JavaNull` is already special-cased in the Typer, but needs to be handled here as well.
// We need `stripAllJavaNull` and not `stripJavaNull` because of the following case:
// `UncheckedNull` is already special-cased in the Typer, but needs to be handled here as well.
// We need `stripAllUncheckedNull` and not `stripUncheckedNull` because of the following case:
//
// val s: (String|JavaNull)&(String|JavaNull) = "hello"
// val s: (String|UncheckedNull)&(String|UncheckedNull) = "hello"
// val l = s.length
//
// The invariant below is that the type of `s`, which isn't a top-level JavaNull union,
// The invariant below is that the type of `s`, which isn't a top-level UncheckedNull union,
// must derive from the type of the owner of `length`, which is `String`. Because we don't
// know which `JavaNull`s were used to find the `length` member, we conservatively remove
// know which `UncheckedNull`s were used to find the `length` member, we conservatively remove
// all of them.
qual.tpe.stripAllJavaNull
qual.tpe.stripAllUncheckedNull
} else {
qual.tpe
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
// Second constructor of ioob that takes a String argument
def filterStringConstructor(s: Symbol): Boolean = s.info match {
case m: MethodType if s.isConstructor && m.paramInfos.size == 1 =>
val pinfo = if (ctx.explicitNulls) m.paramInfos.head.stripJavaNull else m.paramInfos.head
val pinfo = if (ctx.explicitNulls) m.paramInfos.head.stripUncheckedNull else m.paramInfos.head
pinfo == defn.StringType
case _ => false
}
Expand Down
Loading