Skip to content

Commit 172687a

Browse files
authored
Implement SIP-67: strictEquality pattern matching (fixes #22732) (#23803)
Hi, this is my attempt at implementing the [SIP-67](https://github.com/mberndt123/improvement-proposals/blob/strictEquality-pattern-matching/content/strict-equality-pattern-matching.md) specification. Thanks to @noti0na1 and @dwijnand for the hints you gave in #22732. Sorry it took so long, please let me know what you think.
2 parents 0eee3b0 + 5f62655 commit 172687a

File tree

7 files changed

+80
-8
lines changed

7 files changed

+80
-8
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ object Feature:
2828

2929
val dependent = experimental("dependent")
3030
val erasedDefinitions = experimental("erasedDefinitions")
31+
val strictEqualityPatternMatching = experimental("strictEqualityPatternMatching")
3132
val symbolLiterals = deprecated("symbolLiterals")
3233
val saferExceptions = experimental("saferExceptions")
3334
val pureFunctions = experimental("pureFunctions")
@@ -59,6 +60,7 @@ object Feature:
5960
(scala2macros, "Allow Scala 2 macros"),
6061
(dependent, "Allow dependent method types"),
6162
(erasedDefinitions, "Allow erased definitions"),
63+
(strictEqualityPatternMatching, "relaxed CanEqual checks for ADT pattern matching"),
6264
(symbolLiterals, "Allow symbol literals"),
6365
(saferExceptions, "Enable safer exceptions"),
6466
(pureFunctions, "Enable pure functions for capture checking"),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1365,7 +1365,7 @@ trait Applications extends Compatibility {
13651365
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
13661366
val op = fn.symbol
13671367
if (op == defn.Any_== || op == defn.Any_!=)
1368-
checkCanEqual(left.tpe.widen, right.tpe.widen, app.span)
1368+
checkCanEqual(left, right.tpe.widen, app.span)
13691369
case _ =>
13701370
}
13711371
app

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ object Implicits:
8484
def strictEquality(using Context): Boolean =
8585
ctx.mode.is(Mode.StrictEquality) || Feature.enabled(nme.strictEquality)
8686

87+
def strictEqualityPatternMatching(using Context): Boolean =
88+
Feature.enabled(Feature.strictEqualityPatternMatching)
89+
8790

8891
/** A common base class of contextual implicits and of-type implicits which
8992
* represents a set of references to implicit definitions.
@@ -1038,8 +1041,9 @@ trait Implicits:
10381041
* - if one of T, U is an error type, or
10391042
* - if one of T, U is a subtype of the lifted version of the other,
10401043
* unless strict equality is set.
1044+
* - if strictEqualityPatternMatching is set and the necessary conditions are met
10411045
*/
1042-
def assumedCanEqual(ltp: Type, rtp: Type)(using Context) = {
1046+
def assumedCanEqual(ltp: Type, rtp: Type, leftTree: Tree = EmptyTree)(using Context): Boolean = {
10431047
// Map all non-opaque abstract types to their upper bound.
10441048
// This is done to check whether such types might plausibly be comparable to each other.
10451049
val lift = new TypeMap {
@@ -1062,15 +1066,23 @@ trait Implicits:
10621066

10631067
ltp.isError
10641068
|| rtp.isError
1065-
|| !strictEquality && (ltp <:< lift(rtp) || rtp <:< lift(ltp))
1069+
|| locally:
1070+
if strictEquality then
1071+
strictEqualityPatternMatching &&
1072+
(leftTree.symbol.isAllOf(Flags.EnumValue) || leftTree.symbol.isAllOf(Flags.Module | Flags.Case)) &&
1073+
ltp <:< lift(rtp)
1074+
else
1075+
ltp <:< lift(rtp) || rtp <:< lift(ltp)
10661076
}
10671077

1068-
/** Check that equality tests between types `ltp` and `rtp` make sense */
1069-
def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit =
1070-
if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) {
1078+
/** Check that equality tests between types `ltp` and `left.tpe` make sense.
1079+
* `left` is required to check for the condition for language.strictEqualityPatternMatching.
1080+
*/
1081+
def checkCanEqual(left: Tree, rtp: Type, span: Span)(using Context): Unit =
1082+
val ltp = left.tpe.widen
1083+
if !ctx.isAfterTyper && !assumedCanEqual(ltp, rtp, left) then
10711084
val res = implicitArgTree(defn.CanEqualClass.typeRef.appliedTo(ltp, rtp), span)
10721085
implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}")
1073-
}
10741086

10751087
object hasSkolem extends TreeAccumulator[Boolean]:
10761088
def apply(x: Boolean, tree: Tree)(using Context): Boolean =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
175175

176176
override def inferView(from: Tree, to: Type)(using Context): Implicits.SearchResult =
177177
Implicits.NoMatchingImplicitsFailure
178-
override def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit = ()
178+
override def checkCanEqual(left: Tree, rtp: Type, span: Span)(using Context): Unit = ()
179179

180180
override def widenEnumCase(tree: Tree, pt: Type)(using Context): Tree = tree
181181

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package dotty.tools.dotc.typer
2+
3+
import dotty.tools.DottyTest
4+
import dotty.tools.dotc.core.Contexts.*
5+
6+
import org.junit.Test
7+
import org.junit.Assert.fail
8+
9+
class SIP67Tests extends DottyTest:
10+
11+
private def checkNoErrors(source: String): Unit =
12+
val ctx = checkCompile("typer", source)((_, _) => ())
13+
if ctx.reporter.hasErrors then
14+
fail("Unexpected compilation errors were reported")
15+
16+
@Test
17+
def sip67test1: Unit =
18+
checkNoErrors:
19+
"""
20+
import scala.language.strictEquality
21+
import scala.language.experimental.strictEqualityPatternMatching
22+
enum Foo:
23+
case Bar
24+
25+
val _ =
26+
(??? : Foo) match
27+
case Foo.Bar =>
28+
"""
29+
@Test
30+
def sip67test2: Unit =
31+
checkNoErrors:
32+
"""
33+
import scala.language.strictEquality
34+
import scala.language.experimental.strictEqualityPatternMatching
35+
36+
sealed trait Foo
37+
38+
object Foo:
39+
case object Bar extends Foo
40+
41+
val _ =
42+
(??? : Foo) match
43+
case Foo.Bar =>
44+
"""

library/src/scala/language.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ object language {
243243
@compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements")
244244
object erasedDefinitions
245245

246+
/** Experimental support for relaxed CanEqual checks for ADT pattern matching
247+
*
248+
* @see [[https://github.com/scala/improvement-proposals/pull/97]]
249+
*/
250+
@compileTimeOnly("`strictEqualityPatternMatching` can only be used at compile time in import statements")
251+
object strictEqualityPatternMatching
252+
246253
/** Experimental support for using indentation for arguments
247254
*/
248255
@compileTimeOnly("`fewerBraces` can only be used at compile time in import statements")

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ object language:
5050
@compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements")
5151
object erasedDefinitions
5252

53+
/** Experimental support for relaxed CanEqual checks for ADT pattern matching
54+
*
55+
* @see [[https://github.com/scala/improvement-proposals/pull/97]]
56+
*/
57+
@compileTimeOnly("`strictEqualityPatternMatching` can only be used at compile time in import statements")
58+
object strictEqualityPatternMatching
59+
5360
/** Experimental support for using indentation for arguments
5461
*/
5562
@compileTimeOnly("`fewerBraces` can only be used at compile time in import statements")

0 commit comments

Comments
 (0)