Skip to content
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ private sealed trait YSettings:
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed())
val YexplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.")
val YnoFlexibleTypes: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-flexible-types", "Disable turning nullable Java return types and parameter types into flexible types, which behave like abstract types with a nullable lower bound and non-nullable upper bound.")
val YnullifyTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ynullify-tasty", "Apply nullification to Scala code compiled without -Yexplicit-nulls, when reading from tasty.")
val YsafeInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.")
val YrequireTargetName: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.")
val YrecheckTest: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrecheck-test", "Run basic rechecking (internal test only).")
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ object Contexts {
/** Is the flexible types option set? */
def flexibleTypes: Boolean = base.settings.YexplicitNulls.value && !base.settings.YnoFlexibleTypes.value

def nullifyTasty: Boolean = base.settings.YexplicitNulls.value && base.settings.YnullifyTasty.value

/** Is the best-effort option set? */
def isBestEffort: Boolean = base.settings.YbestEffort.value

Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1991,13 +1991,11 @@ class Definitions {
* - the upper bound of a TypeParamRef in the current constraint
*/
def asContextFunctionType(tp: Type)(using Context): Type =
tp.stripTypeVar.dealias match
tp.stripNull().stripTypeVar.dealias match
case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) =>
asContextFunctionType(TypeComparer.bounds(tp1).hiBound)
case tp1 @ PolyFunctionOf(mt: MethodType) if mt.isContextualMethod =>
tp1
case tp1: FlexibleType =>
asContextFunctionType(tp1.underlying)
case tp1 =>
if tp1.typeSymbol.name.isContextFunction && isFunctionNType(tp1) then tp1
else NoType
Expand Down
275 changes: 163 additions & 112 deletions compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6253,6 +6253,8 @@ object Types extends TypeUtils {
tp.derivedAndType(tp1, tp2)
protected def derivedOrType(tp: OrType, tp1: Type, tp2: Type): Type =
tp.derivedOrType(tp1, tp2)
protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type): Type =
tp.derivedAndOrType(tp1, tp2)
protected def derivedMatchType(tp: MatchType, bound: Type, scrutinee: Type, cases: List[Type]): Type =
tp.derivedMatchType(bound, scrutinee, cases)
protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation): Type =
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -921,10 +921,10 @@ class TreeUnpickler(reader: TastyReader,

def ta = ctx.typeAssigner

// If explicit nulls is enabled, and the source file did not have explicit
// If explicit nulls and `YnullifyTasty` is enabled, and the source file did not have explicit
// nulls enabled, nullify the member to allow for compatibility.
def nullify(sym: Symbol) =
if (ctx.explicitNulls && ctx.flexibleTypes && !explicitNulls) then
if (ctx.nullifyTasty && !explicitNulls) then
sym.info = ImplicitNullInterop.nullifyMember(sym, sym.info, sym.is(Enum))

val name = readName()
Expand Down
20 changes: 15 additions & 5 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,18 @@ class CompilationTests {
compileFilesInDir("tests/explicit-nulls/neg", explicitNullsOptions, FileFilter.exclude(TestSources.negExplicitNullsScala2LibraryTastyExcludelisted)),
compileFilesInDir("tests/explicit-nulls/flexible-types-common", explicitNullsOptions and "-Yno-flexible-types"),
compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-Yno-flexible-types", FileFilter.exclude(TestSources.negExplicitNullsScala2LibraryTastyExcludelisted)),
)
}.checkExpectedErrors()
).checkExpectedErrors()

locally {
val unsafeFile = compileFile("tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls")
val flexibleFile = compileFile("tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala",
explicitNullsOptions.and("-Ynullify-tasty").withClasspath(defaultOutputDir + testGroup + "/Unsafe_1/neg/Unsafe_1"))

flexibleFile.keepOutput.checkExpectedErrors()

List(unsafeFile, flexibleFile).foreach(_.delete())
}
}

