Skip to content

Commit 73618f8

Browse files
authored
Merge pull request #2786 from dotty-staging/fix-#2698
Fix #2698: Move eqXYZ instances to Eq
2 parents 82767d1 + b8b201d commit 73618f8

File tree

9 files changed

+158
-117
lines changed

9 files changed

+158
-117
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,6 @@ class Definitions {
347347

348348
def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol
349349

350-
def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny)
351350
lazy val Predef_ImplicitConverterR = DottyPredefModule.requiredClass("ImplicitConverter").typeRef
352351
def Predef_ImplicitConverter(implicit ctx: Context) = Predef_ImplicitConverterR.symbol
353352

@@ -574,6 +573,8 @@ class Definitions {
574573
def EqClass(implicit ctx: Context) = EqType.symbol.asClass
575574
def EqModule(implicit ctx: Context) = EqClass.companionModule
576575

576+
def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethod(nme.eqAny)
577+
577578
lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")
578579

579580
// Annotation base classes

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ object StdNames {
502502
val staticClass : N = "staticClass"
503503
val staticModule : N = "staticModule"
504504
val staticPackage : N = "staticPackage"
505+
val strictEquality: N = "strictEquality"
505506
val synchronized_ : N = "synchronized"
506507
val tag: N = "tag"
507508
val tail: N = "tail"

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -545,29 +545,49 @@ trait Implicits { self: Typer =>
545545
/** If `formal` is of the form ClassTag[T], where `T` is a class type,
546546
* synthesize a class tag for `T`.
547547
*/
548-
def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = {
549-
if (formal.isRef(defn.ClassTagClass))
550-
formal.argTypes match {
551-
case arg :: Nil =>
552-
fullyDefinedType(arg, "ClassTag argument", pos) match {
553-
case defn.ArrayOf(elemTp) =>
554-
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos)
555-
if (etag.isEmpty) etag else etag.select(nme.wrap)
556-
case tp if hasStableErasure(tp) =>
557-
if (defn.isBottomClass(tp.typeSymbol))
558-
error(where => i"attempt to take ClassTag of undetermined type for $where")
559-
ref(defn.ClassTagModule)
560-
.select(nme.apply)
561-
.appliedToType(tp)
562-
.appliedTo(clsOf(erasure(tp)))
563-
.withPos(pos)
564-
case tp =>
565-
EmptyTree
566-
}
567-
case _ =>
568-
EmptyTree
569-
}
570-
else EmptyTree
548+
def synthesizedClassTag(formal: Type)(implicit ctx: Context): Tree =
549+
formal.argTypes match {
550+
case arg :: Nil =>
551+
fullyDefinedType(arg, "ClassTag argument", pos) match {
552+
case defn.ArrayOf(elemTp) =>
553+
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos)
554+
if (etag.isEmpty) etag else etag.select(nme.wrap)
555+
case tp if hasStableErasure(tp) =>
556+
if (defn.isBottomClass(tp.typeSymbol))
557+
error(where => i"attempt to take ClassTag of undetermined type for $where")
558+
ref(defn.ClassTagModule)
559+
.select(nme.apply)
560+
.appliedToType(tp)
561+
.appliedTo(clsOf(erasure(tp)))
562+
.withPos(pos)
563+
case tp =>
564+
EmptyTree
565+
}
566+
case _ =>
567+
EmptyTree
568+
}
569+
570+
/** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for
571+
* either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution.
572+
*/
573+
def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = {
574+
//println(i"synth eq $formal / ${formal.argTypes}%, %")
575+
formal.argTypes match {
576+
case args @ (arg1 :: arg2 :: Nil)
577+
if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) &&
578+
validEqAnyArgs(arg1, arg2)(ctx.fresh.setExploreTyperState) =>
579+
ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos)
580+
case _ =>
581+
EmptyTree
582+
}
583+
}
584+
585+
def hasEq(tp: Type): Boolean =
586+
inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isInstanceOf[SearchSuccess]
587+
588+
def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = {
589+
List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos))
590+
assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2)
571591
}
572592

