Skip to content

Commit 9fa253d

Browse files
committed
Use a BiTypeMap for substitutions when possible
This reduces the chance of information loss in capture set propagation for applications.
1 parent 77aa78f commit 9fa253d

File tree

1 file changed

+48
-3
lines changed

1 file changed

+48
-3
lines changed

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

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ object CheckCaptures:
7373

7474
/** Similar normal substParams, but this is an approximating type map that
7575
* maps parameters in contravariant capture sets to the empty set.
76-
* TODO: check what happens with non-variant.
7776
*/
7877
final class SubstParamsMap(from: BindingType, to: List[Type])(using Context)
7978
extends ApproximatingTypeMap, IdempotentCaptRefMap:
@@ -96,6 +95,36 @@ object CheckCaptures:
9695
mapOver(tp)
9796
end SubstParamsMap
9897

98+
final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context)
99+
extends BiTypeMap:
100+
101+
def apply(tp: Type): Type = tp match
102+
case tp: ParamRef =>
103+
if tp.binder == from then to(tp.paramNum) else tp
104+
case tp: NamedType =>
105+
if tp.prefix `eq` NoPrefix then tp
106+
else tp.derivedSelect(apply(tp.prefix))
107+
case _: ThisType =>
108+
tp
109+
case _ =>
110+
mapOver(tp)
111+
112+
def inverse(tp: Type): Type = tp match
113+
case tp: NamedType =>
114+
var idx = 0
115+
var to1 = to
116+
while idx < to.length && (tp ne to(idx)) do
117+
idx += 1
118+
to1 = to1.tail
119+
if idx < to.length then from.paramRefs(idx)
120+
else if tp.prefix `eq` NoPrefix then tp
121+
else tp.derivedSelect(apply(tp.prefix))
122+
case _: ThisType =>
123+
tp
124+
case _ =>
125+
mapOver(tp)
126+
end SubstParamsBiMap
127+
99128
/** Check that a @retains annotation only mentions references that can be tracked.
100129
* This check is performed at Typer.
101130
*/
@@ -437,18 +466,34 @@ class CheckCaptures extends Recheck, SymTransformer:
437466
case appType => appType
438467
end recheckApply
439468

469+
private def isDistinct(xs: List[Type]): Boolean = xs match
470+
case x :: xs1 => xs1.isEmpty || !xs1.contains(x) && isDistinct(xs1)
471+
case Nil => true
472+
473+
private def isTrackable(tp: Type)(using Context) = tp match
474+
case tp: CaptureRef => tp.canBeTracked
475+
case _ => false
476+
440477
/** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`.
441478
* This means:
442479
* - Instantiate result type with actual arguments
443480
* - If call is to a constructor:
444481
* - remember types of arguments corresponding to tracked
445482
* parameters in refinements.
446483
* - add capture set of instantiated class to capture set of result type.
484+
* If all argument types are mutually disfferent trackable capture references, use a BiTypeMap,
485+
* since that is more precise. Otherwise use a normal idempotent map, which might lose information
486+
* in the case where the result type contains captureset variables that are further
487+
* constrained afterwards.
447488
*/
448489
override def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type =
449490
val ownType =
450-
if mt.isResultDependent then SubstParamsMap(mt, argTypes)(mt.resType)
451-
else mt.resType
491+
if !mt.isResultDependent then
492+
mt.resType
493+
else if argTypes.forall(isTrackable) && isDistinct(argTypes) then
494+
SubstParamsBiMap(mt, argTypes)(mt.resType)
495+
else
496+
SubstParamsMap(mt, argTypes)(mt.resType)
452497

453498
if sym.isConstructor then
454499
val cls = sym.owner.asClass

0 commit comments

Comments
 (0)