@Ignore
@Test def explicitNullsPos: Unit = {
Expand All @@ -226,9 +236,9 @@ class CompilationTests {

locally {
val tests = List(
compileFile("tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls"),
compileFile("tests/explicit-nulls/flexible-unpickle/Flexible_2.scala", explicitNullsOptions.withClasspath(
defaultOutputDir + testGroup + "/Unsafe_1/flexible-unpickle/Unsafe_1")),
compileFile("tests/explicit-nulls/flexible-unpickle/pos/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls"),
compileFile("tests/explicit-nulls/flexible-unpickle/pos/Flexible_2.scala",
explicitNullsOptions.and("-Ynullify-tasty").withClasspath(defaultOutputDir + testGroup + "/Unsafe_1/pos/Unsafe_1")),
).map(_.keepOutput.checkCompile())

tests.foreach(_.delete())
Expand Down
24 changes: 24 additions & 0 deletions tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import unsafe.*

@A[String] class C

def test =

val ii = Bar.f[Int](null) // error
val jj = Bar.g[String](null) // error
val jj2 = Bar.g[String | Null](null) // ok
val kk = Bar.g2[String](null) // error
val kk2 = Bar.g2[String | Null](null) // ok

val bar_x: Int = Bar.x
val bar_y: String | Null = Bar.y.replaceAll(" ","")

def testUsingFoo(using Foo[Option]) = Bar.h(null)

val ii2 = Bar2[String]().f(null) // error
val ii3 = Bar2[String | Null]().f(null) // ok

val a = Bar.ff(
(x: AnyRef) => x.toString,
42
)
22 changes: 22 additions & 0 deletions tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package unsafe

import scala.annotation.*

type XtoY = [X] =>> [Y] =>> X => Y

class Foo[T[_]]

class A[T] extends Annotation

object Bar:
def ff(f: AnyRef => String, g: AnyRef ?=> Int): (AnyRef => String) = ???
var x: Int = 0
var y: String = ""
def f[T <: Int](x: T): T = x
def g[T <: AnyRef](x: T): T = x
def g2[T >: Null <: AnyRef](x: T): T = x
def h(x: String)(using Foo[Option]): String = x
def h2(a: Foo[XtoY[String]]) = ???

class Bar2[T]:
def f(x: T): T = x
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unsafeNulls.Foo.*
import unsafeNulls.Unsafe_1
import unsafeNulls.{A, B, C, F, G, H, I, J, L, M, S, T, U, expects}
import unsafeNulls.{A, B, C, F, G, H, I, J, L, M, N, S, T, U, expects}
import scala.reflect.Selectable.reflectiveSelectable
import scala.quoted.*

Expand Down Expand Up @@ -100,6 +100,11 @@ def Flexible_2() =

val m: String = M.test(null)

// i23911
val n1: List[Map[String, Int]] = ???
val n2 = new N[List]()
val n3 = n2.accept[Any](n1)

// i23845
transparent inline def typeName[A]: String = ${typeNameMacro[A]}

Expand All @@ -109,3 +114,9 @@ def Flexible_2() =
implicit val givenS: S[A] = ???
expects(alphaTypeNameMacro[A])
}

// i23935
opaque type ZArrow[-I, -R, +E, +O] = I => ZIO[R, E, O]
object ZArrow:
def fromZIOAttempt[I, R, E, O](f: I => ZIO[R, E, O]): ZArrow[I, R, Throwable | E, O] =
(in: I) => ZIO.attempt(f(in)).flatten
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,24 @@ object M {
def test(input: => String): String = "foo " + input
}


class N[F[_]] {
def accept[A](arg: F[A]): Nothing = ???
}

class S[X]
object S { def show[X] = "dummyStr" }
class T
class U[Y](a: Y)
def expects(take: (T) ?=> U[String]) = ???

// i23935
object ZIO:
def attempt[A](code: => A): ZIO[Any, Throwable, A] = ???

trait ZIO[-R, +E, +A]:
final def flatten[R1 <: R, E1 >: E, B](using A IsSubtypeOfOutput ZIO[R1, E1, B]): ZIO[R1, E1, B] = ???

infix sealed abstract class IsSubtypeOfOutput[-A, +B]
object IsSubtypeOfOutput:
given [A, B](using A <:< B): IsSubtypeOfOutput[A, B] = ???
14 changes: 14 additions & 0 deletions tests/explicit-nulls/pos/i23933.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
enum FormatPattern:
case AsInt
case AsLong

// some basic operations with enum:
def test =
val p1 = FormatPattern.AsInt
val p2 = FormatPattern.AsLong
val p3 = FormatPattern.valueOf("AsInt")
val p4 = FormatPattern.values(0)
val ord1 = p1.ordinal
val ord2 = p2.ordinal
val str1 = p1.toString()
val str2 = p2.toString()
8 changes: 8 additions & 0 deletions tests/explicit-nulls/pos/i23936.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//> using options -Yexplicit-nulls

sealed abstract class IsSubtypeOfOutput[-A, +B] extends (A => B)
object IsSubtypeOfOutput:
private val instance: IsSubtypeOfOutput[Any, Any] = new IsSubtypeOfOutput[Any, Any] { def apply(a: Any): Any = a }

sealed trait DerivationAnnotation
class Rename(name: String) extends DerivationAnnotation
Loading