Skip to content

Commit a422716

Browse files
authored
[ty] Fix flaky constraint set rendering (#20653)
This doesn't seem to be flaky in the sense of tests failing non-deterministically, but they are flaky in the sense of unrelated changes causing testing failures from the clauses of a constraint set being rendered in different orders. This flakiness is because we're using Salsa IDs to determine the order in which typevars appear in a constraint set BDD, and those IDs are assigned non-deterministically. The fix is ham-fisted but effective: sort the constraints in each clause, and the clauses in each set, as part of the rendering process. Constraint sets are only rendered in our test cases, so we don't need to over-optimize this.
1 parent a3e5c72 commit a422716

File tree

2 files changed

+47
-55
lines changed

2 files changed

+47
-55
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,9 @@ range.
305305

306306
```py
307307
def _[T]() -> None:
308-
# revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Base) ∧ (SubSub ≤ T@_ ≤ Base))]
308+
# revealed: ty_extensions.ConstraintSet[((SubSub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ T@_ ≤ Base))]
309309
reveal_type(range_constraint(SubSub, T, Base) & negated_range_constraint(Sub, T, Super))
310-
# revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Base) ∧ (SubSub ≤ T@_ ≤ Super))]
310+
# revealed: ty_extensions.ConstraintSet[((SubSub ≤ T@_ ≤ Super) ∧ ¬(Sub ≤ T@_ ≤ Base))]
311311
reveal_type(range_constraint(SubSub, T, Super) & negated_range_constraint(Sub, T, Base))
312312
```
313313

@@ -360,7 +360,7 @@ that type _is_ in `SubSub ≤ T ≤ Super`, it is not correct to simplify the un
360360

361361
```py
362362
def _[T]() -> None:
363-
# revealed: ty_extensions.ConstraintSet[(¬(SubSub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ T@_ ≤ Super))]
363+
# revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Super) ∧ ¬(SubSub ≤ T@_ ≤ Base))]
364364
reveal_type(negated_range_constraint(SubSub, T, Base) & negated_range_constraint(Sub, T, Super))
365365
```
366366

@@ -385,7 +385,7 @@ We cannot simplify the union of constraints that refer to different typevars.
385385
def _[T, U]() -> None:
386386
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base) ∨ (Sub ≤ U@_ ≤ Base)]
387387
reveal_type(range_constraint(Sub, T, Base) | range_constraint(Sub, U, Base))
388-
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ U@_ ≤ Base) ∨ ¬(Sub ≤ T@_ ≤ Base)]
388+
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base) ∨ ¬(Sub ≤ U@_ ≤ Base)]
389389
reveal_type(negated_range_constraint(Sub, T, Base) | negated_range_constraint(Sub, U, Base))
390390
```
391391

@@ -437,7 +437,7 @@ not include `Sub`. That means it should not be in the union. Since that type _is
437437

438438
```py
439439
def _[T]() -> None:
440-
# revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Base) ∨ (Sub ≤ T@_ ≤ Super)]
440+
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super) ∨ (SubSub ≤ T@_ ≤ Base)]
441441
reveal_type(range_constraint(SubSub, T, Base) | range_constraint(Sub, T, Super))
442442
```
443443

@@ -575,7 +575,7 @@ class Base: ...
575575
class Unrelated: ...
576576

577577
def _[T, U]() -> None:
578-
# revealed: ty_extensions.ConstraintSet[¬(U@_ ≤ Base) ∨ ¬(T@_ ≤ Base)]
578+
# revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base) ∨ ¬(U@_ ≤ Base)]
579579
reveal_type(~(range_constraint(Never, T, Base) & range_constraint(Never, U, Base)))
580580
```
581581

crates/ty_python_semantic/src/types/constraints.rs

Lines changed: 41 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,38 +1209,37 @@ impl<'db> SatisfiedClause<'db> {
12091209
false
12101210
}
12111211

