@@ -891,30 +891,90 @@ class CheckCaptures extends Recheck, SymTransformer:
891
891
capt.println(i " checked $root with $selfType" )
892
892
end checkSelfTypes
893
893
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
+ }
901
947
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)
906
966
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
911
970
912
971
/** Perform the following kinds of checks
913
972
* - Check all explicitly written capturing types for well-formedness using `checkWellFormedPost`.
914
973
* - Check that externally visible `val`s or `def`s have empty capture sets. If not,
915
974
* suggest an explicit type. This is so that separate compilation (where external
916
975
* symbols have empty capture sets) gives the same results as joint compilation.
917
976
* - Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
977
+ * - Heal ill-formed capture sets of type parameters. See `healTypeParam`.
918
978
*/
919
979
def postCheck (unit : tpd.Tree )(using Context ): Unit =
920
980
unit.foreachSubTree {
@@ -968,18 +1028,7 @@ class CheckCaptures extends Recheck, SymTransformer:
968
1028
checkBounds(normArgs, tl)
969
1029
case _ =>
970
1030
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(_))
983
1032
case _ =>
984
1033
}
985
1034
if ! ctx.reporter.errorsReported then
0 commit comments