Skip to content

Commit 8de6d88

Browse files
authored
Sharpen range approximation for applied types with capture set ranges (#16261)
This is a stopgap to avoid approximating the core type to Nothing. It can probably be dropped once we have capture set ranges that we can keep as inference results. The problem is, what should be the approximation of the range C[{cs1} A .. {cs2} A] where the type constructor `C` is non-variant? If the variance of the enclosing map is positive, this is `C[? >: {cs1} A <: {cs2} A]`, which is a supertype of both range end points `C[{cs1} A]` and `C[{cs2} A]`. But if the variance is negative, we would normally fall back to `Nothing`, since that is the only known subtype of both range end points. This reasoning seems too strict for capture checking. In a sense, we have already inferred `C[A]` before; now we just need to find out what the set should be. What we are after is a notion of a _capture set range_. I.e. something like C[{cs1}..{cs2} A] with the meaning that the capture set of `C` is an unknown set between `cs1` and `cs2`. We don't have that abstraction yet, so for now we approximate by the bounds, which avoids the failures, even though its soundness status is currently a bit unclear.
2 parents 9c68fd7 + ae1fa0b commit 8de6d88

File tree

5 files changed

+45
-3
lines changed

5 files changed

+45
-3
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,18 @@ object TypeOps:
533533
val sym = tp.symbol
534534
forbidden.contains(sym)
535535

536+
/** We need to split the set into upper and lower approximations
537+
* only if it contains a local element. The idea here is that at the
538+
* time we perform an `avoid` all local elements are already accounted for
539+
* and no further elements will be added afterwards. So we can just keep
540+
* the set as it is. See comment by @linyxus on #16261.
541+
*/
542+
override def needsRangeIfInvariant(refs: CaptureSet): Boolean =
543+
refs.elems.exists {
544+
case ref: TermRef => toAvoid(ref)
545+
case _ => false
546+
}
547+
536548
override def apply(tp: Type): Type = tp match
537549
case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) =>
538550
val lo = TypeComparer.instanceType(

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6027,8 +6027,11 @@ object Types {
60276027
tp.derivedLambdaType(tp.paramNames, formals, restpe)
60286028
}
60296029

6030+
/** Overridden in TypeOps.avoid */
6031+
protected def needsRangeIfInvariant(refs: CaptureSet): Boolean = true
6032+
60306033
override def mapCapturingType(tp: Type, parent: Type, refs: CaptureSet, v: Int): Type =
6031-
if v == 0 then
6034+
if v == 0 && needsRangeIfInvariant(refs) then
60326035
range(mapCapturingType(tp, parent, refs, -1), mapCapturingType(tp, parent, refs, 1))
60336036
else
60346037
super.mapCapturingType(tp, parent, refs, v)

tests/neg-custom-args/captures/try.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------
22
23 | val a = handle[Exception, CanThrow[Exception]] { // error
33
| ^
4-
| Found: ? ({*} CT[Exception]) -> {*} CT[? >: ? Exception <: ? Exception]
5-
| Required: CanThrow[Exception] => box {*} CT[Exception]
4+
| Found: ? ({*} CT[Exception]) -> CanThrow[? Exception]
5+
| Required: CanThrow[Exception] => box {*} CT[Exception]
66
24 | (x: CanThrow[Exception]) => x
77
25 | }{
88
|
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@annotation.capability class Cap
2+
3+
class LazyRef[T](val elem: () => T):
4+
val get: {elem} () -> T = elem
5+
def map[U](f: T => U): {f, this} LazyRef[U] =
6+
new LazyRef(() => f(elem()))
7+
8+
def map[A, B](ref: {*} LazyRef[A], f: A => B): {f, ref} LazyRef[B] =
9+
new LazyRef(() => f(ref.elem()))
10+
11+
def main(io: Cap) = {
12+
def mapd[A, B]: ({io} LazyRef[A], A => B) => {*} LazyRef[B] =
13+
(ref1, f1) => map[A, B](ref1, f1)
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Name
2+
class TermName extends Name
3+
class TypeName extends Name
4+
5+
trait ParamInfo:
6+
type ThisName <: Name
7+
def variance: Long
8+
object ParamInfo:
9+
type Of[N <: Name] = ParamInfo { type ThisName = N }
10+
11+
def test(tparams1: List[ParamInfo{ type ThisName = TypeName }], tparams2: List[ParamInfo.Of[TypeName]]) =
12+
tparams1.lazyZip(tparams2).map((p1, p2) => p1.variance + p2.variance)
13+

0 commit comments

Comments
 (0)