mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[red-knot] Understand typing.Protocol
and typing_extensions.Protocol
as equivalent (#17446)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[Knot Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[Knot Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
58807b2980
commit
9965cee998
3 changed files with 29 additions and 13 deletions
|
@ -227,13 +227,15 @@ reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool
|
|||
```py
|
||||
import typing
|
||||
import typing_extensions
|
||||
from knot_extensions import static_assert, is_equivalent_to
|
||||
from knot_extensions import static_assert, is_equivalent_to, TypeOf
|
||||
|
||||
static_assert(is_equivalent_to(TypeOf[typing.Protocol], TypeOf[typing_extensions.Protocol]))
|
||||
static_assert(is_equivalent_to(int | str | TypeOf[typing.Protocol], TypeOf[typing_extensions.Protocol] | str | int))
|
||||
|
||||
class Foo(typing.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: should not error
|
||||
class Bar(typing_extensions.Protocol): # error: [invalid-base]
|
||||
class Bar(typing_extensions.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
|
@ -249,9 +251,8 @@ The same goes for `typing.runtime_checkable` and `typing_extensions.runtime_chec
|
|||
class RuntimeCheckableFoo(typing.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: should not error
|
||||
@typing.runtime_checkable
|
||||
class RuntimeCheckableBar(typing_extensions.Protocol): # error: [invalid-base]
|
||||
class RuntimeCheckableBar(typing_extensions.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
|
@ -264,6 +265,15 @@ isinstance(object(), RuntimeCheckableFoo)
|
|||
isinstance(object(), RuntimeCheckableBar)
|
||||
```
|
||||
|
||||
However, we understand that they are not necessarily the same symbol at the same memory address at
|
||||
runtime -- these reveal `bool` rather than `Literal[True]` or `Literal[False]`, which would be
|
||||
incorrect:
|
||||
|
||||
```py
|
||||
reveal_type(typing.Protocol is typing_extensions.Protocol) # revealed: bool
|
||||
reveal_type(typing.Protocol is not typing_extensions.Protocol) # revealed: bool
|
||||
```
|
||||
|
||||
## Calls to protocol classes
|
||||
|
||||
Neither `Protocol`, nor any protocol class, can be directly instantiated:
|
||||
|
@ -309,8 +319,7 @@ via `typing_extensions`.
|
|||
```py
|
||||
from typing_extensions import Protocol, get_protocol_members
|
||||
|
||||
# TODO: should not error
|
||||
class Foo(Protocol): # error: [invalid-base]
|
||||
class Foo(Protocol):
|
||||
x: int
|
||||
|
||||
@property
|
||||
|
@ -351,8 +360,7 @@ Certain special attributes and methods are not considered protocol members at ru
|
|||
not be considered protocol members by type checkers either:
|
||||
|
||||
```py
|
||||
# TODO: should not error
|
||||
class Lumberjack(Protocol): # error: [invalid-base]
|
||||
class Lumberjack(Protocol):
|
||||
__slots__ = ()
|
||||
__match_args__ = ()
|
||||
x: int
|
||||
|
|
|
@ -1949,8 +1949,16 @@ impl<'db> Type<'db> {
|
|||
| Type::WrapperDescriptor(..)
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::KnownInstance(..) => true,
|
||||
| Type::ModuleLiteral(..) => true,
|
||||
Type::KnownInstance(known_instance) => {
|
||||
// Nearly all `KnownInstance` types are singletons, but if a symbol could validly
|
||||
// originate from either `typing` or `typing_extensions` then this is not guaranteed.
|
||||
// E.g. `typing.Protocol` is equivalent to `typing_extensions.Protocol`, so both are treated
|
||||
// as inhabiting the type `KnownInstanceType::Protocol` in our model, but they are actually
|
||||
// distinct symbols at different memory addresses at runtime.
|
||||
!(known_instance.check_module(KnownModule::Typing)
|
||||
&& known_instance.check_module(KnownModule::TypingExtensions))
|
||||
}
|
||||
Type::Callable(_) => {
|
||||
// A callable type is never a singleton because for any given signature,
|
||||
// there could be any number of distinct objects that are all callable with that
|
||||
|
|
|
@ -2532,7 +2532,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
///
|
||||
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
|
||||
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
|
||||
fn check_module(self, module: KnownModule) -> bool {
|
||||
pub(super) fn check_module(self, module: KnownModule) -> bool {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::ClassVar
|
||||
|
@ -2545,7 +2545,6 @@ impl<'db> KnownInstanceType<'db> {
|
|||
| Self::Counter
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict
|
||||
| Self::Protocol
|
||||
| Self::Optional
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
|
@ -2553,6 +2552,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
| Self::Type
|
||||
| Self::Callable => module.is_typing(),
|
||||
Self::Annotated
|
||||
| Self::Protocol
|
||||
| Self::Literal
|
||||
| Self::LiteralString
|
||||
| Self::Never
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue