Skip to content

Commit 355daf3

Browse files
committed
Fix SAM type check; fix null conversion to AnyRef and eq select
1 parent d0ea812 commit 355daf3

File tree

8 files changed

+55
-23
lines changed

8 files changed

+55
-23
lines changed

compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,7 @@ object NullOpsDecorator {
4646
object RemoveNulls extends TypeMap {
4747
override def apply(tp: Type): Type =
4848
val mapped = mapOver(tp.widenTermRefExpr.stripNull)
49-
tp match {
50-
case tr: TermRef =>
51-
if tp eq mapped then tp
52-
else AndType(tr, mapped)
53-
case _ =>
54-
mapped
55-
}
49+
if tp eq mapped then tp else mapped
5650
}
5751
val rem = RemoveNulls(self)
5852
if rem ne self then rem else self

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,14 @@ class Typer extends Namer
559559
val qual1 = qual.tpe match {
560560
case OrNull(tpe1) if config.Feature.enabled(nme.unsafeNulls) =>
561561
qual.cast(AndType(qual.tpe, tpe1))
562-
case _ => qual
562+
case tp =>
563+
if ctx.explicitNulls &&
564+
tp.isNullType &&
565+
config.Feature.enabled(nme.unsafeNulls) &&
566+
(tree.name == nme.eq || tree.name == nme.ne) then
567+
// Allow selecting `eq` and `ne` on `Null` specially
568+
qual.cast(defn.ObjectType)
569+
else qual
563570
}
564571
typedSelect(tree, pt, qual1).computeNullable()
565572

@@ -1257,8 +1264,9 @@ class Typer extends Namer
12571264
val pt1 = if ctx.explicitNulls then pt.stripNull else pt
12581265
pt1 match {
12591266
case SAMType(sam)
1260-
if !defn.isFunctionType(pt1) && mt <:< sam ||
1261-
JavaNullInterop.convertUnsafeNulls && mt.isUnsafeConvertable(sam) =>
1267+
if !defn.isFunctionType(pt1) && (
1268+
mt <:< sam ||
1269+
JavaNullInterop.convertUnsafeNulls && mt.stripAllNulls <:< sam.stripAllNulls) =>
12621270
// SAMs of the form C[?] where C is a class cannot be conversion targets.
12631271
// The resulting class `class $anon extends C[?] {...}` would be illegal,
12641272
// since type arguments to `C`'s super constructor cannot be constructed.
@@ -3439,7 +3447,7 @@ class Typer extends Namer
34393447
case SAMType(sam)
34403448
if wtp <:< sam.toFunctionType() ||
34413449
(JavaNullInterop.convertUnsafeNulls &&
3442-
wtp.isUnsafeConvertable(sam.toFunctionType())) =>
3450+
wtp.stripAllNulls <:< sam.toFunctionType().stripAllNulls) =>
34433451
// was ... && isFullyDefined(pt, ForceDegree.flipBottom)
34443452
// but this prevents case blocks from implementing polymorphic partial functions,
34453453
// since we do not know the result parameter a priori. Have to wait until the
@@ -3508,10 +3516,12 @@ class Typer extends Namer
35083516
}
35093517
}
35103518

3519+
val treeTpe = tree.tpe
3520+
35113521
def tryUnsafeNullConver(fail: => Tree)(using Context): Tree =
35123522
// If explicitNulls and unsafeNulls are enabled, and
35133523
if ctx.mode.is(Mode.UnsafeNullConversion) && pt.isValueType &&
3514-
tree.tpe.isUnsafeConvertable(pt)
3524+
treeTpe.isUnsafeConvertable(pt)
35153525
then tree.cast(pt)
35163526
else fail
35173527

@@ -3533,17 +3543,23 @@ class Typer extends Namer
35333543
inContext(searchCtx) {
35343544
if ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType then
35353545
if pt.isRef(defn.AnyValClass) || pt.isRef(defn.ObjectClass) then
3536-
report.error(em"the result of an implicit conversion must be more specific than $pt", tree.srcPos)
3537-
def normalSearch =
3538-
searchTree(tree)(failure => tryUnsafeNullConver(cannotFind(failure)))
3539-
tree.tpe match {
3540-
case OrNull(tpe1) if ctx.mode.is(Mode.UnsafeNullConversion) =>
3541-
// If the type of the tree is nullable, and unsafeNullConversion is enabled,
3542-
// then we search the tree without the `Null` type first.
3543-
// If this fails, we search the original tree.
3544-
searchTree(tree.cast(tpe1)) { _ => normalSearch }
3545-
case _ =>
3546-
normalSearch
3546+
// We want to allow `null` to `AnyRef` if UnsafeNullConversion is enabled
3547+
if !(ctx.explicitNulls &&
3548+
treeTpe.isUnsafeConvertable(pt) &&
3549+
ctx.mode.is(Mode.UnsafeNullConversion)) then
3550+
report.error(em"the result of an implicit conversion must be more specific than $pt", tree.srcPos)
3551+
tree.cast(pt)
3552+
else
3553+
def normalSearch =
3554+
searchTree(tree)(failure => tryUnsafeNullConver(cannotFind(failure)))
3555+
treeTpe match {
3556+
case OrNull(tpe1) if ctx.mode.is(Mode.UnsafeNullConversion) =>
3557+
// If the type of the tree is nullable, and unsafeNullConversion is enabled,
3558+
// then we search the tree without the `Null` type first.
3559+
// If this fails, we search the original tree.
3560+
searchTree(tree.cast(tpe1)) { _ => normalSearch }
3561+
case _ =>
3562+
normalSearch
35473563
}
35483564
else tryUnsafeNullConver(recover(NoMatchingImplicits))
35493565
}

docs/docs/reference/other-new-features/explicit-nulls.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ Users can import `scala.language.unsafeNulls` to create such scopes, or use `-la
443443
3. convert `T1` to `T2` if `T1.stripAllNulls <:< T2.stripAllNulls` or `T1` is `Null` and `T2` has null value after erasure
444444
4. allow equality check between `T` and `T | Null`
445445
446+
Addtionally, `null` can be used as `AnyRef` (`Object`), which means you can select `.eq` or `.toString` on it.
447+
446448
The intention of this `unsafeNulls` is to give users a better migration path for explicit nulls. Projects for Scala 2 or regular dotty can try this by adding `-Yexplicit-nulls -language:unsafeNulls` to the compile options. A small number of manual modifications are expected (for example, some code relies on the fact of `Null <:< AnyRef`). To migrate to full explicit nulls in the future, `-language:unsafeNulls` can be dropped and add `import scala.language.unsafeNulls` only when needed.
447449
448450
```scala
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f = {
2+
val smap: Map[String, String] = ???
3+
val ss = smap.map { case (n, v) => (n, n + v) }
4+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
val s1: String = ???
2+
val s2: String | Null = ???
3+
4+
def f = {
5+
s1 eq s2 // error
6+
s2 eq s1 // error
7+
8+
s1 ne s2 // error
9+
s2 ne s1 // error
10+
11+
s1 eq null // error
12+
s2 eq null // error
13+
14+
null eq s1 // error
15+
null eq s2 // error
16+
}

0 commit comments

Comments
 (0)