Compare commits

...

4 Commits

Author SHA1 Message Date
Charlie Marsh
70859e4ba7 Add fast path for object vs unbounded inferable TypeVar
This handles the common case where we're checking if `object` (the
implicit positive element of a pure negation like `~str`) is assignable
to a generic type parameter like `_T` in `Iterator[_T]`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:57:49 -05:00
Charlie Marsh
0eb28df0b0 Add more fast-paths 2026-01-13 20:36:53 -05:00
Charlie Marsh
3dba88b2fe Add more fast-paths 2026-01-13 20:19:29 -05:00
Charlie Marsh
6de4b1daf7 Add neg 2026-01-13 20:07:10 -05:00
2 changed files with 86 additions and 0 deletions

View File

@@ -951,3 +951,17 @@ for x in Bar:
# TODO: should reveal `Any`
reveal_type(x) # revealed: Unknown
```
## 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
```

View File

@@ -651,6 +651,48 @@ impl<'db> Type<'db> {
ConstraintSet::from(true)
}
// Fast path: `object` is not a subtype of any nominal instance type other than itself.
// This is important for performance when checking intersections with no positive
// elements (pure negations like `~str`), which are treated as having `object` as
// the implicit positive element.
(Type::NominalInstance(source), Type::NominalInstance(_)) if source.is_object() => {
ConstraintSet::from(false)
}
// Fast path: `object` (an instance type) is not a subtype of any `type[X]` (a class type).
(Type::NominalInstance(source), Type::SubclassOf(_)) 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)
}
// Fast path: `object` is assignable to any inferable type variable with no upper bound
// (or with `object` as its upper bound), which is the common case for generic
// type parameters like `_T` in `Iterator[_T]`.
(Type::NominalInstance(source), Type::TypeVar(typevar))
if source.is_object()
&& typevar.is_inferable(db, inferable)
&& relation.is_assignability()
&& typevar
.typevar(db)
.upper_bound(db)
.is_none_or(|bound| bound.is_object()) =>
{
ConstraintSet::from(true)
}
// Fast path: `object` is not a subtype of any callable type, since not all objects
// are callable.
(Type::NominalInstance(source), Type::Callable(_)) if source.is_object() => {
ConstraintSet::from(false)
}
// `Never` is the bottom type, the empty set.
(_, Type::Never) => ConstraintSet::from(false),
@@ -796,7 +838,37 @@ impl<'db> Type<'db> {
})
}),
// Fast path for pure negations (~X): these are semantically `object & ~X`, so they're
// only assignable to types that `object` is assignable to. Since `object` is only
// assignable to `object`, dynamic types, unions/protocols/intersections that might
// contain `object`, we can short-circuit most cases directly.
(Type::Intersection(intersection), _) if intersection.positive(db).is_empty() => {
match target {
// `object` is a subtype of `object`
_ if target.is_object() => ConstraintSet::from(true),
// `object` is a subtype of dynamic types
Type::Dynamic(_) => ConstraintSet::from(true),
// These cases need more complex checking - delegate to full machinery
// (TypeVar needs special handling for inference)
Type::Union(_)
| Type::ProtocolInstance(_)
| Type::Intersection(_)
| Type::TypeVar(_) => Type::object().has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
),
// `object` is not a subtype of any other type
_ => ConstraintSet::from(false),
}
}
(Type::Intersection(intersection), _) => {
// An intersection type is a subtype of another type if at least one of its
// positive elements is a subtype of that type.
intersection.positive(db).iter().when_any(db, |&elem_ty| {
elem_ty.has_relation_to_impl(
db,