JavaNullInterop

This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, as Scala types, which are explicitly nullable.

The transformation is (conceptually) a function n that adheres to the following rules: (1) n(T) = T | Null if T is a reference type (2) n(T) = T if T is a value type (3) n(C[T]) = C[T] | Null if C is Java-defined (4) n(C[T]) = C[n(T)] | Null if C is Scala-defined (5) n(A|B) = n(A) | n(B) | Null (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] | Null. That is, we don't recurse on the type argument, and only add Null 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)] | Null. 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 to handle the full spectrum of Scala types. Additionally, some kinds of symbols like constructors and enum instances get special treatment.

class Object
trait Matchable
class Any

Value members

Concrete methods

def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type

Transforms the type tp of Java member sym to be explicitly nullable. tp is needed because the type inside sym might not be set when this method is called.

Transforms the type tp of Java member sym to be explicitly nullable. tp is needed because the type inside sym might not be set when this method is called.

e.g. given a Java method String foo(String arg) { return arg; }

After calling nullifyMember, Scala will see the method as

def foo(arg: String | Null): String | Null

If unsafeNulls is enabled, we can select on the return of foo:

val len = foo("hello").length

But the selection can throw an NPE if the returned value is null.