Skip to content

Commit 97ce2b9

Browse files
committed
Fixes to maps in Setup
- Drop ToResultInResults - Move its functionality to separate methods in Setup - Implement dealias suppression logic for capset arguments. This is needed to be able to express referring with `fresh` to enclosing method types that don't immediately have the type containing the `fresh` as their result type. # Conflicts: # tests/neg/i20481.check
1 parent 8bc2164 commit 97ce2b9

25 files changed

+427
-346
lines changed

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@ class CCState:
2323

2424
// ------ BiTypeMap adjustment -----------------------
2525

26-
private var myMapFutureElems = true
26+
private var myMapVars = true
2727

28-
/** When mapping a capture set with a BiTypeMap, should we create a BiMapped set
28+
/** When mapping a capture set variable with a BiTypeMap, should we create a BiMapped set
2929
* so that future elements can also be mapped, and elements added to the BiMapped
30-
* are back-propagated? Turned off when creating capture set variables for the
31-
* first time, since we then do not want to change the binder to the original type
32-
* without capture sets when back propagating. Error case where this shows:
33-
* pos-customargs/captures/lists.scala, method m2c.
30+
* are back-propagated? Or should we return the capture set as is? Turned off when
31+
* creating capture set variables for the first time, since we then do not want to
32+
* change the binder to the original type without capture sets when back propagating.
33+
* Error cases where this shows: pos-customargs/captures/lists.scala, method m2c, and
34+
* pos-customargs/captures/infer-exists.scala,
3435
*/
35-
def mapFutureElems(using Context) = myMapFutureElems
36+
def mapVars(using Context) = myMapVars
3637

37-
/** Don't map future elements in this `op` */
38-
inline def withoutMappedFutureElems[T](op: => T)(using Context): T =
39-
val saved = mapFutureElems
40-
myMapFutureElems = false
41-
try op finally myMapFutureElems = saved
38+
/** Don't map capset variables with BiTypeMaps during this `op` */
39+
inline def withNoVarsMapped[T](op: => T)(using Context): T =
40+
val saved = mapVars
41+
myMapVars = false
42+
try op finally myMapVars = saved
4243

4344
// ------ Iteration count of capture checking run
4445

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

Lines changed: 17 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,16 +1058,14 @@ object Capabilities:
10581058
case t @ CapturingType(_, _) =>
10591059
mapOver(t)
10601060
case t @ AnnotatedType(parent, ann: RetainingAnnotation)
1061-
if ann.isStrict && ann.toCaptureSet.containsGlobalCapDerivs =>
1061+
if ann.isStrict && ann.toCaptureSet.elems.exists(_.core.isInstanceOf[GlobalCap]) =>
10621062
// Applying `this` can cause infinite recursion in some cases during printing.
10631063
// scalac -Xprint:all tests/pos/i23885/S_1.scala tests/pos/i23885/S_2.scala
10641064
mapOver(CapturingType(this(parent), ann.toCaptureSet))
10651065
case t @ AnnotatedType(parent, ann) =>
10661066
t.derivedAnnotatedType(this(parent), ann)
10671067
case t @ defn.RefinedFunctionOf(mt) =>
1068-
if ccConfig.newScheme
1069-
then t.derivedRefinedType(refinedInfo = mapOver(mt))
1070-
else t
1068+
t.derivedRefinedType(refinedInfo = mapOver(mt))
10711069
case _ =>
10721070
mapFollowingAliases(t)
10731071

@@ -1100,11 +1098,11 @@ object Capabilities:
11001098
/** Maps caps.any to LocalCap instances. GlobalToLocalCap is a BiTypeMap since we don't want to
11011099
* freeze a set when it is mapped. On the other hand, we do not want LocalCap
11021100
* values to flow back to caps.any since that would fail disallowRootCapability
1103-
* tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent
1101+
* tests elsewhere. We therefore use `withNoVarsMapped` to prevent
11041102
* the map being installed for future use.
11051103
*/
11061104
def globalCapToLocal(tp: Type, origin: Origin)(using Context): Type =
1107-
ccState.withoutMappedFutureElems:
1105+
ccState.withNoVarsMapped:
11081106
GlobalCapToLocal(origin)(tp)
11091107

11101108
/** Maps all LocalCap instances to caps.any */
@@ -1220,36 +1218,26 @@ object Capabilities:
12201218

12211219
class ToResult(localResType: Type, mt: MethodicType, sym: Symbol, fail: Message => Unit)(using Context) extends CapMap:
12221220

