Skip to content

Commit a7b1e78

Browse files
authored
Merge pull request swiftlang#77174 from slavapestov/fix-undo-order
Sema: Undo changes in chronological order in SolverTrail::undo()
2 parents e6b4e0f + a19c92a commit a7b1e78

File tree

7 files changed

+116
-82
lines changed

7 files changed

+116
-82
lines changed

include/swift/Sema/ConstraintGraph.h

+28-12
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,12 @@ class ConstraintGraphNode {
137137
/// gets removed for a constraint graph.
138138
void retractFromInference(Constraint *constraint);
139139

140-
/// Re-evaluate the given constraint. This happens when there are changes
141-
/// in associated type variables e.g. bound/unbound to/from a fixed type,
142-
/// equivalence class changes.
143-
void reintroduceToInference(Constraint *constraint);
144-
145-
/// Similar to \c introduceToInference(Constraint *, ...) this method is going
146-
/// to notify inference that this type variable has been bound to a concrete
147-
/// type.
140+
/// Perform graph updates that must be undone after we bind a fixed type
141+
/// to a type variable.
142+
void retractFromInference(Type fixedType);
143+
144+
/// Perform graph updates that must be undone before we bind a fixed type
145+
/// to a type variable.
148146
///
149147
/// The reason why this can't simplify be a part of \c bindTypeVariable
150148
/// is related to the fact that it's sometimes expensive to re-compute
@@ -161,12 +159,18 @@ class ConstraintGraphNode {
161159
///
162160
/// This is useful in situations when type variable gets bound and unbound,
163161
/// or equivalence class changes.
164-
void notifyReferencingVars() const;
162+
void notifyReferencingVars(
163+
llvm::function_ref<void(ConstraintGraphNode &,
164+
Constraint *)> notification) const;
165165

166166
/// Notify all of the type variables referenced by this one about a change.
167167
void notifyReferencedVars(
168-
llvm::function_ref<void(ConstraintGraphNode &)> notification);
168+
llvm::function_ref<void(ConstraintGraphNode &)> notification) const;
169169

170+
void updateFixedType(
171+
Type fixedType,
172+
llvm::function_ref<void (ConstraintGraphNode &,
173+
Constraint *)> notification) const;
170174
/// }
171175

172176
/// The constraint graph this node belongs to.
@@ -261,16 +265,28 @@ class ConstraintGraph {
261265
/// Primitive form for SolverTrail::Change::undo().
262266
void removeConstraint(TypeVariableType *typeVar, Constraint *constraint);
263267

268+
/// Prepare to merge the given node into some other node.
269+
///
270+
/// This records graph changes that must be undone after the merge has
271+
/// been undone.
272+
void mergeNodesPre(TypeVariableType *typeVar2);
273+
264274
/// Merge the two nodes for the two given type variables.
265275
///
266276
/// The type variables must actually have been merged already; this
267-
/// operation merges the two nodes.
277+
/// operation merges the two nodes. This also records graph changes
278+
/// that must be undone before the merge can be undone.
268279
void mergeNodes(TypeVariableType *typeVar1, TypeVariableType *typeVar2);
269280

270281
/// Bind the given type variable to the given fixed type.
271282
void bindTypeVariable(TypeVariableType *typeVar, Type fixedType);
272283

273-
/// Introduce the type variable's fixed type to inference.
284+
/// Perform graph updates that must be undone after we bind a fixed type
285+
/// to a type variable.
286+
void retractFromInference(TypeVariableType *typeVar, Type fixedType);
287+
288+
/// Perform graph updates that must be undone before we bind a fixed type
289+
/// to a type variable.
274290
void introduceToInference(TypeVariableType *typeVar, Type fixedType);
275291

276292
/// Describes which constraints \c gatherConstraints should gather.

include/swift/Sema/ConstraintSystem.h

+1-6
Original file line numberDiff line numberDiff line change
@@ -555,12 +555,7 @@ class TypeVariableType::Implementation {
555555
/// \param trail The record of state changes.
556556
void mergeEquivalenceClasses(TypeVariableType *other,
557557
constraints::SolverTrail *trail) {
558-
// Merge the equivalence classes corresponding to these two type
559-
// variables. Always merge 'up' the constraint stack, because it is simpler.
560-
if (getID() > other->getImpl().getID()) {
561-
other->getImpl().mergeEquivalenceClasses(getTypeVariable(), trail);
562-
return;
563-
}
558+
ASSERT(getID() < other->getImpl().getID());
564559

565560
auto otherRep = other->getImpl().getRepresentative(trail);
566561
if (trail)

lib/Sema/CSTrail.cpp

+2-13
Original file line numberDiff line numberDiff line change
@@ -730,21 +730,10 @@ void SolverTrail::undo(unsigned toIndex) {
730730
ASSERT(!UndoActive);
731731
UndoActive = true;
732732

733-
// FIXME: Undo all changes in the correct order!
734733
for (unsigned i = Changes.size(); i > toIndex; i--) {
735734
auto change = Changes[i - 1];
736-
if (change.Kind == ChangeKind::UpdatedTypeVariable) {
737-
LLVM_DEBUG(llvm::dbgs() << "- "; change.dump(llvm::dbgs(), CS, 0));
738-
change.undo(CS);
739-
}
740-
}
741-
742-
for (unsigned i = Changes.size(); i > toIndex; i--) {
743-
auto change = Changes[i - 1];
744-
if (change.Kind != ChangeKind::UpdatedTypeVariable) {
745-
LLVM_DEBUG(llvm::dbgs() << "- "; change.dump(llvm::dbgs(), CS, 0));
746-
change.undo(CS);
747-
}
735+
LLVM_DEBUG(llvm::dbgs() << "- "; change.dump(llvm::dbgs(), CS, 0));
736+
change.undo(CS);
748737
}
749738

750739
Changes.resize(toIndex);

lib/Sema/ConstraintGraph.cpp

+76-44
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ ConstraintGraph::lookupNode(TypeVariableType *typeVar) {
8989
// If this type variable is not the representative of its equivalence class,
9090
// add it to its representative's set of equivalences.
9191
auto typeVarRep = CS.getRepresentative(typeVar);
92-
if (typeVar != typeVarRep)
93-
mergeNodes(typeVar, typeVarRep);
92+
if (typeVar != typeVarRep) {
93+
mergeNodesPre(typeVar);
94+
mergeNodes(typeVarRep, typeVar);
95+
}
9496
else if (auto fixed = CS.getFixedType(typeVarRep)) {
9597
// Bind the type variable.
9698
bindTypeVariable(typeVar, fixed);
@@ -177,7 +179,9 @@ void ConstraintGraphNode::removeConstraint(Constraint *constraint) {
177179
Constraints.pop_back();
178180
}
179181

180-
void ConstraintGraphNode::notifyReferencingVars() const {
182+
void ConstraintGraphNode::notifyReferencingVars(
183+
llvm::function_ref<void(ConstraintGraphNode &,
184+
Constraint *)> notification) const {
181185
SmallVector<TypeVariableType *, 4> stack;
182186

183187
stack.push_back(TypeVar);
@@ -199,7 +203,7 @@ void ConstraintGraphNode::notifyReferencingVars() const {
199203
affectedVar->getImpl().getRepresentative(/*record=*/nullptr);
200204

201205
if (!repr->getImpl().getFixedType(/*record=*/nullptr))
202-
CG[repr].reintroduceToInference(constraint);
206+
notification(CG[repr], constraint);
203207
}
204208
}
205209
};
@@ -236,7 +240,7 @@ void ConstraintGraphNode::notifyReferencingVars() const {
236240
}
237241

238242
void ConstraintGraphNode::notifyReferencedVars(
239-
llvm::function_ref<void(ConstraintGraphNode &)> notification) {
243+
llvm::function_ref<void(ConstraintGraphNode &)> notification) const {
240244
for (auto *fixedBinding : getReferencedVars()) {
241245
notification(CG[fixedBinding]);
242246
}
@@ -249,25 +253,6 @@ void ConstraintGraphNode::addToEquivalenceClass(
249253
if (EquivalenceClass.empty())
250254
EquivalenceClass.push_back(getTypeVariable());
251255
EquivalenceClass.append(typeVars.begin(), typeVars.end());
252-
253-
{
254-
for (auto *newMember : typeVars) {
255-
auto &node = CG[newMember];
256-
257-
for (auto *constraint : node.getConstraints()) {
258-
introduceToInference(constraint);
259-
260-
if (!isUsefulForReferencedVars(constraint))
261-
continue;
262-
263-
notifyReferencedVars([&](ConstraintGraphNode &referencedVar) {
264-
referencedVar.introduceToInference(constraint);
265-
});
266-
}
267-
268-
node.notifyReferencingVars();
269-
}
270-
}
271256
}
272257

273258
void ConstraintGraphNode::truncateEquivalenceClass(unsigned prevSize) {
@@ -343,19 +328,17 @@ void ConstraintGraphNode::retractFromInference(Constraint *constraint) {
343328
}
344329
}
345330

346-
void ConstraintGraphNode::reintroduceToInference(Constraint *constraint) {
347-
retractFromInference(constraint);
348-
introduceToInference(constraint);
349-
}
350-
351-
void ConstraintGraphNode::introduceToInference(Type fixedType) {
331+
void ConstraintGraphNode::updateFixedType(
332+
Type fixedType,
333+
llvm::function_ref<void (ConstraintGraphNode &,
334+
Constraint *)> notification) const {
352335
// Notify all of the type variables that reference this one.
353336
//
354337
// Since this type variable has been replaced with a fixed type
355338
// all of the concrete types that reference it are going to change,
356339
// which means that all of the not-yet-attempted bindings should
357340
// change as well.
358-
notifyReferencingVars();
341+
notifyReferencingVars(notification);
359342

360343
if (!fixedType->hasTypeVariable())
361344
return;
@@ -371,11 +354,27 @@ void ConstraintGraphNode::introduceToInference(Type fixedType) {
371354
// all of the constraints that reference bound type variable.
372355
for (auto *constraint : getConstraints()) {
373356
if (isUsefulForReferencedVars(constraint))
374-
node.reintroduceToInference(constraint);
357+
notification(node, constraint);
375358
}
376359
}
377360
}
378361

362+
void ConstraintGraphNode::retractFromInference(Type fixedType) {
363+
return updateFixedType(
364+
fixedType,
365+
[](ConstraintGraphNode &node, Constraint *constraint) {
366+
node.retractFromInference(constraint);
367+
});
368+
}
369+
370+
void ConstraintGraphNode::introduceToInference(Type fixedType) {
371+
return updateFixedType(
372+
fixedType,
373+
[](ConstraintGraphNode &node, Constraint *constraint) {
374+
node.introduceToInference(constraint);
375+
});
376+
}
377+
379378
#pragma mark Graph mutation
380379

381380
void ConstraintGraph::removeNode(TypeVariableType *typeVar) {
@@ -486,31 +485,60 @@ void ConstraintGraph::removeConstraint(TypeVariableType *typeVar,
486485
OrphanedConstraints.pop_back();
487486
}
488487

488+
void ConstraintGraph::mergeNodesPre(TypeVariableType *typeVar2) {
489+
// Merge equivalence class from the non-representative type variable.
490+
auto &nonRepNode = (*this)[typeVar2];
491+
492+
for (auto *newMember : nonRepNode.getEquivalenceClassUnsafe()) {
493+
auto &node = (*this)[newMember];
494+
495+
node.notifyReferencingVars(
496+
[&](ConstraintGraphNode &node, Constraint *constraint) {
497+
node.retractFromInference(constraint);
498+
});
499+
}
500+
}
501+
489502
void ConstraintGraph::mergeNodes(TypeVariableType *typeVar1,
490503
TypeVariableType *typeVar2) {
491-
assert(CS.getRepresentative(typeVar1) == CS.getRepresentative(typeVar2) &&
492-
"type representatives don't match");
493-
494504
// Retrieve the node for the representative that we're merging into.
495-
auto typeVarRep = CS.getRepresentative(typeVar1);
496-
auto &repNode = (*this)[typeVarRep];
505+
ASSERT(CS.getRepresentative(typeVar1) == typeVar1);
497506

498-
// Retrieve the node for the non-representative.
499-
assert((typeVar1 == typeVarRep || typeVar2 == typeVarRep) &&
500-
"neither type variable is the new representative?");
501-
auto typeVarNonRep = typeVar1 == typeVarRep? typeVar2 : typeVar1;
507+
auto &repNode = (*this)[typeVar1];
502508

503509
// Record the change, if there are active scopes.
504510
if (CS.isRecordingChanges()) {
505511
CS.recordChange(
506512
SolverTrail::Change::ExtendedEquivalenceClass(
507-
typeVarRep,
513+
typeVar1,
508514
repNode.getEquivalenceClass().size()));
509515
}
510516

511517
// Merge equivalence class from the non-representative type variable.
512-
auto &nonRepNode = (*this)[typeVarNonRep];
513-
repNode.addToEquivalenceClass(nonRepNode.getEquivalenceClassUnsafe());
518+
auto &nonRepNode = (*this)[typeVar2];
519+
520+
auto typeVars = nonRepNode.getEquivalenceClassUnsafe();
521+
repNode.addToEquivalenceClass(typeVars);
522+
523+
for (auto *newMember : typeVars) {
524+
auto &node = (*this)[newMember];
525+
526+
for (auto *constraint : node.getConstraints()) {
527+
repNode.introduceToInference(constraint);
528+
529+
if (!isUsefulForReferencedVars(constraint))
530+
continue;
531+
532+
repNode.notifyReferencedVars([&](ConstraintGraphNode &referencedVar) {
533+
referencedVar.introduceToInference(constraint);
534+
});
535+
}
536+
537+
node.notifyReferencingVars(
538+
[&](ConstraintGraphNode &node, Constraint *constraint) {
539+
node.introduceToInference(constraint);
540+
});
541+
}
514542
}
515543

516544
void ConstraintGraph::bindTypeVariable(TypeVariableType *typeVar, Type fixed) {
@@ -537,6 +565,10 @@ void ConstraintGraph::bindTypeVariable(TypeVariableType *typeVar, Type fixed) {
537565
}
538566
}
539567

568+
void ConstraintGraph::retractFromInference(TypeVariableType *typeVar, Type fixed) {
569+
(*this)[typeVar].retractFromInference(fixed);
570+
}
571+
540572
void ConstraintGraph::introduceToInference(TypeVariableType *typeVar, Type fixed) {
541573
(*this)[typeVar].introduceToInference(fixed);
542574
}

lib/Sema/ConstraintSystem.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,13 @@ void ConstraintSystem::mergeEquivalenceClasses(TypeVariableType *typeVar1,
173173
assert(typeVar2 == getRepresentative(typeVar2) &&
174174
"typeVar2 is not the representative");
175175
assert(typeVar1 != typeVar2 && "cannot merge type with itself");
176-
typeVar1->getImpl().mergeEquivalenceClasses(typeVar2, getTrail());
177176

178-
// Merge nodes in the constraint graph.
177+
// Always merge 'up' the constraint stack, because it is simpler.
178+
if (typeVar1->getImpl().getID() > typeVar2->getImpl().getID())
179+
std::swap(typeVar1, typeVar2);
180+
181+
CG.mergeNodesPre(typeVar2);
182+
typeVar1->getImpl().mergeEquivalenceClasses(typeVar2, getTrail());
179183
CG.mergeNodes(typeVar1, typeVar2);
180184

181185
if (updateWorkList) {
@@ -205,6 +209,7 @@ void ConstraintSystem::assignFixedType(TypeVariableType *typeVar, Type type,
205209
assert(!type->hasError() &&
206210
"Should not be assigning a type involving ErrorType!");
207211

212+
CG.retractFromInference(typeVar, type);
208213
typeVar->getImpl().assignFixedType(type, getTrail());
209214

210215
if (!updateState)

test/Sema/issue-46000.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct Data {}
1111
extension DispatchData {
1212
func asFoundationData<T>(execute: (Data) throws -> T) rethrows -> T {
1313
return try withUnsafeBytes { (ptr: UnsafePointer<Int8>) -> Void in
14-
// expected-error@-1 {{cannot convert return expression of type 'Void' to return type 'T'}}
14+
// expected-error@-1 {{declared closure result 'Void' is incompatible with contextual type 'T'}}
1515
let data = Data()
1616
return try execute(data) // expected-error {{cannot convert value of type 'T' to closure result type 'Void'}}
1717
}

test/type/opaque.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,8 @@ func associatedTypeIdentity() {
273273
sameType(cr, dr) // expected-error {{conflicting arguments to generic parameter 'T' ('(some R).S' (result type of 'candace') vs. '(some R).S' (result type of 'doug'))}}
274274
sameType(gary(candace()).r_out(), gary(candace()).r_out())
275275
sameType(gary(doug()).r_out(), gary(doug()).r_out())
276-
// TODO(diagnostics): This is not great but the problem comes from the way solver discovers and attempts bindings, if we could detect that
277-
// `(some R).S` from first reference to `gary()` in inconsistent with the second one based on the parent type of `S` it would be much easier to diagnose.
278276
sameType(gary(doug()).r_out(), gary(candace()).r_out())
279-
// expected-error@-1:12 {{conflicting arguments to generic parameter 'T' ('some R' (result type of 'doug') vs. 'some R' (result type of 'candace'))}}
280-
// expected-error@-2:34 {{conflicting arguments to generic parameter 'T' ('some R' (result type of 'doug') vs. 'some R' (result type of 'candace'))}}
277+
// expected-error@-1:39 {{cannot convert value of type 'some R' (result of 'candace()') to expected argument type 'some R' (result of 'doug()')}}
281278
}
282279

283280
func redeclaration() -> some P { return 0 } // expected-note 2{{previously declared}}

0 commit comments

Comments
 (0)