Skip to content
14 changes: 14 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/loops/for.md
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,17 @@ def g[T: tuple[int, ...] | list[str]](x: T):
# so iterating gives `int`, NOT `int | str`.
reveal_type(item) # revealed: int
```

## Iterating over a list with a negated type parameter

When we have a list with a negated type parameter (e.g., `list[~str]`), we should still be able to
iterate over it correctly. The negated type parameter represents all types except `str`, and
`list[~str]` is still a valid list that can be iterated.

```py
from ty_extensions import Not

def _(value: list[Not[str]]):
for x in value:
reveal_type(x) # revealed: ~str
```
45 changes: 35 additions & 10 deletions crates/ty_python_semantic/src/types/relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,25 @@ impl<'db> Type<'db> {
ConstraintSet::from(true)
}

// Fast path for various types that we know `object` is never a subtype of
// (`object` can be a subtype of some protocols, or of itself, but those cases are
// handled above).
(
Type::NominalInstance(source),
Type::NominalInstance(_)
| Type::SubclassOf(_)
| Type::Callable(_)
| Type::ProtocolInstance(_),
) if source.is_object() => ConstraintSet::from(false),

// Fast path: `object` is not a subtype of any non-inferable type variable, since the
// type variable could be specialized to a type smaller than `object`.
(Type::NominalInstance(source), Type::TypeVar(typevar))
if source.is_object() && !typevar.is_inferable(db, inferable) =>
{
ConstraintSet::from(false)
}

// `Never` is the bottom type, the empty set.
(_, Type::Never) => ConstraintSet::from(false),

Expand Down Expand Up @@ -808,16 +827,22 @@ impl<'db> Type<'db> {
}),

(Type::Intersection(intersection), _) => {
intersection.positive(db).iter().when_any(db, |&elem_ty| {
elem_ty.has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
// An intersection type is a subtype of another type if at least one of its
// positive elements is a subtype of that type. If there are no positive elements,
// we treat `object` as the implicit positive element (e.g., `~str` is semantically
// `object & ~str`).
intersection
.positive_elements_or_object(db)
.when_any(db, |elem_ty| {
elem_ty.has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
}

// Other than the special cases checked above, no other types are a subtype of a
Expand Down