@@ -22,6 +22,22 @@ object TyperState {
2222 .init(null , OrderingConstraint .empty)
2323 .setReporter(new ConsoleReporter ())
2424 .setCommittable(true )
25+
26+ opaque type Snapshot = (Constraint , TypeVars , TypeVars )
27+
28+ extension (ts : TyperState )
29+ def snapshot ()(using Context ): Snapshot =
30+ var previouslyInstantiated : TypeVars = SimpleIdentitySet .empty
31+ for tv <- ts.ownedVars do if tv.inst.exists then previouslyInstantiated += tv
32+ (ts.constraint, ts.ownedVars, previouslyInstantiated)
33+
34+ def resetTo (state : Snapshot )(using Context ): Unit =
35+ val (c, tvs, previouslyInstantiated) = state
36+ for tv <- tvs do
37+ if tv.inst.exists && ! previouslyInstantiated.contains(tv) then
38+ tv.resetInst(ts)
39+ ts.ownedVars = tvs
40+ ts.constraint = c
2541}
2642
2743class TyperState () {
@@ -44,6 +60,8 @@ class TyperState() {
4460 def constraint_= (c : Constraint )(using Context ): Unit = {
4561 if (Config .debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed()
4662 myConstraint = c
63+ if Config .checkConsistentVars && ! ctx.reporter.errorsReported then
64+ c.checkConsistentVars()
4765 }
4866
4967 private var previousConstraint : Constraint = _
@@ -61,7 +79,11 @@ class TyperState() {
6179
6280 private var isCommitted : Boolean = _
6381
64- /** The set of uninstantiated type variables which have this state as their owning state */
82+ /** The set of uninstantiated type variables which have this state as their owning state
83+ * NOTE: It could be that a variable in `ownedVars` is already instantiated. This is because
84+ * the link between ownedVars and variable instantiation in TypeVar#setInst is made up
85+ * from a weak reference and weak references can have spurious nulls.
86+ */
6587 private var myOwnedVars : TypeVars = _
6688 def ownedVars : TypeVars = myOwnedVars
6789 def ownedVars_= (vs : TypeVars ): Unit = myOwnedVars = vs
@@ -120,19 +142,50 @@ class TyperState() {
120142 if constraint ne targetState.constraint then
121143 Stats .record(" typerState.commit.new constraint" )
122144 constr.println(i " committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}" )
123- if targetState.constraint eq previousConstraint then targetState.constraint = constraint
124- else targetState.mergeConstraintWith(this )
125- if ! ownedVars.isEmpty then
126- for tvar <- ownedVars do
127- tvar.owningState = new WeakReference (targetState)
128- targetState.ownedVars ++= ownedVars
145+ if targetState.constraint eq previousConstraint then
146+ targetState.constraint = constraint
147+ if ! ownedVars.isEmpty then ownedVars.foreach(targetState.includeVar)
148+ else
149+ targetState.mergeConstraintWith(this )
129150 targetState.gc()
130151 reporter.flush()
131152 isCommitted = true
132153 }
133154
155+ /** Ensure that this constraint does not associate different TypeVars for the
156+ * same type lambda than the `other` constraint. Do this by renaming type lambdas
157+ * in this constraint and its predecessors where necessary.
158+ */
159+ def ensureNotConflicting (other : Constraint )(using Context ): Unit =
160+ def hasConflictingTypeVarsFor (tl : TypeLambda ) =
161+ constraint.typeVarOfParam(tl.paramRefs(0 )) ne other.typeVarOfParam(tl.paramRefs(0 ))
162+ // Note: Since TypeVars are allocated in bulk for each type lambda, we only
163+ // have to check the first one to find out if some of them are different.
164+ val conflicting = constraint.domainLambdas.find(tl =>
165+ other.contains(tl) && hasConflictingTypeVarsFor(tl))
166+ for tl <- conflicting do
167+ val tl1 = constraint.ensureFresh(tl)
168+ for case (tvar : TypeVar , pref1) <- tl.paramRefs.map(constraint.typeVarOfParam).lazyZip(tl1.paramRefs) do
169+ tvar.setOrigin(pref1)
170+ var ts = this
171+ while ts.constraint.domainLambdas.contains(tl) do
172+ ts.constraint = ts.constraint.subst(tl, tl1)
173+ ts = ts.previous
174+
134175 def mergeConstraintWith (that : TyperState )(using Context ): Unit =
176+ that.ensureNotConflicting(constraint)
135177 constraint = constraint & (that.constraint, otherHasErrors = that.reporter.errorsReported)
178+ for tvar <- constraint.uninstVars do
179+ if ! isOwnedAnywhere(this , tvar) then ownedVars += tvar
180+ for tl <- constraint.domainLambdas do
181+ if constraint.isRemovable(tl) then constraint = constraint.remove(tl)
182+
183+ private def includeVar (tvar : TypeVar )(using Context ): Unit =
184+ tvar.owningState = new WeakReference (this )
185+ ownedVars += tvar
186+
187+ private def isOwnedAnywhere (ts : TyperState , tvar : TypeVar ): Boolean =
188+ ts.ownedVars.contains(tvar) || ts.previous != null && isOwnedAnywhere(ts.previous, tvar)
136189
137190 /** Make type variable instances permanent by assigning to `inst` field if
138191 * type variable instantiation cannot be retracted anymore. Then, remove
@@ -143,14 +196,14 @@ class TyperState() {
143196 Stats .record(" typerState.gc" )
144197 val toCollect = new mutable.ListBuffer [TypeLambda ]
145198 for tvar <- ownedVars do
146- if ! tvar.inst.exists then
199+ if ! tvar.inst.exists then // See comment of `ownedVars` for why this test is necessary
147200 val inst = constraint.instType(tvar)
148201 if inst.exists then
149- tvar.inst = inst
150- val lam = tvar.origin.binder
151- if constraint.isRemovable(lam ) then toCollect += lam
152- for poly <- toCollect do
153- constraint = constraint.remove(poly )
202+ tvar.setInst( inst)
203+ val tl = tvar.origin.binder
204+ if constraint.isRemovable(tl ) then toCollect += tl
205+ for tl <- toCollect do
206+ constraint = constraint.remove(tl )
154207
155208 override def toString : String = {
156209 def ids (state : TyperState ): List [String ] =
0 commit comments