mirror of https://github.com/astral-sh/ruff
[red-knot] Ban direct instantiation of generic protocols as well as non-generic ones (#17741)
This commit is contained in:
parent
18bac94226
commit
b6de01b9a5
|
|
@ -304,6 +304,11 @@ reveal_type(typing.Protocol is not typing_extensions.Protocol) # revealed: bool
|
||||||
|
|
||||||
Neither `Protocol`, nor any protocol class, can be directly instantiated:
|
Neither `Protocol`, nor any protocol class, can be directly instantiated:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import Protocol, reveal_type
|
from typing_extensions import Protocol, reveal_type
|
||||||
|
|
||||||
|
|
@ -315,6 +320,12 @@ class MyProtocol(Protocol):
|
||||||
|
|
||||||
# error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
# error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||||
reveal_type(MyProtocol()) # revealed: MyProtocol
|
reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||||
|
|
||||||
|
class GenericProtocol[T](Protocol):
|
||||||
|
x: T
|
||||||
|
|
||||||
|
# error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
|
||||||
|
reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
|
||||||
```
|
```
|
||||||
|
|
||||||
But a non-protocol class can be instantiated, even if it has `Protocol` in its MRO:
|
But a non-protocol class can be instantiated, even if it has `Protocol` in its MRO:
|
||||||
|
|
@ -323,6 +334,10 @@ But a non-protocol class can be instantiated, even if it has `Protocol` in its M
|
||||||
class SubclassOfMyProtocol(MyProtocol): ...
|
class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
|
||||||
reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||||
|
|
||||||
|
class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||||
|
|
||||||
|
reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
```
|
```
|
||||||
|
|
||||||
And as a corollary, `type[MyProtocol]` can also be called:
|
And as a corollary, `type[MyProtocol]` can also be called:
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,21 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/protocols.md
|
||||||
8 |
|
8 |
|
||||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
11 |
|
||||||
12 |
|
12 | class GenericProtocol[T](Protocol):
|
||||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
13 | x: T
|
||||||
14 | def f(x: type[MyProtocol]):
|
14 |
|
||||||
15 | reveal_type(x()) # revealed: MyProtocol
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
|
||||||
|
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
|
||||||
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
18 |
|
||||||
|
19 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||||
|
20 |
|
||||||
|
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||||
|
22 |
|
||||||
|
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
|
24 | def f(x: type[MyProtocol]):
|
||||||
|
25 | reveal_type(x()) # revealed: MyProtocol
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
@ -64,7 +74,8 @@ error: lint:call-non-callable: Cannot instantiate class `MyProtocol`
|
||||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||||
| ^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
| ^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
11 |
|
||||||
|
12 | class GenericProtocol[T](Protocol):
|
||||||
|
|
|
|
||||||
info: Protocol classes cannot be instantiated
|
info: Protocol classes cannot be instantiated
|
||||||
--> src/mdtest_snippet.py:6:7
|
--> src/mdtest_snippet.py:6:7
|
||||||
|
|
@ -85,32 +96,80 @@ info: revealed-type: Revealed type
|
||||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyProtocol`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyProtocol`
|
||||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
11 |
|
||||||
|
12 | class GenericProtocol[T](Protocol):
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error: lint:call-non-callable: Cannot instantiate class `GenericProtocol`
|
||||||
|
--> src/mdtest_snippet.py:16:13
|
||||||
|
|
|
||||||
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
|
||||||
|
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||||
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
|
|
||||||
|
info: Protocol classes cannot be instantiated
|
||||||
|
--> src/mdtest_snippet.py:12:7
|
||||||
|
|
|
||||||
|
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||||
|
11 |
|
||||||
|
12 | class GenericProtocol[T](Protocol):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol` declared as a protocol here
|
||||||
|
13 | x: T
|
||||||
|
|
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
info: revealed-type: Revealed type
|
info: revealed-type: Revealed type
|
||||||
--> src/mdtest_snippet.py:13:1
|
--> src/mdtest_snippet.py:16:1
|
||||||
|
|
|
|
||||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
|
||||||
12 |
|
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
|
||||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
|
||||||
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info: revealed-type: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:19:1
|
||||||
|
|
|
||||||
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
18 |
|
||||||
|
19 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
|
||||||
14 | def f(x: type[MyProtocol]):
|
20 |
|
||||||
15 | reveal_type(x()) # revealed: MyProtocol
|
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||||
|
|
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
info: revealed-type: Revealed type
|
info: revealed-type: Revealed type
|
||||||
--> src/mdtest_snippet.py:15:5
|
--> src/mdtest_snippet.py:23:1
|
||||||
|
|
|
|
||||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||||
14 | def f(x: type[MyProtocol]):
|
22 |
|
||||||
15 | reveal_type(x()) # revealed: MyProtocol
|
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
|
||||||
|
24 | def f(x: type[MyProtocol]):
|
||||||
|
25 | reveal_type(x()) # revealed: MyProtocol
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info: revealed-type: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:25:5
|
||||||
|
|
|
||||||
|
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
|
24 | def f(x: type[MyProtocol]):
|
||||||
|
25 | reveal_type(x()) # revealed: MyProtocol
|
||||||
| ^^^^^^^^^^^^^^^^ `MyProtocol`
|
| ^^^^^^^^^^^^^^^^ `MyProtocol`
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4555,18 +4555,23 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It might look odd here that we emit an error for class-literals but not `type[]` types.
|
// It might look odd here that we emit an error for class-literals and generic aliases but not
|
||||||
// But it's deliberate! The typing spec explicitly mandates that `type[]` types can be called
|
// `type[]` types. But it's deliberate! The typing spec explicitly mandates that `type[]` types
|
||||||
// even though class-literals cannot. This is because even though a protocol class `SomeProtocol`
|
// can be called even though class-literals cannot. This is because even though a protocol class
|
||||||
// is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of that protocol
|
// `SomeProtocol` is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of
|
||||||
// -- and indeed, according to the spec, type checkers must disallow abstract subclasses of the
|
// that protocol -- and indeed, according to the spec, type checkers must disallow abstract
|
||||||
// protocol to be passed to parameters that accept `type[SomeProtocol]`.
|
// subclasses of the protocol to be passed to parameters that accept `type[SomeProtocol]`.
|
||||||
// <https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols>.
|
// <https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols>.
|
||||||
if let Some(protocol_class) = callable_type
|
let possible_protocol_class = match callable_type {
|
||||||
.into_class_literal()
|
Type::ClassLiteral(class) => Some(class),
|
||||||
.and_then(|class| class.into_protocol_class(self.db()))
|
Type::GenericAlias(generic) => Some(generic.origin(self.db())),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(protocol) =
|
||||||
|
possible_protocol_class.and_then(|class| class.into_protocol_class(self.db()))
|
||||||
{
|
{
|
||||||
report_attempted_protocol_instantiation(&self.context, call_expression, protocol_class);
|
report_attempted_protocol_instantiation(&self.context, call_expression, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For class literals we model the entire class instantiation logic, so it is handled
|
// For class literals we model the entire class instantiation logic, so it is handled
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue