Skip to content

Commit 158ccd7

Browse files
committed
Fix #5592: Fix & of method type refinements
In `distributeAnd` we need to combine refinements in the same way denotation infos are combined, with special treatement of method and poly types.
1 parent fcc35d2 commit 158ccd7

File tree

3 files changed

+165
-126
lines changed

3 files changed

+165
-126
lines changed

compiler/src/dotty/tools/dotc/core/Denotations.scala

+130-124
Original file line numberDiff line numberDiff line change
@@ -334,17 +334,6 @@ object Denotations {
334334
else asSingleDenotation
335335
}
336336

337-
/** Handle merge conflict by throwing a `MergeError` exception */
338-
private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type =
339-
throw new MergeError(this.symbol, that.symbol, tp1, tp2, NoPrefix)
340-
341-
/** Merge parameter names of lambda types. If names in corresponding positions match, keep them,
342-
* otherwise generate new synthetic names.
343-
*/
344-
private def mergeParamNames(tp1: LambdaType, tp2: LambdaType): List[tp1.ThisName] =
345-
(for ((name1, name2, idx) <- (tp1.paramNames, tp2.paramNames, tp1.paramNames.indices).zipped)
346-
yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList
347-
348337
/** Form a denotation by conjoining with denotation `that`.
349338
*
350339
* NoDenotations are dropped. MultiDenotations are handled by merging
@@ -374,72 +363,6 @@ object Denotations {
374363
* If SingleDenotations with different signatures are joined, return NoDenotation.
375364
*/
376365
def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = {
377-
378-
/** Normally, `tp1 & tp2`. Special cases for matching methods and classes, with
379-
* the possibility of raising a merge error.
380-
*/
381-
def infoMeet(tp1: Type, tp2: Type): Type = {
382-
if (tp1 eq tp2) tp1
383-
else tp1 match {
384-
case tp1: TypeBounds =>
385-
tp2 match {
386-
case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2
387-
case tp2: ClassInfo if tp1 contains tp2 => tp2
388-
case _ => mergeConflict(tp1, tp2, that)
389-
}
390-
case tp1: ClassInfo =>
391-
tp2 match {
392-
case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix)
393-
case tp2: TypeBounds if tp2 contains tp1 => tp1
394-
case _ => mergeConflict(tp1, tp2, that)
395-
}
396-
397-
// Two remedial strategies:
398-
//
399-
// 1. Prefer method types over poly types. This is necessary to handle
400-
// overloaded definitions like the following
401-
//
402-
// def ++ [B >: A](xs: C[B]): D[B]
403-
// def ++ (xs: C[A]): D[A]
404-
//
405-
// (Code like this is found in the collection strawman)
406-
//
407-
// 2. In the case of two method types or two polytypes with matching
408-
// parameters and implicit status, merge corresponding parameter
409-
// and result types.
410-
case tp1: MethodType =>
411-
tp2 match {
412-
case tp2: PolyType =>
413-
tp1
414-
case tp2: MethodType if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
415-
tp1.isImplicitMethod == tp2.isImplicitMethod =>
416-
tp1.derivedLambdaType(
417-
mergeParamNames(tp1, tp2),
418-
tp1.paramInfos,
419-
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1)))
420-
case _ =>
421-
mergeConflict(tp1, tp2, that)
422-
}
423-
case tp1: PolyType =>
424-
tp2 match {
425-
case tp2: MethodType =>
426-
tp2
427-
case tp2: PolyType if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
428-
tp1.derivedLambdaType(
429-
mergeParamNames(tp1, tp2),
430-
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
431-
infoMeet(p1, p2.subst(tp2, tp1)).bounds
432-
},
433-
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1)))
434-
case _ =>
435-
mergeConflict(tp1, tp2, that)
436-
}
437-
438-
case _ =>
439-
tp1 & tp2
440-
}
441-
}
442-
443366
/** Try to merge denot1 and denot2 without adding a new signature. */
444367
def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match {
445368
case denot1 @ MultiDenotation(denot11, denot12) =>
@@ -535,7 +458,7 @@ object Denotations {
535458
if (preferSym(sym2, sym1)) sym2
536459
else sym1
537460
val jointInfo =
538-
try infoMeet(info1, info2)
461+
try infoMeet(info1, info2, sym1, sym2, safeIntersection)
539462
catch {
540463
case ex: MergeError =>
541464
// TODO: this picks one type over the other whereas it might be better
@@ -571,51 +494,6 @@ object Denotations {
571494
*/
572495
def | (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = {
573496

574-
/** Normally, `tp1 | tp2`. Special cases for matching methods and classes, with
575-
* the possibility of raising a merge error.
576-
*/
577-
def infoJoin(tp1: Type, tp2: Type): Type = tp1 match {
578-
case tp1: TypeBounds =>
579-
tp2 match {
580-
case tp2: TypeBounds => tp1 | tp2
581-
case tp2: ClassInfo if tp1 contains tp2 => tp1
582-
case _ => mergeConflict(tp1, tp2, that)
583-
}
584-
case tp1: ClassInfo =>
585-
tp2 match {
586-
case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix)
587-
case tp2: TypeBounds if tp2 contains tp1 => tp2
588-
case _ => mergeConflict(tp1, tp2, that)
589-
}
590-
case tp1: MethodType =>
591-
tp2 match {
592-
case tp2: MethodType
593-
if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
594-
tp1.isImplicitMethod == tp2.isImplicitMethod =>
595-
tp1.derivedLambdaType(
596-
mergeParamNames(tp1, tp2),
597-
tp1.paramInfos,
598-
tp1.resultType | tp2.resultType.subst(tp2, tp1))
599-
case _ =>
600-
mergeConflict(tp1, tp2, that)
601-
}
602-
case tp1: PolyType =>
603-
tp2 match {
604-
case tp2: PolyType
605-
if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
606-
tp1.derivedLambdaType(
607-
mergeParamNames(tp1, tp2),
608-
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
609-
(p1 | p2.subst(tp2, tp1)).bounds
610-
},
611-
tp1.resultType | tp2.resultType.subst(tp2, tp1))
612-
case _ =>
613-
mergeConflict(tp1, tp2, that)
614-
}
615-
case _ =>
616-
tp1 | tp2
617-
}
618-
619497
def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation =
620498
if (denot1.matches(denot2)) {
621499
val sym1 = denot1.symbol
@@ -646,7 +524,7 @@ object Denotations {
646524
lubSym(sym1.allOverriddenSymbols, NoSymbol)
647525
}
648526
new JointRefDenotation(
649-
jointSym, infoJoin(info1, info2), denot1.validFor & denot2.validFor)
527+
jointSym, infoJoin(info1, info2, sym1, sym2), denot1.validFor & denot2.validFor)
650528
}
651529
}
652530
else NoDenotation
@@ -678,6 +556,134 @@ object Denotations {
678556
final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym)
679557
}
680558

559+
// ------ Info meets and joins ---------------------------------------------
560+
561+
/** Handle merge conflict by throwing a `MergeError` exception */
562+
private def mergeConflict(sym1: Symbol, sym2: Symbol, tp1: Type, tp2: Type)(implicit ctx: Context): Type =
563+
throw new MergeError(sym1, sym2, tp1, tp2, NoPrefix)
564+
565+
/** Merge parameter names of lambda types. If names in corresponding positions match, keep them,
566+
* otherwise generate new synthetic names.
567+
*/
568+
private def mergeParamNames(tp1: LambdaType, tp2: LambdaType): List[tp1.ThisName] =
569+
(for ((name1, name2, idx) <- (tp1.paramNames, tp2.paramNames, tp1.paramNames.indices).zipped)
570+
yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList
571+
572+
/** Normally, `tp1 & tp2`. Special cases for matching methods and classes, with
573+
* the possibility of raising a merge error.
574+
*/
575+
def infoMeet(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol, safeIntersection: Boolean)(implicit ctx: Context): Type = {
576+
if (tp1 eq tp2) tp1
577+
else tp1 match {
578+
case tp1: TypeBounds =>
579+
tp2 match {
580+
case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2
581+
case tp2: ClassInfo if tp1 contains tp2 => tp2
582+
case _ => mergeConflict(sym1, sym2, tp1, tp2)
583+
}
584+
case tp1: ClassInfo =>
585+
tp2 match {
586+
case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix)
587+
case tp2: TypeBounds if tp2 contains tp1 => tp1
588+
case _ => mergeConflict(sym1, sym2, tp1, tp2)
589+
}
590+
591+
// Two remedial strategies:
592+
//
593+
// 1. Prefer method types over poly types. This is necessary to handle
594+
// overloaded definitions like the following
595+
//
596+
// def ++ [B >: A](xs: C[B]): D[B]
597+
// def ++ (xs: C[A]): D[A]
598+
//
599+
// (Code like this is found in the collection strawman)
600+
//
601+
// 2. In the case of two method types or two polytypes with matching
602+
// parameters and implicit status, merge corresponding parameter
603+
// and result types.
604+
case tp1: MethodType =>
605+
tp2 match {
606+
case tp2: PolyType =>
607+
tp1
608+
case tp2: MethodType if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
609+
tp1.isImplicitMethod == tp2.isImplicitMethod =>
610+
tp1.derivedLambdaType(
611+
mergeParamNames(tp1, tp2),
612+
tp1.paramInfos,
613+
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2, safeIntersection))
614+
case _ =>
615+
mergeConflict(sym1, sym2, tp1, tp2)
616+
}
617+
case tp1: PolyType =>
618+
tp2 match {
619+
case tp2: MethodType =>
620+
tp2
621+
case tp2: PolyType if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
622+
tp1.derivedLambdaType(
623+
mergeParamNames(tp1, tp2),
624+
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
625+
infoMeet(p1, p2.subst(tp2, tp1), sym1, sym2, safeIntersection).bounds
626+
},
627+
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2, safeIntersection))
628+
case _ =>
629+
mergeConflict(sym1, sym2, tp1, tp2)
630+
}
631+
632+
case _ =>
633+
try tp1 & tp2
634+
catch {
635+
case ex: Throwable =>
636+
println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}")
637+
throw ex
638+
}
639+
}
640+
}
641+
642+
/** Normally, `tp1 | tp2`. Special cases for matching methods and classes, with
643+
* the possibility of raising a merge error.
644+
*/
645+
def infoJoin(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Type = tp1 match {
646+
case tp1: TypeBounds =>
647+
tp2 match {
648+
case tp2: TypeBounds => tp1 | tp2
649+
case tp2: ClassInfo if tp1 contains tp2 => tp1
650+
case _ => mergeConflict(sym1, sym2, tp1, tp2)
651+
}
652+
case tp1: ClassInfo =>
653+
tp2 match {
654+
case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix)
655+
case tp2: TypeBounds if tp2 contains tp1 => tp2
656+
case _ => mergeConflict(sym1, sym2, tp1, tp2)
657+
}
658+
case tp1: MethodType =>
659+
tp2 match {
660+
case tp2: MethodType
661+
if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
662+
tp1.isImplicitMethod == tp2.isImplicitMethod =>
663+
tp1.derivedLambdaType(
664+
mergeParamNames(tp1, tp2),
665+
tp1.paramInfos,
666+
tp1.resultType | tp2.resultType.subst(tp2, tp1))
667+
case _ =>
668+
mergeConflict(sym1, sym2, tp1, tp2)
669+
}
670+
case tp1: PolyType =>
671+
tp2 match {
672+
case tp2: PolyType
673+
if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
674+
tp1.derivedLambdaType(
675+
mergeParamNames(tp1, tp2),
676+
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
677+
(p1 | p2.subst(tp2, tp1)).bounds
678+
},
679+
tp1.resultType | tp2.resultType.subst(tp2, tp1))
680+
case _ =>
681+
mergeConflict(sym1, sym2, tp1, tp2)
682+
}
683+
case _ =>
684+
tp1 | tp2
685+
}
686+
681687
/** A non-overloaded denotation */
682688
abstract class SingleDenotation(symbol: Symbol, initInfo: Type) extends Denotation(symbol, initInfo) {
683689
protected def newLikeThis(symbol: Symbol, info: Type): SingleDenotation

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+6-2
Original file line numberDiff line numberDiff line change
@@ -1643,8 +1643,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling {
16431643
case tp1: RefinedType =>
16441644
tp2 match {
16451645
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
1646-
tp1.derivedRefinedType(tp1.parent & tp2.parent, tp1.refinedName,
1647-
tp1.refinedInfo & tp2.refinedInfo)
1646+
val jointInfo =
1647+
try Denotations.infoMeet(tp1.refinedInfo, tp2.refinedInfo, NoSymbol, NoSymbol, safeIntersection = false)
1648+
catch {
1649+
case ex: MergeError => NoType
1650+
}
1651+
tp1.derivedRefinedType(tp1.parent & tp2.parent, tp1.refinedName, jointInfo)
16481652
case _ =>
16491653
NoType
16501654
}

tests/neg/i5592.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object Test {
2+
type Obj
3+
type Forall[F[_]] = (x: Obj) => F[x.type]
4+
5+
trait EQ[A, B] {
6+
def sub[F[_]]: F[A] => F[B];
7+
def commute: EQ[B, A] = this.sub[[b] => EQ[b, A]](implicitly[EQ[A, A]])
8+
}
9+
implicit def typeEq[A]: EQ[A, A] = new EQ[A, A] {
10+
def sub[F[_]]: F[A] => F[A] = identity
11+
}
12+
13+
// these are both fine
14+
val eqReflexive1: (x: Obj) => (EQ[x.type, x.type]) = { x: Obj => implicitly }
15+
val eqReflexive2: Forall[[x] => EQ[x, x]] = { x: Obj => implicitly }
16+
17+
// this compiles
18+
val eqSymmetric1: (x: Obj) => (y: Obj) => EQ[x.type, y.type] => EQ[y.type, x.type] = {
19+
{ x: Obj => { y: Obj => { xEqy: EQ[x.type, y.type] => xEqy.commute } } }
20+
}
21+
22+
val eqSymmetric2: Forall[[x] => (y: Obj) => (EQ[x, y.type]) => (EQ[y.type, x])] = {
23+
{ x: Obj => { y: Obj => { xEqy: EQ[x.type, y.type] => xEqy.commute } } } // error // error
24+
}
25+
26+
val eqSymmetric3: Forall[[x] => Forall[[y] => EQ[x, y] => EQ[y, x]]] = {
27+
{ x: Obj => { y: Obj => { xEqy: EQ[x.type, y.type] => xEqy.commute } } } // error // error
28+
}
29+
}

0 commit comments

Comments
 (0)