Skip to content

Commit 1d08aa1

Browse files
committed
Change rules for multiversal equality
1. Don't apply the lifting rules when strictEquality is set. 2. Be more strict about null checking 3. Compensate for (2) by changing errorTerm in parser so that it has an error type instead of Null. 4. Reorganize implementation to match new docs.
1 parent 941e697 commit 1d08aa1

File tree

1 file changed

+72
-25
lines changed

1 file changed

+72
-25
lines changed

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

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -699,16 +699,65 @@ trait Implicits { self: Typer =>
699699
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext)
700700
else EmptyTree
701701

702-
/** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for
703-
* either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution.
702+
/** If `formal` is of the form Eq[T, U], try to synthesize an
703+
* `Eq.eqAny[T, U]` as solution.
704704
*/
705705
def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = {
706-
//println(i"synth eq $formal / ${formal.argTypes}%, %")
706+
707+
/** Is there an `Eql[T, T]` instance, assuming -strictEquality? */
708+
def hasEq(tp: Type)(implicit ctx: Context): Boolean = {
709+
val inst = inferImplicitArg(defn.EqType.appliedTo(tp, tp), span)
710+
!inst.isEmpty && !inst.tpe.isError
711+
}
712+
713+
/** Can we assume the eqAny instance for `tp1`, `tp2`?
714+
* This is the case if assumedCanEqual(tp1, tp2), or
715+
* one of `tp1`, `tp2` has a reflexive `Eql` instance.
716+
*/
717+
def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) =
718+
assumedCanEqual(tp1, tp2) || {
719+
val nestedCtx = ctx.fresh.addMode(Mode.StrictEquality)
720+
!hasEq(tp1)(nestedCtx) && !hasEq(tp2)(nestedCtx)
721+
}
722+
723+
/** Is an `Eql[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */
724+
def canComparePredefinedClasses(cls1: ClassSymbol, cls2: ClassSymbol): Boolean = {
725+
def cmpWithBoxed(cls1: ClassSymbol, cls2: ClassSymbol) =
726+
cls2 == defn.boxedType(cls1.typeRef).symbol ||
727+
cls1.isNumericValueClass && cls2.derivesFrom(defn.BoxedNumberClass)
728+
729+
if (cls1.isPrimitiveValueClass)
730+
if (cls2.isPrimitiveValueClass)
731+
cls1 == cls2 || cls1.isNumericValueClass && cls2.isNumericValueClass
732+
else
733+
cmpWithBoxed(cls1, cls2)
734+
else if (cls2.isPrimitiveValueClass)
735+
cmpWithBoxed(cls2, cls1)
736+
else if (cls1 == defn.NullClass)
737+
cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass)
738+
else if (cls2 == defn.NullClass)
739+
cls1.derivesFrom(defn.ObjectClass)
740+
else
741+
false
742+
}
743+
744+
/** Some simulated `Eql` instances for predefined types. It's more efficient
745+
* to do this directly instead of setting up a lot of `Eql` instances to
746+
* interpret.
747+
*/
748+
def canComparePredefined(tp1: Type, tp2: Type) =
749+
tp1.classSymbols.exists(cls1 =>
750+
tp2.classSymbols.exists(cls2 => canComparePredefinedClasses(cls1, cls2)))
751+
707752
formal.argTypes match {
708-
case args @ (arg1 :: arg2 :: Nil)
709-
if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) &&
710-
ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2)) =>
711-
ref(defn.Eq_eqAny).appliedToTypes(args).withSpan(span)
753+
case args @ (arg1 :: arg2 :: Nil) =>
754+
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span))
755+
if (canComparePredefined(arg1, arg2)
756+
||
757+
!strictEquality &&
758+
ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2)))
759+
ref(defn.Eq_eqAny).appliedToTypes(args).withSpan(span)
760+
else EmptyTree
712761
case _ =>
713762
EmptyTree
714763
}
@@ -737,14 +786,6 @@ trait Implicits { self: Typer =>
737786
}
738787
}
739788

740-
def hasEq(tp: Type): Boolean =
741-
inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, span).isSuccess
742-
743-
def validEqAnyArgs(tp1: Type, tp2: Type)(implicit ctx: Context) = {
744-
List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", span))
745-
assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2)
746-
}
747-
748789
/** If `formal` is of the form `scala.reflect.Generic[T]` for some class type `T`,
749790
* synthesize an instance for it.
750791
*/
@@ -885,16 +926,16 @@ trait Implicits { self: Typer =>
885926
em"parameter ${paramName} of $methodStr"
886927
}
887928

888-
private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = {
889-
def eqNullable: Boolean = {
890-
val other =
891-
if (ltp.isRef(defn.NullClass)) rtp
892-
else if (rtp.isRef(defn.NullClass)) ltp
893-
else NoType
894-
895-
(other ne NoType) && !other.derivesFrom(defn.AnyValClass)
896-
}
929+
private def strictEquality(implicit ctx: Context): Boolean =
930+
ctx.mode.is(Mode.StrictEquality) ||
931+
ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality)
897932

933+
/** An Eql[T, U] instance is assumed
934+
* - if one of T, U is an error type, or
935+
* - if one of T, U is a subtype of the lifted version of the other,
936+
* unless strict equality is set.
937+
*/
938+
private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = {
898939
// Map all non-opaque abstract types to their upper bound.
899940
// This is done to check whether such types might plausibly be comparable to each other.
900941
val lift = new TypeMap {
@@ -910,7 +951,13 @@ trait Implicits { self: Typer =>
910951
if (variance > 0) mapOver(t) else t
911952
}
912953
}
913-
ltp.isError || rtp.isError || ltp <:< lift(rtp) || rtp <:< lift(ltp) || eqNullable
954+
955+
ltp.isError ||
956+
rtp.isError ||
957+
!strictEquality && {
958+
ltp <:< lift(rtp) ||
959+
rtp <:< lift(ltp)
960+
}
914961
}
915962

916963
/** Check that equality tests between types `ltp` and `rtp` make sense */

0 commit comments

Comments
 (0)