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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
pass

class VeryBad(
Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
# error: [invalid-generic-class]
# error: [invalid-generic-class]
Protocol[T1, T2, DefaultStrT, T3],
Generic[T1, T2, DefaultStrT, T3],
): ...
```
54 changes: 52 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,15 @@ T = TypeVar("T")
class Bar0(Protocol[T]):
x: T

# Note that this class definition *will* actually succeed at runtime,
# but is banned by the typing spec anyway
# error: [invalid-generic-class] "Cannot both inherit from subscripted `Protocol` and subscripted `Generic`"
class Bar1(Protocol[T], Generic[T]):
x: T

class Bar1Point5(Protocol, Generic[T]):
x: T

class Bar2[T](Protocol):
x: T

Expand Down Expand Up @@ -193,8 +199,8 @@ reveal_mro(Fine) # revealed: (<class 'Fine'>, typing.Protocol, typing.Generic,

class StillFine(Protocol, Generic[T], object): ...
class EvenThis[T](Protocol, object): ...
class OrThis(Protocol[T], Generic[T]): ...
class AndThis(Protocol[T], Generic[T], object): ...
class OrThis(Protocol, Generic[T]): ...
class AndThis(Protocol, Generic[T], object): ...
```

And multiple inheritance from a mix of protocol and non-protocol classes is fine as long as
Expand Down Expand Up @@ -282,6 +288,50 @@ static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta))
reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool
```

## Diagnostics and autofixes for `Protocol` classes defined in invalid ways

<!-- snapshot-diagnostics -->

```toml
[environment]
python-version = "3.12"
```

```py
from typing import Protocol, Generic, TypeVar

T = TypeVar("T")

class Foo(Protocol[T], Generic[T]): ... # error: [invalid-generic-class]

# fmt: off

# error: [invalid-generic-class]
class Bar(Protocol[
T,
], Generic[T]): ...

class Spam( # docs
# error: [invalid-generic-class]
Protocol[ # some comment
# another comment
T, # just love my comments
# very well documented code
], # important comma!
# and a newline...
Generic[ # look at this
# wow
T, # wow
# wowwwwwww
] # oof
# another newline?
): ...

# fmt: on

class Foo[T](Protocol[T]): ... # error: [invalid-generic-class]
```

## `typing.Protocol` versus `typing_extensions.Protocol`

`typing.Protocol` and its backport in `typing_extensions` should be treated as exactly equivalent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_type
27 | pass
28 |
29 | class VeryBad(
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
31 | Generic[T1, T2, DefaultStrT, T3],
32 | ): ...
30 | # error: [invalid-generic-class]
31 | # error: [invalid-generic-class]
32 | Protocol[T1, T2, DefaultStrT, T3],
33 | Generic[T1, T2, DefaultStrT, T3],
34 | ): ...
```

# Diagnostics
Expand Down Expand Up @@ -163,17 +165,42 @@ info: rule `invalid-generic-class` is enabled by default
```

```
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
--> src/mdtest_snippet.py:30:14
error[invalid-generic-class]: Cannot both inherit from subscripted `Protocol` and subscripted `Generic`
--> src/mdtest_snippet.py:32:5
|
30 | # error: [invalid-generic-class]
31 | # error: [invalid-generic-class]
32 | Protocol[T1, T2, DefaultStrT, T3],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33 | Generic[T1, T2, DefaultStrT, T3],
34 | ): ...
|
help: Remove the type parameters from the `Protocol` base
info: rule `invalid-generic-class` is enabled by default
29 | class VeryBad(
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
30 | # error: [invalid-generic-class]
31 | # error: [invalid-generic-class]
- Protocol[T1, T2, DefaultStrT, T3],
32 + Protocol,
33 | Generic[T1, T2, DefaultStrT, T3],
34 | ): ...
note: This is an unsafe fix and may change runtime behavior

```

```
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
--> src/mdtest_snippet.py:32:14
|
30 | # error: [invalid-generic-class]
31 | # error: [invalid-generic-class]
32 | Protocol[T1, T2, DefaultStrT, T3],
| ^^^^^^^^^^^^^^^^^^^^^^^
| |
| Type variables `T2` and `T3` do not have defaults
| Earlier TypeVar `T1` does
31 | Generic[T1, T2, DefaultStrT, T3],
32 | ): ...
33 | Generic[T1, T2, DefaultStrT, T3],
34 | ): ...
|
::: src/mdtest_snippet.py:3:1
|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---

