mirror of https://github.com/astral-sh/ruff
Merge d1ceb75960 into b0bc990cbf
This commit is contained in:
commit
af6240e7ce
|
|
@ -337,6 +337,70 @@ for x in Test():
|
||||||
reveal_type(x) # revealed: int
|
reveal_type(x) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Intersection type via isinstance narrowing
|
||||||
|
|
||||||
|
When we have an intersection type via `isinstance` narrowing, we should be able to infer the
|
||||||
|
iterable element type precisely:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
def _(x: Sequence[int], y: object):
|
||||||
|
reveal_type(x) # revealed: Sequence[int]
|
||||||
|
for item in x:
|
||||||
|
reveal_type(item) # revealed: int
|
||||||
|
|
||||||
|
if isinstance(y, list):
|
||||||
|
reveal_type(y) # revealed: Top[list[Unknown]]
|
||||||
|
for item in y:
|
||||||
|
reveal_type(item) # revealed: object
|
||||||
|
|
||||||
|
if isinstance(x, list):
|
||||||
|
reveal_type(x) # revealed: Sequence[int] & Top[list[Unknown]]
|
||||||
|
for item in x:
|
||||||
|
# int & object simplifies to int
|
||||||
|
reveal_type(item) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
## Intersection where some elements are not iterable
|
||||||
|
|
||||||
|
When iterating over an intersection type, we should only fail if all positive elements fail to
|
||||||
|
iterate. If some elements are iterable and some are not, we should iterate over the iterable ones
|
||||||
|
and intersect their element types.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import Intersection
|
||||||
|
|
||||||
|
class NotIterable:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _(x: Intersection[list[int], NotIterable]):
|
||||||
|
# `list[int]` is iterable (yielding `int`), but `NotIterable` is not.
|
||||||
|
# We should still be able to iterate over the intersection.
|
||||||
|
for item in x:
|
||||||
|
reveal_type(item) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
## Intersection where all elements are not iterable
|
||||||
|
|
||||||
|
When iterating over an intersection type where all positive elements are not iterable, we should
|
||||||
|
fail to iterate.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import Intersection
|
||||||
|
|
||||||
|
class NotIterable1:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class NotIterable2:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _(x: Intersection[NotIterable1, NotIterable2]):
|
||||||
|
# error: [not-iterable]
|
||||||
|
for item in x:
|
||||||
|
reveal_type(item) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
## Possibly-not-callable `__iter__` method
|
## Possibly-not-callable `__iter__` method
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
||||||
|
|
@ -6695,6 +6695,25 @@ impl<'db> Type<'db> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Type::Intersection(intersection) => {
|
||||||
|
// For intersections, we iterate over each positive element and intersect
|
||||||
|
// the resulting element types. Negative elements don't affect iteration.
|
||||||
|
// We only fail if all elements fail to iterate; as long as at least one
|
||||||
|
// element can be iterated over, we can produce a result.
|
||||||
|
let mut specs_iter = intersection
|
||||||
|
.positive_elements_or_object(db)
|
||||||
|
.filter_map(|element| {
|
||||||
|
element
|
||||||
|
.try_iterate_with_mode(db, EvaluationMode::Sync)
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
let first_spec = specs_iter.next()?;
|
||||||
|
let mut builder = TupleSpecBuilder::from(&*first_spec);
|
||||||
|
for spec in specs_iter {
|
||||||
|
builder = builder.intersect(db, &spec);
|
||||||
|
}
|
||||||
|
Some(Cow::Owned(builder.build()))
|
||||||
|
}
|
||||||
// N.B. These special cases aren't strictly necessary, they're just obvious optimizations
|
// N.B. These special cases aren't strictly necessary, they're just obvious optimizations
|
||||||
Type::LiteralString | Type::Dynamic(_) => Some(Cow::Owned(TupleSpec::homogeneous(ty))),
|
Type::LiteralString | Type::Dynamic(_) => Some(Cow::Owned(TupleSpec::homogeneous(ty))),
|
||||||
|
|
||||||
|
|
@ -6717,7 +6736,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::SpecialForm(_)
|
| Type::SpecialForm(_)
|
||||||
| Type::KnownInstance(_)
|
| Type::KnownInstance(_)
|
||||||
| Type::PropertyInstance(_)
|
| Type::PropertyInstance(_)
|
||||||
| Type::Intersection(_)
|
|
||||||
| Type::AlwaysTruthy
|
| Type::AlwaysTruthy
|
||||||
| Type::AlwaysFalsy
|
| Type::AlwaysFalsy
|
||||||
| Type::IntLiteral(_)
|
| Type::IntLiteral(_)
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||||
use crate::types::generics::InferableTypeVars;
|
use crate::types::generics::InferableTypeVars;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||||
IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
IntersectionType, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping,
|
||||||
UnionBuilder, UnionType,
|
TypeRelation, UnionBuilder, UnionType,
|
||||||
};
|
};
|
||||||
use crate::types::{Truthiness, TypeContext};
|
use crate::types::{Truthiness, TypeContext};
|
||||||
use crate::{Db, FxOrderSet, Program};
|
use crate::{Db, FxOrderSet, Program};
|
||||||
|
|
@ -1787,6 +1787,42 @@ impl<'db> TupleSpecBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a new tuple-spec builder that reflects the intersection of this tuple and another tuple.
|
||||||
|
///
|
||||||
|
/// For example, if `self` is a tuple-spec builder for `tuple[int, str]` and `other` is a
|
||||||
|
/// tuple-spec for `tuple[object, object]`, the result will be a tuple-spec builder for
|
||||||
|
/// `tuple[int, str]` (since `int & object` simplifies to `int`, and `str & object` to `str`).
|
||||||
|
///
|
||||||
|
/// To keep things simple, we currently only attempt to preserve the "fixed-length-ness" of
|
||||||
|
/// a tuple spec if both `self` and `other` have the exact same length. For example,
|
||||||
|
/// if `self` is a tuple-spec builder for `tuple[int, str]` and `other` is a tuple-spec for
|
||||||
|
/// `tuple[int, str, bytes]`, the result will be a tuple-spec builder for
|
||||||
|
/// `tuple[int & str & bytes, ...]`.
|
||||||
|
pub(crate) fn intersect(mut self, db: &'db dyn Db, other: &TupleSpec<'db>) -> Self {
|
||||||
|
match (&mut self, other) {
|
||||||
|
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Fixed(new_elements))
|
||||||
|
if our_elements.len() == new_elements.len() =>
|
||||||
|
{
|
||||||
|
for (existing, new) in our_elements.iter_mut().zip(new_elements.elements()) {
|
||||||
|
*existing = IntersectionType::from_elements(db, [*existing, *new]);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
let intersected = IntersectionType::from_elements(
|
||||||
|
db,
|
||||||
|
self.all_elements().chain(other.all_elements()),
|
||||||
|
);
|
||||||
|
TupleSpecBuilder::Variable {
|
||||||
|
prefix: vec![],
|
||||||
|
variable: intersected,
|
||||||
|
suffix: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn build(self) -> TupleSpec<'db> {
|
pub(super) fn build(self) -> TupleSpec<'db> {
|
||||||
match self {
|
match self {
|
||||||
TupleSpecBuilder::Fixed(elements) => {
|
TupleSpecBuilder::Fixed(elements) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue