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 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/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 5caa389a8203..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 + 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/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/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 26c9d9d5e777..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 + 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 f5fa0ba3edd9..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 + 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 b453533f5610..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 + implicit def eqType: Eql[Type, Type] = Eql.derived /** Main class representing types. * 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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ffd36af6feeb..b29e905e655b 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 Eql[T, U], try to synthesize an + * `Eql.eqlAny[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.EqlType.appliedTo(tp, tp), span) + !inst.isEmpty && !inst.tpe.isError + } + + /** 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. + */ + 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.Eql_eqlAny).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. */ @@ -776,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)))))) } } @@ -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,14 +951,20 @@ 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 */ 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. @@ -985,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/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/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 07d3e886142c..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 + 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 f177cc5d9da2..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 -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/contextual/multiversal-equality.md b/docs/docs/reference/contextual/multiversal-equality.md new file mode 100644 index 000000000000..739230bd6963 --- /dev/null +++ b/docs/docs/reference/contextual/multiversal-equality.md @@ -0,0 +1,158 @@ +--- +layout: doc-page +title: "Multiversal Equality" +--- + +Previously, Scala had universal equality: Two values of any types +could be compared with each other with `==` and `!=`. This came from +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 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 +val y = ... // of type S, but should be T +x == y // typechecks, will always yield false +``` + +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. 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 +class T derives Eql +``` +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 `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 Eql[-L, -R] + +object Eql { + object derived extends Eql[Any, Any] +} +``` + +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 +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). + +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 eqlAny[L, R]: Eql[L, R] = Eql.derived +``` + +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 `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`. + +## 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. + +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/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/docs/docs/reference/other-new-features/multiversal-equality.md b/docs/docs/reference/other-new-features/multiversal-equality.md deleted file mode 100644 index 709ee2f1543e..000000000000 --- a/docs/docs/reference/other-new-features/multiversal-equality.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -layout: doc-page -title: "Multiversal Equality" ---- - -Previously, Scala had universal equality: Two values of any types -could be compared with each other with `==` and `!=`. This came from -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`. - -```scala -val x = ... // of type T -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. - -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`): - -```scala -implicit def eqT: Eq[T, T] = Eq -``` - -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 -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: - -```scala -package scala -import annotation.implicitNotFound - -@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") -sealed trait Eq[-L, -R] - -object Eq extends Eq[Any, Any] -``` - -One can have several `Eq` 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 -implicit def eqB : Eq[B, B] = Eq -implicit def eqAB: Eq[A, B] = Eq -implicit def eqBA: Eq[B, A] = Eq -``` - -(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 -defined as follows: - -```scala -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 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. - -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. - -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/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 diff --git a/library/src/scala/Eq.scala b/library/src/scala/Eq.scala deleted file mode 100644 index 15869024d003..000000000000 --- a/library/src/scala/Eq.scala +++ /dev/null @@ -1,61 +0,0 @@ -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 !=") -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`. - */ -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 - - implicit def eqSBoolJBool: Eq[Boolean, java.lang.Boolean] = Eq - implicit def eqJBoolSBool: Eq[java.lang.Boolean, Boolean] = Eq -} diff --git a/library/src/scala/Eql.scala b/library/src/scala/Eql.scala new file mode 100644 index 000000000000..d9ef982e6827 --- /dev/null +++ b/library/src/scala/Eql.scala @@ -0,0 +1,37 @@ +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 !=") +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 Eql { + /** A non-implied universal `Eql` instance. */ + 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 eqlAny[L, R]: Eql[L, R] = 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 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 eqlProxy : Eql[Proxy, AnyRef] = derived +} diff --git a/tests/neg/EqualityStrawman1.scala b/tests/neg/EqualityStrawman1.scala index b1b6c0380a50..395e578740ad 100644 --- a/tests/neg/EqualityStrawman1.scala +++ b/tests/neg/EqualityStrawman1.scala @@ -3,12 +3,14 @@ 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 extends Eq[Any] + object Eql { + object derived extends Eql[Any] + } trait Base { def === (other: Any): Boolean = this.equals(other) @@ -16,7 +18,7 @@ object EqualityStrawman1 { } 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 = ??? } @@ -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: 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 + implicit def eqEq[T <: Equals[T]]: Eql[T] = Eql.derived def main(args: Array[String]): Unit = { val x = Str("abc") @@ -57,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 new file mode 100644 index 000000000000..f12761036aee --- /dev/null +++ b/tests/neg/derive-eq.scala @@ -0,0 +1,30 @@ + +case class One() derives Eql +case class Two() derives Eql + +implied for Eql[One, Two] = Eql.derived + +enum Lst[T] derives Eql { + case Cons(x: T, xs: Lst[T]) + case Nil() +} + +case class Triple[S, T, U] derives Eql + + +object Test extends App { + 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] = ??? + 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/enums.scala b/tests/neg/enums.scala index 1ff70ed9abcf..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] { +enum Option[+T] derives Eql { case Some(x: T) case None } diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala index 595d1de6564f..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 - 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: 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 - 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: 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 @@ -58,15 +58,13 @@ 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 - - implicit def eqFruit: Eq[Fruit, Fruit] = Eq + class Fruit derives Eql class Apple extends Fruit class Pear extends Fruit 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 } } diff --git a/tests/neg/i3976.scala b/tests/neg/i3976.scala index d1892749dc0a..6ffd7a9707a4 100644 --- a/tests/neg/i3976.scala +++ b/tests/neg/i3976.scala @@ -1,5 +1,5 @@ object Test { - enum Hoge[F[_]] { + 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[_]]] { + 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[_]]] { + 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/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..7305f1018cbe 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 Eql { // error // error case Foo extends Maybe[Int] } - enum Maybe[T] { // error + enum Maybe[T] derives Eql { // 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/neg/i5546.scala b/tests/neg/i5546.scala index bb6668e5a8a5..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 + 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 + 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 e72e675c29ab..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[T, T] + type EqEq[T] = Eql.derived[T, T] trait EqClass[T] - implicit def eqAny[T, U]: Eq[T, U] = Eq + implicit def eqAny[T, U]: Eql[T, U] = Eql.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 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 - implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + 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 - //implicit def eqNum: Eq[Num, Num] = Eq + //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 new file mode 100644 index 000000000000..5d590a3342e7 --- /dev/null +++ b/tests/pos/derive-eq.scala @@ -0,0 +1,23 @@ + +case class One() derives Eql +case class Two() derives Eql + +implied for Eql[One, Two] = Eql.derived + +enum Lst[T] derives Eql { + case Cons(x: T, xs: Lst[T]) + case Nil() +} + +class Triple[S, T, U] derives Eql + + +object Test { + 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] = ??? + val y: Triple[Lst[Two], One, Two] = ??? + x == y +} diff --git a/tests/pos/multiversal.scala b/tests/pos/multiversal.scala new file mode 100644 index 000000000000..27197dbc6e2d --- /dev/null +++ b/tests/pos/multiversal.scala @@ -0,0 +1,30 @@ +object Test { + import scala.Eql + + implied [X, Y] given Eql[X, Y] for Eql[List[X], List[Y]] = Eql.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)))