Skip to content

Commit b48b6b2

Browse files
oderskyneko-kai
authored andcommitted
Treat Refinements more like AndTypes
Fixes #12306 When comparing with an AndType on the left and a RefinedType on the right, decompose the right hand side into an AndType of types that have a single refinement each. This makes sure we lose the least information in the necessary subsequent `either` call.
1 parent 1e3b0f5 commit b48b6b2

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -570,10 +570,19 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
570570
if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype)
571571
tp1 match {
572572
case tp1: AndType =>
573-
// Delay calling `compareRefinedSlow` because looking up a member
574-
// of an `AndType` can lead to a cascade of subtyping checks
575-
// This twist is needed to make collection/generic/ParFactory.scala compile
576-
fourthTry || compareRefinedSlow
573+
// TODO: this should really be an in depth analysis whether LHS contains
574+
// an AndType, or has an AndType as bound. What matters is to predict
575+
// whether we will be forced into an either later on.
576+
tp2.parent match
577+
case _: RefinedType | _: AndType =>
578+
// maximally decompose RHS to limit the bad effects of the `either` that is necessary
579+
// since LHS is an AndType
580+
recur(tp1, decomposeRefinements(tp2, Nil))
581+
case _ =>
582+
// Delay calling `compareRefinedSlow` because looking up a member
583+
// of an `AndType` can lead to a cascade of subtyping checks
584+
// This twist is needed to make collection/generic/ParFactory.scala compile
585+
fourthTry || compareRefinedSlow
577586
case tp1: HKTypeLambda =>
578587
// HKTypeLambdas do not have members.
579588
fourthTry
@@ -1691,6 +1700,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
16911700
else op2
16921701
end necessaryEither
16931702

1703+
/** Decompose into conjunction of types each of which has only a single refinement */
1704+
def decomposeRefinements(tp: Type, refines: List[(Name, Type)]): Type = tp match
1705+
case RefinedType(parent, rname, rinfo) =>
1706+
decomposeRefinements(parent, (rname, rinfo) :: refines)
1707+
case AndType(tp1, tp2) =>
1708+
AndType(decomposeRefinements(tp1, refines), decomposeRefinements(tp2, refines))
1709+
case _ =>
1710+
refines.map(RefinedType(tp, _, _): Type).reduce(AndType(_, _))
1711+
16941712
/** Does type `tp1` have a member with name `name` whose normalized type is a subtype of
16951713
* the normalized type of the refinement `tp2`?
16961714
* Normalization is as follows: If `tp2` contains a skolem to its refinement type,

tests/pos/i12306.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Record(elems: Map[String, Any]) extends Selectable:
2+
val fields = elems.toMap
3+
def selectDynamic(name: String): Any = fields(name)
4+
5+
extension [A <: Record] (a:A) {
6+
def join[B <: Record] (b:B): A & B = {
7+
Record(a.fields ++ b.fields).asInstanceOf[A & B]
8+
}
9+
}
10+
11+
type Person = Record { val name: String; val age: Int }
12+
type Child = Record { val parent: String }
13+
type PersonAndChild = Record { val name: String; val age: Int; val parent: String }
14+
15+
@main def hello = {
16+
val person = Record(Map("name" -> "Emma", "age" -> 42)).asInstanceOf[Person]
17+
val child = Record(Map("parent" -> "Alice")).asInstanceOf[Child]
18+
val personAndChild = person.join(child)
19+
20+
val v1: PersonAndChild = personAndChild
21+
val v2: PersonAndChild = person.join(child)
22+
}

0 commit comments

Comments
 (0)