mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 19:41:34 +00:00
[ty] Fix panic due to simplifying Divergent types out of intersections types (#21253)
This commit is contained in:
parent
39f105bc4a
commit
b5305b5f32
3 changed files with 60 additions and 23 deletions
|
|
@ -0,0 +1,10 @@
|
|||
# Regression test for https://github.com/astral-sh/ruff/pull/20962
|
||||
# error message:
|
||||
# `infer_definition_types(Id(1804)): execute: too many cycle iterations`
|
||||
|
||||
for name_1 in {
|
||||
{{0: name_4 for unique_name_0 in unique_name_1}: 0 for unique_name_2 in unique_name_3 if name_4}: 0
|
||||
for unique_name_4 in name_1
|
||||
for name_4 in name_1
|
||||
}:
|
||||
pass
|
||||
|
|
@ -35,16 +35,3 @@ else:
|
|||
async def name_5():
|
||||
pass
|
||||
```
|
||||
|
||||
## Too many cycle iterations in `infer_definition_types`
|
||||
|
||||
<!-- expect-panic: too many cycle iterations -->
|
||||
|
||||
```py
|
||||
for name_1 in {
|
||||
{{0: name_4 for unique_name_0 in unique_name_1}: 0 for unique_name_2 in unique_name_3 if name_4}: 0
|
||||
for unique_name_4 in name_1
|
||||
for name_4 in name_1
|
||||
}:
|
||||
pass
|
||||
```
|
||||
|
|
|
|||
|
|
@ -873,6 +873,10 @@ impl<'db> Type<'db> {
|
|||
matches!(self, Type::Dynamic(_))
|
||||
}
|
||||
|
||||
const fn is_non_divergent_dynamic(&self) -> bool {
|
||||
self.is_dynamic() && !self.is_divergent()
|
||||
}
|
||||
|
||||
/// Is a value of this type only usable in typing contexts?
|
||||
pub(crate) fn is_type_check_only(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
|
|
@ -1695,22 +1699,33 @@ impl<'db> Type<'db> {
|
|||
// holds true if `T` is also a dynamic type or a union that contains a dynamic type.
|
||||
// Similarly, `T <: Any` only holds true if `T` is a dynamic type or an intersection
|
||||
// that contains a dynamic type.
|
||||
(Type::Dynamic(_), _) => ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping => false,
|
||||
TypeRelation::Assignability => true,
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
_ => false,
|
||||
},
|
||||
}),
|
||||
(Type::Dynamic(dynamic), _) => {
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
debug_assert!(
|
||||
!matches!(dynamic, DynamicType::Divergent(_)),
|
||||
"DynamicType::Divergent should have been handled in an earlier branch"
|
||||
);
|
||||
ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping => false,
|
||||
TypeRelation::Assignability => true,
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping => false,
|
||||
TypeRelation::Assignability => true,
|
||||
TypeRelation::Redundancy => match self {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Intersection(intersection) => {
|
||||
intersection.positive(db).iter().any(Type::is_dynamic)
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(Type::is_non_divergent_dynamic)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
|
|
@ -9991,6 +10006,10 @@ pub(crate) enum TypeRelation {
|
|||
/// materialization of `Any` and `int | Any` may be the same type (`object`), but the
|
||||
/// two differ in their bottom materializations (`Never` and `int`, respectively).
|
||||
///
|
||||
/// Despite the above principles, there is one exceptional type that should never be union-simplified: the `Divergent` type.
|
||||
/// This is a kind of dynamic type, but it acts as a marker to track recursive type structures.
|
||||
/// If this type is accidentally eliminated by simplification, the fixed-point iteration will not converge.
|
||||
///
|
||||
/// [fully static]: https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type
|
||||
/// [materializations]: https://typing.python.org/en/latest/spec/glossary.html#term-materialize
|
||||
Redundancy,
|
||||
|
|
@ -12103,6 +12122,27 @@ pub(crate) mod tests {
|
|||
assert!(div.is_equivalent_to(&db, div));
|
||||
assert!(!div.is_equivalent_to(&db, Type::unknown()));
|
||||
assert!(!Type::unknown().is_equivalent_to(&db, div));
|
||||
assert!(!div.is_redundant_with(&db, Type::unknown()));
|
||||
assert!(!Type::unknown().is_redundant_with(&db, div));
|
||||
|
||||
let truthy_div = IntersectionBuilder::new(&db)
|
||||
.add_positive(div)
|
||||
.add_negative(Type::AlwaysFalsy)
|
||||
.build();
|
||||
|
||||
let union = UnionType::from_elements(&db, [Type::unknown(), truthy_div]);
|
||||
assert!(!truthy_div.is_redundant_with(&db, Type::unknown()));
|
||||
assert_eq!(
|
||||
union.display(&db).to_string(),
|
||||
"Unknown | (Divergent & ~AlwaysFalsy)"
|
||||
);
|
||||
|
||||
let union = UnionType::from_elements(&db, [truthy_div, Type::unknown()]);
|
||||
assert!(!Type::unknown().is_redundant_with(&db, truthy_div));
|
||||
assert_eq!(
|
||||
union.display(&db).to_string(),
|
||||
"(Divergent & ~AlwaysFalsy) | Unknown"
|
||||
);
|
||||
|
||||
// The `object` type has a good convergence property, that is, its union with all other types is `object`.
|
||||
// (e.g. `object | tuple[Divergent] == object`, `object | tuple[object] == object`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue