Skip to content

Commit fd2fc0d

Browse files
committed
[ty] Fix TODO for meta-type of intersections
1 parent 51f0243 commit fd2fc0d

6 files changed

Lines changed: 39 additions & 11 deletions

File tree

crates/ty_python_semantic/resources/mdtest/annotations/callable.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def f_okay(c: Callable[[], None]):
496496

497497
# TODO: should be `property`
498498
# (or complain that we don't know that `type(c)` has the attribute at all!)
499-
reveal_type(type(c).__qualname__) # revealed: @Todo(Intersection meta-type)
499+
reveal_type(type(c).__qualname__) # revealed: Unknown
500500

501501
# `hasattr` only guarantees that an attribute is readable.
502502
#

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,7 @@ The type of `x.__class__` is the same as `x`'s meta-type. `x.__class__` is alway
23012301

23022302
```py
23032303
import typing_extensions
2304+
from ty_extensions import Intersection
23042305

23052306
reveal_type(typing_extensions.__class__) # revealed: <class 'ModuleType'>
23062307
reveal_type(type(typing_extensions)) # revealed: <class 'ModuleType'>
@@ -2321,7 +2322,10 @@ reveal_type(d.__class__) # revealed: <class 'bool'>
23212322
e = (42, 42)
23222323
reveal_type(e.__class__) # revealed: type[tuple[Literal[42], Literal[42]]]
23232324

2324-
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
2325+
class A: ...
2326+
class B: ...
2327+
2328+
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str], e: Intersection[A, B]):
23252329
reveal_type(a.__class__) # revealed: type[int]
23262330
reveal_type(type(a)) # revealed: type[int]
23272331

@@ -2337,6 +2341,8 @@ def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
23372341
# All we know is that the metaclass must be a (non-strict) subclass of `type`.
23382342
reveal_type(d.__class__) # revealed: type[type]
23392343

2344+
reveal_type(e.__class__) # revealed: type[A] & type[B]
2345+
23402346
reveal_type(f.__class__) # revealed: <class 'FunctionType'>
23412347

23422348
class Foo: ...

crates/ty_python_semantic/resources/mdtest/named_tuple.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@ def expects_named_tuple(x: typing.NamedTuple):
14301430
reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object]
14311431

14321432
def _(y: type[typing.NamedTuple]):
1433-
reveal_type(y) # revealed: @Todo(unsupported type[X] special form)
1433+
reveal_type(y) # revealed: type[tuple[object, ...]] & type[NamedTupleLike]
14341434

14351435
# error: [invalid-type-form] "Special form `typing.NamedTuple` expected no type parameter"
14361436
def _(z: typing.NamedTuple[int]): ...

crates/ty_python_semantic/resources/mdtest/narrow/isinstance.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,3 +826,13 @@ def f():
826826
else:
827827
reveal_type(x) # revealed: str
828828
```
829+
830+
## Interaction of `isinstance()` narrowing and the meta-type of intersections
831+
832+
```py
833+
def f(x: object):
834+
if not isinstance(x, int):
835+
reveal_type(x) # revealed: ~int
836+
reveal_type(x.__class__) # revealed: type & ~type[int]
837+
reveal_type(x.__class__.__name__) # revealed: str
838+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5480,11 +5480,8 @@ impl<'db> Type<'db> {
54805480
Type::SubclassOf(subclass_of_ty) => subclass_of_ty.to_meta_type(db),
54815481
Type::Dynamic(dynamic) => SubclassOfType::from(db, SubclassOfInner::Dynamic(dynamic)),
54825482
Type::Divergent(_) => self,
5483-
// TODO intersections
5484-
Type::Intersection(_) => {
5485-
SubclassOfType::try_from_type(db, todo_type!("Intersection meta-type"))
5486-
.expect("Type::Todo should be a valid `SubclassOfInner`")
5487-
}
5483+
Type::Intersection(_) => SubclassOfType::try_from_instance(db, self)
5484+
.unwrap_or_else(SubclassOfType::subclass_of_unknown),
54885485
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
54895486
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
54905487
Type::ProtocolInstance(protocol) => protocol.to_meta_type(db),

crates/ty_python_semantic/src/types/subclass_of.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::types::relation::{DisjointnessChecker, TypeRelationChecker};
66
use crate::types::variance::VarianceInferable;
77
use crate::types::{
88
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, ClassType, DynamicType,
9-
FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, MemberLookupPolicy,
10-
SpecialFormType, Type, TypeContext, TypeMapping, TypeVarBoundOrConstraints, TypeVarVariance,
11-
TypedDictType, UnionType, todo_type,
9+
FindLegacyTypeVarsVisitor, IntersectionBuilder, KnownClass, MaterializationKind,
10+
MemberLookupPolicy, SpecialFormType, Type, TypeContext, TypeMapping, TypeVarBoundOrConstraints,
11+
TypeVarVariance, TypedDictType, UnionType, todo_type,
1212
};
1313
use crate::{Db, FxOrderSet};
1414
use ty_python_core::definition::Definition;
@@ -88,6 +88,21 @@ impl<'db> SubclassOfType<'db> {
8888
.iter()
8989
.map(|element| Self::try_from_instance(db, *element)),
9090
),
91+
Type::Intersection(intersection) => {
92+
let mut builder = IntersectionBuilder::new(db);
93+
let positive = intersection.positive(db);
94+
if positive.is_empty() {
95+
builder = builder.add_positive(KnownClass::Type.to_instance(db));
96+
} else {
97+
for &element in intersection.positive(db) {
98+
builder = builder.add_positive(Self::try_from_instance(db, element)?);
99+
}
100+
}
101+
for &element in intersection.negative(db) {
102+
builder = builder.add_negative(Self::try_from_instance(db, element)?);
103+
}
104+
Some(builder.build())
105+
}
91106
Type::ProtocolInstance(protocol) => Some(protocol.to_meta_type(db)),
92107
_ => SubclassOfInner::try_from_instance(db, ty)
93108
.map(|subclass_of| Self::from(db, subclass_of)),

0 commit comments

Comments
 (0)