@@ -886,7 +886,7 @@ class CheckCaptures extends Recheck, SymTransformer:
886
886
*/
887
887
override def checkConformsExpr (actual : Type , expected : Type , tree : Tree , addenda : Addenda )(using Context ): Type =
888
888
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
889
- val actualBoxed = adaptBoxed (actual, expected1, tree.srcPos)
889
+ val actualBoxed = adapt (actual, expected1, tree.srcPos)
890
890
// println(i"check conforms $actualBoxed <<< $expected1")
891
891
892
892
if actualBoxed eq actual then
@@ -985,183 +985,134 @@ class CheckCaptures extends Recheck, SymTransformer:
985
985
*
986
986
* @param alwaysConst always make capture set variables constant after adaptation
987
987
*/
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 =
989
989
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
1001
992
*/
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 ())
1085
1014
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
1125
1061
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
1128
1077
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
1143
1101
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 =
1144
1107
if expected == LhsProto || expected.isSingleton && actual.isSingleton then
1145
1108
actual
1146
1109
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
1165
1116
1166
1117
/** Check overrides again, taking capture sets into account.
1167
1118
* TODO: Can we avoid doing overrides checks twice?
@@ -1180,7 +1131,7 @@ class CheckCaptures extends Recheck, SymTransformer:
1180
1131
val saved = curEnv
1181
1132
try
1182
1133
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 )
1184
1135
actual match
1185
1136
case _ : MethodType =>
1186
1137
// We remove the capture set resulted from box adaptation for method types,
0 commit comments