@@ -128,6 +128,20 @@ object CheckCaptures:
128128 if remaining.accountsFor(firstRef) then
129129 report.warning(em " redundant capture: $remaining already accounts for $firstRef" , ann.srcPos)
130130
131+ def disallowRootCapabilitiesIn (tp : Type , what : String , have : String , addendum : String , pos : SrcPos )(using Context ) =
132+ val check = new TypeTraverser :
133+ def traverse (t : Type ) =
134+ if variance >= 0 then
135+ t.captureSet.disallowRootCapability: () =>
136+ def part = if t eq tp then " " else i " the part $t of "
137+ report.error(
138+ em """ $what cannot $have $tp since
139+ | ${part}that type captures the root capability `cap`.
140+ | $addendum""" ,
141+ pos)
142+ traverseChildren(t)
143+ check.traverse(tp)
144+
131145class CheckCaptures extends Recheck , SymTransformer :
132146 thisPhase =>
133147
@@ -525,6 +539,15 @@ class CheckCaptures extends Recheck, SymTransformer:
525539 case _ =>
526540 super .recheckTyped(tree)
527541
542+ override def recheckTry (tree : Try , pt : Type )(using Context ): Type =
543+ val tp = super .recheckTry(tree, pt)
544+ if allowUniversalInBoxed && Feature .enabled(Feature .saferExceptions) then
545+ disallowRootCapabilitiesIn(tp,
546+ " Result of `try`" , " have type" ,
547+ " This is often caused by a locally generated exception capability leaking as part of its result." ,
548+ tree.srcPos)
549+ tp
550+
528551 /* Currently not needed, since capture checking takes place after ElimByName.
529552 * Keep around in case we need to get back to it
530553 def recheckByNameArg(tree: Tree, pt: Type)(using Context): Type =
@@ -588,7 +611,7 @@ class CheckCaptures extends Recheck, SymTransformer:
588611 }
589612 checkNotUniversal(parent)
590613 case _ =>
591- checkNotUniversal(typeToCheck)
614+ if ! allowUniversalInBoxed then checkNotUniversal(typeToCheck)
592615 super .recheckFinish(tpe, tree, pt)
593616
594617 /** Massage `actual` and `expected` types using the methods below before checking conformance */
@@ -771,21 +794,22 @@ class CheckCaptures extends Recheck, SymTransformer:
771794 val criticalSet = // the set which is not allowed to have `cap`
772795 if covariant then cs1 // can't box with `cap`
773796 else expected.captureSet // can't unbox with `cap`
774- if criticalSet.isUniversal && expected.isValueType then
797+ if criticalSet.isUniversal && expected.isValueType && ! allowUniversalInBoxed then
775798 // We can't box/unbox the universal capability. Leave `actual` as it is
776799 // so we get an error in checkConforms. This tends to give better error
777800 // messages than disallowing the root capability in `criticalSet`.
778801 if ctx.settings.YccDebug .value then
779802 println(i " cannot box/unbox $actual vs $expected" )
780803 actual
781804 else
782- // Disallow future addition of `cap` to `criticalSet`.
783- criticalSet.disallowRootCapability { () =>
784- report.error(
785- em """ $actual cannot be box-converted to $expected
786- |since one of their capture sets contains the root capability `cap` """ ,
787- pos)
788- }
805+ if ! allowUniversalInBoxed then
806+ // Disallow future addition of `cap` to `criticalSet`.
807+ criticalSet.disallowRootCapability { () =>
808+ report.error(
809+ em """ $actual cannot be box-converted to $expected
810+ |since one of their capture sets contains the root capability `cap` """ ,
811+ pos)
812+ }
789813 if ! insertBox then // unboxing
790814 markFree(criticalSet, pos)
791815 adaptedType(! boxed)
@@ -860,7 +884,7 @@ class CheckCaptures extends Recheck, SymTransformer:
860884
861885 /** Check that self types of subclasses conform to self types of super classes.
862886 * (See comment below how this is achieved). The check assumes that classes
863- * without an explicit self type have the universal capture set `{* }` on the
887+ * without an explicit self type have the universal capture set `{cap }` on the
864888 * self type. If a class without explicit self type is not `effectivelyFinal`
865889 * it is checked that the inferred self type is universal, in order to assure
866890 * that joint and separate compilation give the same result.
@@ -917,9 +941,9 @@ class CheckCaptures extends Recheck, SymTransformer:
917941 * that this type parameter can't see.
918942 * For example, when capture checking the following expression:
919943 *
920- * def usingLogFile[T](op: (f: {* } File) => T): T = ...
944+ * def usingLogFile[T](op: (f: {cap } File) => T): T = ...
921945 *
922- * usingLogFile[box ?1 () -> Unit] { (f: {* } File) => () => { f.write(0) } }
946+ * usingLogFile[box ?1 () -> Unit] { (f: {cap } File) => () => { f.write(0) } }
923947 *
924948 * We may propagate `f` into ?1, making ?1 ill-formed.
925949 * This also causes soundness issues, since `f` in ?1 should be widened to `cap`,
@@ -986,61 +1010,67 @@ class CheckCaptures extends Recheck, SymTransformer:
9861010 * - Heal ill-formed capture sets of type parameters. See `healTypeParam`.
9871011 */
9881012 def postCheck (unit : tpd.Tree )(using Context ): Unit =
989- unit.foreachSubTree {
990- case _ : InferredTypeTree =>
991- case tree : TypeTree if ! tree.span.isZeroExtent =>
992- tree.knownType.foreachPart { tp =>
993- checkWellformedPost(tp, tree.srcPos)
994- tp match
995- case AnnotatedType (_, annot) if annot.symbol == defn.RetainsAnnot =>
996- warnIfRedundantCaptureSet(annot.tree)
1013+ val checker = new TreeTraverser :
1014+ def traverse (tree : Tree )(using Context ): Unit =
1015+ traverseChildren(tree)
1016+ check(tree)
1017+ def check (tree : Tree ) = tree match
1018+ case _ : InferredTypeTree =>
1019+ case tree : TypeTree if ! tree.span.isZeroExtent =>
1020+ tree.knownType.foreachPart { tp =>
1021+ checkWellformedPost(tp, tree.srcPos)
1022+ tp match
1023+ case AnnotatedType (_, annot) if annot.symbol == defn.RetainsAnnot =>
1024+ warnIfRedundantCaptureSet(annot.tree)
1025+ case _ =>
1026+ }
1027+ case t : ValOrDefDef
1028+ if t.tpt.isInstanceOf [InferredTypeTree ] && ! Synthetics .isExcluded(t.symbol) =>
1029+ val sym = t.symbol
1030+ val isLocal =
1031+ sym.owner.ownersIterator.exists(_.isTerm)
1032+ || sym.accessBoundary(defn.RootClass ).isContainedIn(sym.topLevelClass)
1033+ def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
1034+ sym.is(Private ) // private symbols can always have inferred types
1035+ || sym.name.is(DefaultGetterName ) // default getters are exempted since otherwise it would be
1036+ // too annoying. This is a hole since a defualt getter's result type
1037+ // might leak into a type variable.
1038+ || // non-local symbols cannot have inferred types since external capture types are not inferred
1039+ isLocal // local symbols still need explicit types if
1040+ && ! sym.owner.is(Trait ) // they are defined in a trait, since we do OverridingPairs checking before capture inference
1041+ def isNotPureThis (ref : CaptureRef ) = ref match {
1042+ case ref : ThisType => ! ref.cls.isPureClass
1043+ case _ => true
1044+ }
1045+ if ! canUseInferred then
1046+ val inferred = t.tpt.knownType
1047+ def checkPure (tp : Type ) = tp match
1048+ case CapturingType (_, refs)
1049+ if ! refs.elems.filter(isNotPureThis).isEmpty =>
1050+ val resultStr = if t.isInstanceOf [DefDef ] then " result" else " "
1051+ report.error(
1052+ em """ Non-local $sym cannot have an inferred $resultStr type
1053+ | $inferred
1054+ |with non-empty capture set $refs.
1055+ |The type needs to be declared explicitly. """ .withoutDisambiguation(),
1056+ t.srcPos)
1057+ case _ =>
1058+ inferred.foreachPart(checkPure, StopAt .Static )
1059+ case t @ TypeApply (fun, args) =>
1060+ fun.knownType.widen match
1061+ case tl : PolyType =>
1062+ val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) =>
1063+ arg.withType(arg.knownType.forceBoxStatus(
1064+ bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
1065+ }
1066+ checkBounds(normArgs, tl)
9971067 case _ =>
998- }
999- case t : ValOrDefDef
1000- if t.tpt.isInstanceOf [InferredTypeTree ] && ! Synthetics .isExcluded(t.symbol) =>
1001- val sym = t.symbol
1002- val isLocal =
1003- sym.owner.ownersIterator.exists(_.isTerm)
1004- || sym.accessBoundary(defn.RootClass ).isContainedIn(sym.topLevelClass)
1005- def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
1006- sym.is(Private ) // private symbols can always have inferred types
1007- || sym.name.is(DefaultGetterName ) // default getters are exempted since otherwise it would be
1008- // too annoying. This is a hole since a defualt getter's result type
1009- // might leak into a type variable.
1010- || // non-local symbols cannot have inferred types since external capture types are not inferred
1011- isLocal // local symbols still need explicit types if
1012- && ! sym.owner.is(Trait ) // they are defined in a trait, since we do OverridingPairs checking before capture inference
1013- def isNotPureThis (ref : CaptureRef ) = ref match {
1014- case ref : ThisType => ! ref.cls.isPureClass
1015- case _ => true
1016- }
1017- if ! canUseInferred then
1018- val inferred = t.tpt.knownType
1019- def checkPure (tp : Type ) = tp match
1020- case CapturingType (_, refs)
1021- if ! refs.elems.filter(isNotPureThis).isEmpty =>
1022- val resultStr = if t.isInstanceOf [DefDef ] then " result" else " "
1023- report.error(
1024- em """ Non-local $sym cannot have an inferred $resultStr type
1025- | $inferred
1026- |with non-empty capture set $refs.
1027- |The type needs to be declared explicitly. """ .withoutDisambiguation(),
1028- t.srcPos)
1029- case _ =>
1030- inferred.foreachPart(checkPure, StopAt .Static )
1031- case t @ TypeApply (fun, args) =>
1032- fun.knownType.widen match
1033- case tl : PolyType =>
1034- val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) =>
1035- arg.withType(arg.knownType.forceBoxStatus(
1036- bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
1037- }
1038- checkBounds(normArgs, tl)
1039- case _ =>
10401068
1041- args.foreach(healTypeParam(_))
1042- case _ =>
1043- }
1069+ args.foreach(healTypeParam(_))
1070+ case _ =>
1071+ end check
1072+ end checker
1073+ checker.traverse(unit)
10441074 if ! ctx.reporter.errorsReported then
10451075 // We dont report errors here if previous errors were reported, because other
10461076 // errors often result in bad applied types, but flagging these bad types gives
0 commit comments