---
mdtest name: protocols.md - Protocols - Diagnostics and autofixes for `Protocol` classes defined in invalid ways
mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
---

# Python source files

## mdtest_snippet.py

```
1 | from typing import Protocol, Generic, TypeVar
2 |
3 | T = TypeVar("T")
4 |
5 | class Foo(Protocol[T], Generic[T]): ... # error: [invalid-generic-class]
6 |
7 | # fmt: off
8 |
9 | # error: [invalid-generic-class]
10 | class Bar(Protocol[
11 | T,
12 | ], Generic[T]): ...
13 |
14 | class Spam( # docs
15 | # error: [invalid-generic-class]
16 | Protocol[ # some comment
17 | # another comment
18 | T, # just love my comments
19 | # very well documented code
20 | ], # important comma!
21 | # and a newline...
22 | Generic[ # look at this
23 | # wow
24 | T, # wow
25 | # wowwwwwww
26 | ] # oof
27 | # another newline?
28 | ): ...
29 |
30 | # fmt: on
31 |
32 | class Foo[T](Protocol[T]): ... # error: [invalid-generic-class]
```

# Diagnostics

```
error[invalid-generic-class]: Cannot both inherit from subscripted `Protocol` and subscripted `Generic`
--> src/mdtest_snippet.py:5:11
|
3 | T = TypeVar("T")
4 |
5 | class Foo(Protocol[T], Generic[T]): ... # error: [invalid-generic-class]
| ^^^^^^^^^^^
6 |
7 | # fmt: off
|
help: Remove the type parameters from the `Protocol` base
info: rule `invalid-generic-class` is enabled by default
2 |
3 | T = TypeVar("T")
4 |
- class Foo(Protocol[T], Generic[T]): ... # error: [invalid-generic-class]
5 + class Foo(Protocol, Generic[T]): ... # error: [invalid-generic-class]
6 |
7 | # fmt: off
8 |
note: This is an unsafe fix and may change runtime behavior

```

```
error[invalid-generic-class]: Cannot both inherit from subscripted `Protocol` and subscripted `Generic`
--> src/mdtest_snippet.py:10:11
|
9 | # error: [invalid-generic-class]
10 | class Bar(Protocol[
| ___________^
11 | | T,
12 | | ], Generic[T]): ...
| |_^
13 |
14 | class Spam( # docs
|
help: Remove the type parameters from the `Protocol` base
info: rule `invalid-generic-class` is enabled by default
7 | # fmt: off
8 |
9 | # error: [invalid-generic-class]
- class Bar(Protocol[
- T,
- ], Generic[T]): ...
10 + class Bar(Protocol, Generic[T]): ...
11 |
12 | class Spam( # docs
13 | # error: [invalid-generic-class]
note: This is an unsafe fix and may change runtime behavior

```

```
error[invalid-generic-class]: Cannot both inherit from subscripted `Protocol` and subscripted `Generic`
--> src/mdtest_snippet.py:16:3
|
14 | class Spam( # docs
15 | # error: [invalid-generic-class]
16 | / Protocol[ # some comment
17 | | # another comment
18 | | T, # just love my comments
19 | | # very well documented code
20 | | ], # important comma!
| |_^
21 | # and a newline...
22 | Generic[ # look at this
|
help: Remove the type parameters from the `Protocol` base
info: rule `invalid-generic-class` is enabled by default
13 |
14 | class Spam( # docs
15 | # error: [invalid-generic-class]
- Protocol[ # some comment
- # another comment
- T, # just love my comments
- # very well documented code
- ], # important comma!
16 + Protocol, # important comma!
17 | # and a newline...
18 | Generic[ # look at this
19 | # wow
note: This is an unsafe fix and may change runtime behavior

```

```
error[invalid-generic-class]: Cannot both inherit from subscripted `Protocol` and use PEP 695 type variables
--> src/mdtest_snippet.py:32:14
|
30 | # fmt: on
31 |
32 | class Foo[T](Protocol[T]): ... # error: [invalid-generic-class]
| ^^^^^^^^^^^
|
help: Remove the type parameters from the `Protocol` base
info: rule `invalid-generic-class` is enabled by default
29 |
30 | # fmt: on
31 |
- class Foo[T](Protocol[T]): ... # error: [invalid-generic-class]
32 + class Foo[T](Protocol): ... # error: [invalid-generic-class]
note: This is an unsafe fix and may change runtime behavior

```
Loading