Skip to content

Commit

Permalink
polish implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Linyxus committed Nov 13, 2022
1 parent 406f51b commit 0e7d33a
Showing 1 changed file with 76 additions and 27 deletions.
103 changes: 76 additions & 27 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -891,30 +891,90 @@ class CheckCaptures extends Recheck, SymTransformer:
capt.println(i"checked $root with $selfType")
end checkSelfTypes

private def healTypeParamCaptureSet(cs: CaptureSet)(using Context) = {
def avoidParam(tpr: TermParamRef): List[CaptureSet] =
val refs = tpr.binder.paramInfos(tpr.paramNum).captureSet
refs.filter(!_.isInstanceOf[TermParamRef]) :: refs.elems.toList.flatMap {
case tpr: TermParamRef => avoidParam(tpr)
case _ => Nil
}
/** Heal ill-formed capture sets in the type parameter.
*
* We can push parameter refs into a capture set in type parameters
* that this type parameter can't see.
* For example, when capture checking the following expression:
*
* def usingLogFile[T](op: (f: {*} File) => T): T = ...
*
* usingLogFile[box ?1 () -> Unit] { (f: {*} File) => () => { f.write(0) } }
*
* We may propagate `f` into ?1, making ?1 ill-formed.
* This also causes soundness issues, since `f` in ?1 should be widened to `*`,
* giving rise to an error that `*` cannot be included in a boxed capture set.
*
* To solve this, we still allow ?1 to capture parameter refs like `f`, but
* compensate this by pushing the widened capture set of `f` into ?1.
* This solves the soundness issue caused by the ill-formness of ?1.
*/
private def healTypeParam(tree: Tree)(using Context): Unit =
val tm = new TypeMap with IdempotentCaptRefMap:
private def isAllowed(ref: CaptureRef): Boolean = ref match
case ref: TermParamRef => allowed.contains(ref)
case _ => true

private def widenParamRefs(refs: List[TermParamRef]): List[CaptureSet] =
@scala.annotation.tailrec
def recur(todos: List[TermParamRef], acc: List[CaptureSet]): List[CaptureSet] =
todos match
case Nil => acc
case ref :: rem =>
val cs = ref.binder.paramInfos(ref.paramNum).captureSet
val nextAcc = cs.filter(isAllowed(_)) :: acc
val nextRem: List[TermParamRef] = (cs.elems.toList.filter(!isAllowed(_)) ++ rem).asInstanceOf
recur(nextRem, nextAcc)
recur(refs, Nil)

private def healCaptureSet(cs: CaptureSet): Unit =
val toInclude = widenParamRefs(cs.elems.toList.filter(!isAllowed(_)).asInstanceOf)
toInclude foreach { cs1 =>
// We omit the check of the result of capture set inclusion here,
// since there are only two possible kinds of errors.
// Both kinds will be detected in other places and tend to
// give better error messages.
//
// The two kinds of errors are:
// - Pushing `*` to a boxed capture set.
// This triggers error reporting registered as the `rootAddedHandler`
// in `CaptureSet`.
// - Failing to include a capture reference in a capture set.
// This is mostly due to the restriction placed by explicit type annotations,
// and should already be reported as a type mismatch during `checkConforms`.
cs1.subCaptures(cs, frozen = false)
}

val widenedSets = cs.elems.toList flatMap {
case tpr: TermParamRef => avoidParam(tpr)
case _ => Nil
}
private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty

def apply(tp: Type) =
tp match
case CapturingType(parent, refs) =>
healCaptureSet(refs)
mapOver(tp)
case tp @ RefinedType(parent, rname, rinfo: MethodType) =>
this(rinfo)
case tp: TermLambda =>
val localParams: List[TermParamRef] = tp.paramRefs
val saved = allowed
try
localParams foreach { x => allowed = allowed + x }
mapOver(tp)
finally allowed = saved
case _ =>
mapOver(tp)

widenedSets foreach { cs1 =>
cs1.subCaptures(cs, frozen = false)
}
}
if tree.isInstanceOf[InferredTypeTree] then
tm(tree.knownType)
end healTypeParam

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

args foreach { targ =>
val tp = targ.knownType
val tm = new TypeMap with IdempotentCaptRefMap:
def apply(tp: Type): Type =
tp match
case CapturingType(parent, refs) =>
healTypeParamCaptureSet(refs)
mapOver(tp)
case _ =>
mapOver(tp)
tm(tp)
}
args.foreach(healTypeParam(_))
case _ =>
}
if !ctx.reporter.errorsReported then
Expand Down

0 comments on commit 0e7d33a

Please sign in to comment.