Skip to content

Commit 18b8ff4

Browse files
committed
Tweak: Widen skolem types before conformity checks
1 parent d157daa commit 18b8ff4

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,8 @@ class CheckCaptures extends Recheck, SymTransformer:
762762
// We can't box/unbox the universal capability. Leave `actual` as it is
763763
// so we get an error in checkConforms. This tends to give better error
764764
// messages than disallowing the root capability in `criticalSet`.
765-
capt.println(i"cannot box/unbox $actual vs $expected")
765+
if ctx.settings.YccDebug.value then
766+
println(i"cannot box/unbox $actual vs $expected")
766767
actual
767768
else
768769
// Disallow future addition of `*` to `criticalSet`.

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,14 @@ abstract class Recheck extends Phase, SymTransformer:
300300

301301
val rawType = recheck(tree.expr)
302302
val ownType = avoidMap(rawType)
303+
303304
// The pattern matching translation, which runs before this phase
304305
// sometimes instantiates return types with singleton type alternatives
305306
// but the returned expression is widened. We compensate by widening the expected
306-
// type as well.
307+
// type as well. See also `widenSkolems` in `checkConformsExpr` which fixes
308+
// a more general problem. It turns out that pattern matching returns
309+
// are not checked by Ycheck, that's why these problems were allowed to slip
310+
// through.
307311
def widened(tp: Type): Type = tp match
308312
case tp: SingletonType => tp.widen
309313
case tp: AndOrType => tp.derivedAndOrType(widened(tp.tp1), widened(tp.tp2))
@@ -435,6 +439,27 @@ abstract class Recheck extends Phase, SymTransformer:
435439
throw ex
436440
}
437441

442+
/** Typing and previous transforms sometiems leaves skolem types in prefixes of
443+
* NamedTypes in `expected` that do not match the `actual` Type. -Ycheck does
444+
* not complain (need to find out why), but a full recheck does. We compensate
445+
* by de-skolemizing everywhere in `expected` except when variance is negative.
446+
* @return If `tp` contains SkolemTypes in covariant or invariant positions,
447+
* the type where these SkolemTypes are mapped to their underlying type.
448+
* Otherwise, `tp` itself
449+
*/
450+
def widenSkolems(tp: Type)(using Context): Type =
451+
object widenSkolems extends TypeMap, IdempotentCaptRefMap:
452+
var didWiden: Boolean = false
453+
def apply(t: Type): Type = t match
454+
case t: SkolemType if variance >= 0 =>
455+
didWiden = true
456+
apply(t.underlying)
457+
case t: LazyRef => t
458+
case t @ AnnotatedType(t1, ann) => t.derivedAnnotatedType(apply(t1), ann)
459+
case _ => mapOver(t)
460+
val tp1 = widenSkolems(tp)
461+
if widenSkolems.didWiden then tp1 else tp
462+
438463
/** If true, print info for some successful checkConforms operations (failing ones give
439464
* an error message in any case).
440465
*/
@@ -450,18 +475,24 @@ abstract class Recheck extends Phase, SymTransformer:
450475

451476
def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit =
452477
//println(i"check conforms $actual <:< $expected")
453-
val isCompatible =
478+
479+
def isCompatible(expected: Type): Boolean =
454480
actual <:< expected
455481
|| expected.isRepeatedParam
456-
&& actual <:< expected.translateFromRepeated(toArray = tree.tpe.isRef(defn.ArrayClass))
457-
if !isCompatible then
482+
&& isCompatible(expected.translateFromRepeated(toArray = tree.tpe.isRef(defn.ArrayClass)))
483+
|| {
484+
val widened = widenSkolems(expected)
485+
(widened ne expected) && isCompatible(widened)
486+
}
487+
if !isCompatible(expected) then
458488
recheckr.println(i"conforms failed for ${tree}: $actual vs $expected")
459489
err.typeMismatch(tree.withType(actual), expected)
460490
else if debugSuccesses then
461491
tree match
462492
case _: Ident =>
463493
println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}")
464494
case _ =>
495+
end checkConformsExpr
465496

466497
def checkUnit(unit: CompilationUnit)(using Context): Unit =
467498
recheck(unit.tpdTree)

tests/pending/pos/i16268.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import language.experimental.captureChecking
2+
class Tree
3+
case class Thicket(trees: List[Tree]) extends Tree
4+
5+
def test1(segments: List[{*} Tree]) =
6+
val elems = segments flatMap { (t: {*} Tree) => t match // error
7+
case ts: Thicket => ts.trees.tail
8+
case t => Nil
9+
}
10+
elems
11+
12+
def test2(segments: List[{*} Tree]) =
13+
val f = (t: {*} Tree) => t match
14+
case ts: Thicket => ts.trees.tail
15+
case t => Nil
16+
val elems = segments.flatMap(f) // error
17+
elems
18+
19+
def test3(c: {*} Any)(segments: List[{c} Tree]) =
20+
val elems = segments flatMap { (t: {c} Tree) => t match
21+
case ts: Thicket => ts.trees.tail
22+
case t => Nil
23+
}
24+
elems
25+

0 commit comments

Comments
 (0)