Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/call/type.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,20 @@ reveal_type(T.__bases__) # revealed: tuple[type, ...]
reveal_type(T.__mro__) # revealed: tuple[type, ...]
```

`type[Any]` and `type[Unknown]` are gradual forms with an unknown metaclass that is at least `type`.
Attributes defined as data descriptors on `type` (like `__mro__`) resolve to their declared types
intersected with `Unknown`, reflecting uncertainty about whether the unknown metaclass overrides
them:

```py
from typing import Any
from ty_extensions import Unknown

def f(a: type[Any], b: type[Unknown]):
reveal_type(a.__mro__) # revealed: tuple[type, ...] & Any
reveal_type(b.__mro__) # revealed: tuple[type, ...] & Unknown
```

## Invalid calls

Other numbers of arguments are invalid:
Expand Down
41 changes: 40 additions & 1 deletion crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,27 @@ impl<'db> Type<'db> {
inner: Protocol::Synthesized(_),
..
}) => self.instance_member(db, &name),

// `type[Any]` (or `type[Unknown]`, etc.) has an unknown metaclass, but all
// metaclasses inherit from `type`. Check `type`'s class-level attributes
// first so that data descriptors like `__mro__` and `__bases__` resolve to
// their correct types instead of collapsing to `Any`/`Unknown`.
Type::SubclassOf(subclass_of) if subclass_of.is_dynamic() => {
let type_result = KnownClass::Type
.to_class_literal(db)
.find_name_in_mro_with_policy(db, name.as_str(), policy)
.expect("`find_name_in_mro` should return `Some` for a class literal");
if !type_result.place.is_undefined() {
type_result
} else {
self.to_meta_type(db)
.find_name_in_mro_with_policy(db, name.as_str(), policy)
.expect(
"`Type::find_name_in_mro()` should return `Some()` when called on a meta-type",
)
}
}

_ => self
.to_meta_type(db)
.find_name_in_mro_with_policy(db, name.as_str(), policy)
Expand Down Expand Up @@ -3472,7 +3493,25 @@ impl<'db> Type<'db> {
// attribute access falls back to `__getattr__`/`__getattribute__` on the
// class. `try_call_dunder` adds `NO_INSTANCE_FALLBACK`, which causes the
// lookup to hit the catch-all that only checks the meta-type (the metaclass).
self.fallback_to_getattr(db, &name, result, policy)
let result = self.fallback_to_getattr(db, &name, result, policy);

// `type[Any]`/`type[Unknown]` are gradual forms with an unknown metaclass
// (which is at least `type`). Attributes resolved via `type`'s descriptors
// are intersected with the dynamic type to reflect uncertainty about
// whether the unknown metaclass overrides them.
if let Type::SubclassOf(subclass_of) = self
&& let SubclassOfInner::Dynamic(dynamic) = subclass_of.subclass_of()
{
result.map_type(|ty| {
if ty.is_dynamic() {
ty
} else {
IntersectionType::from_two_elements(db, ty, Type::Dynamic(dynamic))
}
})
} else {
result
}
}

// Unlike other objects, `super` has a unique member lookup behavior.
Expand Down
Loading