Skip to content

Commit 844537a

Browse files
committed
Improve handling of no-cap-under-box/unbox errors
- Improve error messages - Better propagation of @uncheckedCaptures - -un-deprecacte caps.unsafeUnbox and friends.
1 parent 23f9245 commit 844537a

File tree

3 files changed

+53
-29
lines changed

3 files changed

+53
-29
lines changed

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

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ast.{tpd, untpd, Trees}
1212
import Trees.*
1313
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker}
1414
import typer.Checking.{checkBounds, checkAppliedTypesIn}
15-
import typer.ErrorReporting.{Addenda, err}
15+
import typer.ErrorReporting.{Addenda, NothingToAdd, err}
1616
import typer.ProtoTypes.{AnySelectionProto, LhsProto}
1717
import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
@@ -22,7 +22,7 @@ import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
25-
import reporting.trace
25+
import reporting.{trace, Message}
2626

2727
/** The capture checker */
2828
object CheckCaptures:
@@ -866,7 +866,10 @@ class CheckCaptures extends Recheck, SymTransformer:
866866
}
867867
checkNotUniversal(parent)
868868
case _ =>
869-
if !ccConfig.allowUniversalInBoxed && needsUniversalCheck then
869+
if !ccConfig.allowUniversalInBoxed
870+
&& !tpe.hasAnnotation(defn.UncheckedCapturesAnnot)
871+
&& needsUniversalCheck
872+
then
870873
checkNotUniversal(tpe)
871874
super.recheckFinish(tpe, tree, pt)
872875
end recheckFinish
@@ -884,6 +887,17 @@ class CheckCaptures extends Recheck, SymTransformer:
884887

885888
private inline val debugSuccesses = false
886889

890+
type BoxErrors = mutable.ListBuffer[Message] | Null
891+
892+
private def boxErrorAddenda(boxErrors: BoxErrors) =
893+
if boxErrors == null then NothingToAdd
894+
else new Addenda:
895+
override def toAdd(using Context): List[String] =
896+
boxErrors.toList.map: msg =>
897+
i"""
898+
|
899+
|Note that ${msg.toString}"""
900+
887901
/** Massage `actual` and `expected` types before checking conformance.
888902
* Massaging is done by the methods following this one:
889903
* - align dependent function types and add outer references in the expected type
@@ -893,7 +907,8 @@ class CheckCaptures extends Recheck, SymTransformer:
893907
*/
894908
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
895909
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
896-
val actualBoxed = adapt(actual, expected1, tree.srcPos)
910+
val boxErrors = new mutable.ListBuffer[Message]
911+
val actualBoxed = adapt(actual, expected1, tree.srcPos, boxErrors)
897912
//println(i"check conforms $actualBoxed <<< $expected1")
898913

899914
if actualBoxed eq actual then
@@ -907,7 +922,8 @@ class CheckCaptures extends Recheck, SymTransformer:
907922
actualBoxed
908923
else
909924
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
910-
err.typeMismatch(tree.withType(actualBoxed), expected1, addenda ++ CaptureSet.levelErrors)
925+
err.typeMismatch(tree.withType(actualBoxed), expected1,
926+
addenda ++ CaptureSet.levelErrors ++ boxErrorAddenda(boxErrors))
911927
actual
912928
end checkConformsExpr
913929

@@ -991,7 +1007,7 @@ class CheckCaptures extends Recheck, SymTransformer:
9911007
*
9921008
* @param alwaysConst always make capture set variables constant after adaptation
9931009
*/
994-
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean)(using Context): Type =
1010+
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type =
9951011

