Skip to content

Commit 300bf40

Browse files
committed
Add a special rule for CanEqualNull
1 parent 0181ee7 commit 300bf40

File tree

4 files changed

+72
-37
lines changed

4 files changed

+72
-37
lines changed

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -741,8 +741,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
741741
}
742742
compareTypeBounds
743743
case tp2: AnnotatedType if tp2.isRefining =>
744-
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || tp1.isBottomType) &&
745-
recur(tp1, tp2.parent)
744+
// `CanEqualNull` is a special refining annotation.
745+
// An annotated type is equivalent to the original type.
746+
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation)
747+
|| tp2.annot.matches(defn.CanEqualNullAnnot)
748+
|| tp1.isBottomType)
749+
&& recur(tp1, tp2.parent)
746750
case ClassInfo(pre2, cls2, _, _, _) =>
747751
def compareClassInfo = tp1 match {
748752
case ClassInfo(pre1, cls1, _, _, _) =>

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
168168
// val x: String = null.asInstanceOf[String]
169169
// if (x == null) {} // error: x is non-nullable
170170
// if (x.asInstanceOf[String|Null] == null) {} // ok
171-
if cls1 == defn.NullClass then cls1 == cls2
172-
else cls1 == defn.NothingClass || cls2 == defn.NothingClass
171+
cls1 == defn.NullClass && cls1 == cls2
173172
else if cls1 == defn.NullClass then
174173
cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass)
175174
else if cls2 == defn.NullClass then
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
11
package scala.annotation
22

3+
/** An annotation makes reference types comparable to `null` in explicit nulls.
4+
* `CanEqualNull` is a special refining annotation. An annotated type is equivalent to the original type.
5+
*
6+
* For example:
7+
* ```scala
8+
* val s1: String = ???
9+
* s1 == null // error
10+
* val s2: String @CanEqualNull = ???
11+
* s2 == null // ok
12+
*
13+
* // String =:= String @CanEqualNull
14+
* val s3: String = s2
15+
* val s4: String @CanEqualNull = s1
16+
*
17+
* val ss: Array[String @CanEqualNull] = ??
18+
* ss.map(_ == null)
19+
* ```
20+
*/
321
final class CanEqualNull extends RefiningAnnotation
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,55 @@
11
import scala.language.unsafeJavaReturn
2+
3+
import scala.annotation.CanEqualNull
24
import java.{util => ju}
35

46
class S {
57

68
def test[T <: AnyRef](jc: JC) = {
7-
// val a: Int = jc.a
8-
9-
// val b = jc.b // it returns String @CanEqualNull
10-
// val b2: String = b
11-
// val b3: String = jc.b
12-
// val bb = jc.b == null // it's ok to compare String @CanEqualNull with Null
13-
// val btl = jc.b.trim().length() // String @CanEqualNull is just String, unsafe selecting
14-
15-
// val c = jc.c
16-
// val cl = c.length
17-
// val c2: Array[String] = c
18-
val c3: Array[String] = jc.c
19-
// val c4: Array[Int] = c.map(_.length())
20-
21-
// val f1: Int = jc.f1()
22-
// val f2: Array[Int] = jc.f2()
23-
// val f2n = jc.f2() == null
24-
25-
// val g1: String = jc.g1()
26-
// val g1n = jc.g1() == null
27-
// val g1tl = jc.g1().trim().length()
28-
29-
// val g2h: ju.List[String] = jc.g2()
30-
31-
// val g3: Array[String] = jc.g3()
32-
// val g3n = jc.g3() == null
33-
// val g3m: Array[Boolean] = jc.g3().map(_ == null)
34-
35-
// val h1: T = jc.h1[T]()
36-
37-
// val h2: ju.List[T] = jc.h2()
38-
39-
// val h3: Array[T] = jc.h3()
9+
val a: Int = jc.a
10+
11+
val b = jc.b // it returns String @CanEqualNull
12+
val b2: String = b
13+
val b3: String @CanEqualNull = jc.b
14+
val b4: String = jc.b
15+
val bb = jc.b == null // it's ok to compare String @CanEqualNull with Null
16+
val btl = jc.b.trim().length() // String @CanEqualNull is just String, unsafe selecting
17+
18+
val c = jc.c
19+
val cl = c.length
20+
val c2: Array[String] = c
21+
val c3: Array[String @CanEqualNull] @CanEqualNull = jc.c
22+
val c4: Array[String] = jc.c
23+
val cml: Array[Int] = c.map(_.length())
24+
25+
val f1: Int = jc.f1()
26+
27+
val f21: Array[Int] @CanEqualNull = jc.f2()
28+
val f22: Array[Int] = jc.f2()
29+
val f2n = jc.f2() == null
30+
31+
val g11: String @CanEqualNull = jc.g1()
32+
val g12: String = jc.g1()
33+
val g1n = jc.g1() == null
34+
val g1tl = jc.g1().trim().length()
35+
36+
val g21: ju.List[String] @CanEqualNull = jc.g2()
37+
val g22: ju.List[String] = jc.g2()
38+
39+
val g31: Array[String @CanEqualNull] @CanEqualNull = jc.g3()
40+
val g32: Array[String] = jc.g3()
41+
val g3n = jc.g3() == null
42+
val g3m: Array[Boolean] = jc.g3().map(_ == null)
43+
44+
val h11: T @CanEqualNull = jc.h1[T]()
45+
val h12: T = jc.h1[T]()
46+
val h1n = jc.h1[T]() == null
47+
48+
val h21: ju.List[T] @CanEqualNull = jc.h2[T]()
49+
val h22: ju.List[T] = jc.h2[T]()
50+
51+
val h31: Array[T @CanEqualNull] @CanEqualNull = jc.h3[T]()
52+
val h32: Array[T] = jc.h3[T]()
53+
val h3m = jc.h3[T]().map(_ == null)
4054
}
4155
}

0 commit comments

Comments
 (0)