1223-
def apply(t: Type) = t match
1224-
case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction && !(ccConfig.newScheme) =>
1225-
if variance > 0 then
1226-
super.mapOver:
1227-
defn.FunctionNOf(args, res, contextual)
1228-
.capturing(ResultCap(mt).singletonCaptureSet)
1229-
else mapOver(t)
1230-
case _ =>
1231-
mapOver(t)
1221+
def apply(t: Type) = mapOver(t)
12321222

12331223
override def mapCapability(c: Capability, deep: Boolean) = c match
1234-
case c: (LocalCap | GlobalCap) =>
1224+
case c: LocalCap =>
12351225
if variance > 0 then
1236-
c match
1237-
case c: LocalCap =>
1238-
if sym.isAnonymousFunction && c.classifier.derivesFrom(defn.Caps_Unscoped) then
1239-
c
1240-
else if sym.exists && !c.ccOwner.isContainedIn(sym.skipAnonymousOwners) then
1241-
//println(i"not mapping $c with ${c.ccOwner} in $sym")
1242-
c
1243-
else
1244-
ResultCap(mt).setOrigin(c)
1245-
case _ =>
1246-
if c == GlobalFresh || !(ccConfig.newScheme) then ResultCap(mt) else c
1226+
if sym.isAnonymousFunction && c.classifier.derivesFrom(defn.Caps_Unscoped) then
1227+
c
1228+
else if sym.exists && !c.ccOwner.isContainedIn(sym.skipAnonymousOwners) then
1229+
//println(i"not mapping $c with ${c.ccOwner} in $sym")
1230+
c
1231+
else
1232+
ResultCap(mt).setOrigin(c)
12471233
else
12481234
if variance == 0 then
12491235
fail(em"""$localResType captures the root capability `any` in invariant position.
1250-
|This capability cannot be converted to an existential in the result type of a function.""")
1251-
// we accept variance < 0, and leave the `any` as it is
1236+
|This capability cannot be converted to a fresh capability in the result type of a function.""")
1237+
// we accept variance < 0, and leave the `any` as it is c
12521238
c
1239+
case GlobalFresh if variance > 0 =>
1240+
ResultCap(mt) // if variance <= 0 we leave the fresh to be flagged later
12531241
case _ =>
12541242
super.mapCapability(c, deep)
12551243

@@ -1282,54 +1270,4 @@ object Capabilities:
12821270
*/
12831271
def toResult(tp: Type, mt: MethodicType, sym: Symbol, fail: Message => Unit)(using Context): Type =
12841272
ToResult(tp, mt, sym, fail)(tp)
1285-
1286-
/** Map global roots in function results to result roots. Also,
1287-
* map roots in the types of def methods that are parameterless
1288-
* or have only type parameters.
1289-
*/
1290-
def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type =
1291-
val m = new TypeMap with FollowAliasesMap:
1292-
def apply(t: Type): Type = t match
1293-
case rt @ defn.RefinedFunctionOf(mt) =>
1294-
rt.derivedRefinedType(refinedInfo =
1295-
if rt.isInstanceOf[InferredRefinedType]
1296-
then mapOver(mt) // Don't map to Result for dependent function types created from non-dependent ones in inferred types
1297-
else apply(mt))
1298-
case t @ AppliedType(tycon, args)
1299-
if defn.isNonRefinedFunction(t) && args.last.containsFresh && ccConfig.newScheme =>
1300-
// Convert to dependent function so that we have a binder for `fresh` in result type.
1301-
apply(
1302-
depFun(args.init, args.last,
1303-
isContextual = defn.isContextFunctionClass(tycon.classSymbol)))
1304-
case t: MethodType if variance > 0 && t.marksExistentialScope =>
1305-
val t1 = mapOver(t).asInstanceOf[MethodType]
1306-
t1.derivedLambdaType(resType = toResult(t1.resType, t1, sym, fail))
1307-
case CapturingType(parent, refs) =>
1308-
t.derivedCapturingType(this(parent), refs)
1309-
case t: (LazyRef | TypeVar) =>
1310-
mapConserveSuper(t)
1311-
case _ =>
1312-
try
1313-
if keepAliases then mapOver(t)
1314-
else mapFollowingAliases(t)
1315-
catch case ex: AssertionError =>
1316-
println(i"error while mapping $t")
1317-
throw ex
1318-
m(tp) match
1319-
case tp1: ExprType if sym.is(Method, butNot = Accessor) =>
1320-
// Map the result of parameterless `def` methods.
1321-
tp1.derivedExprType(toResult(tp1.resType, tp1, sym, fail))
1322-
case tp1: PolyType if !tp1.resType.isInstanceOf[MethodicType] =>
1323-
// Map also the result type of method with only type parameters.
1324-
// This way, the `^` in the following method will be mapped to a `ResultCap`:
1325-
// ```
1326-
// object Buffer:
1327-
// def empty[T]: Buffer[T]^
1328-
// ```
1329-
// This is more desirable than interpreting `^` as a `^{any}` at the level of `Buffer.empty`
1330-
// in most cases.
1331-
tp1.derivedLambdaType(resType = toResult(tp1.resType, tp1, sym, fail))
1332-
case tp1 => tp1
1333-
end toResultInResults
1334-
13351273
end Capabilities

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

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,12 @@ extension (tp: Type)
105105

106106
/** A list of capabilities of a retained set. */
107107
def retainedElements(using Context): List[Capability] =
108-
retainedElementsRaw.map(_.toCapability)
108+
retainedElementsRaw.flatMap: elem =>
109+
elem match
110+
case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) =>
111+
refs.elems.toList
112+
case _ =>
113+
elem.toCapability :: Nil
109114

110115
/** Is this type a Capability that can be tracked?
111116
* This is true for
@@ -456,20 +461,31 @@ extension (tp: Type)
456461
tp
457462
end withReachCaptures
458463

459-
/** Does this type contain no-flip covariant occurrences of `any`? */
460-
def containsCap(using Context): Boolean =
461-
val acc = new TypeAccumulator[Boolean]:
464+
private def containsGlobal(c: GlobalCap, directly: Boolean)(using Context): Boolean =
465+
val search = new TypeAccumulator[Boolean]:
462466
def apply(x: Boolean, t: Type) =
463-
x
464-
|| variance > 0 && t.dealiasKeepAnnots.match
465-
case t @ CapturingType(p, cs) if cs.containsGlobalCapDerivs =>
467+
if x then true
468+
else if variance <= 0 then false
469+
else if directly && defn.isFunctionSymbol(t.typeSymbol) then false
470+
else t match
471+
case CapturingType(_, refs) if refs.elems.exists(_.core == c) =>
466472
true
467473
case t @ AnnotatedType(parent, ann) =>
468474
// Don't traverse annotations, which includes capture sets
469475
this(x, parent)
470476
case _ =>
471477
foldOver(x, t)
472-
acc(false, tp)
478+
search(false, tp)
479+
480+
/** Does this type contain no-flip covariant occurrences of `any`? */
481+
def containsGlobalAny(using Context): Boolean =
482+
containsGlobal(GlobalAny, directly = false)
483+
484+
/** Does `tp` contain contain no-flip covariant occurrences of `fresh` directly,
485+
* which are not in the result of some function type?
486+
*/
487+
def containsGlobalFreshDirectly(using Context): Boolean =
488+
containsGlobal(GlobalFresh, directly = true)
473489

474490
def refinedOverride(name: Name, rinfo: Type)(using Context): Type =
475491
RefinedType.precise(tp, name, rinfo)
@@ -502,20 +518,7 @@ extension (tp: Type)
502518
if tp.isArrayUnderStrictMut then defn.Caps_Unscoped
503519
else tp.classSymbols.map(_.classifier).foldLeft(defn.AnyClass)(leastClassifier)
504520

505-
/** Does `tp` contain a `fresh` directly, which is not in the result of some function type?
506-
*/
507-
def containsFresh(using Context): Boolean =
508-
val search = new TypeAccumulator[Boolean]:
509-
def apply(x: Boolean, tp: Type): Boolean =
510-
if x then true
511-
else if defn.isFunctionType(tp) then false
512-
else tp match
513-
case CapturingType(parent, refs) =>
514-
refs.elems.exists(_.core == GlobalFresh) || apply(x, parent)
515-
case _ => foldOver(x, tp)
516-
search(false, tp)
517-
518-
extension (tp: MethodType)
521+
extension (tp: MethodOrPoly)
519522
/** A method marks an existential scope unless it is the prefix of a curried method */
520523
def marksExistentialScope(using Context): Boolean =
521524
!tp.resType.isInstanceOf[MethodOrPoly]

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,6 @@ sealed abstract class CaptureSet extends Showable:
143143
case _: LocalCap => true
144144
case _ => false
145145

146-
final def containsGlobalCapDerivs(using Context) =
147-
elems.exists(_.core.isInstanceOf[GlobalCap])
148-
149146
final def isReadOnly(using Context): Boolean =
150147
elems.forall(_.isReadOnly)
151148

@@ -392,9 +389,13 @@ sealed abstract class CaptureSet extends Showable:
392389
/** Capture set obtained by applying `tm` to all elements of the current capture set
393390
* and joining the results. If the current capture set is a variable we handle this as
394391
* follows:
395-
* - If the map is a BiTypeMap, the same transformation is applied to all
396-
* future additions of new elements. We try to fuse with previous maps to
397-
* avoid long paths of BiTypeMapped sets.
392+
* - If the map is a BiTypeMap, and CCState.mapVars is true,
393+
* the same transformation is applied to all future additions of new elements.
394+
* We try to fuse with previous maps to avoid long paths of BiTypeMapped sets.
395+
* - If the map is a BiTypeMap, and CCState.mapVars is false,
396+
* we return the original capture set. In this case any elements that are
397+
* already in the set must be invariant under the mapping. This mode is
398+
* necessary for bootstrap when we create capset variables the first time.
398399
* - If the map is some other map that maps the current set of elements
399400
* to itself, return the current var. We implicitly assume that the map
400401
* will also map any elements added in the future to themselves. This assumption
@@ -410,7 +411,7 @@ sealed abstract class CaptureSet extends Showable:
410411
if isConst then
411412
if mappedElems == elems then this
412413
else Const(mappedElems)
413-
else if ccState.mapFutureElems then
414+
else if ccState.mapVars then
414415
def unfused =
415416
if debugVars then
416417
try BiMapped(asVar, tm, mappedElems)
@@ -423,7 +424,9 @@ sealed abstract class CaptureSet extends Showable:
423424
case Some(fused: BiTypeMap) => BiMapped(self.source, fused, mappedElems)
424425
case _ => unfused
425426
case _ => unfused
426-
else this
427+
else
428+
assert(mappedElems == elems)
429+
this
427430
case tm: IdentityCaptRefMap =>
428431
this
429432
case tm: AvoidMap if this.isInstanceOf[HiddenSet] =>
@@ -928,7 +931,7 @@ object CaptureSet:
928931
this
929932
else if isUniversal || computingApprox then
930933
universal
931-
else if containsGlobalCapDerivs && isReadOnly then
934+
else if elems.exists(_.core == GlobalAny) && isReadOnly then
932935
shared
933936
else
934937
computingApprox = true
@@ -1091,8 +1094,8 @@ object CaptureSet:
10911094

10921095
// ----------- Longest path recording -------------------------
10931096

1094-
/** Summarize for set displaying in a path */
1095-
def summarize: String = getClass.toString
1097+
/** Summarize set when displaying a propagation path */
1098+
def summarize(using Context): String = getClass.toString
10961099

10971100
/** The length of the path of DerivedVars ending in this set */
10981101
def pathLength: Int = source match
@@ -1132,7 +1135,6 @@ object CaptureSet:
11321135
try
11331136
reporting.trace(i"prop backwards $elem from $this # $id to $source # ${source.id} via $summarize"):
11341137
source.tryInclude(bimap.inverse.mapCapability(elem), this)
1135-
.showing(i"propagating new elem $elem backward from $this/$id to $source = $result", captDebug)
11361138
catch case ex: AssertionError =>
11371139
println(i"fail while prop backwards tryInclude $elem of ${elem.getClass} from $this # $id / ${this.summarize} to $source # ${source.id}")
11381140
throw ex
@@ -1151,7 +1153,7 @@ object CaptureSet:
11511153

11521154
override def isMaybeSet: Boolean = bimap.isInstanceOf[MaybeMap]
11531155
override def toString = s"BiMapped$id($source, elems = $elems)"
1154-
override def summarize = bimap.getClass.toString
1156+
override def summarize(using Context) = bimap.summarize
11551157
override def repr(using Context): Name = source.repr
11561158
end BiMapped
11571159

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ class CheckCaptures extends Recheck, SymTransformer:
880880
// See [[MethodTypeCompanion.adaptParamInfo]].
881881
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
882882
markFree(argType.deepCaptureSet, arg)
883-
if formal.containsCap then
883+
if formal.containsGlobalAny then
884884
sepCheckFormals(arg) = instantiatedFormal
885885
argType
886886

0 commit comments

Comments
 (0)