Skip to content

Commit d9418ec

Browse files
committed
Drop IdempotentCaptRefMap and Mapped sets
1 parent efc05b2 commit d9418ec

File tree

11 files changed

+59
-162
lines changed

11 files changed

+59
-162
lines changed

compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class TreeTypeMap(
5656
/** Replace occurrences of `This(oldOwner)` in some prefix of a type
5757
* by the corresponding `This(newOwner)`.
5858
*/
59-
private val mapOwnerThis = new TypeMap with cc.CaptureSet.IdempotentCaptRefMap {
59+
private val mapOwnerThis = new TypeMap {
6060
private def mapPrefix(from: List[Symbol], to: List[Symbol], tp: Type): Type = from match {
6161
case Nil => tp
6262
case (cls: ClassSymbol) :: from1 => mapPrefix(from1, to.tail, tp.substThis(cls, to.head.thisType))

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@ val PrintFresh: Key[Unit] = Key()
2626

2727
object ccConfig:
2828

29-
/** If true, allow mapping capture set variables under captureChecking with maps that are neither
30-
* bijective nor idempotent. We currently do now know how to do this correctly in all
31-
* cases, though.
32-
*/
33-
inline val allowUnsoundMaps = false
34-
3529
/** If enabled, use a special path in recheckClosure for closures
3630
* to compare the result tpt of the anonymous functon with the expected
3731
* result type. This can narrow the scope of error messages.
@@ -49,6 +43,14 @@ object ccConfig:
4943
*/
5044
inline val deferredReaches = false
5145

46+
/** Check that if a type map (which is not a BiTypeMap) maps initial capture
47+
* set variable elements to themselves it will not map any elements added in
48+
* the future to something else. That is, we can safely use a capture set
49+
* variable itself as the image under the map. By default this is off since it
50+
* is a bit expensive to check.
51+
*/
52+
inline val checkSkippedMaps = false
53+
5254
/** If true, turn on separation checking */
5355
def useSepChecks(using Context): Boolean =
5456
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)

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

Lines changed: 34 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -302,25 +302,18 @@ sealed abstract class CaptureSet extends Showable:
302302
case _ => Filtered(asVar, p)
303303

304304
/** Capture set obtained by applying `tm` to all elements of the current capture set
305-
* and joining the results. If the current capture set is a variable, the same
306-
* transformation is applied to all future additions of new elements.
307-
*
308-
* Note: We have a problem how we handle the situation where we have a mapped set
309-
*
310-
* cs2 = tm(cs1)
311-
*
312-
* and then the propagation solver adds a new element `x` to `cs2`. What do we
313-
* know in this case about `cs1`? We can answer this question in a sound way only
314-
* if `tm` is a bijection on capture references or it is idempotent on capture references.
315-
* (see definition in IdempotentCapRefMap).
316-
* If `tm` is a bijection we know that `tm^-1(x)` must be in `cs1`. If `tm` is idempotent
317-
* one possible solution is that `x` is in `cs1`, which is what we assume in this case.
318-
* That strategy is sound but not complete.
319-
*
320-
* If `tm` is some other map, we don't know how to handle this case. For now,
321-
* we simply refuse to handle other maps. If they do need to be handled,
322-
* `OtherMapped` provides some approximation to a solution, but it is neither
323-
* sound nor complete.
305+
* and joining the results. If the current capture set is a variable we handle this as
306+
* follows:
307+
* - If the map is a BiTypeMap, the same transformation is applied to all
308+
* future additions of new elements. We try to fuse with previous maps to
309+
* avoid long paths of BiTypeMapped sets.
310+
* - If the map is some other map that maps the current set of elements
311+
* to itself, return the current var. We implicitly assume that the map
312+
* will also map any elements added in the future to themselves. This assumption
313+
* can be tested to hold by setting the ccConfig.checkSkippedMaps setting to true.
314+
* - If the map is some other map that does not map all elements to themselves,
315+
* freeze the current set (i.e. make it porvisionally solved) and return
316+
* the mapped elements as a constant set.
324317
*/
325318
def map(tm: TypeMap)(using Context): CaptureSet =
326319
tm match
@@ -342,16 +335,12 @@ sealed abstract class CaptureSet extends Showable:
342335
this
343336
case _ =>
344337
val mapped = mapRefs(elems, tm, tm.variance)
345-
if isConst then
346-
if mapped.isConst && mapped.elems == elems && !mapped.keepAlways then this
347-
else mapped
348-
else if true || ccConfig.newScheme then
349-
if mapped.elems == elems then this
350-
else
351-
asVar.markSolved(provisional = true)
352-
mapped
338+
if mapped.elems == elems then
339+
if ccConfig.checkSkippedMaps && !isConst then asVar.skippedMaps += tm
340+
this
353341
else
354-
Mapped(asVar, tm, tm.variance, mapped)
342+
if !isConst then asVar.markSolved(provisional = true)
343+
mapped
355344

356345
/** A mapping resulting from substituting parameters of a BindingType to a list of types */
357346
def substParams(tl: BindingType, to: List[Type])(using Context) =
@@ -571,6 +560,16 @@ object CaptureSet:
571560
def resetDeps()(using state: VarState): Unit =
572561
deps = state.deps(this)
573562

563+
/** Check that all maps recorded in skippedMaps map `elem` to itself
564+
* or something subsumed by it.
565+
*/
566+
private def checkSkippedMaps(elem: CaptureRef)(using Context): Unit =
567+
for tm <- skippedMaps do
568+
val elem1 = tm(elem)
569+
for elem1 <- tm(elem).captureSet.elems do
570+
assert(elem.subsumes(elem1),
571+
i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this")
572+
574573
final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult =
575574
if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen
576575
addIfHiddenOrFail(elem)
@@ -588,6 +587,7 @@ object CaptureSet:
588587
// assert(id != 5 || elems.size != 3, this)
589588
val res = (CompareResult.OK /: deps): (r, dep) =>
590589
r.andAlso(dep.tryInclude(normElem, this))
590+
if ccConfig.checkSkippedMaps && res.isOK then checkSkippedMaps(elem)
591591
res.orElse:
592592
elems -= elem
593593
res.addToTrace(this)
@@ -615,7 +615,7 @@ object CaptureSet:
615615
elem.symbol.ccLevel <= level
616616
case elem: ThisType if level.isDefined =>
617617
elem.cls.ccLevel.nextInner <= level
618-
case elem: ParamRef if !this.isInstanceOf[Mapped | BiMapped] =>
618+
case elem: ParamRef if !this.isInstanceOf[BiMapped] =>
619619
isPartOf(elem.binder.resType)
620620
|| {
621621
capt.println(i"LEVEL ERROR $elem for $this")
@@ -700,6 +700,8 @@ object CaptureSet:
700700
solved = if provisional then ccState.iterCount else Int.MaxValue
701701
deps.foreach(_.propagateSolved(provisional))
702702

703+
var skippedMaps: Set[TypeMap] = Set.empty
704+
703705
def withDescription(description: String): this.type =
704706
this.description = this.description.join(" and ", description)
705707
this
@@ -748,8 +750,8 @@ object CaptureSet:
748750
extends Var(owner, initialElems):
749751

750752
// For debugging: A trace where a set was created. Note that logically it would make more
751-
// sense to place this variable in Mapped, but that runs afoul of the initializatuon checker.
752-
val stack = if debugSets && this.isInstanceOf[Mapped] then (new Throwable).getStackTrace().nn.take(20) else null
753+
// sense to place this variable in BiMapped, but that runs afoul of the initializatuon checker.
754+
// val stack = if debugSets && this.isInstanceOf[BiMapped] then (new Throwable).getStackTrace().nn.take(20) else null
753755

754756
/** The variable from which this variable is derived */
755757
def source: Var
@@ -760,7 +762,7 @@ object CaptureSet:
760762
if source.isConst && !isConst then markSolved(provisional)
761763

762764
// ----------- Longest path recording -------------------------
763-
765+
764766
/** Summarize for set displaying in a path */
765767
def summarize: String = getClass.toString
766768

@@ -779,103 +781,6 @@ object CaptureSet:
779781

780782
end DerivedVar
781783

782-
/** A variable that changes when `source` changes, where all additional new elements are mapped
783-
* using ∪ { tm(x) | x <- source.elems }.
784-
* @param source the original set that is mapped
785-
* @param tm the type map, which is assumed to be idempotent on capture refs
786-
* (except if ccUnsoundMaps is enabled)
787-
* @param variance the assumed variance with which types with capturesets of size >= 2 are approximated
788-
* (i.e. co: full capture set, contra: empty set, nonvariant is not allowed.)
789-
* @param initial The initial mappings of source's elements at the point the Mapped set is created.
790-
*/
791-
class Mapped private[CaptureSet]
792-
(val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context)
793-
extends DerivedVar(source.owner, initial.elems):
794-
addAsDependentTo(initial) // initial mappings could change by propagation
795-
796-
private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap]
797-
798-
assert(ccConfig.allowUnsoundMaps || mapIsIdempotent, tm.getClass)
799-
800-
private def whereCreated(using Context): String =
801-
if stack == null then ""
802-
else i"""
803-
|Stack trace of variable creation:"
804-
|${stack.mkString("\n")}"""
805-
806-
override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult =
807-
def propagate: CompareResult =
808-
if (origin ne source) && (origin ne initial) && mapIsIdempotent then
809-
// `tm` is idempotent, propagate back elems from image set.
810-
// This is sound, since we know that for `r in newElems: tm(r) = r`, hence
811-
// `r` is _one_ possible solution in `source` that would make an `r` appear in this set.
812-
// It's not necessarily the only possible solution, so the scheme is incomplete.
813-
source.tryInclude(elem, this)
814-
else if ccConfig.allowUnsoundMaps && !mapIsIdempotent
815-
&& variance <= 0 && !origin.isConst && (origin ne initial) && (origin ne source)
816-
then
817-
// The map is neither a BiTypeMap nor an idempotent type map.
818-
// In that case there's no much we can do.
819-
// The scheme then does not propagate added elements back to source and rejects adding
820-
// elements from variable sources in contra- and non-variant positions. In essence,
821-
// we approximate types resulting from such maps by returning a possible super type
822-
// from the actual type. But this is neither sound nor complete.
823-
report.warning(em"trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated")
824-
CompareResult.Fail(this :: Nil)
825-
else
826-
CompareResult.OK
827-
def propagateIf(cond: Boolean): CompareResult =
828-
if cond then propagate else CompareResult.OK
829-
830-
val mapped = extrapolateCaptureRef(elem, tm, variance)
831-
832-
def isFixpoint =
833-
mapped.isConst && mapped.elems.size == 1 && mapped.elems.contains(elem)
834-
835-
def failNoFixpoint =
836-
val reason =
837-
if variance <= 0 then i"the set's variance is $variance"
838-
else i"$elem gets mapped to $mapped, which is not a supercapture."
839-
report.warning(em"""trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated
840-
|The reference cannot be added since $reason""")
841-
CompareResult.Fail(this :: Nil)
842-
843-
if origin eq source then // elements have to be mapped
844-
val added = mapped.elems.filter(!accountsFor(_))
845-
addNewElems(added)
846-
.andAlso:
847-
if mapped.isConst then CompareResult.OK
848-
else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult.OK }
849-
else CompareResult.Fail(this :: Nil)
850-
.andAlso:
851-
propagateIf(!added.isEmpty)
852-
else if accountsFor(elem) then
853-
CompareResult.OK
854-
else if variance > 0 then
855-
// we can soundly add nothing to source and `x` to this set
856-
addNewElem(elem)
857-
else if isFixpoint then
858-
// We can soundly add `x` to both this set and source since `f(x) = x`
859-
addNewElem(elem).andAlso(propagate)
860-
else
861-
// we are out of options; fail (which is always sound).
862-
failNoFixpoint
863-
end tryInclude
864-
865-
override def computeApprox(origin: CaptureSet)(using Context): CaptureSet =
866-
if source eq origin then
867-
// it's a mapping of origin, so not a superset of `origin`,
868-
// therefore don't contribute to the intersection.
869-
universal
870-
else
871-
source.upperApprox(this).map(tm)
872-
873-
override def propagateSolved(provisional: Boolean)(using Context) =
874-
if initial.isConst then super.propagateSolved(provisional)
875-
876-
override def toString = s"Mapped$id($source, elems = $elems)"
877-
end Mapped
878-
879784
/** A mapping where the type map is required to be a bijection.
880785
* Parameters as in Mapped.
881786
*/
@@ -1127,12 +1032,6 @@ object CaptureSet:
11271032
case _ =>
11281033
false
11291034

1130-
/** A TypeMap with the property that every capture reference in the image
1131-
* of the map is mapped to itself. I.e. for all capture references r1, r2,
1132-
* if M(r1) == r2 then M(r2) == r2.
1133-
*/
1134-
trait IdempotentCaptRefMap extends TypeMap
1135-
11361035
/** A TypeMap that is the identity on capture references */
11371036
trait IdentityCaptRefMap extends TypeMap
11381037

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
21-
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, CompareFailure, ExistentialSubsumesFailure}
21+
import CaptureSet.{withCaptureSetsExplained, CompareResult, CompareFailure, ExistentialSubsumesFailure}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -77,7 +77,7 @@ object CheckCaptures:
7777
* maps parameters in contravariant capture sets to the empty set.
7878
*/
7979
final class SubstParamsMap(from: BindingType, to: List[Type])(using Context)
80-
extends ApproximatingTypeMap, IdempotentCaptRefMap:
80+
extends ApproximatingTypeMap:
8181
def apply(tp: Type): Type =
8282
tp match
8383
case tp: ParamRef =>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import config.Feature
1111
import config.Printers.{capt, captDebug}
1212
import ast.tpd, tpd.*
1313
import transform.{PreRecheck, Recheck}, Recheck.*
14-
import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap}
1514
import Synthetics.isExcluded
1615
import util.SimpleIdentitySet
1716
import util.chaining.*
@@ -841,7 +840,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
841840
* We don't need to add <fluid> in covariant positions since pure types are
842841
* anyway compatible with capturing types.
843842
*/
844-
private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap:
843+
private def fluidify(using Context) = new TypeMap:
845844
def apply(t: Type): Type = t match
846845
case t: MethodType =>
847846
mapOver(t)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import typer.ErrorReporting.errorType
1111
import Names.TermName
1212
import NameKinds.ExistentialBinderName
1313
import NameOps.isImpureFunction
14-
import CaptureSet.IdempotentCaptRefMap
1514
import reporting.Message
1615
import util.{SimpleIdentitySet, EqHashMap}
1716
import util.Spans.NoSpan
@@ -219,7 +218,7 @@ object root:
219218

220219
/** Map top-level free existential variables one-to-one to Fresh instances */
221220
def resultToFresh(tp: Type)(using Context): Type =
222-
val subst = new IdempotentCaptRefMap:
221+
val subst = new TypeMap:
223222
val seen = EqHashMap[Annotation, CaptureRef]()
224223
var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty
225224

compiler/src/dotty/tools/dotc/core/Substituters.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dotty.tools.dotc
22
package core
33

44
import Types.*, Symbols.*, Contexts.*
5-
import cc.CaptureSet.IdempotentCaptRefMap
65

76
/** Substitution operations on types. See the corresponding `subst` and
87
* `substThis` methods on class Type for an explanation.
@@ -214,15 +213,15 @@ object Substituters:
214213
def apply(tp: Type): Type = substThis(tp, from, to, this)(using mapCtx)
215214
}
216215

217-
final class SubstRecThisMap(from: Type, to: Type)(using Context) extends DeepTypeMap, IdempotentCaptRefMap {
216+
final class SubstRecThisMap(from: Type, to: Type)(using Context) extends DeepTypeMap {
218217
def apply(tp: Type): Type = substRecThis(tp, from, to, this)(using mapCtx)
219218
}
220219

221-
final class SubstParamMap(from: ParamRef, to: Type)(using Context) extends DeepTypeMap, IdempotentCaptRefMap {
220+
final class SubstParamMap(from: ParamRef, to: Type)(using Context) extends DeepTypeMap {
222221
def apply(tp: Type): Type = substParam(tp, from, to, this)(using mapCtx)
223222
}
224223

225-
final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends DeepTypeMap, IdempotentCaptRefMap {
224+
final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends DeepTypeMap {
226225
def apply(tp: Type): Type = substParams(tp, from, to, this)(using mapCtx)
227226
}
228227

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
256256
report.log(explained(_.isSubType(tp1, tp2, approx), short = false))
257257
}
258258
// Eliminate LazyRefs before checking whether we have seen a type before
259-
val normalize = new TypeMap with CaptureSet.IdempotentCaptRefMap {
259+
val normalize = new TypeMap {
260260
val DerefLimit = 10
261261
var derefCount = 0
262262
def apply(t: Type) = t match {

0 commit comments

Comments
 (0)