Skip to content

Commit 9df6423

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 668838f commit 9df6423

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
@@ -698,16 +698,65 @@ trait Implicits { self: Typer =>
698698
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext)
699699
else EmptyTree
700700

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

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

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

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

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

0 commit comments

Comments
 (0)