Skip to content

Fix #2698: Move eqXYZ instances to Eq #2786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
110 changes: 57 additions & 53 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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 _ =>
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -761,40 +789,16 @@ 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) &&
!shadowing.tpe.isError && !refSameAs(shadowing)) {
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,
Expand All @@ -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))
Expand Down
43 changes: 24 additions & 19 deletions docs/docs/reference/multiversal-equality.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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].
Expand Down
41 changes: 0 additions & 41 deletions library/src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,8 @@ 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.
*/
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
Expand Down
46 changes: 45 additions & 1 deletion library/src/scala/Eq.scala
Original file line number Diff line number Diff line change
@@ -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 !=")
Expand All @@ -10,5 +11,48 @@ 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] {

/** 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
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[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
}
3 changes: 3 additions & 0 deletions library/src/scalaShadowing/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,7 @@ object language {

/** Where imported, auto-tupling is disabled */
object noAutoTupling

/* Where imported loose equality using eqAny is disabled */
object strictEquality
}
Loading