Skip to content

Base multiversal equality on typeclass derivation #5843

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 10 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
43 changes: 2 additions & 41 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would give it another name because here the instance is not really derived, bur rather declared or something.


/** 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.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/util/SourceFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/i4184
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
12 changes: 6 additions & 6 deletions docs/docs/reference/other-new-features/multiversal-equality.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
71 changes: 36 additions & 35 deletions library/src/scala/Eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,52 @@ 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 {
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]`.
*/
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
}
14 changes: 8 additions & 6 deletions tests/neg/EqualityStrawman1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ???
Expand All @@ -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")
Expand Down
30 changes: 30 additions & 0 deletions tests/neg/derive-eq.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

case class One() derives Eq
case class Two() derives Eq

implied for Eq[One, Two] = Eq.derived

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 {
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
}
2 changes: 1 addition & 1 deletion tests/neg/enums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
20 changes: 9 additions & 11 deletions tests/neg/equality.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions tests/neg/i3976.scala
Original file line number Diff line number Diff line change
@@ -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]
}
Expand All @@ -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]]
}
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i4470a.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
object RepeatedEnum {

enum Maybe { // error // error
case Foo
enum Maybe { // error
case Foo // error
}

enum Maybe { // error
Expand Down
Loading