mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-20 04:29:47 +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():
|
async def name_5():
|
||||||
pass
|
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(_))
|
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?
|
/// Is a value of this type only usable in typing contexts?
|
||||||
pub(crate) fn is_type_check_only(&self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_type_check_only(&self, db: &'db dyn Db) -> bool {
|
||||||
match self {
|
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.
|
// 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
|
// Similarly, `T <: Any` only holds true if `T` is a dynamic type or an intersection
|
||||||
// that contains a dynamic type.
|
// that contains a dynamic type.
|
||||||
(Type::Dynamic(_), _) => ConstraintSet::from(match relation {
|
(Type::Dynamic(dynamic), _) => {
|
||||||
TypeRelation::Subtyping => false,
|
// If a `Divergent` type is involved, it must not be eliminated.
|
||||||
TypeRelation::Assignability => true,
|
debug_assert!(
|
||||||
TypeRelation::Redundancy => match target {
|
!matches!(dynamic, DynamicType::Divergent(_)),
|
||||||
Type::Dynamic(_) => true,
|
"DynamicType::Divergent should have been handled in an earlier branch"
|
||||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
);
|
||||||
_ => false,
|
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 {
|
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||||
TypeRelation::Subtyping => false,
|
TypeRelation::Subtyping => false,
|
||||||
TypeRelation::Assignability => true,
|
TypeRelation::Assignability => true,
|
||||||
TypeRelation::Redundancy => match self {
|
TypeRelation::Redundancy => match self {
|
||||||
Type::Dynamic(_) => true,
|
Type::Dynamic(_) => true,
|
||||||
Type::Intersection(intersection) => {
|
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,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
@ -9991,6 +10006,10 @@ pub(crate) enum TypeRelation {
|
||||||
/// materialization of `Any` and `int | Any` may be the same type (`object`), but the
|
/// materialization of `Any` and `int | Any` may be the same type (`object`), but the
|
||||||
/// two differ in their bottom materializations (`Never` and `int`, respectively).
|
/// 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
|
/// [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
|
/// [materializations]: https://typing.python.org/en/latest/spec/glossary.html#term-materialize
|
||||||
Redundancy,
|
Redundancy,
|
||||||
|
|
@ -12103,6 +12122,27 @@ pub(crate) mod tests {
|
||||||
assert!(div.is_equivalent_to(&db, div));
|
assert!(div.is_equivalent_to(&db, div));
|
||||||
assert!(!div.is_equivalent_to(&db, Type::unknown()));
|
assert!(!div.is_equivalent_to(&db, Type::unknown()));
|
||||||
assert!(!Type::unknown().is_equivalent_to(&db, div));
|
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`.
|
// 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`)
|
// (e.g. `object | tuple[Divergent] == object`, `object | tuple[object] == object`)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue