From bfcb4c6fb13bef2fd4938e474d75ecbcb84ce0fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Feb 2019 17:51:07 +0100 Subject: [PATCH 01/10] Make Eq a derivable typeclass --- library/src/scala/Eq.scala | 1 + tests/neg/derive-eq.scala | 31 +++++++++++++++++++++++++++++++ tests/neg/equality.scala | 4 +--- tests/run/derive-eq.scala | 23 +++++++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 tests/neg/derive-eq.scala create mode 100644 tests/run/derive-eq.scala diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala index 15869024d003..f8a5e28561e1 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eq.scala @@ -12,6 +12,7 @@ sealed trait Eq[-L, -R] * any instance of `Eq`. */ object Eq extends Eq[Any, Any] { + def derived: Eq[Any, Any] = Eq /** A fall-back "implicit" to compare values of any types. * Even though this method is not declared implicit, the compiler will diff --git a/tests/neg/derive-eq.scala b/tests/neg/derive-eq.scala new file mode 100644 index 000000000000..27096b58dc16 --- /dev/null +++ b/tests/neg/derive-eq.scala @@ -0,0 +1,31 @@ + +case class One() derives Eq +case class Two() derives Eq + +implied for Eq[One, Two] = Eq + +enum Lst[T] derives Eq { + case Cons(x: T, xs: Lst[T]) + case Nil() +} + +case class Triple[S, T, U] derives Eq + + +object Test extends App { + Test1 + implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[Eq[Triple[One, One, One], + Triple[Two, Two, Two]]] + + val x: Triple[List[One], One, Two] = ??? + val y: Triple[List[Two], One, Two] = ??? + val z: Triple[One, List[Two], One] = ??? + x == y // OK + x == x // OK + y == y // OK + + y == x // error + x == z // error + z == y // error +} diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 595d1de6564f..f1e31b167a6b 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -64,9 +64,7 @@ object equality { 1 == null // OK by eqIntNum - class Fruit - - implicit def eqFruit: Eq[Fruit, Fruit] = Eq + class Fruit derives Eq class Apple extends Fruit class Pear extends Fruit diff --git a/tests/run/derive-eq.scala b/tests/run/derive-eq.scala new file mode 100644 index 000000000000..ad58af4eaf84 --- /dev/null +++ b/tests/run/derive-eq.scala @@ -0,0 +1,23 @@ + +case class One() derives Eq +case class Two() derives Eq + +implied for Eq[One, Two] = Eq + +enum Lst[T] derives Eq { + case Cons(x: T, xs: Lst[T]) + case Nil() +} + +class Triple[S, T, U] derives Eq + + +object Test extends App { + implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[Eq[Triple[One, One, One], + Triple[Two, Two, Two]]] + + val x: Triple[List[One], One, Two] = ??? + val y: Triple[List[Two], One, Two] = ??? + assert(x == y) +} From eb1f5f36f5e431f88172dbcc2b586c7ced681263 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Feb 2019 20:27:29 +0100 Subject: [PATCH 02/10] Drop generation of Eq instances for enums Treat Eq like any other derivable typeclass. This caused a bit of changes in fuzzing tests. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 43 +------------------ .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- tests/neg/derive-eq.scala | 1 - tests/neg/enums.scala | 2 +- tests/neg/i3976.scala | 6 +-- tests/neg/i4470a.scala | 4 +- tests/neg/i4470b.scala | 4 +- tests/neg/i4470c.scala | 4 +- tests/{run => pos}/derive-eq.scala | 8 ++-- 9 files changed, 17 insertions(+), 57 deletions(-) rename tests/{run => pos}/derive-eq.scala (71%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index dd15a25031d5..11e55f1e367f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -538,45 +538,6 @@ object desugar { if (isEnum) parents1 = parents1 :+ ref(defn.EnumType) - // The Eq instance for an Enum class. For an enum class - // - // enum class C[T1, ..., Tn] - // - // we generate: - // - // implicit def eqInstance[T1$1, ..., Tn$1, T1$2, ..., Tn$2](implicit - // ev1: Eq[T1$1, T1$2], ..., evn: Eq[Tn$1, Tn$2]]) - // : Eq[C[T1$, ..., Tn$1], C[T1$2, ..., Tn$2]] = Eq - // - // Higher-kinded type arguments `Ti` are omitted as evidence parameters. - // - // FIXME: This is too simplistic. Instead of just generating evidence arguments - // for every first-kinded type parameter, we should look instead at the - // actual types occurring in cases and derive parameters from these. E.g. in - // - // enum HK[F[_]] { - // case C1(x: F[Int]) extends HK[F[Int]] - // case C2(y: F[String]) extends HL[F[Int]] - // - // we would need evidence parameters for `F[Int]` and `F[String]` - // We should generate Eq instances with the techniques - // of typeclass derivation once that is available. - def eqInstance = { - val leftParams = constrTparams.map(derivedTypeParam(_, "$1")) - val rightParams = constrTparams.map(derivedTypeParam(_, "$2")) - val subInstances = - for ((param1, param2) <- leftParams `zip` rightParams if !isHK(param1)) - yield appliedRef(ref(defn.EqType), List(param1, param2), widenHK = true) - DefDef( - name = nme.eqInstance, - tparams = leftParams ++ rightParams, - vparamss = if (subInstances.isEmpty) Nil else List(makeImplicitParameters(subInstances)), - tpt = appliedTypeTree(ref(defn.EqType), - appliedRef(classTycon, leftParams) :: appliedRef(classTycon, rightParams) :: Nil), - rhs = ref(defn.EqModule.termRef)).withFlags(Synthetic | Implicit) - } - def eqInstances = if (isEnum) eqInstance :: Nil else Nil - // derived type classes of non-module classes go to their companions val (clsDerived, companionDerived) = if (mods.is(Module)) (impl.derived, Nil) else (Nil, impl.derived) @@ -595,7 +556,7 @@ object desugar { mdefs } - val companionMembers = defaultGetters ::: eqInstances ::: enumCases + val companionMembers = defaultGetters ::: enumCases // The companion object definitions, if a companion is needed, Nil otherwise. // companion definitions include: @@ -645,7 +606,7 @@ object desugar { } companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers) } - else if (companionMembers.nonEmpty || companionDerived.nonEmpty) + else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum) companionDefs(anyRef, companionMembers) else if (isValueClass) { impl.constr.vparamss match { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fd71bff9a696..f69f5defcdb2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1029,7 +1029,7 @@ class Namer { typer: Typer => if (impl.derived.nonEmpty) { val (derivingClass, derivePos) = original.removeAttachment(desugar.DerivingCompanion) match { - case Some(pos) => (cls.companionClass.asClass, pos) + case Some(pos) => (cls.companionClass.orElse(cls).asClass, pos) case None => (cls, impl.sourcePos.startPos) } val deriver = new Deriver(derivingClass, derivePos)(localCtx) diff --git a/tests/neg/derive-eq.scala b/tests/neg/derive-eq.scala index 27096b58dc16..c64ea6461f66 100644 --- a/tests/neg/derive-eq.scala +++ b/tests/neg/derive-eq.scala @@ -13,7 +13,6 @@ case class Triple[S, T, U] derives Eq object Test extends App { - Test1 implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] implicitly[Eq[Triple[One, One, One], Triple[Two, Two, Two]]] diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index 1ff70ed9abcf..74e3002223ef 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -24,7 +24,7 @@ enum E4 { case class C4() extends E4 // error: cannot extend enum case object O4 extends E4 // error: cannot extend enum -enum Option[+T] { +enum Option[+T] derives Eq { case Some(x: T) case None } diff --git a/tests/neg/i3976.scala b/tests/neg/i3976.scala index d1892749dc0a..ca476e34ab84 100644 --- a/tests/neg/i3976.scala +++ b/tests/neg/i3976.scala @@ -1,5 +1,5 @@ object Test { - enum Hoge[F[_]] { + enum Hoge[F[_]] derives Eq { case A extends Hoge[List] case B extends Hoge[[X] => String] } @@ -18,7 +18,7 @@ object Test { } object Test2 { - enum Hoge[F[G[_]]] { + enum Hoge[F[G[_]]] derives Eq { 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[_]]] { + enum Hoge[F[G[_]]] derives Eq { case A extends Hoge[[X] => List] // error: wrong kind case B extends Hoge[[X] => [Y] => String] // error: wrong kind } diff --git a/tests/neg/i4470a.scala b/tests/neg/i4470a.scala index b0c4b2c3d405..001b286a1d74 100644 --- a/tests/neg/i4470a.scala +++ b/tests/neg/i4470a.scala @@ -1,7 +1,7 @@ object RepeatedEnum { - enum Maybe { // error // error - case Foo + enum Maybe { // error + case Foo // error } enum Maybe { // error diff --git a/tests/neg/i4470b.scala b/tests/neg/i4470b.scala index 716f3edc1089..8a84c42478ee 100644 --- a/tests/neg/i4470b.scala +++ b/tests/neg/i4470b.scala @@ -1,10 +1,10 @@ object RepeatedExtendEnum { - enum Maybe[T] { // error // error + enum Maybe[T] derives Eq { // error // error case Foo extends Maybe[Int] } - enum Maybe[T] { // error + enum Maybe[T] derives Eq { // error case Foo extends Maybe[Int] } } diff --git a/tests/neg/i4470c.scala b/tests/neg/i4470c.scala index b5f222905bce..a77d185818ee 100644 --- a/tests/neg/i4470c.scala +++ b/tests/neg/i4470c.scala @@ -1,6 +1,6 @@ object DuplicatedEnum { - enum Maybe[+T] { // error // error - case Some(x: T) + enum Maybe[+T] { // error + case Some(x: T) // error } enum Maybe[+T] { // error diff --git a/tests/run/derive-eq.scala b/tests/pos/derive-eq.scala similarity index 71% rename from tests/run/derive-eq.scala rename to tests/pos/derive-eq.scala index ad58af4eaf84..30827425c640 100644 --- a/tests/run/derive-eq.scala +++ b/tests/pos/derive-eq.scala @@ -12,12 +12,12 @@ enum Lst[T] derives Eq { class Triple[S, T, U] derives Eq -object Test extends App { +object Test { implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] implicitly[Eq[Triple[One, One, One], Triple[Two, Two, Two]]] - val x: Triple[List[One], One, Two] = ??? - val y: Triple[List[Two], One, Two] = ??? - assert(x == y) + val x: Triple[Lst[One], One, Two] = ??? + val y: Triple[Lst[Two], One, Two] = ??? + x == y } From 941e697137f12aa972b0d982f4d61f00eb258a72 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 5 Feb 2019 10:30:29 +0100 Subject: [PATCH 03/10] Use `Eq.derived` as the universal Eq instance --- .../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 +- .../dotty/tools/dotc/util/SourceFile.scala | 2 +- compiler/test-resources/repl/i4184 | 2 +- .../multiversal-equality.md | 12 ++-- library/src/scala/Eq.scala | 72 +++++++++---------- tests/neg/EqualityStrawman1.scala | 14 ++-- tests/neg/derive-eq.scala | 2 +- tests/neg/equality.scala | 16 ++--- tests/neg/i5546.scala | 4 +- tests/pending/neg/EqualityStrawman2.scala | 22 +++--- tests/pos/derive-eq.scala | 2 +- 14 files changed, 79 insertions(+), 77 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 5caa389a8203..d70abd29f16a 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -67,7 +67,7 @@ import collection.mutable.ListBuffer */ object Denotations { - implicit def eqDenotation: Eq[Denotation, Denotation] = Eq + implicit def eqDenotation: Eq[Denotation, Denotation] = Eq.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 26c9d9d5e777..eab4118a9cfe 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -25,7 +25,7 @@ object Names { def toTermName: TermName } - implicit def eqName: Eq[Name, Name] = Eq + implicit def eqName: Eq[Name, Name] = Eq.derived /** A common superclass of Name and Symbol. After bootstrap, this should be * just the type alias Name | Symbol diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index f5fa0ba3edd9..6e6a4df8fdce 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -406,7 +406,7 @@ trait Symbols { this: Context => object Symbols { - implicit def eqSymbol: Eq[Symbol, Symbol] = Eq + implicit def eqSymbol: Eq[Symbol, Symbol] = Eq.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 b453533f5610..ee3562bcad33 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -40,7 +40,7 @@ object Types { @sharable private[this] var nextId = 0 - implicit def eqType: Eq[Type, Type] = Eq + implicit def eqType: Eq[Type, Type] = Eq.derived /** Main class representing types. * diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 07d3e886142c..f148de84844f 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -190,7 +190,7 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends } } object SourceFile { - implicit def eqSource: Eq[SourceFile, SourceFile] = Eq + implicit def eqSource: Eq[SourceFile, SourceFile] = Eq.derived implicit def fromContext(implicit ctx: Context): SourceFile = ctx.source diff --git a/compiler/test-resources/repl/i4184 b/compiler/test-resources/repl/i4184 index f177cc5d9da2..2d954a9090ed 100644 --- a/compiler/test-resources/repl/i4184 +++ b/compiler/test-resources/repl/i4184 @@ -2,7 +2,7 @@ scala> object foo { class Foo } // defined object foo scala> object bar { class Foo } // defined object bar -scala> implicit def eqFoo: Eq[foo.Foo, foo.Foo] = Eq +scala> implicit def eqFoo: Eq[foo.Foo, foo.Foo] = Eq.derived def eqFoo: Eq[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/docs/docs/reference/other-new-features/multiversal-equality.md b/docs/docs/reference/other-new-features/multiversal-equality.md index 709ee2f1543e..282fe3bc8205 100644 --- a/docs/docs/reference/other-new-features/multiversal-equality.md +++ b/docs/docs/reference/other-new-features/multiversal-equality.md @@ -29,7 +29,7 @@ would not typecheck if an implicit was declared like this for type `T` (or an analogous one for type `S`): ```scala -implicit def eqT: Eq[T, T] = Eq +implicit def eqT: Eq[T, T] = Eq.derived ``` This definition effectively says that value of type `T` can (only) be @@ -55,10 +55,10 @@ definitions below make values of type `A` and type `B` comparable with each other, but not comparable to anything else: ```scala -implicit def eqA : Eq[A, A] = Eq -implicit def eqB : Eq[B, B] = Eq -implicit def eqAB: Eq[A, B] = Eq -implicit def eqBA: Eq[B, A] = Eq +implicit def eqA : Eq[A, A] = Eq.derived +implicit def eqB : Eq[B, B] = Eq.derived +implicit def eqAB: Eq[A, B] = Eq.derived +implicit def eqBA: Eq[B, A] = Eq.derived ``` (As usual, the names of the implicit definitions don't matter, we have @@ -75,7 +75,7 @@ over all types that do not themselves have an `Eq` instance. `eqAny` is defined as follows: ```scala -def eqAny[L, R]: Eq[L, R] = Eq +def eqAny[L, R]: Eq[L, R] = Eq.derived ``` Even though `eqAny` is not declared implicit, the compiler will still diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala index f8a5e28561e1..1a7e485a4b31 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eq.scala @@ -11,8 +11,8 @@ sealed trait Eq[-L, -R] * can also be used as a value that's compatible with * any instance of `Eq`. */ -object Eq extends Eq[Any, Any] { - def derived: Eq[Any, Any] = Eq +object Eq { + object derived extends Eq[Any, Any] /** A fall-back "implicit" to compare values of any types. * Even though this method is not declared implicit, the compiler will @@ -20,43 +20,43 @@ object Eq extends Eq[Any, Any] { * or both `T` and `U` are Eq-free. A type `S` is Eq-free if there is no * implicit instance of type `Eq[S, S]`. */ - def eqAny[L, R]: Eq[L, R] = Eq + def eqAny[L, R]: Eq[L, R] = derived // Instances of `Eq` for common types - implicit def eqNumber : Eq[Number, Number] = Eq - implicit def eqString : Eq[String, String] = Eq - implicit def eqBoolean : Eq[Boolean, Boolean] = Eq - implicit def eqByte : Eq[Byte, Byte] = Eq - implicit def eqShort : Eq[Short, Short] = Eq - implicit def eqChar : Eq[Char, Char] = Eq - implicit def eqInt : Eq[Int, Int] = Eq - implicit def eqLong : Eq[Long, Long] = Eq - implicit def eqFloat : Eq[Float, Float] = Eq - implicit def eqDouble : Eq[Double, Double] = Eq - implicit def eqUnit : Eq[Unit, Unit] = Eq + implicit def eqNumber : Eq[Number, Number] = derived + implicit def eqString : Eq[String, String] = derived + implicit def eqBoolean : Eq[Boolean, Boolean] = derived + implicit def eqByte : Eq[Byte, Byte] = derived + implicit def eqShort : Eq[Short, Short] = derived + implicit def eqChar : Eq[Char, Char] = derived + implicit def eqInt : Eq[Int, Int] = derived + implicit def eqLong : Eq[Long, Long] = derived + implicit def eqFloat : Eq[Float, Float] = derived + implicit def eqDouble : Eq[Double, Double] = derived + implicit def eqUnit : Eq[Unit, Unit] = derived // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies - implicit def eqProxy : Eq[Proxy, Any] = Eq - - implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = Eq - implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = Eq - - implicit def eqByteNum : Eq[Byte, Number] = Eq - implicit def eqNumByte : Eq[Number, Byte] = Eq - implicit def eqCharNum : Eq[Char, Number] = Eq - implicit def eqNumChar : Eq[Number, Char] = Eq - implicit def eqShortNum : Eq[Short, Number] = Eq - implicit def eqNumShort : Eq[Number, Short] = Eq - implicit def eqIntNum : Eq[Int, Number] = Eq - implicit def eqNumInt : Eq[Number, Int] = Eq - implicit def eqLongNum : Eq[Long, Number] = Eq - implicit def eqNumLong : Eq[Number, Long] = Eq - implicit def eqFloatNum : Eq[Float, Number] = Eq - implicit def eqNumFloat : Eq[Number, Float] = Eq - implicit def eqDoubleNum: Eq[Double, Number] = Eq - implicit def eqNumDouble: Eq[Number, Double] = Eq - - implicit def eqSBoolJBool: Eq[Boolean, java.lang.Boolean] = Eq - implicit def eqJBoolSBool: Eq[java.lang.Boolean, Boolean] = Eq + implicit def eqProxy : Eq[Proxy, Any] = derived + + implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = derived + implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = derived + + implicit def eqByteNum : Eq[Byte, Number] = derived + implicit def eqNumByte : Eq[Number, Byte] = derived + implicit def eqCharNum : Eq[Char, Number] = derived + implicit def eqNumChar : Eq[Number, Char] = derived + implicit def eqShortNum : Eq[Short, Number] = derived + implicit def eqNumShort : Eq[Number, Short] = derived + implicit def eqIntNum : Eq[Int, Number] = derived + implicit def eqNumInt : Eq[Number, Int] = derived + implicit def eqLongNum : Eq[Long, Number] = derived + implicit def eqNumLong : Eq[Number, Long] = derived + implicit def eqFloatNum : Eq[Float, Number] = derived + implicit def eqNumFloat : Eq[Number, Float] = derived + implicit def eqDoubleNum: Eq[Double, Number] = derived + implicit def eqNumDouble: Eq[Number, Double] = derived + + implicit def eqSBoolJBool: Eq[Boolean, java.lang.Boolean] = derived + implicit def eqJBoolSBool: Eq[java.lang.Boolean, Boolean] = derived } diff --git a/tests/neg/EqualityStrawman1.scala b/tests/neg/EqualityStrawman1.scala index b1b6c0380a50..5a0c9aff02f2 100644 --- a/tests/neg/EqualityStrawman1.scala +++ b/tests/neg/EqualityStrawman1.scala @@ -8,8 +8,10 @@ object EqualityStrawman1 { @implicitNotFound("cannot compare value of type ${T} with a value outside its equality class") trait Impossible[T] - object Eq extends Eq[Any] - + object Eq { + object derived extends Eq[Any] + } + trait Base { def === (other: Any): Boolean = this.equals(other) def === [T <: CondEquals](other: T)(implicit ce: Impossible[T]): Boolean = ??? @@ -32,11 +34,11 @@ object EqualityStrawman1 { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - implicit def eqStr: Eq[Str] = Eq - //implicit def eqNum: Eq[Num] = Eq - implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq + implicit def eqStr: Eq[Str] = Eq.derived + //implicit def eqNum: Eq[Num] = Eq.derived + implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq.derived - implicit def eqEq[T <: Equals[T]]: Eq[T] = Eq + implicit def eqEq[T <: Equals[T]]: Eq[T] = Eq.derived def main(args: Array[String]): Unit = { val x = Str("abc") diff --git a/tests/neg/derive-eq.scala b/tests/neg/derive-eq.scala index c64ea6461f66..73be12769667 100644 --- a/tests/neg/derive-eq.scala +++ b/tests/neg/derive-eq.scala @@ -2,7 +2,7 @@ case class One() derives Eq case class Two() derives Eq -implied for Eq[One, Two] = Eq +implied for Eq[One, Two] = Eq.derived enum Lst[T] derives Eq { case Cons(x: T, xs: Lst[T]) diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index f1e31b167a6b..0fa8a432d32e 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -10,20 +10,20 @@ object equality { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - implicit def eqStr: Eq[Str, Str] = Eq - implicit def eqNum: Eq[Num, Num] = Eq - implicit def eqOption[T, U](implicit e: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + implicit def eqStr: Eq[Str, Str] = Eq.derived + implicit def eqNum: Eq[Num, Num] = Eq.derived + implicit def eqOption[T, U](implicit e: Eq[T, U]): Eq[Option[T], Option[U]] = Eq.derived case class PString(a: String) extends Proxy { def self = a } /* - implicit def eqString: Eq[String, String] = Eq - implicit def eqInt: Eq[Int, Int] = Eq - implicit def eqNumber: Eq[Number, Number] = Eq - implicit def eqIntNumber: Eq[Int, Number] = Eq - implicit def eqNumberInt: Eq[Number, Int] = Eq + implicit def eqString: Eq[String, String] = Eq.derived + implicit def eqInt: Eq[Int, Int] = Eq.derived + implicit def eqNumber: Eq[Number, Number] = Eq.derived + implicit def eqIntNumber: Eq[Int, Number] = Eq.derived + implicit def eqNumberInt: Eq[Number, Int] = Eq.derived */ def main(args: Array[String]): Unit = { Some(Other(3)) == None diff --git a/tests/neg/i5546.scala b/tests/neg/i5546.scala index bb6668e5a8a5..ff119abd7873 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: Eq[Meters, Meters] = Eq + implicit def eqM: Eq[Meters, Meters] = Eq.derived opaque type Feet = Double object Feet { def apply(d: Double): Feet = d } - implicit def eqF: Eq[Feet, Feet] = Eq + implicit def eqF: Eq[Feet, Feet] = Eq.derived def main(args: Array[String]): Unit = { println(Feet(3) == Meters(3)) // error: cannot compare diff --git a/tests/pending/neg/EqualityStrawman2.scala b/tests/pending/neg/EqualityStrawman2.scala index e72e675c29ab..50ccb5049f3d 100644 --- a/tests/pending/neg/EqualityStrawman2.scala +++ b/tests/pending/neg/EqualityStrawman2.scala @@ -7,23 +7,23 @@ object equality { def ===[U] (y: U)(implicit ce: Eq[T, U]) = x.equals(y) } - type EqEq[T] = Eq[T, T] + type EqEq[T] = Eq.derived[T, T] trait EqClass[T] - implicit def eqAny[T, U]: Eq[T, U] = Eq + implicit def eqAny[T, U]: Eq[T, U] = Eq.derived /* - implicit def mixedEq1[T, U](implicit ce: Eq[T, U]): Eq[T, Any] = Eq - implicit def mixedEq2[T, U](implicit ce: Eq[T, U]): Eq[Any, T] = Eq - implicit def mixedEq1alt : Eq[Any, EqClass] = Eq - implicit def mixedEq2 : Eq[EqClass, Any] = Eq - implicit def mixedEq2alt : Eq[EqClass, Any] = Eq + implicit def mixedEq1[T, U](implicit ce: Eq[T, U]): Eq[T, Any] = Eq.derived + implicit def mixedEq2[T, U](implicit ce: Eq[T, U]): Eq[Any, T] = Eq.derived + implicit def mixedEq1alt : Eq[Any, EqClass] = Eq.derived + implicit def mixedEq2 : Eq[EqClass, Any] = Eq.derived + implicit def mixedEq2alt : Eq[EqClass, Any] = Eq.derived implicit def mixedNull1[T]: Eq[T, Null] = Eq implicit def mixedNull2[T]: Eq[Null, T] = Eq */ implicit def eqString: Eq[String, String] = Eq - implicit def eqInt: Eq[Int, Int] = Eq - implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + implicit def eqInt: Eq[Int, Int] = Eq.derived + implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq.derived /* implicit def eqEq[UE, TE <: UE with EqClass[UE]]: Eq[TE, TE] = Eq @@ -37,8 +37,8 @@ object equality { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - //implicit def eqStr: Eq[Str, Str] = Eq - //implicit def eqNum: Eq[Num, Num] = Eq + //implicit def eqStr: Eq[Str, Str] = Eq.derived + //implicit def eqNum: Eq[Num, Num] = Eq.derived implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq */ diff --git a/tests/pos/derive-eq.scala b/tests/pos/derive-eq.scala index 30827425c640..18dd646cf3e8 100644 --- a/tests/pos/derive-eq.scala +++ b/tests/pos/derive-eq.scala @@ -2,7 +2,7 @@ case class One() derives Eq case class Two() derives Eq -implied for Eq[One, Two] = Eq +implied for Eq[One, Two] = Eq.derived enum Lst[T] derives Eq { case Cons(x: T, xs: Lst[T]) From 1d08aa1ca51e8579d66432e18dc108ebd955f3c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Feb 2019 13:43:57 +0100 Subject: [PATCH 04/10] Change rules for multiversal equality 1. Don't apply the lifting rules when strictEquality is set. 2. Be more strict about null checking 3. Compensate for (2) by changing errorTerm in parser so that it has an error type instead of Null. 4. Reorganize implementation to match new docs. --- .../dotty/tools/dotc/typer/Implicits.scala | 97 ++++++++++++++----- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ffd36af6feeb..f12c4053032e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -699,16 +699,65 @@ trait Implicits { self: Typer => if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext) else EmptyTree - /** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for - * either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution. + /** If `formal` is of the form Eq[T, U], try to synthesize an + * `Eq.eqAny[T, U]` as solution. */ def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = { - //println(i"synth eq $formal / ${formal.argTypes}%, %") + + /** Is there an `Eql[T, T]` instance, assuming -strictEquality? */ + def hasEq(tp: Type)(implicit ctx: Context): Boolean = { + val inst = inferImplicitArg(defn.EqType.appliedTo(tp, tp), span) + !inst.isEmpty && !inst.tpe.isError + } + + /** Can we assume the eqAny instance for `tp1`, `tp2`? + * This is the case if assumedCanEqual(tp1, tp2), or + * one of `tp1`, `tp2` has a reflexive `Eql` instance. + */ + def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = + assumedCanEqual(tp1, tp2) || { + val nestedCtx = ctx.fresh.addMode(Mode.StrictEquality) + !hasEq(tp1)(nestedCtx) && !hasEq(tp2)(nestedCtx) + } + + /** Is an `Eql[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */ + def canComparePredefinedClasses(cls1: ClassSymbol, cls2: ClassSymbol): Boolean = { + def cmpWithBoxed(cls1: ClassSymbol, cls2: ClassSymbol) = + cls2 == defn.boxedType(cls1.typeRef).symbol || + cls1.isNumericValueClass && cls2.derivesFrom(defn.BoxedNumberClass) + + if (cls1.isPrimitiveValueClass) + if (cls2.isPrimitiveValueClass) + cls1 == cls2 || cls1.isNumericValueClass && cls2.isNumericValueClass + else + cmpWithBoxed(cls1, cls2) + else if (cls2.isPrimitiveValueClass) + cmpWithBoxed(cls2, cls1) + else if (cls1 == defn.NullClass) + cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) + else if (cls2 == defn.NullClass) + cls1.derivesFrom(defn.ObjectClass) + else + false + } + + /** 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 + * interpret. + */ + def canComparePredefined(tp1: Type, tp2: Type) = + tp1.classSymbols.exists(cls1 => + tp2.classSymbols.exists(cls2 => canComparePredefinedClasses(cls1, cls2))) + formal.argTypes match { - case args @ (arg1 :: arg2 :: Nil) - if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) && - ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2)) => - ref(defn.Eq_eqAny).appliedToTypes(args).withSpan(span) + case args @ (arg1 :: arg2 :: Nil) => + List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span)) + if (canComparePredefined(arg1, arg2) + || + !strictEquality && + ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2))) + ref(defn.Eq_eqAny).appliedToTypes(args).withSpan(span) + else EmptyTree case _ => EmptyTree } @@ -737,14 +786,6 @@ trait Implicits { self: Typer => } } - def hasEq(tp: Type): Boolean = - inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, span).isSuccess - - def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = { - List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", span)) - assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) - } - /** If `formal` is of the form `scala.reflect.Generic[T]` for some class type `T`, * synthesize an instance for it. */ @@ -885,16 +926,16 @@ trait Implicits { self: Typer => em"parameter ${paramName} of $methodStr" } - private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { - def eqNullable: Boolean = { - val other = - if (ltp.isRef(defn.NullClass)) rtp - else if (rtp.isRef(defn.NullClass)) ltp - else NoType - - (other ne NoType) && !other.derivesFrom(defn.AnyValClass) - } + private def strictEquality(implicit ctx: Context): Boolean = + ctx.mode.is(Mode.StrictEquality) || + ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) + /** An Eql[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. + */ + private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { // 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 { @@ -910,7 +951,13 @@ trait Implicits { self: Typer => if (variance > 0) mapOver(t) else t } } - ltp.isError || rtp.isError || ltp <:< lift(rtp) || rtp <:< lift(ltp) || eqNullable + + ltp.isError || + rtp.isError || + !strictEquality && { + ltp <:< lift(rtp) || + rtp <:< lift(ltp) + } } /** Check that equality tests between types `ltp` and `rtp` make sense */ From c45bd8a08f320093ca6328de63e0682feb21fab7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Feb 2019 15:08:11 +0100 Subject: [PATCH 05/10] Change rules for multiversal equality 1. Don't apply the lifting rules when strictEquality is set. 2. Be more strict about null checking 3. Compensate for (2) by changing errorTerm in parser so that it has an error type instead of Null. 4. Reorganize implementation to match new docs. 5. Handle Eql instances involving primitive types or Null directly in the compiler (previously, we were missing some of them, see pos/multivarsal.scala as a test) 6. Sharpen Eql instance of Proxy to only work for AnyRef arguments. This avoids false negatives when comparing value types with null. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 3 + .../multiversal-equality.md | 164 +++++++++++------- library/src/scala/Eq.scala | 53 ++---- tests/neg/equality.scala | 8 +- tests/pos/multiversal.scala | 30 ++++ tests/run/lst/Lst.scala | 2 +- 6 files changed, 153 insertions(+), 107 deletions(-) create mode 100644 tests/pos/multiversal.scala diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index c23fbb4f4ad8..5afd3aa68b7e 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -52,6 +52,9 @@ object Mode { /** Allow GADTFlexType labelled types to have their bounds adjusted */ val GADTflexible: Mode = newMode(8, "GADTflexible") + /** Assume -language:strictEquality */ + val StrictEquality: Mode = newMode(9, "StrictEquality") + /** We are currently printing something: avoid to produce more logs about * the printing */ diff --git a/docs/docs/reference/other-new-features/multiversal-equality.md b/docs/docs/reference/other-new-features/multiversal-equality.md index 282fe3bc8205..739230bd6963 100644 --- a/docs/docs/reference/other-new-features/multiversal-equality.md +++ b/docs/docs/reference/other-new-features/multiversal-equality.md @@ -9,9 +9,9 @@ the fact that `==` and `!=` are implemented in terms of Java's `equals` method, which can also compare values of any two reference types. -Universal equality is convenient but also dangerous since it -undermines type safety. Say you have an erroneous program where -a value `y` has type `S` instead of the expected type `T`. +Universal equality is convenient. But it is also dangerous since it +undermines type safety. For instance, let's assume one is left after some refactoring +with an erroneous program where a value `y` has type `S` instead of the correct type `T`. ```scala val x = ... // of type T @@ -19,102 +19,140 @@ val y = ... // of type S, but should be T x == y // typechecks, will always yield false ``` -If all you do with `y` is compare it to other values of type `T`, the program will -typecheck but probably give unexpected results. +If all the program does with `y` is compare it to other values of type `T`, the program will still typecheck, since values of all types can be compared with each other. +But it will probably give unexpected results and fail at runtime. Multiversal equality is an opt-in way to make universal equality -safer. The idea is that by declaring an `implicit` value one can -restrict the types that are legal in comparisons. The example above -would not typecheck if an implicit was declared like this for type `T` -(or an analogous one for type `S`): - +safer. It uses a binary typeclass `Eql` 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. ```scala -implicit def eqT: Eq[T, T] = Eq.derived +class T derives Eql ``` - -This definition effectively says that value of type `T` can (only) be -compared with `==` or `!=` to other values of type `T`. The definition -is used only for type checking; it has no significance for runtime +Alternatively, one can also provide the derived implied instance directly, like this: +```scala +implied for Eql[T, T] = Eql.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 of the definition is a value -that has any `Eq` instance as its type. Here is the definition of class -`Eq` and its companion object: - +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: ```scala package scala import annotation.implicitNotFound @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") -sealed trait Eq[-L, -R] +sealed trait Eql[-L, -R] -object Eq extends Eq[Any, Any] +object Eql { + object derived extends Eql[Any, Any] +} ``` -One can have several `Eq` instances for a type. For example, the four +One can have several `Eql` 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 -implicit def eqA : Eq[A, A] = Eq.derived -implicit def eqB : Eq[B, B] = Eq.derived -implicit def eqAB: Eq[A, B] = Eq.derived -implicit def eqBA: Eq[B, A] = Eq.derived +implied for Eql[A, A] = Eql.derived +implied for Eql[B, B] = Eql.derived +implied for Eql[A, B] = Eql.derived +implied for Eql[B, A] = Eql.derived ``` +The `scala.Eql` object defines a number of `Eql` instances that together +define a rule book for what standard types can be compared (more details below). -(As usual, the names of the implicit definitions don't matter, we have -chosen `eqA`, ..., `eqBA` only for illustration). - -The `scala.Eq` object defines a number of `Eq` implicits that make -values of types `String`, `Boolean` and `Unit` only comparable to -values of the same type. They also make numbers only comparable to -other numbers, sequences only comparable to other -sequences and sets only comparable to other sets. - -There's also a "fallback" instance named `eqAny` that allows comparisons -over all types that do not themselves have an `Eq` instance. `eqAny` is +There's also a "fallback" instance named `eqlAny` that allows comparisons +over all types that do not themselves have an `Eql` instance. `eqlAny` is defined as follows: ```scala -def eqAny[L, R]: Eq[L, R] = Eq.derived +def eqlAny[L, R]: Eql[L, R] = Eql.derived ``` -Even though `eqAny` is not declared implicit, the compiler will still -construct an `eqAny` instance as answer to an implicit search for the -type `Eq[L, R]`, provided that neither `L` nor `R` have `Eq` instances -defined on them. +Even though `eqlAny` is not declared `implied`, 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 +defined on them, or the language feature `strictEquality` is enabled -The primary motivation for having `eqAny` is backwards compatibility, -if this is of no concern one can disable `eqAny` by enabling the language +The primary motivation for having `eqlAny` is backwards compatibility, +if this is of no concern one can disable `eqlAny` by enabling the language feature `strictEquality`. As for all language features this can be either done with an import ```scala import scala.language.strictEquality ``` - or with a command line option `-language:strictEquality`. -All `enum` types also come with `Eq` instances that make values of the -`enum` type comparable only to other values of that `enum` type. +## Deriving Eql Instances + +Instead of defining `Eql` instances directly, it is often more convenient to derive them. Example: +```scala +class Box[T](x: T) derives Eql +``` +By the usual rules if [typeclass derivation](./derivation.html), +this generates the following `Eql` instance in the companion object of `Box`: +```scala +implied [T, U] given Eql[T, U] for Eql[Box[T], Box[U]] = Eql.derived +``` +That is, two boxes are comparable with `==` or `!=` if their elements are. Examples: +```scala +new Box(1) == new Box(1L) // ok since `Eql[Int, Long]` is an implied instance +new Box(1) == new Box("a") // error: can't compare +new Box(1) == 1 // error: can't compare +``` + +## Precise Rules for Equality Checking The precise rules for equality checking are as follows. - 1. A comparison using `x == y` or `x != y` between values `x: T` and `y: U` - is legal if either `T` and `U` are the same, or one of the types is a subtype - of the "lifted" version of the other type, or an implicit value of type `scala.Eq[T, U]` is found. - See the [description on Github](https://github.com/lampepfl/dotty/issues/1247) for - a definition of lifting. - - 2. The usual rules for implicit search apply also to `Eq` instances, - with one modification: If the `strictEquality` feature is not enabled, - an instance of `scala.Eq.eqAny[T, U]` is constructed if neither `T` - nor `U` have a reflexive `Eq` instance themselves. Here, a type `T` - has a reflexive `Eq` instance if the implicit search for `Eq[T, T]` - succeeds and constructs an instance different from `eqAny`. - - Here _lifting_ a type `S` means replacing all references to abstract types - in covariant positions of `S` by their upper bound, and to replacing - all refinement types in covariant positions of `S` by their parent. +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 + + 1. there is an implied instance of type `Eql[T, U]`, or + 2. one of `T`, `U` is `Null`. + +In the default case where the `strictEquality` feature is not enabled the comparison is +also legal if + + 1. `T` and `U` the same, or + 2. one of `T` and `U`is a subtype of the _lifted_ version of the other type, or + 3. neither `T` nor `U` have a _reflexive `Eql` instance_. + +Explanations: + + - _lifting_ a type `S` means replacing all references to abstract types + in covariant positions of `S` by their upper bound, and to 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]` + succeeds. + +## Predefined Eql Instances + +The `Eql` object defines implied instances for + - 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`. + +Implied instances are defined so that everyone of these types is has a reflexive `Eql` 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_). + - `Boolean` can be compared with `java.lang.Boolean` (and _vice versa_). + - `Char` can be compared with `java.lang.Character` (and _vice versa_). + - Two sequences (of arbitrary subtypes of `scala.collection.Seq`) can be compared + with each other if their element types can be compared. The two sequence types + need not be the same. + - Two sets (of arbitrary subtypes of `scala.collection.Set`) can be compared + with each other if their element types can be compared. The two set types + need not be the same. + - Any subtype of `AnyRef` can be compared with `Null` (and _vice versa_). More on multiversal equality is found in a [blog post](http://www.scala-lang.org/blog/2016/05/06/multiversal-equality.html) and a [Github issue](https://github.com/lampepfl/dotty/issues/1247). diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala index 1a7e485a4b31..8dfb13845a51 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eq.scala @@ -7,56 +7,31 @@ import scala.collection.{GenSeq, Set} @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") sealed trait Eq[-L, -R] -/** Besides being a companion object, this object - * can also be used as a value that's compatible with - * any instance of `Eq`. +/** 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.synthesizedEq), so they are not included here. */ object Eq { + /** A non-implied universal `Eql` instance. */ object derived extends Eq[Any, Any] - /** A fall-back "implicit" to compare values of any types. - * Even though this method is not declared implicit, the compiler will - * compute instances as solutions to `Eq[T, U]` queries if `T <: U` or `U <: T` - * or both `T` and `U` are Eq-free. A type `S` is Eq-free if there is no - * implicit instance of type `Eq[S, S]`. + /** A fall-back instance to compare values of any types. + * Even though this method is not declared implied, the compiler will + * compute implied instances as solutions to `Eql[T, U]` queries if + * the rules of multiversal equality require it. */ def eqAny[L, R]: Eq[L, R] = derived - // Instances of `Eq` for common types - + // Instances of `Eq` for common Java types implicit def eqNumber : Eq[Number, Number] = derived implicit def eqString : Eq[String, String] = derived - implicit def eqBoolean : Eq[Boolean, Boolean] = derived - implicit def eqByte : Eq[Byte, Byte] = derived - implicit def eqShort : Eq[Short, Short] = derived - implicit def eqChar : Eq[Char, Char] = derived - implicit def eqInt : Eq[Int, Int] = derived - implicit def eqLong : Eq[Long, Long] = derived - implicit def eqFloat : Eq[Float, Float] = derived - implicit def eqDouble : Eq[Double, Double] = derived - implicit def eqUnit : Eq[Unit, Unit] = derived - - // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies - implicit def eqProxy : Eq[Proxy, Any] = derived + // The next three definitions can go into the companion objects of classes + // Seq, Set, and Proxy. For now they are here in order not to have to touch the + // source code of these classes implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = derived implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = derived - implicit def eqByteNum : Eq[Byte, Number] = derived - implicit def eqNumByte : Eq[Number, Byte] = derived - implicit def eqCharNum : Eq[Char, Number] = derived - implicit def eqNumChar : Eq[Number, Char] = derived - implicit def eqShortNum : Eq[Short, Number] = derived - implicit def eqNumShort : Eq[Number, Short] = derived - implicit def eqIntNum : Eq[Int, Number] = derived - implicit def eqNumInt : Eq[Number, Int] = derived - implicit def eqLongNum : Eq[Long, Number] = derived - implicit def eqNumLong : Eq[Number, Long] = derived - implicit def eqFloatNum : Eq[Float, Number] = derived - implicit def eqNumFloat : Eq[Number, Float] = derived - implicit def eqDoubleNum: Eq[Double, Number] = derived - implicit def eqNumDouble: Eq[Number, Double] = derived - - implicit def eqSBoolJBool: Eq[Boolean, java.lang.Boolean] = derived - implicit def eqJBoolSBool: Eq[java.lang.Boolean, Boolean] = derived + // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies + implicit def eqProxy : Eq[Proxy, AnyRef] = derived } diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 0fa8a432d32e..5838bc56e392 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -58,10 +58,10 @@ object equality { 1 == true // error - null == true // OK by eqProxy or eqJBoolSBool - true == null // OK by eqSBoolJBool - null == 1 // OK by eqProxy or eqNumInt - 1 == null // OK by eqIntNum + null == true // error + true == null // error + null == 1 // error + 1 == null // error class Fruit derives Eq diff --git a/tests/pos/multiversal.scala b/tests/pos/multiversal.scala new file mode 100644 index 000000000000..f845bbb07a12 --- /dev/null +++ b/tests/pos/multiversal.scala @@ -0,0 +1,30 @@ +object Test { + import scala.Eq + + implied [X, Y] given Eq[X, Y] for Eq[List[X], List[Y]] = Eq.derived + + val b: Byte = 1 + val c: Char = 2 + val i: Int = 3 + val l: Long = 4L + val ii: Integer = i + + List(b) == List(l) + List(l) == List(c) + List(b) != List(c) + List(i) == List(l) + List(i) == List(ii) + List(ii) == List(l) + List(b) == List(ii) + List(ii) == List(l) + + import reflect.ClassTag + val BooleanTag: ClassTag[Boolean] = ClassTag.Boolean + + class Setting[T: ClassTag] { + def doSet() = implicitly[ClassTag[T]] match { + case BooleanTag => + case _ => + } + } +} \ No newline at end of file diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 8887c83d078e..63a8236ef211 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -675,7 +675,7 @@ object Lst { def fromIterable[T](xs: Iterable[T]): Lst[T] = fromIterator(xs.iterator) object :: { - def unapply[T](xs: Lst[T]): Option[(T, Lst[T])] = xs match { + def unapply[T](xs: Lst[T]): Option[(T, Lst[T])] = xs.elems match { case null => None case elems: Arr => Some((elems(0).asInstanceOf[T], _fromArray[T](elems, 1, elems.length))) From 996f6a6a87c4c9eeb28567a1ddab631be3255e6d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Feb 2019 15:27:17 +0100 Subject: [PATCH 06/10] Fix neg tests Some neg tests now return more errors since we do stricter checking that comparisons with null make sense. There's an unfortunate interaction with `errorTerm` in Parser which returns a `null` literal as a tree when the parser gets confused. That null literal now causes follow on errors in some cases. I believe the right thing to do would be to let `errorTerm` return a tree with an `ErrorType`. I tried that, and it would remove again all the added errors in this commit. Unfortunately, it breaks SignatureHelpTest in a way I cannot fix. --- tests/neg/errpos.scala | 2 +- tests/neg/i1846.scala | 20 ++++++++++---------- tests/neg/i3812.scala | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/neg/errpos.scala b/tests/neg/errpos.scala index 7e7092fc7f2c..2ad13feae425 100644 --- a/tests/neg/errpos.scala +++ b/tests/neg/errpos.scala @@ -10,7 +10,7 @@ object Test { val b = type // error: expression expected (on "type") 1 match { - case // error: pattern expected + case // error: pattern expected // error: cannot compare with Null case 2 => "" } } \ No newline at end of file diff --git a/tests/neg/i1846.scala b/tests/neg/i1846.scala index f661a86d5daf..b7cdc9f75136 100644 --- a/tests/neg/i1846.scala +++ b/tests/neg/i1846.scala @@ -3,17 +3,17 @@ object Test { val x = 42 val Y = "42" - x match { case { 42 } => () } // error - x match { case { 42.toString } => () } // error - x match { case { 42 }.toString => () } // error + x match { case { 42 } => () } // error // error + x match { case { 42.toString } => () } // error // error + x match { case { 42 }.toString => () } // error // error x match { case "42".toInt => () } // error - x match { case { "42".toInt } => () } // error - x match { case { "42" }.toInt => () } // error - x match { case { "42".toInt } => () } // error + x match { case { "42".toInt } => () } // error // error + x match { case { "42" }.toInt => () } // error // error + x match { case { "42".toInt } => () } // error // error x match { case Y => () } // error - x match { case { Y.toInt } => () } // error - x match { case { Y }.toInt => () } // error - x match { case { Y }.toString => () } // error - x match { case { Y.toString } => () } // error + x match { case { Y.toInt } => () } // error // error + x match { case { Y }.toInt => () } // error // error + x match { case { Y }.toString => () } // error // error + x match { case { Y.toString } => () } // error // error } } diff --git a/tests/neg/i3812.scala b/tests/neg/i3812.scala index 182721d65ea0..d8cc21ec7293 100644 --- a/tests/neg/i3812.scala +++ b/tests/neg/i3812.scala @@ -3,11 +3,11 @@ object Test { val x = 42 val Y = "42" - x match { case { 42 } => () } // error - x match { case { "42".toInt } => () } // error - x match { case { "42" }.toInt => () } // error - x match { case { "42".toInt } => () } // error - x match { case { Y.toInt } => () } // error - x match { case { Y }.toInt => () } // error + x match { case { 42 } => () } // error // error + x match { case { "42".toInt } => () } // error // error + x match { case { "42" }.toInt => () } // error // error + x match { case { "42".toInt } => () } // error // error + x match { case { Y.toInt } => () } // error // error + x match { case { Y }.toInt => () } // error // error } } From 7c49b4432c2b7c0483ea32b5d798263f06587fba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Feb 2019 16:14:03 +0100 Subject: [PATCH 07/10] Move equality reference doc under contextual abstractions Sine it now uses typeclass derivation, it makes sense if it comes directly afterwards. --- .../multiversal-equality.md | 0 docs/sidebar.yml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/docs/reference/{other-new-features => contextual}/multiversal-equality.md (100%) diff --git a/docs/docs/reference/other-new-features/multiversal-equality.md b/docs/docs/reference/contextual/multiversal-equality.md similarity index 100% rename from docs/docs/reference/other-new-features/multiversal-equality.md rename to docs/docs/reference/contextual/multiversal-equality.md diff --git a/docs/sidebar.yml b/docs/sidebar.yml index b73d695be4af..b6e08a949fb4 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -53,6 +53,8 @@ sidebar: url: docs/reference/contextual/typeclasses.html - title: Typeclass Derivation url: docs/reference/contextual/derivation.html + - title: Multiversal Equality + url: docs/reference/contextual/multiversal-equality.html - title: Context Queries url: docs/reference/contextual/query-types.html - title: Implicit Conversions @@ -63,8 +65,6 @@ sidebar: url: docs/reference/contextual/relationship-implicits.html - title: Other New Features subsection: - - title: Multiversal Equality - url: docs/reference/other-new-features/multiversal-equality.html - title: Trait Parameters url: docs/reference/other-new-features/trait-parameters.html - title: Inlining by Rewriting From a9ef28c4fbb28963ea6ba30869d076aac263b690 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Feb 2019 20:15:19 +0100 Subject: [PATCH 08/10] Rename Eq -> Eql Eq is already used with a slightly different meaning as a unary typeclass, e.g. cats.Eq. --- .../dotty/tools/dotc/core/Definitions.scala | 8 ++-- .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../src/dotty/tools/dotc/core/Names.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 2 +- .../src/dotty/tools/dotc/core/Symbols.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 18 ++++----- .../dotty/tools/dotc/util/SourceFile.scala | 2 +- compiler/test-resources/repl/i4184 | 4 +- docs/docs/reference/enums/desugarEnums.md | 9 ----- library/src/scala/{Eq.scala => Eql.scala} | 20 +++++----- tests/neg/EqualityStrawman1.scala | 20 +++++----- tests/neg/derive-eq.scala | 14 +++---- tests/neg/enums.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 +- 21 files changed, 95 insertions(+), 104 deletions(-) rename library/src/scala/{Eq.scala => Eql.scala} (68%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 79d0e4afedc7..d3b9b655b65f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -734,11 +734,11 @@ class Definitions { lazy val TastyReflectionModule: TermSymbol = ctx.requiredModule("scala.tasty.Reflection") lazy val TastyReflection_macroContext: TermSymbol = TastyReflectionModule.requiredMethod("macroContext") - lazy val EqType: TypeRef = ctx.requiredClassRef("scala.Eq") - def EqClass(implicit ctx: Context): ClassSymbol = EqType.symbol.asClass - def EqModule(implicit ctx: Context): Symbol = EqClass.companionModule + lazy val EqlType: TypeRef = ctx.requiredClassRef("scala.Eql") + def EqlClass(implicit ctx: Context): ClassSymbol = EqlType.symbol.asClass + def EqlModule(implicit ctx: Context): Symbol = EqlClass.companionModule - def Eq_eqAny(implicit ctx: Context): TermSymbol = EqModule.requiredMethod(nme.eqAny) + def Eql_eqlAny(implicit ctx: Context): TermSymbol = EqlModule.requiredMethod(nme.eqlAny) lazy val NotType: TypeRef = ctx.requiredClassRef("scala.implicits.Not") def NotClass(implicit ctx: Context): ClassSymbol = NotType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index d70abd29f16a..9b9969f1f5c8 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -67,7 +67,7 @@ import collection.mutable.ListBuffer */ object Denotations { - implicit def eqDenotation: Eq[Denotation, Denotation] = Eq.derived + implicit def eqDenotation: Eql[Denotation, Denotation] = Eql.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 eab4118a9cfe..8c76620a5ae5 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -25,7 +25,7 @@ object Names { def toTermName: TermName } - implicit def eqName: Eq[Name, Name] = Eq.derived + implicit def eqName: Eql[Name, Name] = Eql.derived /** A common superclass of Name and Symbol. After bootstrap, this should be * just the type alias Name | Symbol diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 3d3da013af4b..9330a46da568 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -418,7 +418,7 @@ object StdNames { val equals_ : N = "equals" val error: N = "error" val eval: N = "eval" - val eqAny: N = "eqAny" + val eqlAny: N = "eqlAny" val ex: N = "ex" val experimental: N = "experimental" val f: N = "f" diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 6e6a4df8fdce..e97a9866c224 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -406,7 +406,7 @@ trait Symbols { this: Context => object Symbols { - implicit def eqSymbol: Eq[Symbol, Symbol] = Eq.derived + implicit def eqSymbol: Eql[Symbol, Symbol] = Eql.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 ee3562bcad33..3cb3f45f5f5b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -40,7 +40,7 @@ object Types { @sharable private[this] var nextId = 0 - implicit def eqType: Eq[Type, Type] = Eq.derived + implicit def eqType: Eql[Type, Type] = Eql.derived /** Main class representing types. * diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f12c4053032e..b29e905e655b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -699,18 +699,18 @@ trait Implicits { self: Typer => if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext) else EmptyTree - /** If `formal` is of the form Eq[T, U], try to synthesize an - * `Eq.eqAny[T, U]` as solution. + /** If `formal` is of the form Eql[T, U], try to synthesize an + * `Eql.eqlAny[T, U]` as solution. */ def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = { /** Is there an `Eql[T, T]` instance, assuming -strictEquality? */ def hasEq(tp: Type)(implicit ctx: Context): Boolean = { - val inst = inferImplicitArg(defn.EqType.appliedTo(tp, tp), span) + val inst = inferImplicitArg(defn.EqlType.appliedTo(tp, tp), span) !inst.isEmpty && !inst.tpe.isError } - /** Can we assume the eqAny instance for `tp1`, `tp2`? + /** Can we assume the eqlAny instance for `tp1`, `tp2`? * This is the case if assumedCanEqual(tp1, tp2), or * one of `tp1`, `tp2` has a reflexive `Eql` instance. */ @@ -756,7 +756,7 @@ trait Implicits { self: Typer => || !strictEquality && ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2))) - ref(defn.Eq_eqAny).appliedToTypes(args).withSpan(span) + ref(defn.Eql_eqlAny).appliedToTypes(args).withSpan(span) else EmptyTree case _ => EmptyTree @@ -817,7 +817,7 @@ trait Implicits { self: Typer => trySpecialCase(defn.QuotedTypeClass, synthesizedTypeTag, trySpecialCase(defn.GenericClass, synthesizedGeneric, trySpecialCase(defn.TastyReflectionClass, synthesizedTastyContext, - trySpecialCase(defn.EqClass, synthesizedEq, + trySpecialCase(defn.EqlClass, synthesizedEq, trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed)))))) } } @@ -963,8 +963,8 @@ trait Implicits { self: Typer => /** Check that equality tests between types `ltp` and `rtp` make sense */ def checkCanEqual(ltp: Type, rtp: Type, span: Span)(implicit ctx: Context): Unit = if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) { - val res = implicitArgTree(defn.EqType.appliedTo(ltp, rtp), span) - implicits.println(i"Eq witness found for $ltp / $rtp: $res: ${res.tpe}") + val res = implicitArgTree(defn.EqlType.appliedTo(ltp, rtp), span) + implicits.println(i"Eql witness found for $ltp / $rtp: $res: ${res.tpe}") } /** Find an implicit parameter or conversion. @@ -1032,7 +1032,7 @@ trait Implicits { self: Typer => if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. - private def isCoherent = pt.isRef(defn.EqClass) + private def isCoherent = pt.isRef(defn.EqlClass) private val cmpContext = nestedContext() private val cmpCandidates = (c1: Candidate, c2: Candidate) => compare(c1.ref, c2.ref, c1.level, c2.level)(cmpContext) diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index f148de84844f..4600cfcd47a3 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -190,7 +190,7 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends } } object SourceFile { - implicit def eqSource: Eq[SourceFile, SourceFile] = Eq.derived + implicit def eqSource: Eql[SourceFile, SourceFile] = Eql.derived implicit def fromContext(implicit ctx: Context): SourceFile = ctx.source diff --git a/compiler/test-resources/repl/i4184 b/compiler/test-resources/repl/i4184 index 2d954a9090ed..c18b804d69ff 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: Eq[foo.Foo, foo.Foo] = Eq.derived -def eqFoo: Eq[foo.Foo, foo.Foo] +scala> implicit def eqFoo: Eql[foo.Foo, foo.Foo] = Eql.derived +def eqFoo: Eql[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/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index 5068bc0047fa..f2ab5982f08e 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -134,15 +134,6 @@ map into case classes or vals. where `n` is the ordinal number of the case in the companion object, starting from 0. -### Equality - -An `enum` type contains a `scala.Eq` instance that restricts values of the `enum` type to -be compared only to other values of the same enum type. Furtermore, generic -`enum` types are comparable only if their type arguments are. For instance the -`Option` enum type will get the following definition in its companion object: - - implicit def eqOption[T, U](implicit ev1: Eq[T, U]): Eq[Option[T], Option[U]] = Eq - ### Translation of Enumerations Non-generic enums `E` that define one or more singleton cases diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eql.scala similarity index 68% rename from library/src/scala/Eq.scala rename to library/src/scala/Eql.scala index 8dfb13845a51..d9ef982e6827 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eql.scala @@ -5,33 +5,33 @@ import scala.collection.{GenSeq, 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 Eq[-L, -R] +sealed trait Eql[-L, -R] /** 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.synthesizedEq), so they are not included here. */ -object Eq { +object Eql { /** A non-implied universal `Eql` instance. */ - object derived extends Eq[Any, Any] + object derived extends Eql[Any, Any] /** A fall-back instance to compare values of any types. * Even though this method is not declared implied, the compiler will * compute implied instances as solutions to `Eql[T, U]` queries if * the rules of multiversal equality require it. */ - def eqAny[L, R]: Eq[L, R] = derived + def eqlAny[L, R]: Eql[L, R] = derived - // Instances of `Eq` for common Java types - implicit def eqNumber : Eq[Number, Number] = derived - implicit def eqString : Eq[String, String] = derived + // Instances of `Eql` for common Java types + implicit def eqlNumber : Eql[Number, Number] = derived + implicit def eqlString : Eql[String, String] = derived // The next three definitions can go into the companion objects of classes // Seq, Set, and Proxy. For now they are here in order not to have to touch the // source code of these classes - implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = derived - implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = derived + implicit def eqlSeq[T, U](implicit eq: Eql[T, U]): Eql[GenSeq[T], GenSeq[U]] = derived + implicit def eqlSet[T, U](implicit eq: Eql[T, U]): Eql[Set[T], Set[U]] = derived // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies - implicit def eqProxy : Eq[Proxy, AnyRef] = derived + implicit def eqlProxy : Eql[Proxy, AnyRef] = derived } diff --git a/tests/neg/EqualityStrawman1.scala b/tests/neg/EqualityStrawman1.scala index 5a0c9aff02f2..395e578740ad 100644 --- a/tests/neg/EqualityStrawman1.scala +++ b/tests/neg/EqualityStrawman1.scala @@ -3,22 +3,22 @@ import annotation.implicitNotFound object EqualityStrawman1 { - trait Eq[-T] + trait Eql[-T] @implicitNotFound("cannot compare value of type ${T} with a value outside its equality class") trait Impossible[T] - object Eq { - object derived extends Eq[Any] + object Eql { + object derived extends Eql[Any] } - + trait Base { def === (other: Any): Boolean = this.equals(other) def === [T <: CondEquals](other: T)(implicit ce: Impossible[T]): Boolean = ??? } trait CondEquals extends Base { - def === [T >: this.type <: CondEquals](other: T)(implicit ce: Eq[T]): Boolean = this.equals(other) + def === [T >: this.type <: CondEquals](other: T)(implicit ce: Eql[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: Eq[Str] = Eq.derived - //implicit def eqNum: Eq[Num] = Eq.derived - implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq.derived + 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 eqEq[T <: Equals[T]]: Eq[T] = Eq.derived + implicit def eqEq[T <: Equals[T]]: Eql[T] = Eql.derived def main(args: Array[String]): Unit = { val x = Str("abc") @@ -59,7 +59,7 @@ object EqualityStrawman1 { None === z - def ddistinct[T <: Base: Eq](xs: List[T]): List[T] = xs match { + def ddistinct[T <: Base: Eql](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 73be12769667..f12761036aee 100644 --- a/tests/neg/derive-eq.scala +++ b/tests/neg/derive-eq.scala @@ -1,20 +1,20 @@ -case class One() derives Eq -case class Two() derives Eq +case class One() derives Eql +case class Two() derives Eql -implied for Eq[One, Two] = Eq.derived +implied for Eql[One, Two] = Eql.derived -enum Lst[T] derives Eq { +enum Lst[T] derives Eql { case Cons(x: T, xs: Lst[T]) case Nil() } -case class Triple[S, T, U] derives Eq +case class Triple[S, T, U] derives Eql object Test extends App { - implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] - implicitly[Eq[Triple[One, One, One], + implicitly[Eql[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[Eql[Triple[One, One, One], Triple[Two, Two, Two]]] val x: Triple[List[One], One, Two] = ??? diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index 74e3002223ef..0e120e2bc420 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -24,7 +24,7 @@ enum E4 { case class C4() extends E4 // error: cannot extend enum case object O4 extends E4 // error: cannot extend enum -enum Option[+T] derives Eq { +enum Option[+T] derives Eql { case Some(x: T) case None } diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 5838bc56e392..3d01418cd561 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -10,20 +10,20 @@ object equality { case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] - implicit def eqStr: Eq[Str, Str] = Eq.derived - implicit def eqNum: Eq[Num, Num] = Eq.derived - implicit def eqOption[T, U](implicit e: Eq[T, U]): Eq[Option[T], Option[U]] = Eq.derived + 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 case class PString(a: String) extends Proxy { def self = a } /* - implicit def eqString: Eq[String, String] = Eq.derived - implicit def eqInt: Eq[Int, Int] = Eq.derived - implicit def eqNumber: Eq[Number, Number] = Eq.derived - implicit def eqIntNumber: Eq[Int, Number] = Eq.derived - implicit def eqNumberInt: Eq[Number, Int] = Eq.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 */ def main(args: Array[String]): Unit = { Some(Other(3)) == None @@ -64,7 +64,7 @@ object equality { 1 == null // error - class Fruit derives Eq + class Fruit derives Eql class Apple extends Fruit class Pear extends Fruit diff --git a/tests/neg/i3976.scala b/tests/neg/i3976.scala index ca476e34ab84..6ffd7a9707a4 100644 --- a/tests/neg/i3976.scala +++ b/tests/neg/i3976.scala @@ -1,5 +1,5 @@ object Test { - enum Hoge[F[_]] derives Eq { + enum Hoge[F[_]] derives Eql { 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 typeclass drivation of `Eq` to get there. + A == B // should be error: cannot be compared, needs proper typeclass drivation of `Eql` to get there. class C @@ -18,7 +18,7 @@ object Test { } object Test2 { - enum Hoge[F[G[_]]] derives Eq { + enum Hoge[F[G[_]]] derives Eql { 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 Eq { + enum Hoge[F[G[_]]] derives Eql { 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 8a84c42478ee..7305f1018cbe 100644 --- a/tests/neg/i4470b.scala +++ b/tests/neg/i4470b.scala @@ -1,10 +1,10 @@ object RepeatedExtendEnum { - enum Maybe[T] derives Eq { // error // error + enum Maybe[T] derives Eql { // error // error case Foo extends Maybe[Int] } - enum Maybe[T] derives Eq { // error + enum Maybe[T] derives Eql { // error case Foo extends Maybe[Int] } } diff --git a/tests/neg/i5546.scala b/tests/neg/i5546.scala index ff119abd7873..4456feacc0ac 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: Eq[Meters, Meters] = Eq.derived + implicit def eqM: Eql[Meters, Meters] = Eql.derived opaque type Feet = Double object Feet { def apply(d: Double): Feet = d } - implicit def eqF: Eq[Feet, Feet] = Eq.derived + implicit def eqF: Eql[Feet, Feet] = Eql.derived def main(args: Array[String]): Unit = { println(Feet(3) == Meters(3)) // error: cannot compare diff --git a/tests/pending/neg/EqualityStrawman2.scala b/tests/pending/neg/EqualityStrawman2.scala index 50ccb5049f3d..52a7185158c3 100644 --- a/tests/pending/neg/EqualityStrawman2.scala +++ b/tests/pending/neg/EqualityStrawman2.scala @@ -1,32 +1,32 @@ object equality { - trait Eq[T, U] - def Eq[T, U]: Eq[T, U] = new Eq[T, U]{} + trait Eql[T, U] + def Eql[T, U]: Eql[T, U] = new Eql[T, U]{} implicit class EqualsDeco[T](val x: T) extends AnyVal { - def ===[U] (y: U)(implicit ce: Eq[T, U]) = x.equals(y) + def ===[U] (y: U)(implicit ce: Eql[T, U]) = x.equals(y) } - type EqEq[T] = Eq.derived[T, T] + type EqEq[T] = Eql.derived[T, T] trait EqClass[T] - implicit def eqAny[T, U]: Eq[T, U] = Eq.derived + implicit def eqAny[T, U]: Eql[T, U] = Eql.derived /* - implicit def mixedEq1[T, U](implicit ce: Eq[T, U]): Eq[T, Any] = Eq.derived - implicit def mixedEq2[T, U](implicit ce: Eq[T, U]): Eq[Any, T] = Eq.derived - implicit def mixedEq1alt : Eq[Any, EqClass] = Eq.derived - implicit def mixedEq2 : Eq[EqClass, Any] = Eq.derived - implicit def mixedEq2alt : Eq[EqClass, Any] = Eq.derived - implicit def mixedNull1[T]: Eq[T, Null] = Eq - implicit def mixedNull2[T]: Eq[Null, T] = Eq + 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 eqString: Eq[String, String] = Eq - implicit def eqInt: Eq[Int, Int] = Eq.derived - implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq.derived + 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 eqEq[UE, TE <: UE with EqClass[UE]]: Eq[TE, TE] = Eq + implicit def eqEq[UE, TE <: UE with EqClass[UE]]: Eql[TE, TE] = Eql 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: Eq[Str, Str] = Eq.derived - //implicit def eqNum: Eq[Num, Num] = Eq.derived + //implicit def eqStr: Eql[Str, Str] = Eql.derived + //implicit def eqNum: Eql[Num, Num] = Eql.derived - implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + implicit def eqOption[T, U](implicit ce: Eql[T, U]): Eql[Option[T], Option[U]] = Eql */ 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: Eq[Fruit, Fruit] = Eq + implicit def eqFruit: Eql[Fruit, Fruit] = Eql Some(new Apple) === Some(new Pear) diff --git a/tests/pos/derive-eq.scala b/tests/pos/derive-eq.scala index 18dd646cf3e8..5d590a3342e7 100644 --- a/tests/pos/derive-eq.scala +++ b/tests/pos/derive-eq.scala @@ -1,20 +1,20 @@ -case class One() derives Eq -case class Two() derives Eq +case class One() derives Eql +case class Two() derives Eql -implied for Eq[One, Two] = Eq.derived +implied for Eql[One, Two] = Eql.derived -enum Lst[T] derives Eq { +enum Lst[T] derives Eql { case Cons(x: T, xs: Lst[T]) case Nil() } -class Triple[S, T, U] derives Eq +class Triple[S, T, U] derives Eql object Test { - implicitly[Eq[Lst[Lst[One]], Lst[Lst[Two]]]] - implicitly[Eq[Triple[One, One, One], + implicitly[Eql[Lst[Lst[One]], Lst[Lst[Two]]]] + implicitly[Eql[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 f845bbb07a12..27197dbc6e2d 100644 --- a/tests/pos/multiversal.scala +++ b/tests/pos/multiversal.scala @@ -1,7 +1,7 @@ object Test { - import scala.Eq + import scala.Eql - implied [X, Y] given Eq[X, Y] for Eq[List[X], List[Y]] = Eq.derived + implied [X, Y] given Eql[X, Y] for Eql[List[X], List[Y]] = Eql.derived val b: Byte = 1 val c: Char = 2 From f791a527d4bdd03ba0d9614b3c420ebe7745ffdd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 8 Feb 2019 16:08:45 +0100 Subject: [PATCH 09/10] Avoid duplicating parameter names Avoid duplicating parameter names in derivations of n-ary type classes --- compiler/src/dotty/tools/dotc/typer/Deriving.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index add005ae2ba0..88b790cbb94d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -193,7 +193,7 @@ trait Deriving { this: Typer => if (nparams == 0) Nil else if (nparams == 1) tparam :: Nil else typeClass.typeParams.map(tcparam => - tparam.copy(name = s"${tparam.name}_${tcparam.name}".toTypeName) + tparam.copy(name = s"${tparam.name}_$$_${tcparam.name}".toTypeName) .asInstanceOf[TypeSymbol]) } val firstKindedParamss = clsParamss.filter { From 4bc1514334f083e6f5684eb5c8c3f2ea71f31a4a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 12 Feb 2019 17:10:19 +0100 Subject: [PATCH 10/10] update scalatest --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index cfc40267d9fb..f1147035fd11 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit cfc40267d9fbc6bc501f73554c7aeb1abefea3cd +Subproject commit f1147035fd11830eaf658d323d8bf49531a95721