@@ -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