From 9ca4c16d9bd146124aac0a8e855c648a23f5796b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Jun 2017 15:21:25 +0200 Subject: [PATCH 1/3] Fix #2698: Move eqXYZ instances to Eq --- library/src/dotty/DottyPredef.scala | 34 ++------------------------- library/src/scala/Eq.scala | 36 ++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index bb6989df9953..c372ddbc7c23 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -6,46 +6,16 @@ import scala.collection.Seq /** unimplemented implicit for TypeTag */ object DottyPredef { + /** A fall-back implicit to compare values of any types. * The compiler will restrict implicit instances of `eqAny`. An instance * `eqAny[T, U]` is _valid_ 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 `Eq[S, S]`. * An implicit search will fail instead of returning an invalid `eqAny` instance. + * The method is here instead of the `Eq` object so that it can be disabled. */ implicit def eqAny[L, R]: Eq[L, R] = Eq - 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 - - // 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[Seq[T], Seq[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 - /** A class for implicit values that can serve as implicit conversions * The implicit resolution algorithm will act as if there existed * the additional implicit definition: diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala index d6d617cab7c6..5746e1cb3440 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eq.scala @@ -10,5 +10,39 @@ 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] +object Eq extends Eq[Any, Any] { + // 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 + + // 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[Seq[T], Seq[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 +} \ No newline at end of file From adec8422852e65e68392ed0ccda5dad242e1e8a3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Jun 2017 19:26:04 +0200 Subject: [PATCH 2/3] Reorganize Eq checking Two main changes: - We treat `Eq` as coherent, i.e Eq searches are never ambiguous. Instead, the first found instance is returned. This would be compatible with an eventual user-declarable coherence construct, but does not require it. - Instead of invalidating found `eqAny` instances after the fact, we turn things around: `eqAny` is no longer defined `implicit`, but can nevertheless me synthesized for validEqAny` args, similar to how `ClassTag` is synthesized. This simplifies the logic and leads to better debugability of implicit searches. --- .../dotty/tools/dotc/core/Definitions.scala | 3 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/typer/Implicits.scala | 110 +++++++++--------- library/src/dotty/DottyPredef.scala | 11 -- library/src/scala/Eq.scala | 12 +- library/src/scalaShadowing/language.scala | 3 + tests/neg/equality.scala | 25 ++++ tests/neg/equality1.scala | 3 +- 8 files changed, 100 insertions(+), 68 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5e58e60c3603..17b5c64c9ca7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -347,7 +347,6 @@ class Definitions { def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol - def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny) lazy val Predef_ImplicitConverterR = DottyPredefModule.requiredClass("ImplicitConverter").typeRef def Predef_ImplicitConverter(implicit ctx: Context) = Predef_ImplicitConverterR.symbol @@ -574,6 +573,8 @@ class Definitions { def EqClass(implicit ctx: Context) = EqType.symbol.asClass def EqModule(implicit ctx: Context) = EqClass.companionModule + def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethod(nme.eqAny) + lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope") // Annotation base classes diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a0ab31b70105..04a540629f80 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -502,6 +502,7 @@ object StdNames { val staticClass : N = "staticClass" val staticModule : N = "staticModule" val staticPackage : N = "staticPackage" + val strictEquality: N = "strictEquality" val synchronized_ : N = "synchronized" val tag: N = "tag" val tail: N = "tail" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f0788665ca17..00d291487626 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -545,29 +545,49 @@ trait Implicits { self: Typer => /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. */ - def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { - if (formal.isRef(defn.ClassTagClass)) - formal.argTypes match { - case arg :: Nil => - fullyDefinedType(arg, "ClassTag argument", pos) match { - case defn.ArrayOf(elemTp) => - val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) - if (etag.isEmpty) etag else etag.select(nme.wrap) - case tp if hasStableErasure(tp) => - if (defn.isBottomClass(tp.typeSymbol)) - error(where => i"attempt to take ClassTag of undetermined type for $where") - ref(defn.ClassTagModule) - .select(nme.apply) - .appliedToType(tp) - .appliedTo(clsOf(erasure(tp))) - .withPos(pos) - case tp => - EmptyTree - } - case _ => - EmptyTree - } - else EmptyTree + def synthesizedClassTag(formal: Type)(implicit ctx: Context): Tree = + formal.argTypes match { + case arg :: Nil => + fullyDefinedType(arg, "ClassTag argument", pos) match { + case defn.ArrayOf(elemTp) => + val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) + if (etag.isEmpty) etag else etag.select(nme.wrap) + case tp if hasStableErasure(tp) => + if (defn.isBottomClass(tp.typeSymbol)) + error(where => i"attempt to take ClassTag of undetermined type for $where") + ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(erasure(tp))) + .withPos(pos) + case tp => + EmptyTree + } + case _ => + 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. + */ + def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = { + //println(i"synth eq $formal / ${formal.argTypes}%, %") + formal.argTypes match { + case args @ (arg1 :: arg2 :: Nil) + if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) && + validEqAnyArgs(arg1, arg2)(ctx.fresh.setExploreTyperState) => + ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos) + case _ => + EmptyTree + } + } + + def hasEq(tp: Type): Boolean = + inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isInstanceOf[SearchSuccess] + + def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = { + List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) + assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) } /** The context to be used when resolving a by-name implicit argument. @@ -606,7 +626,13 @@ trait Implicits { self: Typer => error(where => s"ambiguous implicits: ${ambi.explanation} of $where") EmptyTree case failure: SearchFailure => - val arg = synthesizedClassTag(formalValue, pos) + val arg = + if (formalValue.isRef(defn.ClassTagClass)) + synthesizedClassTag(formalValue) + else if (formalValue.isRef(defn.EqClass)) + synthesizedEq(formalValue) + else + EmptyTree if (!arg.isEmpty) arg else { var msgFn = (where: String) => @@ -638,10 +664,10 @@ trait Implicits { self: Typer => } val lift = new TypeMap { - def apply(t: Type) = t match { + def apply(t: Type): Type = t match { case t: TypeRef => t.info match { - case TypeBounds(lo, hi) if lo ne hi => hi + case TypeBounds(lo, hi) if lo ne hi => apply(hi) case _ => t } case _ => @@ -714,6 +740,8 @@ 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) + assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], em"found: $argument: ${argument.tpe}, expected: $pt") @@ -761,26 +789,7 @@ trait Implicits { self: Typer => case _ => false } } - // Does there exist an implicit value of type `Eq[tp, tp]` - // which is different from `eqAny`? - def hasEq(tp: Type): Boolean = { - def search(contextual: Boolean): Boolean = - new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos) - .bestImplicit(contextual) match { - case result: SearchSuccess => - result.ref.symbol != defn.Predef_eqAny || - contextual && search(contextual = false) - case result: AmbiguousImplicits => true - case _ => false - } - search(contextual = true) - } - def validEqAnyArgs(tp1: Type, tp2: Type) = { - List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) - assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) || - { implicits.println(i"invalid eqAny[$tp1, $tp2]"); false } - } if (ctx.reporter.hasErrors) nonMatchingImplicit(ref, ctx.reporter.removeBufferedMessages) else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) && @@ -788,13 +797,8 @@ trait Implicits { self: Typer => implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}") shadowedImplicit(ref, methPart(shadowing).tpe) } - else generated1 match { - case TypeApply(fn, targs @ (arg1 :: arg2 :: Nil)) - if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) => - nonMatchingImplicit(ref, Nil) - case _ => - SearchSuccess(generated1, ref, cand.level, ctx.typerState) - } + else + SearchSuccess(generated1, ref, cand.level, ctx.typerState) }} /** Given a list of implicit references, produce a list of all implicit search successes, @@ -812,7 +816,7 @@ trait Implicits { self: Typer => case fail: SearchFailure => rankImplicits(pending1, acc) case best: SearchSuccess => - if (ctx.mode.is(Mode.ImplicitExploration)) best :: Nil + if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil else { val newPending = pending1.filter(cand1 => isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext.setExploreTyperState)) diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index c372ddbc7c23..75bc60988b42 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -2,20 +2,9 @@ package dotty import scala.reflect.ClassTag import scala.Predef.??? -import scala.collection.Seq -/** unimplemented implicit for TypeTag */ object DottyPredef { - /** A fall-back implicit to compare values of any types. - * The compiler will restrict implicit instances of `eqAny`. An instance - * `eqAny[T, U]` is _valid_ 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 `Eq[S, S]`. - * An implicit search will fail instead of returning an invalid `eqAny` instance. - * The method is here instead of the `Eq` object so that it can be disabled. - */ - implicit def eqAny[L, R]: Eq[L, R] = Eq - /** A class for implicit values that can serve as implicit conversions * The implicit resolution algorithm will act as if there existed * the additional implicit definition: diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala index 5746e1cb3440..1e443e76f7db 100644 --- a/library/src/scala/Eq.scala +++ b/library/src/scala/Eq.scala @@ -1,6 +1,7 @@ package scala import annotation.implicitNotFound +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 !=") @@ -12,6 +13,14 @@ sealed trait Eq[-L, -R] */ object Eq 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]`. + */ + def eqAny[L, R]: Eq[L, R] = Eq + // Instances of `Eq` for common types implicit def eqNumber : Eq[Number, Number] = Eq @@ -29,7 +38,8 @@ object Eq extends Eq[Any, Any] { // 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[Seq[T], Seq[U]] = 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 diff --git a/library/src/scalaShadowing/language.scala b/library/src/scalaShadowing/language.scala index e2fc5ec61bf9..50d2bf069c04 100644 --- a/library/src/scalaShadowing/language.scala +++ b/library/src/scalaShadowing/language.scala @@ -195,4 +195,7 @@ object language { /** Where imported, auto-tupling is disabled */ object noAutoTupling + + /* Where imported loose equality using eqAny is disabled */ + object strictEquality } diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 680a12725dd0..e4042e9a7266 100644 --- a/tests/neg/equality.scala +++ b/tests/neg/equality.scala @@ -56,6 +56,14 @@ object equality { null == Str("x") null == null + 1 == true // error + + null == true // OK by eqProxy + true == null // error + null == 1 // OK by eqProxy or eqNumInt + 1 == null // OK by eqIntNum + + class Fruit implicit def eqFruit: Eq[Fruit, Fruit] = Eq @@ -105,5 +113,22 @@ object equality { "abc" == bi // error bi == "abc" // error "world" == ps // error + + val s1 = Set(1, 2, 3) + val s2 = Set() + + Nil == s1 // error + s1 == Nil // error + Nil == s2 // error + s2 == Nil // error + + import collection.parallel._ + val p1 = ParSeq(1, 2, 3) + val p2 = ParSeq() + Nil == p1 // OK + p1 == Nil // OK + Nil == p2 // OK + p2 == Nil // Ok + } } diff --git a/tests/neg/equality1.scala b/tests/neg/equality1.scala index e9bc2cbaaf1a..77ddb371051a 100644 --- a/tests/neg/equality1.scala +++ b/tests/neg/equality1.scala @@ -1,5 +1,4 @@ -import dotty.DottyPredef.{eqAny => _, _} - +import language.strictEquality object equality1 { class A class B From b8b201d5215a77ad6e0453b5da9198e1b70c53da Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Jun 2017 10:32:10 +0200 Subject: [PATCH 3/3] Update reference --- docs/docs/reference/multiversal-equality.md | 43 ++++++++++++--------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/docs/reference/multiversal-equality.md b/docs/docs/reference/multiversal-equality.md index c330d567e573..11def45aee5c 100644 --- a/docs/docs/reference/multiversal-equality.md +++ b/docs/docs/reference/multiversal-equality.md @@ -56,26 +56,31 @@ each other, but not comparable to anything else: (As usual, the names of the implicit definitions don't matter, we have chosen `eqA`, ..., `eqBA` only for illustration). -The `dotty.DottyPredef` object defines a number of `Eq` -implicits. `dotty.DottyPredef` is a temporary `Predef`-like object. -The contents of this object are by default imported into every -program. Once dotty becomes standard Scala, `DottyPredef` will go away -and its contents will be merged with `scala.Predef`. +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. -The `Eq` instances defined by `DottyPredef` 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, and -sequences only comparable to other sequences. There's also a -"fallback" instance `eqAny` that allows comparisons over types that do -not themeselves have an `Eq` instance. `eqAny` is defined as follows: +There's also a "fallback" instance named `eqAny` that allows comparisons +over all types that do not themeselves have an `Eq` instance. `eqAny` is +defined as follows: - implicit def eqAny[L, R]: Eq[L, R] = Eq + def eqAny[L, R]: Eq[L, R] = Eq + +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. The primary motivation for having `eqAny` is backwards compatibility, -if this is of no concern one can disable `eqAny` by unimporting it -from `DottyPredef` like this +if this is of no concern one can disable `eqAny` by enabling the language +feature `strictEquality`. As for all language features this can be either +done with an import + + import scala.language.strictEquality - import dotty.DottyPredef.{eqAny => _, _} +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. @@ -87,11 +92,11 @@ The precise rules for equality checking are as follows. of the other, or an implicit value of type `scala.Eq[T, U]` is found. 2. The usual rules for implicit search apply also to `Eq` instances, - with one modification: The value `eqAny` in `dotty.DottyPredef` is - eligible only if neither `T` nor `U` have a reflexive `Eq` + with one modification: 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]` where `eqAny` is - not eligible is successful. + instance if the implicit search for `Eq[T, T]` succeeds + and constructs an instance different from `eqAny`. More on multiversal equality is found in a [blog post] and a [Github issue].