Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Implement SIP-67: strictEquality pattern matching (fixes #22732) #23803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mberndt123 wants to merge 13 commits into scala:main
base: main
Choose a base branch
Loading
from mberndt123:sip-67-strict-equality-improvements
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
5778bea
Implement SIP-67: strictEquality pattern matching (Github issue #22732)
mberndt123 Aug 24, 2025
0a565d0
use braceless syntax in SIP67Tests
mberndt123 Aug 26, 2025
d433ebe
change SIP-67 implementation based on @odersky's review
mberndt123 Aug 31, 2025
4f7a9a3
hide SIP-67 behaviour behind experimental feature flag
mberndt123 Aug 31, 2025
56f11c2
remove excess parens
mberndt123 Aug 31, 2025
6570fb2
minor refactoring of SIP67Tests
mberndt123 Sep 1, 2025
51b34cb
whitespace fix
mberndt123 Sep 15, 2025
79fc91d
add `strictEqualityPatternMatching` in `scala.language.experimental`
mberndt123 Sep 15, 2025
45e188c
Merge branch 'main' into sip-67-strict-equality-improvements
mberndt123 Sep 16, 2025
f23deba
address review comments
mberndt123 Sep 25, 2025
341e927
Merge branch 'main' into sip-67-strict-equality-improvements
mberndt123 Sep 26, 2025
684663a
comments
mberndt123 Sep 26, 2025
5f62655
Merge branch 'main' into sip-67-strict-equality-improvements
mberndt123 Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ object Feature:

val dependent = experimental("dependent")
val erasedDefinitions = experimental("erasedDefinitions")
val strictEqualityPatternMatching = experimental("strictEqualityPatternMatching")
val symbolLiterals = deprecated("symbolLiterals")
val saferExceptions = experimental("saferExceptions")
val pureFunctions = experimental("pureFunctions")
Expand Down Expand Up @@ -58,6 +59,7 @@ object Feature:
(scala2macros, "Allow Scala 2 macros"),
(dependent, "Allow dependent method types"),
(erasedDefinitions, "Allow erased definitions"),
(strictEqualityPatternMatching, "relaxed CanEqual checks for ADT pattern matching"),
(symbolLiterals, "Allow symbol literals"),
(saferExceptions, "Enable safer exceptions"),
(pureFunctions, "Enable pure functions for capture checking"),
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,7 @@ trait Applications extends Compatibility {
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
val op = fn.symbol
if (op == defn.Any_== || op == defn.Any_!=)
checkCanEqual(left.tpe.widen, right.tpe.widen, app.span)
checkCanEqual(left, right.tpe.widen, app.span)
case _ =>
}
app
Expand Down
24 changes: 18 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ object Implicits:
def strictEquality(using Context): Boolean =
ctx.mode.is(Mode.StrictEquality) || Feature.enabled(nme.strictEquality)

def strictEqualityPatternMatching(using Context): Boolean =
Feature.enabled(Feature.strictEqualityPatternMatching)


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

ltp.isError
|| rtp.isError
|| !strictEquality && (ltp <:< lift(rtp) || rtp <:< lift(ltp))
|| locally:
if strictEquality then
strictEqualityPatternMatching &&
(leftTree.symbol.isAllOf(Flags.EnumValue) || leftTree.symbol.isAllOf(Flags.Module | Flags.Case)) &&
ltp <:< lift(rtp)
else
ltp <:< lift(rtp) || rtp <:< lift(ltp)
}

/** Check that equality tests between types `ltp` and `rtp` make sense */
def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit =
if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) {
/** Check that equality tests between types `ltp` and `left.tpe` make sense.
* `left` is required to check for the condition for language.strictEqualityPatternMatching.
*/
def checkCanEqual(left: Tree, rtp: Type, span: Span)(using Context): Unit =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ltp is not part of the API anymore, so the doc comment needs to be updated.

Copy link
Author

@mberndt123 mberndt123 Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've updated the comment

val ltp = left.tpe.widen
if !ctx.isAfterTyper && !assumedCanEqual(ltp, rtp, left) then
val res = implicitArgTree(defn.CanEqualClass.typeRef.appliedTo(ltp, rtp), span)
implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}")
}

object hasSkolem extends TreeAccumulator[Boolean]:
def apply(x: Boolean, tree: Tree)(using Context): Boolean =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ReTyper.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking

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

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

Expand Down
44 changes: 44 additions & 0 deletions compiler/test/dotty/tools/dotc/typer/SIP67Tests.scala
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dotty.tools.dotc.typer

import dotty.tools.DottyTest
import dotty.tools.dotc.core.Contexts.*

import org.junit.Test
import org.junit.Assert.fail

class SIP67Tests extends DottyTest:

private def checkNoErrors(source: String): Unit =
val ctx = checkCompile("typer", source)((_, _) => ())
if ctx.reporter.hasErrors then
fail("Unexpected compilation errors were reported")

@Test
def sip67test1: Unit =
checkNoErrors:
"""
import scala.language.strictEquality
import scala.language.experimental.strictEqualityPatternMatching
enum Foo:
case Bar

val _ =
(??? : Foo) match
case Foo.Bar =>
"""
@Test
def sip67test2: Unit =
checkNoErrors:
"""
import scala.language.strictEquality
import scala.language.experimental.strictEqualityPatternMatching

sealed trait Foo

object Foo:
case object Bar extends Foo

val _ =
(??? : Foo) match
case Foo.Bar =>
"""
7 changes: 7 additions & 0 deletions library/src/scala/language.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ object language {
@compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements")
object erasedDefinitions

/** Experimental support for relaxed CanEqual checks for ADT pattern matching
*
* @see [[https://github.com/scala/improvement-proposals/pull/97]]
*/
@compileTimeOnly("`strictEqualityPatternMatching` can only be used at compile time in import statements")
object strictEqualityPatternMatching

/** Experimental support for using indentation for arguments
*/
@compileTimeOnly("`fewerBraces` can only be used at compile time in import statements")
Expand Down
7 changes: 7 additions & 0 deletions library/src/scala/runtime/stdLibPatches/language.scala
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ object language:
@compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements")
object erasedDefinitions

/** Experimental support for relaxed CanEqual checks for ADT pattern matching
*
* @see [[https://github.com/scala/improvement-proposals/pull/97]]
*/
@compileTimeOnly("`strictEqualityPatternMatching` can only be used at compile time in import statements")
object strictEqualityPatternMatching

/** Experimental support for using indentation for arguments
*/
@compileTimeOnly("`fewerBraces` can only be used at compile time in import statements")
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /