diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6857e3da38ed..3b2cb0db78c3 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -901,16 +901,24 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // However, `null` can always be a value of `T` for Java side. // So the best solution here is to let `Null` be a subtype of non-primitive // value types temporarily. - def isNullable(tp: Type): Boolean = tp.widenDealias match + def isNullable(tp: Type): Boolean = tp.dealias match case tp: TypeRef => val tpSym = tp.symbol ctx.mode.is(Mode.RelaxedOverriding) && !tpSym.isPrimitiveValueClass || tpSym.isNullableClass + case tp: TermRef => + // https://scala-lang.org/files/archive/spec/2.13/03-types.html#singleton-types + // A singleton type is of the form p.type. Where p is a path pointing to a value which conforms to + // scala.AnyRef [Scala 3: which scala.Null conforms to], the type denotes the set of values consisting + // of null and the value denoted by p (i.e., the value v for which v eq p). [Otherwise,] the type + // denotes the set consisting of only the value denoted by p. + isNullable(tp.underlying) && tp.isStable case tp: RefinedOrRecType => isNullable(tp.parent) case tp: AppliedType => isNullable(tp.tycon) case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) case AnnotatedType(tp1, _) => isNullable(tp1) + case ConstantType(c) => c.tag == Constants.NullTag case _ => false val sym1 = tp1.symbol (sym1 eq NothingClass) && tp2.isValueTypeOrLambda || diff --git a/tests/neg/i17467.scala b/tests/neg/i17467.scala new file mode 100644 index 000000000000..cf2cb7701c55 --- /dev/null +++ b/tests/neg/i17467.scala @@ -0,0 +1,31 @@ +object Test: + def test(): Unit = + val a1: String = "foo" + val a2: a1.type = null // OK + + val b1: "foo" = null // error + + val c1: "foo" = "foo" + val c2: c1.type = null // error + + type MyNullable = String + val d1: MyNullable = "foo" + val d2: d1.type = null // OK + + type MyNonNullable = Int + val e1: MyNonNullable = 5 + val e2: e1.type = null // error + + summon[Null <:< "foo"] // error + + val f1: Mod.type = null // error + + var g1: AnyRef = "foo" + val g2: g1.type = null // error // error + end test + + object Mod + + class Bar: + def me: this.type = null // error +end Test diff --git a/tests/run/t6443b.scala b/tests/run/t6443b.scala index 9320b1dcfe2f..796fd9d95df4 100644 --- a/tests/run/t6443b.scala +++ b/tests/run/t6443b.scala @@ -2,7 +2,10 @@ trait A { type D >: Null <: C def foo(d: D)(d2: d.type): Unit trait C { - def bar: Unit = foo(null)(null) + def bar: Unit = { + val nul = null + foo(nul)(nul) + } } } object B extends A {