9961012
/** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
9971013
* @param boxed if true we adapt to a boxed expected type
@@ -1008,8 +1024,8 @@ class CheckCaptures extends Recheck, SymTransformer:
10081024
case FunctionOrMethod(eargs, eres) => (eargs, eres)
10091025
case _ => (aargs.map(_ => WildcardType), WildcardType)
10101026
val aargs1 = aargs.zipWithConserve(eargs):
1011-
adaptBoxed(_, _, pos, !covariant, alwaysConst)
1012-
val ares1 = adaptBoxed(ares, eres, pos, covariant, alwaysConst)
1027+
adaptBoxed(_, _, pos, !covariant, alwaysConst, boxErrors)
1028+
val ares1 = adaptBoxed(ares, eres, pos, covariant, alwaysConst, boxErrors)
10131029
val resTp =
10141030
if (aargs1 eq aargs) && (ares1 eq ares) then actualShape // optimize to avoid redundant matches
10151031
else actualShape.derivedFunctionOrMethod(aargs1, ares1)
@@ -1057,22 +1073,26 @@ class CheckCaptures extends Recheck, SymTransformer:
10571073
val criticalSet = // the set which is not allowed to have `cap`
10581074
if covariant then captures // can't box with `cap`
10591075
else expected.captureSet // can't unbox with `cap`
1060-
if criticalSet.isUniversal && expected.isValueType && !ccConfig.allowUniversalInBoxed then
1076+
def msg = em"""$actual cannot be box-converted to $expected
1077+
|since at least one of their capture sets contains the root capability `cap`"""
1078+
def allowUniversalInBoxed =
1079+
ccConfig.allowUniversalInBoxed
1080+
|| expected.hasAnnotation(defn.UncheckedCapturesAnnot)
1081+
|| actual.widen.hasAnnotation(defn.UncheckedCapturesAnnot)
1082+
if criticalSet.isUniversal && expected.isValueType && !allowUniversalInBoxed then
10611083
// We can't box/unbox the universal capability. Leave `actual` as it is
1062-
// so we get an error in checkConforms. This tends to give better error
1084+
// so we get an error in checkConforms. Add the error message generated
1085+
// from boxing as an addendum. This tends to give better error
10631086
// messages than disallowing the root capability in `criticalSet`.
1087+
if boxErrors != null then boxErrors += msg
10641088
if ctx.settings.YccDebug.value then
10651089
println(i"cannot box/unbox $actual vs $expected")
10661090
actual
10671091
else
1068-
if !ccConfig.allowUniversalInBoxed then
1092+
if !allowUniversalInBoxed then
10691093
// Disallow future addition of `cap` to `criticalSet`.
1070-
criticalSet.disallowRootCapability { () =>
1071-
report.error(
1072-
em"""$actual cannot be box-converted to $expected
1073-
|since one of their capture sets contains the root capability `cap`""",
1074-
pos)
1075-
}
1094+
criticalSet.disallowRootCapability: () =>
1095+
report.error(msg, pos)
10761096
if !insertBox then // unboxing
10771097
//debugShowEnvs()
10781098
markFree(criticalSet, pos)
@@ -1109,13 +1129,15 @@ class CheckCaptures extends Recheck, SymTransformer:
11091129
*
11101130
* @param alwaysConst always make capture set variables constant after adaptation
11111131
*/
1112-
def adapt(actual: Type, expected: Type, pos: SrcPos)(using Context): Type =
1132+
def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type =
11131133
if expected == LhsProto || expected.isSingleton && actual.isSingleton then
11141134
actual
11151135
else
11161136
val normalized = makeCaptureSetExplicit(actual)
1117-
val widened = improveCaptures(normalized.widenDealias, actual)
1118-
val adapted = adaptBoxed(widened.withReachCaptures(actual), expected, pos, covariant = true, alwaysConst = false)
1137+
val widened = improveCaptures(normalized.widen.dealiasKeepAnnots, actual)
1138+
val adapted = adaptBoxed(
1139+
widened.withReachCaptures(actual), expected, pos,
1140+
covariant = true, alwaysConst = false, boxErrors)
11191141
if adapted eq widened then normalized
11201142
else adapted.showing(i"adapt boxed $actual vs $expected ===> $adapted", capt)
11211143
end adapt
@@ -1137,7 +1159,8 @@ class CheckCaptures extends Recheck, SymTransformer:
11371159
val saved = curEnv
11381160
try
11391161
curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv)
1140-
val adapted = adaptBoxed(/*Existential.strip*/(actual), expected1, srcPos, covariant = true, alwaysConst = true)
1162+
val adapted =
1163+
adaptBoxed(/*Existential.strip*/(actual), expected1, srcPos, covariant = true, alwaysConst = true, null)
11411164
actual match
11421165
case _: MethodType =>
11431166
// We remove the capture set resulted from box adaptation for method types,

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
394394
def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit =
395395
try
396396
transformTT(tpt,
397-
boxed = !ccConfig.allowUniversalInBoxed && sym.is(Mutable, butNot = Method),
397+
boxed =
398+
sym.is(Mutable, butNot = Method)
399+
&& !ccConfig.allowUniversalInBoxed
400+
&& !sym.hasAnnotation(defn.UncheckedCapturesAnnot),
398401
// types of mutable variables are boxed in pre 3.3 code
399402
exact = sym.allOverriddenSymbols.hasNext,
400403
// types of symbols that override a parent don't get a capture set TODO drop
@@ -405,7 +408,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
405408
val addDescription = new TypeTraverser:
406409
def traverse(tp: Type) = tp match
407410
case tp @ CapturingType(parent, refs) =>
408-
if !refs.isConst then refs.withDescription(i"of $sym")
411+
if !refs.isConst && refs.description.isEmpty then
412+
refs.withDescription(i"of $sym")
409413
traverse(parent)
410414
case _ =>
411415
traverseChildren(tp)

library/src/scala/caps.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,19 @@ import annotation.experimental
4343
def unsafeAssumePure: T = x
4444

4545
/** If argument is of type `cs T`, converts to type `box cs T`. This
46-
* avoids the error that would be raised when boxing `*`.
46+
* avoids the error that would be raised when boxing `cap`.
4747
*/
48-
@deprecated(since = "3.3")
4948
def unsafeBox: T = x
5049

5150
/** If argument is of type `box cs T`, converts to type `cs T`. This
52-
* avoids the error that would be raised when unboxing `*`.
51+
* avoids the error that would be raised when unboxing `cap`.
5352
*/
54-
@deprecated(since = "3.3")
5553
def unsafeUnbox: T = x
5654

5755
extension [T, U](f: T => U)
5856
/** If argument is of type `box cs T`, converts to type `cs T`. This
59-
* avoids the error that would be raised when unboxing `*`.
57+
* avoids the error that would be raised when unboxing `cap`.
6058
*/
61-
@deprecated(since = "3.3")
6259
def unsafeBoxFunArg: T => U = f
6360

6461
end unsafe

0 commit comments

Comments
 (0)