Skip to content

Commit 79b284b

Browse files
committed
Simplify level fixing scheme
Don't instantiate co- and contravariant inner type variables eagerly. Lift them instead to the reference level, same as for invariant type variables. Fixes #15934
1 parent 104e8df commit 79b284b

File tree

2 files changed

+61
-31
lines changed

2 files changed

+61
-31
lines changed

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

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -482,14 +482,9 @@ trait ConstraintHandling {
482482
* and computes on the side sets of nested type variables that need
483483
* to be instantiated.
484484
*/
485-
class NeedsLeveling extends TypeAccumulator[Boolean]:
485+
def needsLeveling = new TypeAccumulator[Boolean]:
486486
if !fromBelow then variance = -1
487487

488-
/** Nested type variables that should be instiated to theor lower (respoctively
489-
* upper) bounds.
490-
*/
491-
var nestedVarsLo, nestedVarsHi: SimpleIdentitySet[TypeVar] = SimpleIdentitySet.empty
492-
493488
def apply(need: Boolean, tp: Type) =
494489
need || tp.match
495490
case tp: NamedType =>
@@ -499,39 +494,30 @@ trait ConstraintHandling {
499494
val inst = tp.instanceOpt
500495
if inst.exists then apply(need, inst)
501496
else if tp.nestingLevel > maxLevel then
502-
if variance > 0 then nestedVarsLo += tp
503-
else if variance < 0 then nestedVarsHi += tp
504-
else
505-
// For invariant type variables, we use a different strategy.
506-
// Rather than instantiating to a bound and then propagating in an
507-
// AvoidMap, change the nesting level of an invariant type
508-
// variable to `maxLevel`. This means that the type variable will be
509-
// instantiated later to a less nested type. If there are other references
510-
// to the same type variable that do not come from the type undergoing
511-
// `fixLevels`, this could lead to coarser types. But it has the potential
512-
// to give a better approximation for the current type, since it avoids forming
513-
// a Range in invariant position, which can lead to very coarse types further out.
514-
constr.println(i"widening nesting level of type variable $tp from ${tp.nestingLevel} to $maxLevel")
515-
ctx.typerState.setNestingLevel(tp, maxLevel)
497+
// Change the nesting level of inner type variable to `maxLevel`.
498+
// This means that the type variable will be instantiated later to a
499+
// less nested type. If there are other references to the same type variable
500+
// that do not come from the type undergoing `fixLevels`, this could lead
501+
// to coarser types than intended. An alternative is to instantiate the
502+
// type variable right away, but this also loses information. See
503+
// i15934.scala for a test where the current strategey works but an early instantiation
504+
// of `tp` would fail.
505+
constr.println(i"widening nesting level of type variable $tp from ${tp.nestingLevel} to $maxLevel")
506+
ctx.typerState.setNestingLevel(tp, maxLevel)
516507
true
517508
else false
518509
case _ =>
519510
foldOver(need, tp)
520-
end NeedsLeveling
511+
end needsLeveling
521512

522-
class LevelAvoidMap extends TypeOps.AvoidMap:
513+
def levelAvoid = new TypeOps.AvoidMap:
523514
if !fromBelow then variance = -1
524515
def toAvoid(tp: NamedType) = needsFix(tp)
525516

526-
if !Config.checkLevelsOnInstantiation || ctx.isAfterTyper then tp
527-
else
528-
val needsLeveling = NeedsLeveling()
529-
if needsLeveling(false, tp) then
530-
typr.println(i"instance $tp for $param needs leveling to $maxLevel, nested = ${needsLeveling.nestedVarsLo.toList} | ${needsLeveling.nestedVarsHi.toList}")
531-
needsLeveling.nestedVarsLo.foreach(_.instantiate(fromBelow = true))
532-
needsLeveling.nestedVarsHi.foreach(_.instantiate(fromBelow = false))
533-
LevelAvoidMap()(tp)
534-
else tp
517+
if Config.checkLevelsOnInstantiation && !ctx.isAfterTyper && needsLeveling(false, tp) then
518+
typr.println(i"instance $tp for $param needs leveling to $maxLevel")
519+
levelAvoid(tp)
520+
else tp
535521
end fixLevels
536522

537523
/** Solve constraint set for given type parameter `param`.

tests/pos/i15934.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
trait ReplicatedData
2+
trait ActorRef[-T] {
3+
def tell(msg: T): Unit = ???
4+
}
5+
6+
// shared in both domains
7+
abstract class Key[+T1 <: ReplicatedData]
8+
9+
// domain 1
10+
object dd {
11+
sealed abstract class GetResponse[A1 <: ReplicatedData] {
12+
def key: Key[A1]
13+
}
14+
case class GetSuccess[A2 <: ReplicatedData](key: Key[A2]) extends GetResponse[A2]
15+
case class GetFailure[A3 <: ReplicatedData](key: Key[A3]) extends GetResponse[A3]
16+
}
17+
18+
// domain 2
19+
object JReplicator {
20+
final case class Get[A4 <: ReplicatedData](
21+
key: Key[A4],
22+
replyTo: ActorRef[GetResponse[A4]]
23+
)
24+
sealed abstract class GetResponse[A5 <: ReplicatedData] {
25+
def key: Key[A5]
26+
}
27+
case class GetSuccess[A6 <: ReplicatedData](key: Key[A6]) extends GetResponse[A6]
28+
case class GetFailure[A7 <: ReplicatedData](key: Key[A7]) extends GetResponse[A7]
29+
}
30+
31+
val _ = null.asInstanceOf[Any] match {
32+
case cmd: JReplicator.Get[d] =>
33+
val reply =
34+
util
35+
.Try[dd.GetResponse[d]](???)
36+
.map/*[JReplicator.GetResponse[d]]*/ {
37+
// Needs at least 2 cases to triger failure
38+
case rsp: dd.GetSuccess[d1] => JReplicator.GetSuccess(rsp.key)
39+
case rsp: dd.GetResponse[d2] => JReplicator.GetFailure(rsp.key)
40+
}
41+
// needs recover to trigger failure
42+
.recover { case _ => new JReplicator.GetFailure(cmd.key) }
43+
reply.foreach { cmd.replyTo tell _ } // error
44+
}

0 commit comments

Comments
 (0)