diff --git a/crates/ty_python_semantic/resources/mdtest/loops/for.md b/crates/ty_python_semantic/resources/mdtest/loops/for.md index e0510995e779b..c03c471da87b1 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/for.md @@ -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 +``` diff --git a/crates/ty_python_semantic/src/types/relation.rs b/crates/ty_python_semantic/src/types/relation.rs index f7dcebe105de4..b680b7233a019 100644 --- a/crates/ty_python_semantic/src/types/relation.rs +++ b/crates/ty_python_semantic/src/types/relation.rs @@ -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), @@ -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