From 51dee4be21999890d485286c1a04f7c658365826 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 12:18:44 +0100 Subject: [PATCH 1/7] Use `CanEqual` as an alias of `Eql` --- .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../src/dotty/tools/dotc/core/Names.scala | 2 +- .../src/dotty/tools/dotc/core/Symbols.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/semanticdb/Language.scala | 2 +- .../dotty/tools/dotc/semanticdb/Range.scala | 2 +- .../dotty/tools/dotc/semanticdb/Scala3.scala | 2 +- .../dotty/tools/dotc/semanticdb/Schema.scala | 2 +- .../dotc/semanticdb/SymbolInformation.scala | 6 +-- .../dotc/semanticdb/SymbolOccurrence.scala | 4 +- .../tools/dotc/semanticdb/TextDocument.scala | 2 +- .../tools/dotc/semanticdb/TextDocuments.scala | 2 +- .../dotty/tools/dotc/util/SourceFile.scala | 2 +- library/src/scala/Eql.scala | 4 ++ tests/neg/EqualityStrawman1.scala | 18 ++++----- tests/neg/derive-eq.scala | 17 ++++---- tests/neg/enums.scala | 2 +- tests/neg/eql.scala | 2 +- tests/neg/equality.scala | 18 ++++----- tests/neg/i3976.scala | 8 ++-- tests/neg/i4470b.scala | 4 +- tests/neg/i5546.scala | 4 +- tests/pending/neg/EqualityStrawman2.scala | 40 +++++++++---------- tests/pos/derive-eq.scala | 14 +++---- tests/pos/multiversal.scala | 4 +- tests/run/i7287/Enum_1.scala | 2 +- .../testsuite/compiler/EnumTestScala3.scala | 8 ++-- 27 files changed, 90 insertions(+), 87 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index e219aefa5f47..870a84fb6d2e 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -71,7 +71,7 @@ import collection.mutable.ListBuffer */ object Denotations { - implicit def eqDenotation: Eql[Denotation, Denotation] = Eql.derived + implicit def eqDenotation: CanEqual[Denotation, Denotation] = CanEqual.derived /** A PreDenotation represents a group of single denotations or a single multi-denotation * It is used as an optimization to avoid forming MultiDenotations too eagerly. diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 0ebf3190d5af..acfbd411a0ce 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -32,7 +32,7 @@ object Names { * in a name table. A derived term name adds a tag, and possibly a number * or a further simple name to some other name. */ - abstract class Name extends Designator, Showable derives Eql { + abstract class Name extends Designator, Showable derives CanEqual { /** A type for names of the same kind as this name */ type ThisName <: Name diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 601549d7ebce..3388e5ef1bf5 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -37,7 +37,7 @@ import config.Printers.typr object Symbols { - implicit def eqSymbol: Eql[Symbol, Symbol] = Eql.derived + implicit def eqSymbol: CanEqual[Symbol, Symbol] = CanEqual.derived /** Tree attachment containing the identifiers in a tree as a sorted array */ val Ids: Property.Key[Array[String]] = new Property.Key diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0fed43335a52..49faeff87ac4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -46,7 +46,7 @@ object Types { @sharable private var nextId = 0 - implicit def eqType: Eql[Type, Type] = Eql.derived + implicit def eqType: CanEqual[Type, Type] = CanEqual.derived /** Main class representing types. * diff --git a/compiler/src/dotty/tools/dotc/semanticdb/Language.scala b/compiler/src/dotty/tools/dotc/semanticdb/Language.scala index eeaac6d4e643..cde313b6ec6e 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/Language.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/Language.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc.semanticdb import dotty.tools.dotc.semanticdb.internal._ -sealed trait Language(val value: Int) extends SemanticdbEnum derives Eql +sealed trait Language(val value: Int) extends SemanticdbEnum derives CanEqual object Language { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/Range.scala b/compiler/src/dotty/tools/dotc/semanticdb/Range.scala index ad4249d0b53c..61d9d7f27c74 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/Range.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/Range.scala @@ -12,7 +12,7 @@ final case class Range( startCharacter: Int, endLine: Int, endCharacter: Int -) extends SemanticdbMessage[Range] derives Eql { +) extends SemanticdbMessage[Range] derives CanEqual { @sharable private var __serializedSizeCachedValue: Int = 0 private def __computeSerializedValue(): Int = { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala b/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala index 74547a2a71a7..46584dbf0097 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala @@ -25,7 +25,7 @@ object Scala3: private val WILDCARDTypeName = nme.WILDCARD.toTypeName - enum SymbolKind derives Eql: + enum SymbolKind derives CanEqual: kind => case Val, Var, Setter, Abstract diff --git a/compiler/src/dotty/tools/dotc/semanticdb/Schema.scala b/compiler/src/dotty/tools/dotc/semanticdb/Schema.scala index e23f2cccca6d..33b236d7ac5a 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/Schema.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/Schema.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc.semanticdb import dotty.tools.dotc.semanticdb.internal._ -sealed trait Schema(val value: Int) extends SemanticdbEnum derives Eql +sealed trait Schema(val value: Int) extends SemanticdbEnum derives CanEqual object Schema { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SymbolInformation.scala b/compiler/src/dotty/tools/dotc/semanticdb/SymbolInformation.scala index 5a5f2c0b4e1e..b2508732232a 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SymbolInformation.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SymbolInformation.scala @@ -7,7 +7,7 @@ object SymbolInformation { val defaultInstance = SymbolInformation("", Language.UNKNOWN_LANGUAGE, SymbolInformation.Kind.UNKNOWN_KIND, 0, "") - sealed trait Kind(val value: Int) extends SemanticdbEnum derives Eql { + sealed trait Kind(val value: Int) extends SemanticdbEnum derives CanEqual { def isUnknownKind: Boolean = this == Kind.UNKNOWN_KIND def isLocal: Boolean = this == Kind.LOCAL def isField: Boolean = this == Kind.FIELD @@ -67,7 +67,7 @@ object SymbolInformation { } } - sealed trait Property(val value: Int) extends SemanticdbEnum derives Eql { + sealed trait Property(val value: Int) extends SemanticdbEnum derives CanEqual { def isUnknownProperty: Boolean = this == Property.UNKNOWN_PROPERTY def isAbstract: Boolean = this == Property.ABSTRACT def isFinal: Boolean = this == Property.FINAL @@ -131,7 +131,7 @@ final case class SymbolInformation( kind: SymbolInformation.Kind, properties: Int, displayName: String -) extends SemanticdbMessage[SymbolInformation] derives Eql { +) extends SemanticdbMessage[SymbolInformation] derives CanEqual { @sharable private var __serializedSizeCachedValue: Int = 0 private def __computeSerializedValue(): Int = { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SymbolOccurrence.scala b/compiler/src/dotty/tools/dotc/semanticdb/SymbolOccurrence.scala index 594e265fda4d..3123a54edd56 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SymbolOccurrence.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SymbolOccurrence.scala @@ -5,7 +5,7 @@ import scala.annotation.internal.sharable object SymbolOccurrence { - sealed trait Role(val value: Int) extends SemanticdbEnum derives Eql { + sealed trait Role(val value: Int) extends SemanticdbEnum derives CanEqual { def isDefinition: Boolean = this == Role.DEFINITION def isReference: Boolean = this == Role.REFERENCE } @@ -33,7 +33,7 @@ final case class SymbolOccurrence( symbol: String, range: Option[Range], role: SymbolOccurrence.Role -) extends SemanticdbMessage[SymbolOccurrence] derives Eql { +) extends SemanticdbMessage[SymbolOccurrence] derives CanEqual { @sharable private var __serializedSizeCachedValue: Int = 0 private def __computeSerializedValue(): Int = { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TextDocument.scala b/compiler/src/dotty/tools/dotc/semanticdb/TextDocument.scala index b81632bdd12f..05801340f955 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TextDocument.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TextDocument.scala @@ -15,7 +15,7 @@ final case class TextDocument( md5: String, symbols: Seq[SymbolInformation], occurrences: Seq[SymbolOccurrence] -) extends SemanticdbMessage[TextDocument] derives Eql { +) extends SemanticdbMessage[TextDocument] derives CanEqual { @sharable private var __serializedSizeCachedValue: Int = 0 private def __computeSerializedValue(): Int = { diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TextDocuments.scala b/compiler/src/dotty/tools/dotc/semanticdb/TextDocuments.scala index 4e56641fe3c3..68d667f27f74 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TextDocuments.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TextDocuments.scala @@ -12,7 +12,7 @@ object TextDocuments { } val defaultInstance: TextDocuments = TextDocuments(Nil) } -final case class TextDocuments(documents: Seq[TextDocument]) extends SemanticdbMessage[TextDocuments] derives Eql { +final case class TextDocuments(documents: Seq[TextDocument]) extends SemanticdbMessage[TextDocuments] derives CanEqual { @sharable private var __serializedSizeCachedValue: Int = 0 private def __computeSerializedValue(): Int = { diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 501d9d78e7e6..9f5e2f8d1a7e 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -183,7 +183,7 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends override def toString: String = file.toString } object SourceFile { - implicit def eqSource: Eql[SourceFile, SourceFile] = Eql.derived + implicit def eqSource: CanEqual[SourceFile, SourceFile] = CanEqual.derived implicit def fromContext(using Context): SourceFile = ctx.source diff --git a/library/src/scala/Eql.scala b/library/src/scala/Eql.scala index 2def80f9ec12..2874cc38a84b 100644 --- a/library/src/scala/Eql.scala +++ b/library/src/scala/Eql.scala @@ -7,6 +7,10 @@ import scala.collection.{Seq, Set} @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") sealed trait Eql[-L, -R] +type CanEqual[L, R] = Eql[L, R] + +lazy val CanEqual = Eql + /** Companion object containing a few universally known `Eql` instances. * Eql instances involving primitive types or the Null type are handled directly in * the compiler (see Implicits.synthesizedEql), so they are not included here. diff --git a/tests/neg/EqualityStrawman1.scala b/tests/neg/EqualityStrawman1.scala index 395e578740ad..494daa13069a 100644 --- a/tests/neg/EqualityStrawman1.scala +++ b/tests/neg/EqualityStrawman1.scala @@ -3,13 +3,13 @@ import annotation.implicitNotFound object EqualityStrawman1 { - trait Eql[-T] + trait CanEqual[-T] @implicitNotFound("cannot compare value of type ${T} with a value outside its equality class") trait Impossible[T] - object Eql { - object derived extends Eql[Any] + object CanEqual { + object derived extends CanEqual[Any] } trait Base { @@ -18,7 +18,7 @@ object EqualityStrawman1 { } trait CondEquals extends Base { - def === [T >: this.type <: CondEquals](other: T)(implicit ce: Eql[T]): Boolean = this.equals(other) + def === [T >: this.type <: CondEquals](other: T)(implicit ce: CanEqual[T]): Boolean = this.equals(other) def === [T](other: T)(implicit ce: Impossible[T]): Boolean = ??? } @@ -34,11 +34,11 @@ object EqualityStrawman1 { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - implicit def eqStr: Eql[Str] = Eql.derived - //implicit def eqNum: Eql[Num] = Eql.derived - implicit def eqOption[T: Eql]: Eql[Option[T]] = Eql.derived + implicit def eqStr: CanEqual[Str] = CanEqual.derived + //implicit def eqNum: CanEqual[Num] = CanEqual.derived + implicit def eqOption[T: CanEqual]: CanEqual[Option[T]] = CanEqual.derived - implicit def eqEq[T <: Equals[T]]: Eql[T] = Eql.derived + implicit def eqEq[T <: Equals[T]]: CanEqual[T] = CanEqual.derived def main(args: Array[String]): Unit = { val x = Str("abc") @@ -59,7 +59,7 @@ object EqualityStrawman1 { None === z - def ddistinct[T <: Base: Eql](xs: List[T]): List[T] = xs match { + def ddistinct[T <: Base: CanEqual](xs: List[T]): List[T] = xs match { case Nil => Nil case x :: xs => x :: xs.filterNot(x === _) } diff --git a/tests/neg/derive-eq.scala b/tests/neg/derive-eq.scala index 1fe0b1a66227..fa73cf63a5bf 100644 --- a/tests/neg/derive-eq.scala +++ b/tests/neg/derive-eq.scala @@ -1,21 +1,20 @@ -case class One() derives Eql -case class Two() derives Eql +case class One() derives CanEqual +case class Two() derives CanEqual -given Eql[One, Two] = Eql.derived +given CanEqual[One, Two] = CanEqual.derived -enum Lst[T] derives Eql { +enum Lst[T] derives CanEqual { case Cons(x: T, xs: Lst[T]) case Nil() } -case class Triple[S, T, U]() derives Eql - +case class Triple[S, T, U]() derives CanEqual object Test extends App { - implicitly[Eql[Lst[Lst[One]], Lst[Lst[Two]]]] - implicitly[Eql[Triple[One, One, One], - Triple[Two, Two, Two]]] + implicitly[CanEqual[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[CanEqual[Triple[One, One, One], + Triple[Two, Two, Two]]] val x: Triple[List[One], One, Two] = ??? val y: Triple[List[Two], One, Two] = ??? diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index bb668a1703a7..a41c81bce3df 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -35,7 +35,7 @@ enum Captured[T] { case Case3 extends Captured[T] // error: illegal reference to type parameter T from enum case } -enum Option[+T] derives Eql { +enum Option[+T] derives CanEqual { case Some[T](x: T) extends Option[T] case None } diff --git a/tests/neg/eql.scala b/tests/neg/eql.scala index 04a317e6d9ea..78e7434160c9 100644 --- a/tests/neg/eql.scala +++ b/tests/neg/eql.scala @@ -1,7 +1,7 @@ object lst: opaque type Lst[+T] = Any object Lst: - given lstEql[T, U] as Eql[Lst[T], Lst[U]] = Eql.derived + given lstCanEqual[T, U] as CanEqual[Lst[T], Lst[U]] = CanEqual.derived val Empty: Lst[Nothing] = ??? end lst diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 0147e9c61cdf..84c070f81bd2 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -10,16 +10,16 @@ object equality { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - implicit def eqStr: Eql[Str, Str] = Eql.derived - implicit def eqNum: Eql[Num, Num] = Eql.derived - implicit def eqOption[T, U](implicit e: Eql[T, U]): Eql[Option[T], Option[U]] = Eql.derived + implicit def eqStr: CanEqual[Str, Str] = CanEqual.derived + implicit def eqNum: CanEqual[Num, Num] = CanEqual.derived + implicit def eqOption[T, U](implicit e: CanEqual[T, U]): CanEqual[Option[T], Option[U]] = CanEqual.derived /* - implicit def eqString: Eql[String, String] = Eql.derived - implicit def eqInt: Eql[Int, Int] = Eql.derived - implicit def eqNumber: Eql[Number, Number] = Eql.derived - implicit def eqIntNumber: Eql[Int, Number] = Eql.derived - implicit def eqNumberInt: Eql[Number, Int] = Eql.derived + implicit def eqString: CanEqual[String, String] = CanEqual.derived + implicit def eqInt: CanEqual[Int, Int] = CanEqual.derived + implicit def eqNumber: CanEqual[Number, Number] = CanEqual.derived + implicit def eqIntNumber: CanEqual[Int, Number] = CanEqual.derived + implicit def eqNumberInt: CanEqual[Number, Int] = CanEqual.derived */ def main(args: Array[String]): Unit = { Some(Other(3)) == None @@ -60,7 +60,7 @@ object equality { 1 == null // error - class Fruit derives Eql + class Fruit derives CanEqual class Apple extends Fruit class Pear extends Fruit diff --git a/tests/neg/i3976.scala b/tests/neg/i3976.scala index 52429c3bbeb9..7ec64b6ff23f 100644 --- a/tests/neg/i3976.scala +++ b/tests/neg/i3976.scala @@ -1,5 +1,5 @@ object Test { - enum Hoge[F[_]] derives Eql { + enum Hoge[F[_]] derives CanEqual { case A extends Hoge[List] case B extends Hoge[[X] =>> String] } @@ -8,7 +8,7 @@ object Test { A == A A == (B: Hoge[_]) - A == B // should be error: cannot be compared, needs proper type class drivation of `Eql` to get there. + A == B // should be error: cannot be compared, needs proper type class drivation of `CanEqual` to get there. class C @@ -18,7 +18,7 @@ object Test { } object Test2 { - enum Hoge[F[G[_]]] derives Eql { + enum Hoge[F[G[_]]] derives CanEqual { case A extends Hoge[[F[_]] =>> F[Int]] case B extends Hoge[[F[_]] =>> F[String]] } @@ -37,7 +37,7 @@ object Test2 { } object Test3 { - enum Hoge[F[G[_]]] derives Eql { + enum Hoge[F[G[_]]] derives CanEqual { case A extends Hoge[[X] =>> List] // error: wrong kind case B extends Hoge[[X] =>> [Y] =>> String] // error: wrong kind } diff --git a/tests/neg/i4470b.scala b/tests/neg/i4470b.scala index 68fad4cd2a7e..9a29c674082d 100644 --- a/tests/neg/i4470b.scala +++ b/tests/neg/i4470b.scala @@ -1,10 +1,10 @@ object RepeatedExtendEnum { - enum Maybe[T] derives Eql { // error + enum Maybe[T] derives CanEqual { // error case Foo extends Maybe[Int] } - enum Maybe[T] derives Eql { // error + enum Maybe[T] derives CanEqual { // error case Foo extends Maybe[Int] } } diff --git a/tests/neg/i5546.scala b/tests/neg/i5546.scala index 7984bb95b1fc..e261351b9480 100644 --- a/tests/neg/i5546.scala +++ b/tests/neg/i5546.scala @@ -8,11 +8,11 @@ object O { val m: Meters = 1.0 assert(m == 1.0) // OK } - implicit def eqM: Eql[Meters, Meters] = Eql.derived + implicit def eqM: CanEqual[Meters, Meters] = CanEqual.derived opaque type Feet = Double object Feet { def apply(d: Double): Feet = d } - implicit def eqF: Eql[Feet, Feet] = Eql.derived + implicit def eqF: CanEqual[Feet, Feet] = CanEqual.derived } object Test { diff --git a/tests/pending/neg/EqualityStrawman2.scala b/tests/pending/neg/EqualityStrawman2.scala index 52a7185158c3..504475499f4d 100644 --- a/tests/pending/neg/EqualityStrawman2.scala +++ b/tests/pending/neg/EqualityStrawman2.scala @@ -1,32 +1,32 @@ object equality { - trait Eql[T, U] - def Eql[T, U]: Eql[T, U] = new Eql[T, U]{} + trait CanEqual[T, U] + def CanEqual[T, U]: CanEqual[T, U] = new CanEqual[T, U]{} implicit class EqualsDeco[T](val x: T) extends AnyVal { - def ===[U] (y: U)(implicit ce: Eql[T, U]) = x.equals(y) + def ===[U] (y: U)(implicit ce: CanEqual[T, U]) = x.equals(y) } - type EqEq[T] = Eql.derived[T, T] + type EqEq[T] = CanEqual.derived[T, T] trait EqClass[T] - implicit def eqAny[T, U]: Eql[T, U] = Eql.derived + implicit def eqAny[T, U]: CanEqual[T, U] = CanEqual.derived /* - implicit def mixedEq1[T, U](implicit ce: Eql[T, U]): Eql[T, Any] = Eql.derived - implicit def mixedEq2[T, U](implicit ce: Eql[T, U]): Eql[Any, T] = Eql.derived - implicit def mixedEq1alt : Eql[Any, EqClass] = Eql.derived - implicit def mixedEq2 : Eql[EqClass, Any] = Eql.derived - implicit def mixedEq2alt : Eql[EqClass, Any] = Eql.derived - implicit def mixedNull1[T]: Eql[T, Null] = Eql - implicit def mixedNull2[T]: Eql[Null, T] = Eql + implicit def mixedEq1[T, U](implicit ce: CanEqual[T, U]): CanEqual[T, Any] = CanEqual.derived + implicit def mixedEq2[T, U](implicit ce: CanEqual[T, U]): CanEqual[Any, T] = CanEqual.derived + implicit def mixedEq1alt : CanEqual[Any, EqClass] = CanEqual.derived + implicit def mixedEq2 : CanEqual[EqClass, Any] = CanEqual.derived + implicit def mixedEq2alt : CanEqual[EqClass, Any] = CanEqual.derived + implicit def mixedNull1[T]: CanEqual[T, Null] = CanEqual + implicit def mixedNull2[T]: CanEqual[Null, T] = CanEqual */ - implicit def eqString: Eql[String, String] = Eql - implicit def eqInt: Eql[Int, Int] = Eql.derived - implicit def eqOption[T, U](implicit ce: Eql[T, U]): Eql[Option[T], Option[U]] = Eql.derived + implicit def eqString: CanEqual[String, String] = CanEqual + implicit def eqInt: CanEqual[Int, Int] = CanEqual.derived + implicit def eqOption[T, U](implicit ce: CanEqual[T, U]): CanEqual[Option[T], Option[U]] = CanEqual.derived /* - implicit def eqEq[UE, TE <: UE with EqClass[UE]]: Eql[TE, TE] = Eql + implicit def eqEq[UE, TE <: UE with EqClass[UE]]: CanEqual[TE, TE] = CanEqual case class Str(str: String) extends EqClass[Str] case class Num(x: Int) extends EqClass[Num] @@ -37,10 +37,10 @@ object equality { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - //implicit def eqStr: Eql[Str, Str] = Eql.derived - //implicit def eqNum: Eql[Num, Num] = Eql.derived + //implicit def eqStr: CanEqual[Str, Str] = CanEqual.derived + //implicit def eqNum: CanEqual[Num, Num] = CanEqual.derived - implicit def eqOption[T, U](implicit ce: Eql[T, U]): Eql[Option[T], Option[U]] = Eql + implicit def eqOption[T, U](implicit ce: CanEqual[T, U]): CanEqual[Option[T], Option[U]] = CanEqual */ def some[T](x: T): Option[T] = Some(x) @@ -91,7 +91,7 @@ object equality { class Fruit extends EqClass class Apple extends Fruit class Pear extends Fruit - implicit def eqFruit: Eql[Fruit, Fruit] = Eql + implicit def eqFruit: CanEqual[Fruit, Fruit] = CanEqual Some(new Apple) === Some(new Pear) diff --git a/tests/pos/derive-eq.scala b/tests/pos/derive-eq.scala index 888373660008..6cfcc8f33f3a 100644 --- a/tests/pos/derive-eq.scala +++ b/tests/pos/derive-eq.scala @@ -1,20 +1,20 @@ -case class One() derives Eql -case class Two() derives Eql +case class One() derives CanEqual +case class Two() derives CanEqual -given Eql[One, Two] = Eql.derived +given CanEqual[One, Two] = CanEqual.derived -enum Lst[T] derives Eql { +enum Lst[T] derives CanEqual { case Cons(x: T, xs: Lst[T]) case Nil() } -class Triple[S, T, U] derives Eql +class Triple[S, T, U] derives CanEqual object Test { - implicitly[Eql[Lst[Lst[One]], Lst[Lst[Two]]]] - implicitly[Eql[Triple[One, One, One], + implicitly[CanEqual[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[CanEqual[Triple[One, One, One], Triple[Two, Two, Two]]] val x: Triple[Lst[One], One, Two] = ??? diff --git a/tests/pos/multiversal.scala b/tests/pos/multiversal.scala index 012f761a092a..89364910281b 100644 --- a/tests/pos/multiversal.scala +++ b/tests/pos/multiversal.scala @@ -1,7 +1,7 @@ object Test { - import scala.Eql + import scala.CanEqual - given [X, Y](using Eql[X, Y]) as Eql[List[X], List[Y]] = Eql.derived + given [X, Y](using CanEqual[X, Y]) as CanEqual[List[X], List[Y]] = CanEqual.derived val b: Byte = 1 val c: Char = 2 diff --git a/tests/run/i7287/Enum_1.scala b/tests/run/i7287/Enum_1.scala index 94ce29fa206b..e9fd8a59b25c 100644 --- a/tests/run/i7287/Enum_1.scala +++ b/tests/run/i7287/Enum_1.scala @@ -1,4 +1,4 @@ -enum Color derives Eql { +enum Color derives CanEqual { case Unknown case Blue(v: Int) case Red(v: Int) diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala index 28cc6ef1f17d..01bdb6ccc954 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala @@ -143,19 +143,19 @@ class EnumTestScala3: object EnumTestScala3: - enum Color1 derives Eql: + enum Color1 derives CanEqual: case Red, Green, Blue - enum Color2 extends java.lang.Enum[Color2] derives Eql: + enum Color2 extends java.lang.Enum[Color2] derives CanEqual: case Red, Green, Blue // test "non-simple" cases with anonymous subclasses - enum Currency1(val dollarValue: Double) derives Eql: + enum Currency1(val dollarValue: Double) derives CanEqual: case Dollar extends Currency1(1.0) case SwissFanc extends Currency1(1.09) case Euro extends Currency1(1.18) - enum Currency2(val dollarValue: Double) extends java.lang.Enum[Currency2] derives Eql: + enum Currency2(val dollarValue: Double) extends java.lang.Enum[Currency2] derives CanEqual: case Dollar extends Currency2(1.0) case SwissFanc extends Currency2(1.09) case Euro extends Currency2(1.18) From b80de160f7b43bde5c1320bc5aec2a0c8bec1b06 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 13:05:05 +0100 Subject: [PATCH 2/7] Rename `Eql` to `CanEqual` --- .../dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/test-resources/repl/i4184 | 4 +-- compiler/test-resources/repl/i7644 | 24 ++++++------- library/src-bootstrapped/scala/Eql.scala | 34 +++++++++++++++++++ .../scala/Eql.scala | 0 tests/semanticdb/expect/Enums.expect.scala | 2 +- tests/semanticdb/expect/Enums.scala | 2 +- tests/semanticdb/metac.expect | 4 +-- 8 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 library/src-bootstrapped/scala/Eql.scala rename library/{src => src-non-bootstrapped}/scala/Eql.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d2807152ddf8..e52d42e059c9 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -840,7 +840,7 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") - @tu lazy val EqlClass: ClassSymbol = requiredClass("scala.Eql") + @tu lazy val EqlClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny) @tu lazy val TypeBoxClass: ClassSymbol = requiredClass("scala.runtime.TypeBox") diff --git a/compiler/test-resources/repl/i4184 b/compiler/test-resources/repl/i4184 index c18b804d69ff..385e98014d11 100644 --- a/compiler/test-resources/repl/i4184 +++ b/compiler/test-resources/repl/i4184 @@ -2,8 +2,8 @@ scala> object foo { class Foo } // defined object foo scala> object bar { class Foo } // defined object bar -scala> implicit def eqFoo: Eql[foo.Foo, foo.Foo] = Eql.derived -def eqFoo: Eql[foo.Foo, foo.Foo] +scala> implicit def eqFoo: CanEqual[foo.Foo, foo.Foo] = CanEqual.derived +def eqFoo: CanEqual[foo.Foo, foo.Foo] scala> object Bar { new foo.Foo == new bar.Foo } 1 | object Bar { new foo.Foo == new bar.Foo } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/compiler/test-resources/repl/i7644 b/compiler/test-resources/repl/i7644 index 25d219c24b56..780b3847bb4e 100644 --- a/compiler/test-resources/repl/i7644 +++ b/compiler/test-resources/repl/i7644 @@ -1,14 +1,14 @@ -scala> class T extends Eql -1 | class T extends Eql +scala> class T extends CanEqual +1 | class T extends CanEqual | ^ - | Cannot extend sealed trait Eql in a different source file -1 | class T extends Eql - | ^^^ - | Missing type parameter for Eql -scala> class T extends Eql -1 | class T extends Eql + | Cannot extend sealed trait CanEqual in a different source file +1 | class T extends CanEqual + | ^^^^^^^^ + | Missing type parameter for CanEqual +scala> class T extends CanEqual +1 | class T extends CanEqual | ^ - | Cannot extend sealed trait Eql in a different source file -1 | class T extends Eql - | ^^^ - | Missing type parameter for Eql + | Cannot extend sealed trait CanEqual in a different source file +1 | class T extends CanEqual + | ^^^^^^^^ + | Missing type parameter for CanEqual diff --git a/library/src-bootstrapped/scala/Eql.scala b/library/src-bootstrapped/scala/Eql.scala new file mode 100644 index 000000000000..475057b50b44 --- /dev/null +++ b/library/src-bootstrapped/scala/Eql.scala @@ -0,0 +1,34 @@ +package scala + +import annotation.implicitNotFound +import scala.collection.{Seq, Set} + +/** A marker trait indicating that values of type `L` can be compared to values of type `R`. */ +@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") +sealed trait CanEqual[-L, -R] + +/** Companion object containing a few universally known `CanEqual` instances. + * CanEqual instances involving primitive types or the Null type are handled directly in + * the compiler (see Implicits.synthesizedCanEqual), so they are not included here. + */ +object CanEqual { + /** A universal `CanEqual` instance. */ + object derived extends CanEqual[Any, Any] + + /** A fall-back instance to compare values of any types. + * Even though this method is not declared as given, the compiler will + * synthesize implicit arguments as solutions to `CanEqual[T, U]` queries if + * the rules of multiversal equality require it. + */ + def eqlAny[L, R]: CanEqual[L, R] = derived + + // Instances of `CanEqual` for common Java types + given eqlNumber as CanEqual[Number, Number] = derived + given eqlString as CanEqual[String, String] = derived + + // The next three definitions can go into the companion objects of classes + // Seq and Set. For now they are here in order not to have to touch the + // source code of these classes + given eqlSeq[T, U](using eq: CanEqual[T, U]) as CanEqual[Seq[T], Seq[U]] = derived + given eqlSet[T, U](using eq: CanEqual[T, U]) as CanEqual[Set[T], Set[U]] = derived +} diff --git a/library/src/scala/Eql.scala b/library/src-non-bootstrapped/scala/Eql.scala similarity index 100% rename from library/src/scala/Eql.scala rename to library/src-non-bootstrapped/scala/Eql.scala diff --git a/tests/semanticdb/expect/Enums.expect.scala b/tests/semanticdb/expect/Enums.expect.scala index 6216eb540930..1fc2256cee59 100644 --- a/tests/semanticdb/expect/Enums.expect.scala +++ b/tests/semanticdb/expect/Enums.expect.scala @@ -8,7 +8,7 @@ object Enums/*<-_empty_::Enums.*/: enum Directions/*<-_empty_::Enums.Directions#*/: case North/*<-_empty_::Enums.Directions.North.*/, East/*<-_empty_::Enums.Directions.East.*/, South/*<-_empty_::Enums.Directions.South.*/, West/*<-_empty_::Enums.Directions.West.*/ - enum Suits/*<-_empty_::Enums.Suits#*/ derives /*->scala::Eql.derived.*/Eql: + enum Suits/*<-_empty_::Enums.Suits#*/ derives /*->scala::CanEqual.derived.*/CanEqual: case Hearts/*<-_empty_::Enums.Suits.Hearts.*/, Spades/*<-_empty_::Enums.Suits.Spades.*/, Clubs/*<-_empty_::Enums.Suits.Clubs.*/, Diamonds/*<-_empty_::Enums.Suits.Diamonds.*/ object Suits/*<-_empty_::Enums.Suits.*/: diff --git a/tests/semanticdb/expect/Enums.scala b/tests/semanticdb/expect/Enums.scala index 408b551224fd..56ed483b2d76 100644 --- a/tests/semanticdb/expect/Enums.scala +++ b/tests/semanticdb/expect/Enums.scala @@ -8,7 +8,7 @@ object Enums: enum Directions: case North, East, South, West - enum Suits derives Eql: + enum Suits derives CanEqual: case Hearts, Spades, Clubs, Diamonds object Suits: diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 1322835bb039..025d37f94377 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -755,7 +755,7 @@ _empty_/Enums.Suits.Clubs. => case val static enum method Clubs _empty_/Enums.Suits.Diamonds. => case val static enum method Diamonds _empty_/Enums.Suits.Hearts. => case val static enum method Hearts _empty_/Enums.Suits.Spades. => case val static enum method Spades -_empty_/Enums.Suits.derived$Eql(). => implicit method derived$Eql +_empty_/Enums.Suits.derived$CanEqual(). => implicit method derived$CanEqual _empty_/Enums.Suits.fromOrdinal(). => method fromOrdinal _empty_/Enums.Suits.fromOrdinal().(ordinal) => param ordinal _empty_/Enums.Suits.isBlack(). => method isBlack @@ -844,7 +844,7 @@ Occurrences: [8:29..8:33): West <- _empty_/Enums.Directions.West. [10:7..10:12): Suits <- _empty_/Enums.Suits# [10:21..10:21): <- _empty_/Enums.Suits#``(). -[10:21..10:21): -> scala/Eql.derived. +[10:21..10:21): -> scala/CanEqual.derived. [11:9..11:15): Hearts <- _empty_/Enums.Suits.Hearts. [11:17..11:23): Spades <- _empty_/Enums.Suits.Spades. [11:25..11:30): Clubs <- _empty_/Enums.Suits.Clubs. From 5ec970fd2c2ef05c313cf699ed4d122135cff9f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 13:42:58 +0100 Subject: [PATCH 3/7] Rename methods in CanEqual --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 +++- compiler/src/dotty/tools/dotc/core/StdNames.scala | 2 ++ library/src-bootstrapped/scala/Eql.scala | 10 +++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e52d42e059c9..e457c70a946e 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -841,7 +841,9 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") @tu lazy val EqlClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass - def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny) + def Eql_eqlAny(using Context): TermSymbol = + val methodName = if EqlClass.name == tpnme.Eql then nme.eqlAny else nme.canEqualAny + EqlClass.companionModule.requiredMethod(methodName) @tu lazy val TypeBoxClass: ClassSymbol = requiredClass("scala.runtime.TypeBox") @tu lazy val TypeBox_CAP: TypeSymbol = TypeBoxClass.requiredType(tpnme.CAP) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 0bb2b9bc898b..6c2e675a9a08 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -362,6 +362,7 @@ object StdNames { val CAP: N = "CAP" val Constant: N = "Constant" val ConstantType: N = "ConstantType" + val Eql: N = "Eql" val EnumValue: N = "EnumValue" val ExistentialTypeTree: N = "ExistentialTypeTree" val Flag : N = "Flag" @@ -433,6 +434,7 @@ object StdNames { val bundle: N = "bundle" val bytes: N = "bytes" val canEqual_ : N = "canEqual" + val canEqualAny : N = "canEqualAny" val cbnArg: N = "" val checkInitialized: N = "checkInitialized" val ClassManifestFactory: N = "ClassManifestFactory" diff --git a/library/src-bootstrapped/scala/Eql.scala b/library/src-bootstrapped/scala/Eql.scala index 475057b50b44..8815c086d36c 100644 --- a/library/src-bootstrapped/scala/Eql.scala +++ b/library/src-bootstrapped/scala/Eql.scala @@ -20,15 +20,15 @@ object CanEqual { * synthesize implicit arguments as solutions to `CanEqual[T, U]` queries if * the rules of multiversal equality require it. */ - def eqlAny[L, R]: CanEqual[L, R] = derived + def canEqualAny[L, R]: CanEqual[L, R] = derived // Instances of `CanEqual` for common Java types - given eqlNumber as CanEqual[Number, Number] = derived - given eqlString as CanEqual[String, String] = derived + given canEqualNumber as CanEqual[Number, Number] = derived + given canEqualString as CanEqual[String, String] = derived // The next three definitions can go into the companion objects of classes // Seq and Set. For now they are here in order not to have to touch the // source code of these classes - given eqlSeq[T, U](using eq: CanEqual[T, U]) as CanEqual[Seq[T], Seq[U]] = derived - given eqlSet[T, U](using eq: CanEqual[T, U]) as CanEqual[Set[T], Set[U]] = derived + given canEqualSeq[T, U](using eq: CanEqual[T, U]) as CanEqual[Seq[T], Seq[U]] = derived + given canEqualSet[T, U](using eq: CanEqual[T, U]) as CanEqual[Set[T], Set[U]] = derived } From 82e212d7311fa7dc03adbcbb3a9d0398719a169e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 13:45:05 +0100 Subject: [PATCH 4/7] Rename bootstrapped file Eql.scala to CanEqual.scala --- library/src-bootstrapped/scala/{Eql.scala => CanEqual.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/src-bootstrapped/scala/{Eql.scala => CanEqual.scala} (100%) diff --git a/library/src-bootstrapped/scala/Eql.scala b/library/src-bootstrapped/scala/CanEqual.scala similarity index 100% rename from library/src-bootstrapped/scala/Eql.scala rename to library/src-bootstrapped/scala/CanEqual.scala From 2ecb3bb2673eda28fa3bc7cd84626f0fa57ca74e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 13:50:48 +0100 Subject: [PATCH 5/7] Rename Eql to CanEqual in internal compiler code --- .../dotty/tools/dotc/core/Definitions.scala | 8 +++--- .../src/dotty/tools/dotc/typer/Deriving.scala | 28 +++++++++---------- .../dotty/tools/dotc/typer/Implicits.scala | 8 +++--- .../dotty/tools/dotc/typer/Synthesizer.scala | 26 ++++++++--------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e457c70a946e..5adf3687d454 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -840,10 +840,10 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") - @tu lazy val EqlClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass - def Eql_eqlAny(using Context): TermSymbol = - val methodName = if EqlClass.name == tpnme.Eql then nme.eqlAny else nme.canEqualAny - EqlClass.companionModule.requiredMethod(methodName) + @tu lazy val CanEqualClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass + def CanEqual_canEqualAny(using Context): TermSymbol = + val methodName = if CanEqualClass.name == tpnme.Eql then nme.eqlAny else nme.canEqualAny + CanEqualClass.companionModule.requiredMethod(methodName) @tu lazy val TypeBoxClass: ClassSymbol = requiredClass("scala.runtime.TypeBox") @tu lazy val TypeBox_CAP: TypeSymbol = TypeBoxClass.requiredType(tpnme.CAP) diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 7eadcf2114a5..1ca774fd8a84 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -67,9 +67,9 @@ trait Deriving { * the deriving ADT * (b) a single parameter type class with a parameter of kind * and an ADT with * one or more type parameter of kind * - * (c) the Eql type class + * (c) the CanEqual type class * - * See detailed descriptions in deriveSingleParameter and deriveEql below. + * See detailed descriptions in deriveSingleParameter and deriveCanEqual below. * * If it passes the checks, enter a type class instance for it in the current scope. * @@ -145,7 +145,7 @@ trait Deriving { // // given derived$TC[a, b, c] given TC[a], TC[b], TC[c]: TC[a, b, c] // - // This, like the derivation for Eql, is a special case of the + // This, like the derivation for CanEqual, is a special case of the // earlier more general multi-parameter type class model for which // the heuristic is typically a good one. @@ -185,8 +185,8 @@ trait Deriving { cannotBeUnified } - def deriveEql: Unit = { - // Specific derives rules for the Eql type class ... (c) above + def deriveCanEqual: Unit = { + // Specific derives rules for the CanEqual type class ... (c) above // // This has been extracted from the earlier more general multi-parameter // type class model. Modulo the assumptions below, the implied semantics @@ -196,13 +196,13 @@ trait Deriving { // 1. Type params of the deriving class correspond to all and only // elements of the deriving class which are relevant to equality (but: // type params could be phantom, or the deriving class might have an - // element of a non-Eql type non-parametrically). + // element of a non-CanEqual type non-parametrically). // // 2. Type params of kinds other than * can be assumed to be irrelevant to // the derivation (but: eg. Foo[F[_]](fi: F[Int])). // // Are they reasonable? They cover some important cases (eg. Tuples of all - // arities). derives Eql is opt-in, so if the semantics don't match those + // arities). derives CanEqual is opt-in, so if the semantics don't match those // appropriate for the deriving class the author of that class can provide // their own instance in the normal way. That being so, the question turns // on whether there are enough types which fit these semantics for the @@ -210,12 +210,12 @@ trait Deriving { // Procedure: // We construct a two column matrix of the deriving class type parameters - // and the Eql type class parameters. + // and the CanEqual type class parameters. // // Rows: parameters of the deriving class - // Columns: parameters of the Eql type class (L/R) + // Columns: parameters of the CanEqual type class (L/R) // - // Running example: type class: class Eql[L, R], deriving class: class A[T, U, V] + // Running example: type class: class CanEqual[L, R], deriving class: class A[T, U, V] // clsParamss = // T_L T_R // U_L U_R @@ -225,7 +225,7 @@ trait Deriving { tparam.copy(name = s"${tparam.name}_$$_${tcparam.name}".toTypeName) .asInstanceOf[TypeSymbol]) } - // Retain only rows with L/R params of kind * which Eql can be applied to. + // Retain only rows with L/R params of kind * which CanEqual can be applied to. // No pairwise evidence will be required for params of other kinds. val firstKindedParamss = clsParamss.filter { case param :: _ => !param.info.isLambdaSub @@ -233,7 +233,7 @@ trait Deriving { } // The types of the required evidence parameters. In the running example: - // Eql[T_L, T_R], Eql[U_L, U_R], Eql[V_L, V_R] + // CanEqual[T_L, T_R], CanEqual[U_L, U_R], CanEqual[V_L, V_R] val evidenceParamInfos = for (row <- firstKindedParamss) yield row.map(_.typeRef) @@ -244,12 +244,12 @@ trait Deriving { for (n <- List.range(0, typeClassArity)) yield cls.typeRef.appliedTo(clsParamss.map(row => row(n).typeRef)) - // Eql[A[T_L, U_L, V_L], A[T_R, U_R, V_R]] + // CanEqual[A[T_L, U_L, V_L], A[T_R, U_R, V_R]] addInstance(clsParamss.flatten, evidenceParamInfos, instanceTypes) } if (typeClassArity == 1) deriveSingleParameter - else if (typeClass == defn.EqlClass) deriveEql + else if (typeClass == defn.CanEqualClass) deriveCanEqual else if (typeClassArity == 0) report.error(i"type ${typeClass.name} in derives clause of ${cls.name} has no type parameters", derived.srcPos) else diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 06fd0389cd2c..2d40d9c74ecb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -883,7 +883,7 @@ trait Implicits: else s"parameter $paramName" } of $methodStr" } - /** An Eql[T, U] instance is assumed + /** A CanEqual[T, U] instance is assumed * - 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. @@ -913,8 +913,8 @@ trait Implicits: /** 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)) { - val res = implicitArgTree(defn.EqlClass.typeRef.appliedTo(ltp, rtp), span) - implicits.println(i"Eql witness found for $ltp / $rtp: $res: ${res.tpe}") + val res = implicitArgTree(defn.CanEqualClass.typeRef.appliedTo(ltp, rtp), span) + implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}") } /** Find an implicit parameter or conversion. @@ -1047,7 +1047,7 @@ trait Implicits: private def nestedContext() = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) - private def isCoherent = pt.isRef(defn.EqlClass) + private def isCoherent = pt.isRef(defn.CanEqualClass) val wideProto = pt.widenExpr diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 26825e6e21f9..bcb9f0eac43a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -77,19 +77,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): } end synthesizedTypeTest - /** If `formal` is of the form Eql[T, U], try to synthesize an - * `Eql.eqlAny[T, U]` as solution. + /** If `formal` is of the form CanEqual[T, U], try to synthesize an + * `CanEqual.canEqualAny[T, U]` as solution. */ - val synthesizedEql: SpecialHandler = (formal, span) => + val synthesizedCanEqual: SpecialHandler = (formal, span) => - /** Is there an `Eql[T, T]` instance, assuming -strictEquality? */ + /** Is there an `CanEqual[T, T]` instance, assuming -strictEquality? */ def hasEq(tp: Type)(using Context): Boolean = - val inst = typer.inferImplicitArg(defn.EqlClass.typeRef.appliedTo(tp, tp), span) + val inst = typer.inferImplicitArg(defn.CanEqualClass.typeRef.appliedTo(tp, tp), span) !inst.isEmpty && !inst.tpe.isError - /** Can we assume the eqlAny instance for `tp1`, `tp2`? + /** Can we assume the canEqualAny instance for `tp1`, `tp2`? * This is the case if assumedCanEqual(tp1, tp2), or - * one of `tp1`, `tp2` has a reflexive `Eql` instance. + * one of `tp1`, `tp2` has a reflexive `CanEqual` instance. */ def validEqAnyArgs(tp1: Type, tp2: Type)(using Context) = typer.assumedCanEqual(tp1, tp2) @@ -97,7 +97,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): !hasEq(tp1) && !hasEq(tp2) } - /** Is an `Eql[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */ + /** Is an `CanEqual[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */ def canComparePredefinedClasses(cls1: ClassSymbol, cls2: ClassSymbol): Boolean = def cmpWithBoxed(cls1: ClassSymbol, cls2: ClassSymbol) = @@ -129,8 +129,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): false end canComparePredefinedClasses - /** Some simulated `Eql` instances for predefined types. It's more efficient - * to do this directly instead of setting up a lot of `Eql` instances to + /** Some simulated `CanEqual` instances for predefined types. It's more efficient + * to do this directly instead of setting up a lot of `CanEqual` instances to * interpret. */ def canComparePredefined(tp1: Type, tp2: Type) = @@ -143,10 +143,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span)) if canComparePredefined(arg1, arg2) || !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2)) - then ref(defn.Eql_eqlAny).appliedToTypes(args).withSpan(span) + then ref(defn.CanEqual_canEqualAny).appliedToTypes(args).withSpan(span) else EmptyTree case _ => EmptyTree - end synthesizedEql + end synthesizedCanEqual /** Creates a tree that will produce a ValueOf instance for the requested type. * An EmptyTree is returned if materialization fails. @@ -363,7 +363,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val specialHandlers = List( defn.ClassTagClass -> synthesizedClassTag, defn.TypeTestClass -> synthesizedTypeTest, - defn.EqlClass -> synthesizedEql, + defn.CanEqualClass -> synthesizedCanEqual, defn.ValueOfClass -> synthesizedValueOf, defn.Mirror_ProductClass -> synthesizedProductMirror, defn.Mirror_SumClass -> synthesizedSumMirror, From 6b429a48e2cd7f22cdf962f08986c2506d6399bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 13:55:42 +0100 Subject: [PATCH 6/7] Update doc page --- .../contextual/multiversal-equality.md | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/docs/reference/contextual/multiversal-equality.md b/docs/docs/reference/contextual/multiversal-equality.md index 046d849ac9bf..c420884eda8d 100644 --- a/docs/docs/reference/contextual/multiversal-equality.md +++ b/docs/docs/reference/contextual/multiversal-equality.md @@ -24,62 +24,62 @@ the program will still typecheck, since values of all types can be compared with But it will probably give unexpected results and fail at runtime. Multiversal equality is an opt-in way to make universal equality -safer. It uses a binary type class `Eql` to indicate that values of +safer. It uses a binary type class `CanEqual` to indicate that values of two given types can be compared with each other. The example above would not typecheck if `S` or `T` was a class -that derives `Eql`, e.g. +that derives `CanEqual`, e.g. ```scala -class T derives Eql +class T derives CanEqual ``` -Alternatively, one can also provide an `Eql` given instance directly, like this: +Alternatively, one can also provide a `CanEqual` given instance directly, like this: ```scala -given Eql[T, T] = Eql.derived +given CanEqual[T, T] = CanEqual.derived ``` This definition effectively says that values of type `T` can (only) be compared to other values of type `T` when using `==` or `!=`. The definition affects type checking but it has no significance for runtime behavior, since `==` always maps to `equals` and `!=` always maps to -the negation of `equals`. The right hand side `Eql.derived` of the definition -is a value that has any `Eql` instance as its type. Here is the definition of class -`Eql` and its companion object: +the negation of `equals`. The right hand side `CanEqual.derived` of the definition +is a value that has any `CanEqual` instance as its type. Here is the definition of class +`CanEqual` and its companion object: ```scala package scala import annotation.implicitNotFound @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") -sealed trait Eql[-L, -R] +sealed trait CanEqual[-L, -R] -object Eql { - object derived extends Eql[Any, Any] +object CanEqual { + object derived extends CanEqual[Any, Any] } ``` -One can have several `Eql` given instances for a type. For example, the four +One can have several `CanEqual` given instances for a type. For example, the four definitions below make values of type `A` and type `B` comparable with each other, but not comparable to anything else: ```scala -given Eql[A, A] = Eql.derived -given Eql[B, B] = Eql.derived -given Eql[A, B] = Eql.derived -given Eql[B, A] = Eql.derived +given CanEqual[A, A] = CanEqual.derived +given CanEqual[B, B] = CanEqual.derived +given CanEqual[A, B] = CanEqual.derived +given CanEqual[B, A] = CanEqual.derived ``` -The `scala.Eql` object defines a number of `Eql` given instances that together +The `scala.CanEqual` object defines a number of `CanEqual` given instances that together define a rule book for what standard types can be compared (more details below). -There is also a "fallback" instance named `eqlAny` that allows comparisons -over all types that do not themselves have an `Eql` given. `eqlAny` is defined as follows: +There is also a "fallback" instance named `canEqualAny` that allows comparisons +over all types that do not themselves have a `CanEqual` given. `canEqualAny` is defined as follows: ```scala -def eqlAny[L, R]: Eql[L, R] = Eql.derived +def canEqualAny[L, R]: CanEqual[L, R] = CanEqual.derived ``` -Even though `eqlAny` is not declared as `given`, the compiler will still construct an `eqlAny` instance as answer to an implicit search for the -type `Eql[L, R]`, unless `L` or `R` have `Eql` instances +Even though `canEqualAny` is not declared as `given`, the compiler will still construct an `canEqualAny` instance as answer to an implicit search for the +type `CanEqual[L, R]`, unless `L` or `R` have `CanEqual` instances defined on them, or the language feature `strictEquality` is enabled. -The primary motivation for having `eqlAny` is backwards compatibility. -If this is of no concern, one can disable `eqlAny` by enabling the language +The primary motivation for having `canEqualAny` is backwards compatibility. +If this is of no concern, one can disable `canEqualAny` by enabling the language feature `strictEquality`. As for all language features this can be either done with an import @@ -88,20 +88,20 @@ import scala.language.strictEquality ``` or with a command line option `-language:strictEquality`. -## Deriving Eql Instances +## Deriving CanEqual Instances -Instead of defining `Eql` instances directly, it is often more convenient to derive them. Example: +Instead of defining `CanEqual` instances directly, it is often more convenient to derive them. Example: ```scala -class Box[T](x: T) derives Eql +class Box[T](x: T) derives CanEqual ``` By the usual rules of [type class derivation](./derivation.md), -this generates the following `Eql` instance in the companion object of `Box`: +this generates the following `CanEqual` instance in the companion object of `Box`: ```scala -given [T, U](using Eql[T, U]) as Eql[Box[T], Box[U]] = Eql.derived +given [T, U](using CanEqual[T, U]) as CanEqual[Box[T], Box[U]] = CanEqual.derived ``` That is, two boxes are comparable with `==` or `!=` if their elements are. Examples: ```scala -new Box(1) == new Box(1L) // ok since there is an instance for `Eql[Int, Long]` +new Box(1) == new Box(1L) // ok since there is an instance for `CanEqual[Int, Long]` new Box(1) == new Box("a") // error: can't compare new Box(1) == 1 // error: can't compare ``` @@ -112,31 +112,31 @@ The precise rules for equality checking are as follows. If the `strictEquality` feature is enabled then a comparison using `x == y` or `x != y` between values `x: T` and `y: U` -is legal if there is a `given` of type `Eql[T, U]`. +is legal if there is a `given` of type `CanEqual[T, U]`. In the default case where the `strictEquality` feature is not enabled the comparison is also legal if 1. `T` and `U` are the same, or 2. one of `T`, `U` is a subtype of the _lifted_ version of the other type, or - 3. neither `T` nor `U` have a _reflexive_ `Eql` instance. + 3. neither `T` nor `U` have a _reflexive_ `CanEqual` instance. Explanations: - _lifting_ a type `S` means replacing all references to abstract types in covariant positions of `S` by their upper bound, and replacing all refinement types in covariant positions of `S` by their parent. - - a type `T` has a _reflexive_ `Eql` instance if the implicit search for `Eql[T, T]` + - a type `T` has a _reflexive_ `CanEqual` instance if the implicit search for `CanEqual[T, T]` succeeds. -## Predefined Eql Instances +## Predefined CanEqual Instances -The `Eql` object defines instances for comparing +The `CanEqual` object defines instances for comparing - the primitive types `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, `Double`, `Boolean`, and `Unit`, - `java.lang.Number`, `java.lang.Boolean`, and `java.lang.Character`, - `scala.collection.Seq`, and `scala.collection.Set`. -Instances are defined so that every one of these types has a _reflexive_ `Eql` instance, and the following holds: +Instances are defined so that every one of these types has a _reflexive_ `CanEqual` instance, and the following holds: - Primitive numeric types can be compared with each other. - Primitive numeric types can be compared with subtypes of `java.lang.Number` (and _vice versa_). @@ -152,7 +152,7 @@ Instances are defined so that every one of these types has a _reflexive_ `Eql` i ## Why Two Type Parameters? -One particular feature of the `Eql` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional +One particular feature of the `CanEqual` type is that it takes _two_ type parameters, representing the types of the two items to be compared. By contrast, conventional implementations of an equality type class take only a single type parameter which represents the common type of _both_ operands. One type parameter is simpler than two, so why go through the additional complication? The reason has to do with the fact that, rather than coming up with a type class where no operation existed before, we are dealing with a refinement of pre-existing, universal equality. It is best illustrated through an example. @@ -174,39 +174,39 @@ does not work, since it refers to the covariant parameter `T` in a nonvariant co ``` This generic version of `contains` is the one used in the current (Scala 2.13) version of `List`. It looks different but it admits exactly the same applications as the `contains(x: Any)` definition we started with. -However, we can make it more useful (i.e. restrictive) by adding an `Eql` parameter: +However, we can make it more useful (i.e. restrictive) by adding a `CanEqual` parameter: ```scala - def contains[U >: T](x: U)(using Eql[T, U]): Boolean // (1) + def contains[U >: T](x: U)(using CanEqual[T, U]): Boolean // (1) ``` This version of `contains` is equality-safe! More precisely, given `x: T`, `xs: List[T]` and `y: U`, then `xs.contains(y)` is type-correct if and only if `x == y` is type-correct. -Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `Eql1[T]` type class: +Unfortunately, the crucial ability to "lift" equality type checking from simple equality and pattern matching to arbitrary user-defined operations gets lost if we restrict ourselves to an equality class with a single type parameter. Consider the following signature of `contains` with a hypothetical `CanEqual1[T]` type class: ```scala - def contains[U >: T](x: U)(using Eql1[U]): Boolean // (2) + def contains[U >: T](x: U)(using CanEqual1[U]): Boolean // (2) ``` This version could be applied just as widely as the original `contains(x: Any)` method, -since the `Eql1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `Eql[A, B]` is available only if neither `A` nor `B` have a reflexive `Eql` instance. That rule simply cannot be expressed if there is a single type parameter for `Eql`. +since the `CanEqual1[Any]` fallback is always available! So we have gained nothing. What got lost in the transition to a single parameter type class was the original rule that `CanEqual[A, B]` is available only if neither `A` nor `B` have a reflexive `CanEqual` instance. That rule simply cannot be expressed if there is a single type parameter for `CanEqual`. The situation is different under `-language:strictEquality`. In that case, -the `Eql[Any, Any]` or `Eql1[Any]` instances would never be available, and the +the `CanEqual[Any, Any]` or `CanEqual1[Any]` instances would never be available, and the single and two-parameter versions would indeed coincide for most practical purposes. -But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `Eql` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones. +But assuming `-language:strictEquality` immediately and everywhere poses migration problems which might well be unsurmountable. Consider again `contains`, which is in the standard library. Parameterizing it with the `CanEqual` type class as in (1) is an immediate win since it rules out non-sensical applications while still allowing all sensible ones. So it can be done almost at any time, modulo binary compatibility concerns. -On the other hand, parameterizing `contains` with `Eql1` as in (2) would make `contains` -unusable for all types that have not yet declared an `Eql1` instance, including all +On the other hand, parameterizing `contains` with `CanEqual1` as in (2) would make `contains` +unusable for all types that have not yet declared a `CanEqual1` instance, including all types coming from Java. This is clearly unacceptable. It would lead to a situation where, -rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `Eql1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease. +rather than migrating existing libraries to use safe equality, the only upgrade path is to have parallel libraries, with the new version only catering to types deriving `CanEqual1` and the old version dealing with everything else. Such a split of the ecosystem would be very problematic, which means the cure is likely to be worse than the disease. For these reasons, it looks like a two-parameter type class is the only way forward because it can take the existing ecosystem where it is and migrate it towards a future where more and more code uses safe equality. In applications where `-language:strictEquality` is the default one could also introduce a one-parameter type alias such as ```scala -type Eq[-T] = Eql[T, T] +type Eq[-T] = CanEqual[T, T] ``` -Operations needing safe equality could then use this alias instead of the two-parameter `Eql` class. But it would only +Operations needing safe equality could then use this alias instead of the two-parameter `CanEqual` class. But it would only work under `-language:strictEquality`, since otherwise the universal `Eq[Any]` instance would be available everywhere. From b5fb11b44e6bc58a538b5dc7b1fd03b662f7319e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Nov 2020 14:06:48 +0100 Subject: [PATCH 7/7] Update scala3doc --- .../src/main/scala/MultiversalEquality.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sbt-dotty/sbt-test/sbt-dotty/scala3doc/src/main/scala/MultiversalEquality.scala b/sbt-dotty/sbt-test/sbt-dotty/scala3doc/src/main/scala/MultiversalEquality.scala index 5619a09cbe3b..a4089e75de19 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/scala3doc/src/main/scala/MultiversalEquality.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/scala3doc/src/main/scala/MultiversalEquality.scala @@ -4,7 +4,7 @@ import scala.language.strictEquality /** * Multiversal Equality: https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html - * scala.Eq definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/Eql.scala + * scala.Eq definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala */ object MultiversalEquality { @@ -12,15 +12,15 @@ object MultiversalEquality { // Values of types Int and String cannot be compared with == or !=, // unless we add the derived delegate instance like: - given Eql[Int, String] = Eql.derived + given CanEqual[Int, String] = CanEqual.derived println(3 == "3") // By default, all numbers are comparable, because of; - // implicit def eqlNumber: Eql[Number, Number] = derived + // implicit def eqlNumber: CanEqual[Number, Number] = derived println(3 == 5.1) // By default, all Sequences are comparable, because of; - // implicit def eqlSeq[T, U](implicit eq: Eql[T, U]): Eql[GenSeq[T], GenSeq[U]] = derived + // implicit def eqlSeq[T, U](implicit eq: CanEqual[T, U]): CanEqual[GenSeq[T], GenSeq[U]] = derived println(List(1, 2) == Vector(1, 2)) class A(a: Int) @@ -31,8 +31,8 @@ object MultiversalEquality { // scala.language.strictEquality is enabled, therefore we need some extra delegate instances // to compare instances of A and B. - given Eql[A, B] = Eql.derived - given Eql[B, A] = Eql.derived + given CanEqual[A, B] = CanEqual.derived + given CanEqual[B, A] = CanEqual.derived println(a != b) println(b == a)