@@ -886,7 +886,7 @@ class CheckCaptures extends Recheck, SymTransformer:
886886 */
887887 override def checkConformsExpr (actual : Type , expected : Type , tree : Tree , addenda : Addenda )(using Context ): Type =
888888 var expected1 = alignDependentFunction(expected, actual.stripCapturing)
889- val actualBoxed = adaptBoxed (actual, expected1, tree.srcPos)
889+ val actualBoxed = adapt (actual, expected1, tree.srcPos)
890890 // println(i"check conforms $actualBoxed <<< $expected1")
891891
892892 if actualBoxed eq actual then
@@ -985,183 +985,134 @@ class CheckCaptures extends Recheck, SymTransformer:
985985 *
986986 * @param alwaysConst always make capture set variables constant after adaptation
987987 */
988- def adaptBoxed (actual : Type , expected : Type , pos : SrcPos , alwaysConst : Boolean = false )(using Context ): Type =
988+ def adaptBoxed (actual : Type , expected : Type , pos : SrcPos , covariant : Boolean , alwaysConst : Boolean )(using Context ): Type =
989989
990- inline def inNestedEnv [T ](boxed : Boolean )(op : => T ): T =
991- val saved = curEnv
992- curEnv = Env (curEnv.owner, EnvKind .NestedInOwner , CaptureSet .Var (curEnv.owner), if boxed then null else curEnv)
993- try op
994- finally curEnv = saved
995-
996- /** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
997- * to `expected` type.
998- * It returns the adapted type along with a capture set consisting of the references
999- * that were additionally captured during adaptation.
1000- * @param reconstruct how to rebuild the adapted function type
990+ /** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
991+ * @param boxed if true we adapt to a boxed expected type
1001992 */
1002- def adaptFun (actual : Type , aargs : List [Type ], ares : Type , expected : Type ,
1003- covariant : Boolean , boxed : Boolean ,
1004- reconstruct : (List [Type ], Type ) => Type ): (Type , CaptureSet ) =
1005- inNestedEnv(boxed):
1006- val (eargs, eres) = expected.dealias.stripCapturing match
1007- case defn.FunctionOf (eargs, eres, _) => (eargs, eres)
1008- case expected : MethodType => (expected.paramInfos, expected.resType)
1009- case expected @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionNType(expected) => (rinfo.paramInfos, rinfo.resType)
1010- case _ => (aargs.map(_ => WildcardType ), WildcardType )
1011- val aargs1 = aargs.zipWithConserve(eargs) { (aarg, earg) => adapt(aarg, earg, ! covariant) }
1012- val ares1 = adapt(ares, eres, covariant)
1013-
1014- val resTp =
1015- if (ares1 eq ares) && (aargs1 eq aargs) then actual
1016- else reconstruct(aargs1, ares1)
1017-
1018- (resTp, CaptureSet (curEnv.captured.elems))
1019- end adaptFun
1020-
1021- /** Adapt type function type `actual` to the expected type.
1022- * @see [[adaptFun ]]
1023- */
1024- def adaptTypeFun (
1025- actual : Type , ares : Type , expected : Type ,
1026- covariant : Boolean , boxed : Boolean ,
1027- reconstruct : Type => Type ): (Type , CaptureSet ) =
1028- inNestedEnv(boxed):
1029- val eres = expected.dealias.stripCapturing match
1030- case defn.PolyFunctionOf (rinfo : PolyType ) => rinfo.resType
1031- case expected : PolyType => expected.resType
1032- case _ => WildcardType
1033-
1034- val ares1 = adapt(ares, eres, covariant)
1035-
1036- val resTp =
1037- if ares1 eq ares then actual
1038- else reconstruct(ares1)
1039-
1040- (resTp, CaptureSet (curEnv.captured.elems))
1041- end adaptTypeFun
1042-
1043- def adaptInfo (actual : Type , expected : Type , covariant : Boolean ): String =
1044- val arrow = if covariant then " ~~>" else " <~~"
1045- i " adapting $actual $arrow $expected"
1046-
1047- def adapt (actual : Type , expected : Type , covariant : Boolean ): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true ):
1048- if expected.isInstanceOf [WildcardType ] then actual
1049- else
1050- // Decompose the actual type into the inner shape type, the capture set and the box status
1051- val styp = if actual.isFromJavaObject then actual else actual.stripCapturing
1052- val cs = actual.captureSet
1053- val boxed = actual.isBoxedCapturing
1054-
1055- // A box/unbox should be inserted, if the actual box status mismatches with the expectation
1056- val needsAdaptation = boxed != expected.isBoxedCapturing
1057- // Whether to insert a box or an unbox?
1058- val insertBox = needsAdaptation && covariant != boxed
1059-
1060- // Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
1061- val (styp1, leaked) = styp match {
1062- case actual @ AppliedType (tycon, args) if defn.isNonRefinedFunction(actual) =>
1063- adaptFun(actual, args.init, args.last, expected, covariant, insertBox,
1064- (aargs1, ares1) => actual.derivedAppliedType(tycon, aargs1 :+ ares1))
1065- case actual @ defn.RefinedFunctionOf (rinfo : MethodType ) =>
1066- // TODO Find a way to combine handling of generic and dependent function types (here and elsewhere)
1067- adaptFun(actual, rinfo.paramInfos, rinfo.resType, expected, covariant, insertBox,
1068- (aargs1, ares1) =>
1069- rinfo.derivedLambdaType(paramInfos = aargs1, resType = ares1)
1070- .toFunctionType(alwaysDependent = true ))
1071- case actual : MethodType =>
1072- adaptFun(actual, actual.paramInfos, actual.resType, expected, covariant, insertBox,
1073- (aargs1, ares1) =>
1074- actual.derivedLambdaType(paramInfos = aargs1, resType = ares1))
1075- case actual @ defn.RefinedFunctionOf (rinfo : PolyType ) =>
1076- adaptTypeFun(actual, rinfo.resType, expected, covariant, insertBox,
1077- ares1 =>
1078- val rinfo1 = rinfo.derivedLambdaType(rinfo.paramNames, rinfo.paramInfos, ares1)
1079- val actual1 = actual.derivedRefinedType(refinedInfo = rinfo1)
1080- actual1
1081- )
1082- case _ =>
1083- (styp, CaptureSet ())
1084- }
993+ def adaptShape (actualShape : Type , boxed : Boolean ): (Type , CaptureSet ) = actualShape match
994+ case FunctionOrMethod (aargs, ares) =>
995+ val saved = curEnv
996+ curEnv = Env (
997+ curEnv.owner, EnvKind .NestedInOwner ,
998+ CaptureSet .Var (curEnv.owner),
999+ if boxed then null else curEnv)
1000+ try
1001+ val (eargs, eres) = expected.dealias.stripCapturing match
1002+ case FunctionOrMethod (eargs, eres) => (eargs, eres)
1003+ case _ => (aargs.map(_ => WildcardType ), WildcardType )
1004+ val aargs1 = aargs.zipWithConserve(eargs):
1005+ adaptBoxed(_, _, pos, ! covariant, alwaysConst)
1006+ val ares1 = adaptBoxed(ares, eres, pos, covariant, alwaysConst)
1007+ val resTp =
1008+ if (aargs1 eq aargs) && (ares1 eq ares) then actualShape // optimize to avoid redundant matches
1009+ else actualShape.derivedFunctionOrMethod(aargs1, ares1)
1010+ (resTp, CaptureSet (curEnv.captured.elems))
1011+ finally curEnv = saved
1012+ case _ =>
1013+ (actualShape, CaptureSet ())
10851014
1086- // Capture set of the term after adaptation
1087- val cs1 =
1088- if covariant then cs ++ leaked
1089- else
1090- if ! leaked.subCaptures(cs, frozen = false ).isOK then
1091- report.error(
1092- em """ $expected cannot be box-converted to $actual
1093- |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""" , pos)
1094- cs
1095-
1096- // Compute the adapted type
1097- def adaptedType (resultBoxed : Boolean ) =
1098- if (styp1 eq styp) && leaked.isAlwaysEmpty && boxed == resultBoxed then actual
1099- else styp1.capturing(if alwaysConst then CaptureSet (cs1.elems) else cs1).forceBoxStatus(resultBoxed)
1100-
1101- if needsAdaptation then
1102- val criticalSet = // the set which is not allowed to have `cap`
1103- if covariant then cs1 // can't box with `cap`
1104- else expected.captureSet // can't unbox with `cap`
1105- if criticalSet.isUniversal && expected.isValueType && ! ccConfig.allowUniversalInBoxed then
1106- // We can't box/unbox the universal capability. Leave `actual` as it is
1107- // so we get an error in checkConforms. This tends to give better error
1108- // messages than disallowing the root capability in `criticalSet`.
1109- if ctx.settings.YccDebug .value then
1110- println(i " cannot box/unbox $actual vs $expected" )
1111- actual
1112- else
1113- if ! ccConfig.allowUniversalInBoxed then
1114- // Disallow future addition of `cap` to `criticalSet`.
1115- criticalSet.disallowRootCapability { () =>
1116- report.error(
1117- em """ $actual cannot be box-converted to $expected
1118- |since one of their capture sets contains the root capability `cap` """ ,
1119- pos)
1120- }
1121- if ! insertBox then // unboxing
1122- // debugShowEnvs()
1123- markFree(criticalSet, pos)
1124- adaptedType(! boxed)
1015+ def adaptStr = i " adapting $actual ${if covariant then " ~~>" else " <~~" } $expected"
1016+
1017+ if expected.isInstanceOf [WildcardType ] then actual
1018+ else trace(adaptStr, recheckr, show = true ):
1019+ // Decompose the actual type into the inner shape type, the capture set and the box status
1020+ val actualShape = if actual.isFromJavaObject then actual else actual.stripCapturing
1021+ val actualIsBoxed = actual.isBoxedCapturing
1022+
1023+ // A box/unbox should be inserted, if the actual box status mismatches with the expectation
1024+ val needsAdaptation = actualIsBoxed != expected.isBoxedCapturing
1025+ // Whether to insert a box or an unbox?
1026+ val insertBox = needsAdaptation && covariant != actualIsBoxed
1027+
1028+ // Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
1029+ val (adaptedShape, leaked) = adaptShape(actualShape, insertBox)
1030+
1031+ // Capture set of the term after adaptation
1032+ val captures =
1033+ val cs = actual.captureSet
1034+ if covariant then cs ++ leaked
1035+ else
1036+ if ! leaked.subCaptures(cs, frozen = false ).isOK then
1037+ report.error(
1038+ em """ $expected cannot be box-converted to $actual
1039+ |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""" , pos)
1040+ cs
1041+
1042+ // Compute the adapted type
1043+ def adaptedType (resultBoxed : Boolean ) =
1044+ if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultBoxed
1045+ then actual
1046+ else adaptedShape
1047+ .capturing(if alwaysConst then CaptureSet (captures.elems) else captures)
1048+ .forceBoxStatus(resultBoxed)
1049+
1050+ if needsAdaptation then
1051+ val criticalSet = // the set which is not allowed to have `cap`
1052+ if covariant then captures // can't box with `cap`
1053+ else expected.captureSet // can't unbox with `cap`
1054+ if criticalSet.isUniversal && expected.isValueType && ! ccConfig.allowUniversalInBoxed then
1055+ // We can't box/unbox the universal capability. Leave `actual` as it is
1056+ // so we get an error in checkConforms. This tends to give better error
1057+ // messages than disallowing the root capability in `criticalSet`.
1058+ if ctx.settings.YccDebug .value then
1059+ println(i " cannot box/unbox $actual vs $expected" )
1060+ actual
11251061 else
1126- adaptedType(boxed)
1127- end adapt
1062+ if ! ccConfig.allowUniversalInBoxed then
1063+ // Disallow future addition of `cap` to `criticalSet`.
1064+ criticalSet.disallowRootCapability { () =>
1065+ report.error(
1066+ em """ $actual cannot be box-converted to $expected
1067+ |since one of their capture sets contains the root capability `cap` """ ,
1068+ pos)
1069+ }
1070+ if ! insertBox then // unboxing
1071+ // debugShowEnvs()
1072+ markFree(criticalSet, pos)
1073+ adaptedType(! actualIsBoxed)
1074+ else
1075+ adaptedType(actualIsBoxed)
1076+ end adaptBoxed
11281077
1129- /** If result derives from caps.Capability, yet is not a capturing type itself,
1130- * make its capture set explicit.
1131- */
1132- def makeCaptureSetExplicit (result : Type ) = result match
1133- case CapturingType (_, _) => result
1134- case _ =>
1135- if result.derivesFromCapability then
1136- val cap : CaptureRef = actual match
1137- case ref : CaptureRef if ref.isTracked =>
1138- ref
1139- case _ =>
1140- defn.captureRoot.termRef // TODO: skolemize?
1141- CapturingType (result, cap.singletonCaptureSet)
1142- else result
1078+ /** If actual derives from caps.Capability, yet is not a capturing type itself,
1079+ * make its capture set explicit.
1080+ */
1081+ private def makeCaptureSetExplicit (actual : Type )(using Context ): Type = actual match
1082+ case CapturingType (_, _) => actual
1083+ case _ if actual.derivesFromCapability =>
1084+ val cap : CaptureRef = actual match
1085+ case ref : CaptureRef if ref.isTracked => ref
1086+ case _ => defn.captureRoot.termRef // TODO: skolemize?
1087+ CapturingType (actual, cap.singletonCaptureSet)
1088+ case _ => actual
1089+
1090+ /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C,
1091+ * improve `T^C` to `T^{a}`, following the VAR rule of CC.
1092+ */
1093+ private def improveCaptures (widened : Type , actual : Type )(using Context ): Type = actual match
1094+ case ref : CaptureRef if ref.isTracked =>
1095+ widened match
1096+ case CapturingType (p, refs) if ref.singletonCaptureSet.mightSubcapture(refs) =>
1097+ widened.derivedCapturingType(p, ref.singletonCaptureSet)
1098+ .showing(i " improve $widened to $result" , capt)
1099+ case _ => widened
1100+ case _ => widened
11431101
1102+ /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions
1103+ *
1104+ * @param alwaysConst always make capture set variables constant after adaptation
1105+ */
1106+ def adapt (actual : Type , expected : Type , pos : SrcPos )(using Context ): Type =
11441107 if expected == LhsProto || expected.isSingleton && actual.isSingleton then
11451108 actual
11461109 else
1147- var actualw = actual.widenDealias
1148- actual match
1149- case ref : CaptureRef if ref.isTracked =>
1150- actualw match
1151- case CapturingType (p, refs) if ref.singletonCaptureSet.mightSubcapture(refs) =>
1152- actualw = actualw.derivedCapturingType(p, ref.singletonCaptureSet)
1153- .showing(i " improve $actualw to $result" , capt)
1154- // given `a: T^C`, improve `T^C` to `T^{a}`
1155- case _ =>
1156- case _ =>
1157- val adapted = adapt(actualw.withReachCaptures(actual), expected, covariant = true )
1158- makeCaptureSetExplicit :
1159- if adapted ne actualw then
1160- capt.println(i " adapt boxed $actual vs $expected ===> $adapted" )
1161- adapted
1162- else
1163- actual
1164- end adaptBoxed
1110+ val normalized = makeCaptureSetExplicit(actual)
1111+ val widened = improveCaptures(normalized.widenDealias, actual)
1112+ val adapted = adaptBoxed(widened.withReachCaptures(actual), expected, pos, covariant = true , alwaysConst = false )
1113+ if adapted eq widened then normalized
1114+ else adapted.showing(i " adapt boxed $actual vs $expected ===> $adapted" , capt)
1115+ end adapt
11651116
11661117 /** Check overrides again, taking capture sets into account.
11671118 * TODO: Can we avoid doing overrides checks twice?
@@ -1180,7 +1131,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11801131 val saved = curEnv
11811132 try
11821133 curEnv = Env (clazz, EnvKind .NestedInOwner , capturedVars(clazz), outer0 = curEnv)
1183- val adapted = adaptBoxed(actual, expected1, srcPos, alwaysConst = true )
1134+ val adapted = adaptBoxed(actual, expected1, srcPos, covariant = true , alwaysConst = true )
11841135 actual match
11851136 case _ : MethodType =>
11861137 // We remove the capture set resulted from box adaptation for method types,
0 commit comments