1212-
fn display(&self, db: &'db dyn Db) -> impl Display {
1213-
struct DisplaySatisfiedClause<'a, 'db> {
1214-
clause: &'a SatisfiedClause<'db>,
1215-
db: &'db dyn Db,
1216-
}
1217-
1218-
impl Display for DisplaySatisfiedClause<'_, '_> {
1219-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1220-
if self.clause.constraints.len() > 1 {
1221-
f.write_str("(")?;
1222-
}
1223-
for (i, constraint) in self.clause.constraints.iter().enumerate() {
1224-
if i > 0 {
1225-
f.write_str(" ∧ ")?;
1226-
}
1227-
match constraint {
1228-
ConstraintAssignment::Positive(constraint) => {
1229-
write!(f, "{}", constraint.display(self.db))?;
1230-
}
1231-
ConstraintAssignment::Negative(constraint) => {
1232-
write!(f, "{}", constraint.display_negated(self.db))?;
1233-
}
1234-
}
1235-
}
1236-
if self.clause.constraints.len() > 1 {
1237-
f.write_str(")")?;
1212+
fn display(&self, db: &'db dyn Db) -> String {
1213+
// This is a bit heavy-handed, but we need to output the constraints in a consistent order
1214+
// even though Salsa IDs are assigned non-deterministically. This Display output is only
1215+
// used in test cases, so we don't need to over-optimize it.
1216+
1217+
let mut constraints: Vec<_> = self
1218+
.constraints
1219+
.iter()
1220+
.map(|constraint| match constraint {
1221+
ConstraintAssignment::Positive(constraint) => constraint.display(db).to_string(),
1222+
ConstraintAssignment::Negative(constraint) => {
1223+
constraint.display_negated(db).to_string()
12381224
}
1239-
Ok(())
1225+
})
1226+
.collect();
1227+
constraints.sort();
1228+
1229+
let mut result = String::new();
1230+
if constraints.len() > 1 {
1231+
result.push('(');
1232+
}
1233+
for (i, constraint) in constraints.iter().enumerate() {
1234+
if i > 0 {
1235+
result.push_str(" ∧ ");
12401236
}
1237+
result.push_str(constraint);
12411238
}
1242-
1243-
DisplaySatisfiedClause { clause: self, db }
1239+
if constraints.len() > 1 {
1240+
result.push(')');
1241+
}
1242+
result
12441243
}
12451244
}
12461245

@@ -1324,27 +1323,20 @@ impl<'db> SatisfiedClauses<'db> {
13241323
false
13251324
}
13261325

1327-
fn display(&self, db: &'db dyn Db) -> impl Display {
1328-
struct DisplaySatisfiedClauses<'a, 'db> {
1329-
clauses: &'a SatisfiedClauses<'db>,
1330-
db: &'db dyn Db,
1331-
}
1326+
fn display(&self, db: &'db dyn Db) -> String {
1327+
// This is a bit heavy-handed, but we need to output the clauses in a consistent order
1328+
// even though Salsa IDs are assigned non-deterministically. This Display output is only
1329+
// used in test cases, so we don't need to over-optimize it.
13321330

1333-
impl Display for DisplaySatisfiedClauses<'_, '_> {
1334-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1335-
if self.clauses.clauses.is_empty() {
1336-
return f.write_str("always");
1337-
}
1338-
for (i, clause) in self.clauses.clauses.iter().enumerate() {
1339-
if i > 0 {
1340-
f.write_str(" ∨ ")?;
1341-
}
1342-
clause.display(self.db).fmt(f)?;
1343-
}
1344-
Ok(())
1345-
}
1331+
if self.clauses.is_empty() {
1332+
return String::from("always");
13461333
}
1347-
1348-
DisplaySatisfiedClauses { clauses: self, db }
1334+
let mut clauses: Vec<_> = self
1335+
.clauses
1336+
.iter()
1337+
.map(|clause| clause.display(db))
1338+
.collect();
1339+
clauses.sort();
1340+
clauses.join(" ∨ ")
13491341
}
13501342
}

0 commit comments

Comments
 (0)