Skip to content

Commit 0e7d33a

Browse files
committed
polish implementation
1 parent 406f51b commit 0e7d33a

File tree

1 file changed

+76
-27
lines changed

1 file changed

+76
-27
lines changed

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

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -891,30 +891,90 @@ class CheckCaptures extends Recheck, SymTransformer:
891891
capt.println(i"checked $root with $selfType")
892892
end checkSelfTypes
893893

894-
private def healTypeParamCaptureSet(cs: CaptureSet)(using Context) = {
895-
def avoidParam(tpr: TermParamRef): List[CaptureSet] =
896-
val refs = tpr.binder.paramInfos(tpr.paramNum).captureSet
897-
refs.filter(!_.isInstanceOf[TermParamRef]) :: refs.elems.toList.flatMap {
898-
case tpr: TermParamRef => avoidParam(tpr)
899-
case _ => Nil
900-
}
894+
/** Heal ill-formed capture sets in the type parameter.
895+
*
896+
* We can push parameter refs into a capture set in type parameters
897+
* that this type parameter can't see.
898+
* For example, when capture checking the following expression:
899+
*
900+
* def usingLogFile[T](op: (f: {*} File) => T): T = ...
901+
*
902+
* usingLogFile[box ?1 () -> Unit] { (f: {*} File) => () => { f.write(0) } }
903+
*
904+
* We may propagate `f` into ?1, making ?1 ill-formed.
905+
* This also causes soundness issues, since `f` in ?1 should be widened to `*`,
906+
* giving rise to an error that `*` cannot be included in a boxed capture set.
907+
*
908+
* To solve this, we still allow ?1 to capture parameter refs like `f`, but
909+
* compensate this by pushing the widened capture set of `f` into ?1.
910+
* This solves the soundness issue caused by the ill-formness of ?1.
911+
*/
912+
private def healTypeParam(tree: Tree)(using Context): Unit =
913+
val tm = new TypeMap with IdempotentCaptRefMap:
914+
private def isAllowed(ref: CaptureRef): Boolean = ref match
915+
case ref: TermParamRef => allowed.contains(ref)
916+
case _ => true
917+
918+
private def widenParamRefs(refs: List[TermParamRef]): List[CaptureSet] =
919+
@scala.annotation.tailrec
920+
def recur(todos: List[TermParamRef], acc: List[CaptureSet]): List[CaptureSet] =
921+
todos match
922+
case Nil => acc
923+
case ref :: rem =>
924+
val cs = ref.binder.paramInfos(ref.paramNum).captureSet
925+
val nextAcc = cs.filter(isAllowed(_)) :: acc
926+
val nextRem: List[TermParamRef] = (cs.elems.toList.filter(!isAllowed(_)) ++ rem).asInstanceOf
927+
recur(nextRem, nextAcc)
928+
recur(refs, Nil)
929+
930+
private def healCaptureSet(cs: CaptureSet): Unit =
931+
val toInclude = widenParamRefs(cs.elems.toList.filter(!isAllowed(_)).asInstanceOf)
932+
toInclude foreach { cs1 =>
933+
// We omit the check of the result of capture set inclusion here,
934+
// since there are only two possible kinds of errors.
935+
// Both kinds will be detected in other places and tend to
936+
// give better error messages.
937+
//
938+
// The two kinds of errors are:
939+
// - Pushing `*` to a boxed capture set.
940+
// This triggers error reporting registered as the `rootAddedHandler`
941+
// in `CaptureSet`.
942+
// - Failing to include a capture reference in a capture set.
943+
// This is mostly due to the restriction placed by explicit type annotations,
944+
// and should already be reported as a type mismatch during `checkConforms`.
945+
cs1.subCaptures(cs, frozen = false)
946+
}
901947

902-
val widenedSets = cs.elems.toList flatMap {
903-
case tpr: TermParamRef => avoidParam(tpr)
904-
case _ => Nil
905-
}
948+
private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty
949+
950+
def apply(tp: Type) =
951+
tp match
952+
case CapturingType(parent, refs) =>
953+
healCaptureSet(refs)
954+
mapOver(tp)
955+
case tp @ RefinedType(parent, rname, rinfo: MethodType) =>
956+
this(rinfo)
957+
case tp: TermLambda =>
958+
val localParams: List[TermParamRef] = tp.paramRefs
959+
val saved = allowed
960+
try
961+
localParams foreach { x => allowed = allowed + x }
962+
mapOver(tp)
963+
finally allowed = saved
964+
case _ =>
965+
mapOver(tp)
906966

907-
widenedSets foreach { cs1 =>
908-
cs1.subCaptures(cs, frozen = false)
909-
}
910-
}
967+
if tree.isInstanceOf[InferredTypeTree] then
968+
tm(tree.knownType)
969+
end healTypeParam
911970

912971
/** Perform the following kinds of checks
913972
* - Check all explicitly written capturing types for well-formedness using `checkWellFormedPost`.
914973
* - Check that externally visible `val`s or `def`s have empty capture sets. If not,
915974
* suggest an explicit type. This is so that separate compilation (where external
916975
* symbols have empty capture sets) gives the same results as joint compilation.
917976
* - Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
977+
* - Heal ill-formed capture sets of type parameters. See `healTypeParam`.
918978
*/
919979
def postCheck(unit: tpd.Tree)(using Context): Unit =
920980
unit.foreachSubTree {
@@ -968,18 +1028,7 @@ class CheckCaptures extends Recheck, SymTransformer:
9681028
checkBounds(normArgs, tl)
9691029
case _ =>
9701030

971-
args foreach { targ =>
972-
val tp = targ.knownType
973-
val tm = new TypeMap with IdempotentCaptRefMap:
974-
def apply(tp: Type): Type =
975-
tp match
976-
case CapturingType(parent, refs) =>
977-
healTypeParamCaptureSet(refs)
978-
mapOver(tp)
979-
case _ =>
980-
mapOver(tp)
981-
tm(tp)
982-
}
1031+
args.foreach(healTypeParam(_))
9831032
case _ =>
9841033
}
9851034
if !ctx.reporter.errorsReported then

0 commit comments

Comments
 (0)