diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5a535071f4b3..9ae1b38c8a41 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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 @@ -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( diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 52ba2ac462e4..a02b6def3c2b 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -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 @@ -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 @@ -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? */ @@ -107,7 +107,7 @@ 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) @@ -115,7 +115,7 @@ object JavaNullInterop { }) 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. @@ -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 => @@ -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. diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 401cce87241d..c7849936b035 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -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 { @@ -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)) } @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index afb7d66f0672..f5865c7750c4 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 68e8b91f4f72..50e01e0f2c68 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index ccbf079c33cb..5bf720e831fc 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 2bcf4088f1e4..e25ca9a160d2 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -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 } diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 73d7fb9e7178..40343ef04540 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -10,7 +10,7 @@ with union types: e.g. `val x: String|Null = null`. The implementation of the feature in dotty can be conceptually divided in several parts: 1. changes to the type hierarchy so that `Null` is only a subtype of `Any` 2. a "translation layer" for Java interop that exposes the nullability in Java APIs - 3. a "magic" `JavaNull` type (an alias for `Null`) that is recognized by the compiler and + 3. a "magic" `UncheckedNull` type (an alias for `Null`) that is recognized by the compiler and allows unsound member selections (trading soundness for usability) ## Feature Flag @@ -30,10 +30,10 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by: The problem we're trying to solve here is: if we see a Java method `String foo(String)`, what should that method look like to Scala? - - since we should be able to pass `null` into Java methods, the argument type should be `String|JavaNull` - - since Java methods might return `null`, the return type should be `String|JavaNull` + - since we should be able to pass `null` into Java methods, the argument type should be `String|UncheckedNull` + - since Java methods might return `null`, the return type should be `String|UncheckedNull` -`JavaNull` here is a type alias for `Null` with "magic" properties (see below). +`UncheckedNull` here is a type alias for `Null` with "magic" properties (see below). At a high-level: - we track the loading of Java fields and methods as they're loaded by the compiler @@ -49,31 +49,31 @@ produces what the type of the symbol should be in the explicit nulls world. 1. If the symbol is a Enum value definition or a `TYPE_` field, we don't nullify the type 2. If it is `toString()` method or the constructor, or it has a `@NotNull` annotation, - we nullify the type, without a `JavaNull` at the outmost level. + we nullify the type, without a `UncheckedNull` at the outmost level. 3. Otherwise, we nullify the type in regular way. See `JavaNullMap` in `JavaNullInterop.scala` for more details about how we nullify different types. -## JavaNull +## UncheckedNull -`JavaNull` is just an alias for `Null`, but with magic power. `JavaNull`'s magic (anti-)power is that +`UncheckedNull` is just an alias for `Null`, but with magic power. `UncheckedNull`'s magic (anti-)power is that it's unsound. ```scala -val s: String|JavaNull = "hello" +val s: String|UncheckedNull = "hello" s.length // allowed, but might throw NPE ``` -`JavaNull` is defined as `JavaNullAlias` in `Definitions.scala`. +`UncheckedNull` is defined as `UncheckedNullAlias` in `Definitions.scala`. The logic to allow member selections is defined in `findMember` in `Types.scala`: - if we're finding a member in a type union - - and the union contains `JavaNull` on the r.h.s. after normalization (see below) + - and the union contains `UncheckedNull` on the r.h.s. after normalization (see below) - then we can continue with `findMember` on the l.h.s of the union (as opposed to failing) ## Working with Nullable Unions Within `Types.scala`, we defined some extractors to work with nullable unions: -`OrNull` and `OrJavaNull`. +`OrNull` and `OrUncheckedNull`. ```scala (tp: Type) match { @@ -87,13 +87,13 @@ are methods of the `Type` class, so call them with `this` as a receiver: - `stripNull` syntactically strips all `Null` types in the union: e.g. `String|Null => String`. -- `stripJavaNull` is like `stripNull` but only removes `JavaNull` from the union. +- `stripUncheckedNull` is like `stripNull` but only removes `UncheckedNull` from the union. This is needed when we want to "revert" the Java nullification function. -- `stripAllJavaNull` collapses all `JavaNull` unions within this type, and not just the outermost - ones (as `stripJavaNull` does). +- `stripAllUncheckedNull` collapses all `UncheckedNull` unions within this type, and not just the outermost + ones (as `stripUncheckedNull` does). - `isNullableUnion` determines whether `this` is a nullable union. -- `isJavaNullableUnion` determines whether `this` is syntactically a union of the form - `T|JavaNull`. +- `isUncheckedNullableUnion` determines whether `this` is syntactically a union of the form + `T|UncheckedNull`. ## Flow Typing diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md index fdf870c34e2a..a7af550986c2 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -100,7 +100,7 @@ Specifically, we patch * the type of fields * the argument type and return type of methods -`JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules with following examples: +`UncheckedNull` is an alias for `Null` with magic properties (see below). We illustrate the rules with following examples: * The first two rules are easy: we nullify reference types but not value types. @@ -113,7 +113,7 @@ Specifically, we patch ==> ```scala class C { - val s: String|JavaNull + val s: String|UncheckedNull val x: Int } ``` @@ -125,7 +125,7 @@ Specifically, we patch ``` ==> ```scala - class C[T] { def foo(): T|JavaNull } + class C[T] { def foo(): T|UncheckedNull } ``` Notice this is rule is sometimes too conservative, as witnessed by @@ -145,21 +145,21 @@ Specifically, we patch ``` ==> ```scala - class Box[T] { def get(): T|JavaNull } - class BoxFactory[T] { def makeBox(): Box[T]|JavaNull } + class Box[T] { def get(): T|UncheckedNull } + class BoxFactory[T] { def makeBox(): Box[T]|UncheckedNull } ``` Suppose we have a `BoxFactory[String]`. Notice that calling `makeBox()` on it returns a - `Box[String]|JavaNull`, not a `Box[String|JavaNull]|JavaNull`. This seems at first + `Box[String]|UncheckedNull`, not a `Box[String|UncheckedNull]|UncheckedNull`. This seems at first glance unsound ("What if the box itself has `null` inside?"), but is sound because calling - `get()` on a `Box[String]` returns a `String|JavaNull`. + `get()` on a `Box[String]` returns a `String|UncheckedNull`. Notice that we need to patch _all_ Java-defined classes that transitively appear in the argument or return type of a field or method accessible from the Scala code being compiled. Absent crazy reflection magic, we think that all such Java classes _must_ be visible to the Typer in the first place, so they will be patched. - * We will append `JavaNull` to the type arguments if the generic class is defined in Scala. + * We will append `UncheckedNull` to the type arguments if the generic class is defined in Scala. ```java class BoxFactory { @@ -170,17 +170,17 @@ Specifically, we patch ==> ```scala class BoxFactory[T] { - def makeBox(): Box[T | JavaNull] | JavaNull - def makeCrazyBoxes(): List[Box[List[T] | JavaNull]] | JavaNull + def makeBox(): Box[T | UncheckedNull] | UncheckedNull + def makeCrazyBoxes(): List[Box[List[T] | UncheckedNull]] | UncheckedNull } ``` - In this case, since `Box` is Scala-defined, and we will get `Box[T|JavaNull]|JavaNull`. + In this case, since `Box` is Scala-defined, and we will get `Box[T|UncheckedNull]|UncheckedNull`. This is needed because our nullability function is only applied (modularly) to the Java classes, but not to the Scala ones, so we need a way to tell `Box` that it contains a nullable value. - The `List` is Java-defined, so we don't append `JavaNull` to its type argument. But we + The `List` is Java-defined, so we don't append `UncheckedNull` to its type argument. But we still need to nullify its inside. * We don't nullify _simple_ literal constant (`final`) fields, since they are known to be non-null @@ -205,7 +205,7 @@ Specifically, we patch } ``` - * We don't append `JavaNull` to a field and the return type of a method which is annotated with a + * We don't append `UncheckedNull` to a field and the return type of a method which is annotated with a `NotNull` annotation. ```java @@ -219,8 +219,8 @@ Specifically, we patch ```scala class C { val name: String - def getNames(prefix: String | JavaNull): List[String] // we still need to nullify the paramter types - def getBoxedName(): Box[String | JavaNull] // we don't append `JavaNull` to the outmost level, but we still need to nullify inside + def getNames(prefix: String | UncheckedNull): List[String] // we still need to nullify the paramter types + def getBoxedName(): Box[String | UncheckedNull] // we don't append `UncheckedNull` to the outmost level, but we still need to nullify inside } ``` @@ -248,15 +248,15 @@ Specifically, we patch "io.reactivex.annotations.NonNull" :: Nil map PreNamedString) ``` -### JavaNull +### UncheckedNull To enable method chaining on Java-returned values, we have the special type alias for `Null`: ```scala -type JavaNull = Null +type UncheckedNull = Null ``` -`JavaNull` behaves just like `Null`, except it allows (unsound) member selections: +`UncheckedNull` behaves just like `Null`, except it allows (unsound) member selections: ```scala // Assume someJavaMethod()'s original Java signature is @@ -264,12 +264,12 @@ type JavaNull = Null val s2: String = someJavaMethod().trim().substring(2).toLowerCase() // unsound ``` -Here, all of `trim`, `substring` and `toLowerCase` return a `String|JavaNull`. -The Typer notices the `JavaNull` and allows the member selection to go through. +Here, all of `trim`, `substring` and `toLowerCase` return a `String|UncheckedNull`. +The Typer notices the `UncheckedNull` and allows the member selection to go through. However, if `someJavaMethod` were to return `null`, then the first member selection would throw a `NPE`. -Without `JavaNull`, the chaining becomes too cumbersome +Without `UncheckedNull`, the chaining becomes too cumbersome ```scala val ret = someJavaMethod() diff --git a/tests/explicit-nulls/neg/interop-javanull.scala b/tests/explicit-nulls/neg/interop-javanull.scala index 1a1924016491..c9c6bf6f8a4c 100644 --- a/tests/explicit-nulls/neg/interop-javanull.scala +++ b/tests/explicit-nulls/neg/interop-javanull.scala @@ -1,5 +1,5 @@ -// Test that JavaNull can be assigned to Null. +// Test that UncheckedNull can be assigned to Null. class Foo { import java.util.ArrayList val l = new ArrayList[String]() diff --git a/tests/explicit-nulls/neg/interop-propagate.scala b/tests/explicit-nulls/neg/interop-propagate.scala index c21728fb7395..761acbe9769c 100644 --- a/tests/explicit-nulls/neg/interop-propagate.scala +++ b/tests/explicit-nulls/neg/interop-propagate.scala @@ -1,7 +1,7 @@ class Foo { import java.util.ArrayList - // Test that as we extract return values, we're missing the |JavaNull in the return type. + // Test that as we extract return values, we're missing the |UncheckedNull in the return type. // i.e. test that the nullability is propagated to nested containers. val ll = new ArrayList[ArrayList[ArrayList[String]]] val level1: ArrayList[ArrayList[String]] = ll.get(0) // error diff --git a/tests/explicit-nulls/neg/interop-return.scala b/tests/explicit-nulls/neg/interop-return.scala index 677d9528e6fa..fb1f106f1d47 100644 --- a/tests/explicit-nulls/neg/interop-return.scala +++ b/tests/explicit-nulls/neg/interop-return.scala @@ -5,10 +5,10 @@ class Foo { def foo = { import java.util.ArrayList val x = new ArrayList[String]() - val r: String = x.get(0) // error: got String|JavaNull instead of String + val r: String = x.get(0) // error: got String|UncheckedNull instead of String val x2 = new ArrayList[Int]() val r2: Int = x2.get(0) // error: even though Int is non-nullable in Scala, its counterpart - // (for purposes of generics) in Java (Integer) is. So we're missing |JavaNull + // (for purposes of generics) in Java (Integer) is. So we're missing |UncheckedNull } } diff --git a/tests/explicit-nulls/neg/java-null.scala b/tests/explicit-nulls/neg/java-null.scala index 884cd43745db..bde68466c040 100644 --- a/tests/explicit-nulls/neg/java-null.scala +++ b/tests/explicit-nulls/neg/java-null.scala @@ -1,10 +1,10 @@ -// Test that `JavaNull` is see-through, but `Null` isn't. +// Test that `UncheckedNull` is see-through, but `Null` isn't. class Test { val s: String|Null = "hello" val l = s.length // error: `Null` isn't "see-through" - val s2: String|JavaNull = "world" + val s2: String|UncheckedNull = "world" val l2 = s2.length // ok } diff --git a/tests/explicit-nulls/neg/nullnull.scala b/tests/explicit-nulls/neg/nullnull.scala index 1ebb25bf6238..c5710e17e78a 100644 --- a/tests/explicit-nulls/neg/nullnull.scala +++ b/tests/explicit-nulls/neg/nullnull.scala @@ -11,7 +11,7 @@ class Foo { } def foo2: Unit = { - val x: JavaNull | String | Null = ??? + val x: UncheckedNull | String | Null = ??? if (x == null) return () val y = x.length // ok: x: String is inferred } diff --git a/tests/explicit-nulls/pos-separate/notnull/S_3.scala b/tests/explicit-nulls/pos-separate/notnull/S_3.scala index 96705e36d185..cc55ef54e7e2 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_3.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -10,6 +10,6 @@ class S_3 { def ff(i: Int): String = J_2.f(i) def gg(i: Int): String = J_2.g(i) def hh(i: Int): String = (new J_2).h(i) - def genericff(a: String | Null): Array[String | JavaNull] = (new J_2).genericf(a) + def genericff(a: String | Null): Array[String | UncheckedNull] = (new J_2).genericf(a) def genericgg(a: String | Null): java.util.List[String] = (new J_2).genericg(a) } diff --git a/tests/explicit-nulls/pos/interop-javanull-src/S.scala b/tests/explicit-nulls/pos/interop-javanull-src/S.scala index 0f5c51a18ccc..42693066bf14 100644 --- a/tests/explicit-nulls/pos/interop-javanull-src/S.scala +++ b/tests/explicit-nulls/pos/interop-javanull-src/S.scala @@ -1,5 +1,5 @@ -// Test that JavaNull is "see through" +// Test that UncheckedNull is "see through" class S { val j: J2 = new J2() j.getJ1().getJ2().getJ1().getJ2().getJ1().getJ2() diff --git a/tests/explicit-nulls/pos/interop-javanull.scala b/tests/explicit-nulls/pos/interop-javanull.scala index 636475166cbf..d771b96e5507 100644 --- a/tests/explicit-nulls/pos/interop-javanull.scala +++ b/tests/explicit-nulls/pos/interop-javanull.scala @@ -1,10 +1,10 @@ -// Tests that the "JavaNull" type added to Java types is "see through" w.r.t member selections. +// Tests that the "UncheckedNull" type added to Java types is "see through" w.r.t member selections. class Foo { import java.util.ArrayList import java.util.Iterator - // Test that we can select through "|JavaNull" (unsoundly). + // Test that we can select through "|UncheckedNull" (unsoundly). val x3 = new ArrayList[ArrayList[ArrayList[String]]]() val x4: Int = x3.get(0).get(0).get(0).length() } diff --git a/tests/explicit-nulls/pos/interop-valuetypes.scala b/tests/explicit-nulls/pos/interop-valuetypes.scala index 595a7de8917a..d7b0a867d5d3 100644 --- a/tests/explicit-nulls/pos/interop-valuetypes.scala +++ b/tests/explicit-nulls/pos/interop-valuetypes.scala @@ -1,6 +1,6 @@ // Tests that value (non-reference) types aren't nullified by the Java transform. -class Foo { +class Foo { val x: java.lang.String = "" - val len: Int = x.length() // type is Int and not Int|JavaNull + val len: Int = x.length() // type is Int and not Int|UncheckedNull } diff --git a/tests/explicit-nulls/pos/java-null.scala b/tests/explicit-nulls/pos/java-null.scala index 3739ddc138a1..ddb4a1026338 100644 --- a/tests/explicit-nulls/pos/java-null.scala +++ b/tests/explicit-nulls/pos/java-null.scala @@ -1,16 +1,16 @@ -// Test that `JavaNull`able unions are transparent +// Test that `UncheckedNull`able unions are transparent // w.r.t member selections. class Test { - val s: String|JavaNull = "hello" - val l: Int = s.length // ok: `JavaNull` allows (unsound) member selections. + val s: String|UncheckedNull = "hello" + val l: Int = s.length // ok: `UncheckedNull` allows (unsound) member selections. - val s2: JavaNull|String = "world" + val s2: UncheckedNull|String = "world" val l2: Int = s2.length - val s3: JavaNull|String|JavaNull = "hello" + val s3: UncheckedNull|String|UncheckedNull = "hello" val l3: Int = s3.length - val s4: (String|JavaNull)&(JavaNull|String) = "hello" + val s4: (String|UncheckedNull)&(UncheckedNull|String) = "hello" val l4 = s4.length } diff --git a/tests/explicit-nulls/pos/java-varargs.scala b/tests/explicit-nulls/pos/java-varargs.scala index 79d0bcb7cbfa..8f2e48d287a3 100644 --- a/tests/explicit-nulls/pos/java-varargs.scala +++ b/tests/explicit-nulls/pos/java-varargs.scala @@ -9,7 +9,7 @@ class S { // is a varargs: https://docs.oracle.com/javase/8/docs/api/java/nio/file/Paths.html // static Path get(String first, String... more) // The Scala compiler converts this signature into - // def get(first: String|JavaNUll, more: (String|JavaNull)*) + // def get(first: String|JavaNUll, more: (String|UncheckedNull)*) // Test that we can avoid providing the varargs argument altogether. Paths.get("out").toAbsolutePath diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala index 5e99c45c8547..2700ec939c9b 100644 --- a/tests/explicit-nulls/pos/notnull/S.scala +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -10,6 +10,6 @@ class S_3 { def ff(i: Int): String = J.f(i) def gg(i: Int): String = J.g(i) def hh(i: Int): String = (new J).h(i) - def genericff(a: String | Null): Array[String | JavaNull] = (new J).genericf(a) + def genericff(a: String | Null): Array[String | UncheckedNull] = (new J).genericf(a) def genericgg(a: String | Null): java.util.List[String] = (new J).genericg(a) } diff --git a/tests/explicit-nulls/run/java-null.scala b/tests/explicit-nulls/run/java-null.scala index 39eb1668d9d5..ba3ba46b48dd 100644 --- a/tests/explicit-nulls/run/java-null.scala +++ b/tests/explicit-nulls/run/java-null.scala @@ -1,17 +1,17 @@ -// Check that selecting a member from a `JavaNull`able union is unsound. +// Check that selecting a member from a `UncheckedNull`able union is unsound. object Test { def main(args: Array[String]): Unit = { - val s: String|JavaNull = "hello" + val s: String|UncheckedNull = "hello" assert(s.length == 5) - val s2: String|JavaNull = null + val s2: String|UncheckedNull = null try { s2.length // should throw assert(false) } catch { case e: NullPointerException => - // ok: selecting on a JavaNull can throw + // ok: selecting on a UncheckedNull can throw } } }