573593
/** The context to be used when resolving a by-name implicit argument.
@@ -606,7 +626,13 @@ trait Implicits { self: Typer =>
606626
error(where => s"ambiguous implicits: ${ambi.explanation} of $where")
607627
EmptyTree
608628
case failure: SearchFailure =>
609-
val arg = synthesizedClassTag(formalValue, pos)
629+
val arg =
630+
if (formalValue.isRef(defn.ClassTagClass))
631+
synthesizedClassTag(formalValue)
632+
else if (formalValue.isRef(defn.EqClass))
633+
synthesizedEq(formalValue)
634+
else
635+
EmptyTree
610636
if (!arg.isEmpty) arg
611637
else {
612638
var msgFn = (where: String) =>
@@ -638,10 +664,10 @@ trait Implicits { self: Typer =>
638664
}
639665

640666
val lift = new TypeMap {
641-
def apply(t: Type) = t match {
667+
def apply(t: Type): Type = t match {
642668
case t: TypeRef =>
643669
t.info match {
644-
case TypeBounds(lo, hi) if lo ne hi => hi
670+
case TypeBounds(lo, hi) if lo ne hi => apply(hi)
645671
case _ => t
646672
}
647673
case _ =>
@@ -714,6 +740,8 @@ trait Implicits { self: Typer =>
714740
if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType))
715741
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
716742

743+
private def isCoherent = pt.isRef(defn.EqClass)
744+
717745
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
718746
em"found: $argument: ${argument.tpe}, expected: $pt")
719747

@@ -761,40 +789,16 @@ trait Implicits { self: Typer =>
761789
case _ => false
762790
}
763791
}
764-
// Does there exist an implicit value of type `Eq[tp, tp]`
765-
// which is different from `eqAny`?
766-
def hasEq(tp: Type): Boolean = {
767-
def search(contextual: Boolean): Boolean =
768-
new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos)
769-
.bestImplicit(contextual) match {
770-
case result: SearchSuccess =>
771-
result.ref.symbol != defn.Predef_eqAny ||
772-
contextual && search(contextual = false)
773-
case result: AmbiguousImplicits => true
774-
case _ => false
775-
}
776-
search(contextual = true)
777-
}
778792

779-
def validEqAnyArgs(tp1: Type, tp2: Type) = {
780-
List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos))
781-
assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) ||
782-
{ implicits.println(i"invalid eqAny[$tp1, $tp2]"); false }
783-
}
784793
if (ctx.reporter.hasErrors)
785794
nonMatchingImplicit(ref, ctx.reporter.removeBufferedMessages)
786795
else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) &&
787796
!shadowing.tpe.isError && !refSameAs(shadowing)) {
788797
implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}")
789798
shadowedImplicit(ref, methPart(shadowing).tpe)
790799
}
791-
else generated1 match {
792-
case TypeApply(fn, targs @ (arg1 :: arg2 :: Nil))
793-
if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) =>
794-
nonMatchingImplicit(ref, Nil)
795-
case _ =>
796-
SearchSuccess(generated1, ref, cand.level, ctx.typerState)
797-
}
800+
else
801+
SearchSuccess(generated1, ref, cand.level, ctx.typerState)
798802
}}
799803

800804
/** Given a list of implicit references, produce a list of all implicit search successes,
@@ -812,7 +816,7 @@ trait Implicits { self: Typer =>
812816
case fail: SearchFailure =>
813817
rankImplicits(pending1, acc)
814818
case best: SearchSuccess =>
815-
if (ctx.mode.is(Mode.ImplicitExploration)) best :: Nil
819+
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil
816820
else {
817821
val newPending = pending1.filter(cand1 =>
818822
isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext.setExploreTyperState))

docs/docs/reference/multiversal-equality.md

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,26 +56,31 @@ each other, but not comparable to anything else:
5656
(As usual, the names of the implicit definitions don't matter, we have
5757
chosen `eqA`, ..., `eqBA` only for illustration).
5858

59-
The `dotty.DottyPredef` object defines a number of `Eq`
60-
implicits. `dotty.DottyPredef` is a temporary `Predef`-like object.
61-
The contents of this object are by default imported into every
62-
program. Once dotty becomes standard Scala, `DottyPredef` will go away
63-
and its contents will be merged with `scala.Predef`.
59+
The `scala.Eq` object defines a number of `Eq` implicits that make
60+
values of types `String`, `Boolean` and `Unit` only comparable to
61+
values of the same type. They also make numbers only comparable to
62+
other numbers, sequences only comparable to other
63+
sequences and sets only comparable to other sets.
6464

65-
The `Eq` instances defined by `DottyPredef` make values of types
66-
`String`, `Boolean` and `Unit` only comparable to values of the same
67-
type. They also make numbers only comparable to other numbers, and
68-
sequences only comparable to other sequences. There's also a
69-
"fallback" instance `eqAny` that allows comparisons over types that do
70-
not themeselves have an `Eq` instance. `eqAny` is defined as follows:
65+
There's also a "fallback" instance named `eqAny` that allows comparisons
66+
over all types that do not themeselves have an `Eq` instance. `eqAny` is
67+
defined as follows:
7168

72-
implicit def eqAny[L, R]: Eq[L, R] = Eq
69+
def eqAny[L, R]: Eq[L, R] = Eq
70+
71+
Even though `eqAny` is not declared implicit, the compiler will still
72+
construct an `eqAny` instance as answer to an implicit search for the
73+
type `Eq[L, R]`, provided that neither `L` nor `R` have `Eq` instances
74+
defined on them.
7375

7476
The primary motivation for having `eqAny` is backwards compatibility,
75-
if this is of no concern one can disable `eqAny` by unimporting it
76-
from `DottyPredef` like this
77+
if this is of no concern one can disable `eqAny` by enabling the language
78+
feature `strictEquality`. As for all language features this can be either
79+
done with an import
80+
81+
import scala.language.strictEquality
7782

78-
import dotty.DottyPredef.{eqAny => _, _}
83+
or with a command line option `-language:strictEquality`.
7984

8085
All `enum` types also come with `Eq` instances that make values of the
8186
`enum` type comparable only to other values of that `enum` type.
@@ -87,11 +92,11 @@ The precise rules for equality checking are as follows.
8792
of the other, or an implicit value of type `scala.Eq[T, U]` is found.
8893

8994
2. The usual rules for implicit search apply also to `Eq` instances,
90-
with one modification: The value `eqAny` in `dotty.DottyPredef` is
91-
eligible only if neither `T` nor `U` have a reflexive `Eq`
95+
with one modification: An instance of `scala.Eq.eqAny[T, U]` is
96+
constructed if neither `T` nor `U` have a reflexive `Eq`
9297
instance themselves. Here, a type `T` has a reflexive `Eq`
93-
instance if the implicit search for `Eq[T, T]` where `eqAny` is
94-
not eligible is successful.
98+
instance if the implicit search for `Eq[T, T]` succeeds
99+
and constructs an instance different from `eqAny`.
95100

96101
More on multiversal equality is found in a [blog post]
97102
and a [Github issue].

library/src/dotty/DottyPredef.scala

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,8 @@ package dotty
22

33
import scala.reflect.ClassTag
44
import scala.Predef.???
5-
import scala.collection.Seq
65

7-
/** unimplemented implicit for TypeTag */
86
object DottyPredef {
9-
/** A fall-back implicit to compare values of any types.
10-
* The compiler will restrict implicit instances of `eqAny`. An instance
11-
* `eqAny[T, U]` is _valid_ if `T <: U` or `U <: T` or both `T` and `U` are
12-
* Eq-free. A type `S` is Eq-free if there is no implicit instance of `Eq[S, S]`.
13-
* An implicit search will fail instead of returning an invalid `eqAny` instance.
14-
*/
15-
implicit def eqAny[L, R]: Eq[L, R] = Eq
16-
17-
implicit def eqNumber : Eq[Number, Number] = Eq
18-
implicit def eqString : Eq[String, String] = Eq
19-
implicit def eqBoolean : Eq[Boolean, Boolean] = Eq
20-
implicit def eqByte : Eq[Byte, Byte] = Eq
21-
implicit def eqShort : Eq[Short, Short] = Eq
22-
implicit def eqChar : Eq[Char, Char] = Eq
23-
implicit def eqInt : Eq[Int, Int] = Eq
24-
implicit def eqLong : Eq[Long, Long] = Eq
25-
implicit def eqFloat : Eq[Float, Float] = Eq
26-
implicit def eqDouble : Eq[Double, Double] = Eq
27-
implicit def eqUnit : Eq[Unit, Unit] = Eq
28-
29-
// true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies
30-
implicit def eqProxy : Eq[Proxy, Any] = Eq
31-
32-
implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[Seq[T], Seq[U]] = Eq
33-
34-
implicit def eqByteNum : Eq[Byte, Number] = Eq
35-
implicit def eqNumByte : Eq[Number, Byte] = Eq
36-
implicit def eqCharNum : Eq[Char, Number] = Eq
37-
implicit def eqNumChar : Eq[Number, Char] = Eq
38-
implicit def eqShortNum : Eq[Short, Number] = Eq
39-
implicit def eqNumShort : Eq[Number, Short] = Eq
40-
implicit def eqIntNum : Eq[Int, Number] = Eq
41-
implicit def eqNumInt : Eq[Number, Int] = Eq
42-
implicit def eqLongNum : Eq[Long, Number] = Eq
43-
implicit def eqNumLong : Eq[Number, Long] = Eq
44-
implicit def eqFloatNum : Eq[Float, Number] = Eq
45-
implicit def eqNumFloat : Eq[Number, Float] = Eq
46-
implicit def eqDoubleNum: Eq[Double, Number] = Eq
47-
implicit def eqNumDouble: Eq[Number, Double] = Eq
487

498
/** A class for implicit values that can serve as implicit conversions
509
* The implicit resolution algorithm will act as if there existed

library/src/scala/Eq.scala

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala
22

33
import annotation.implicitNotFound
4+
import scala.collection.{GenSeq, Set}
45

56
/** A marker trait indicating that values of type `L` can be compared to values of type `R`. */
67
@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=")
@@ -10,5 +11,48 @@ sealed trait Eq[-L, -R]
1011
* can also be used as a value that's compatible with
1112
* any instance of `Eq`.
1213
*/
13-
object Eq extends Eq[Any, Any]
14+
object Eq extends Eq[Any, Any] {
1415

16+
/** A fall-back "implicit" to compare values of any types.
17+
* Even though this method is not declared implicit, the compiler will
18+
* compute instances as solutions to `Eq[T, U]` queries if `T <: U` or `U <: T`
19+
* or both `T` and `U` are Eq-free. A type `S` is Eq-free if there is no
20+
* implicit instance of type `Eq[S, S]`.
21+
*/
22+
def eqAny[L, R]: Eq[L, R] = Eq
23+
24+
// Instances of `Eq` for common types
25+
26+
implicit def eqNumber : Eq[Number, Number] = Eq
27+
implicit def eqString : Eq[String, String] = Eq
28+
implicit def eqBoolean : Eq[Boolean, Boolean] = Eq
29+
implicit def eqByte : Eq[Byte, Byte] = Eq
30+
implicit def eqShort : Eq[Short, Short] = Eq
31+
implicit def eqChar : Eq[Char, Char] = Eq
32+
implicit def eqInt : Eq[Int, Int] = Eq
33+
implicit def eqLong : Eq[Long, Long] = Eq
34+
implicit def eqFloat : Eq[Float, Float] = Eq
35+
implicit def eqDouble : Eq[Double, Double] = Eq
36+
implicit def eqUnit : Eq[Unit, Unit] = Eq
37+
38+
// true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies
39+
implicit def eqProxy : Eq[Proxy, Any] = Eq
40+
41+
implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[GenSeq[T], GenSeq[U]] = Eq
42+
implicit def eqSet[T, U](implicit eq: Eq[T, U]): Eq[Set[T], Set[U]] = Eq
43+
44+
implicit def eqByteNum : Eq[Byte, Number] = Eq
45+
implicit def eqNumByte : Eq[Number, Byte] = Eq
46+
implicit def eqCharNum : Eq[Char, Number] = Eq
47+
implicit def eqNumChar : Eq[Number, Char] = Eq
48+
implicit def eqShortNum : Eq[Short, Number] = Eq
49+
implicit def eqNumShort : Eq[Number, Short] = Eq
50+
implicit def eqIntNum : Eq[Int, Number] = Eq
51+
implicit def eqNumInt : Eq[Number, Int] = Eq
52+
implicit def eqLongNum : Eq[Long, Number] = Eq
53+
implicit def eqNumLong : Eq[Number, Long] = Eq
54+
implicit def eqFloatNum : Eq[Float, Number] = Eq
55+
implicit def eqNumFloat : Eq[Number, Float] = Eq
56+
implicit def eqDoubleNum: Eq[Double, Number] = Eq
57+
implicit def eqNumDouble: Eq[Number, Double] = Eq
58+
}

library/src/scalaShadowing/language.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,7 @@ object language {
195195

196196
/** Where imported, auto-tupling is disabled */
197197
object noAutoTupling
198+
199+
/* Where imported loose equality using eqAny is disabled */
200+
object strictEquality
198201
}

0 commit comments

Comments
 (0)