Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions library/src/scala/NamedTuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import compiletime.ops.boolean.*
import collection.immutable.{SeqMap, ListMap}

import language.experimental.captureChecking
import scala.annotation.experimental

object NamedTuple:

Expand All @@ -16,6 +17,16 @@ object NamedTuple:
/** A type which is a supertype of all named tuples. */
opaque type AnyNamedTuple = Any

// This formulation fixes the name types, following the standard established in this file.
// Alternatively you could require additional evidence and compare `N1` and `N2` types.
// However if this is explored, you must not use `CanEqual[N1, N2]` because `CanEqual`
// for Strings widens singletons.
// Comparing different name types with `=:=` could also be possible.
@experimental
given namedTupleCanEqual: [N <: Tuple, V1 <: Tuple, V2 <: Tuple]
=> (eq: CanEqual[V1, V2])
=> CanEqual[NamedTuple[N, V1], NamedTuple[N, V2]] = CanEqual.derived

def apply[N <: Tuple, V <: Tuple](x: V): NamedTuple[N, V] = x

def unapply[N <: Tuple, V <: Tuple](x: NamedTuple[N, V]): Some[V] = Some(x)
Expand Down
2 changes: 2 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ object MiMaFilters {
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.caps.package#package.freeze"),
// scala/scala3#24545 / scala/scala3#24788
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.unchecked.uncheckedOverride"),
// new feature: CanEqual support for NamedTuple
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.NamedTuple.namedTupleCanEqual"),
),

)
Expand Down
18 changes: 18 additions & 0 deletions tests/neg/named-tuples-strictEquality.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- [E172] Type Error: tests/neg/named-tuples-strictEquality.scala:9:20 -------------------------------------------------
9 | val b2: Boolean = u == v // error
| ^^^^^^
|Values of types (name : String, age : Int) and (name : String, birthYear : Int) cannot be compared with == or !=.
|I found:
|
| NamedTuple.namedTupleCanEqual[N, V1, V2]
|
|But given instance namedTupleCanEqual in object NamedTuple does not match type CanEqual[(name : String, age : Int), (name : String, birthYear : Int)].
-- [E172] Type Error: tests/neg/named-tuples-strictEquality.scala:15:11 ------------------------------------------------
15 | case ScalaBook => true // error
| ^^^^^^^^^
|Values of types (name : String, published : Int) and (name : String, age : Int) cannot be compared with == or !=.
|I found:
|
| NamedTuple.namedTupleCanEqual[N, V1, V2]
|
|But given instance namedTupleCanEqual in object NamedTuple does not match type CanEqual[(name : String, published : Int), (name : String, age : Int)].
17 changes: 17 additions & 0 deletions tests/neg/named-tuples-strictEquality.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//> using options -language:strictEquality

object Test:

val u: (name: String, age: Int) = (name = "Bob", age = 25)
val v: (name: String, birthYear: Int) = (name = "Charlie", birthYear = 1990)

val b1: Boolean = u.toTuple == v.toTuple // ok
val b2: Boolean = u == v // error

val ScalaBook = (name = "Programming in Scala, 5th edition", published = 2021)

def hasScalaBook(books: IterableOnce[(name: String, age: Int)]): Boolean =
books.exists {
case ScalaBook => true // error
case _ => false
}
3 changes: 3 additions & 0 deletions tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ val experimentalDefinitionInLibrary = Set(

// New feature: Erased trait
"scala.compiletime.Erased",

// New API: Multiversal equality for Named Tuples
"scala.NamedTuple$.namedTupleCanEqual",
)


Expand Down
24 changes: 24 additions & 0 deletions tests/run/named-tuples-strictEquality.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//> using options -language:strictEquality

@main def Test =

val u: (name: String, age: Int) = (name = "Bob", age = 25)
val v: (name: String, birthYear: Int) = (name = "Charlie", birthYear = 1990)

val ScalaBook = (name = "Programming in Scala, 5th edition", published = 2021)
val books = Map(
ScalaBook,
(name = "Hands on Scala, 2nd edition", published = 2026),
)

assert(u.toTuple != v.toTuple)
assert(u == u)
assert(v == v)

def hasScalaBook(books: IterableOnce[(name: String, published: Int)]): Boolean =
books.exists {
case ScalaBook => true
case _ => false
}

assert(hasScalaBook(books))
Loading