Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flake8-pyi] Skip Annotated and Literal for string-or-bytes-too-long (PYI053) #13020

Closed
wants to merge 7 commits into from
Closed
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
13 changes: 11 additions & 2 deletions crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import warnings
import typing_extensions
from typing_extensions import deprecated
from typing import Literal,Annotated,TypeAlias

def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
def f2(
Expand Down Expand Up @@ -67,5 +68,13 @@ def not_a_deprecated_function() -> None: ...

fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053

# see https://github.com/astral-sh/ruff/issues/12995
def foo(bar: typing.Literal["a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]):...
# See https://github.com/astral-sh/ruff/issues/12995
def foo(bar: Literal["a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]):... #OK

def foo(bar: Annotated[int,"aaaaaaaloooooooooooooongannnnnoooottttatiooooooooooooooon"]):... #OK

x: TypeAlias = Literal["fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooO"] #OK
y: TypeAlias = Annotated[int, "metadataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] #OK

def f(x:int) -> "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb":... #OK

1 change: 1 addition & 0 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
}
// Ex) Annotated[int, "Hello, world!"]
Some(typing::SubscriptKind::PEP593Annotation) => {
self.semantic.flags |= SemanticModelFlags::TYPING_ANNOTATED_PEP593;
// First argument is a type (including forward references); the
// rest are arbitrary Python objects.
if let Expr::Tuple(ast::ExprTuple {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,16 @@ pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, string: StringLike
return;
}

if semantic.in_annotation() {
// Ignore strings in typing annotations like `Literal["YouAreHere"]` or `Annotated[int, "YouAreHere"]`
//
// This does not ignore other instances of strings in annotations, such as return types
// for long classes wrapped in quotes:
// ```python
// def f(x:int) -> "StillChecksThisString"
// ```
// If it becomes desirable to skip annotations more broadly, replace the
// below with `if semantic.in_annotation()`.
if semantic.in_typing_literal() | semantic.in_typing_annotated_pep593() {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,168 +1,168 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI053.pyi:7:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
6 | def f2(
7 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
8 | ) -> None: ...
9 | def f3(
|
= help: Replace with `...`
PYI053.pyi:8:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
6 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
7 | def f2(
8 | x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
9 | ) -> None: ...
10 | def f3(
|
= help: Replace with `...`

ℹ Safe fix
4 4 |
5 5 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
6 6 | def f2(
7 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
7 |+ x: str = ..., # Error: PYI053
8 8 | ) -> None: ...
9 9 | def f3(
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
5 5 |
6 6 | def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
7 7 | def f2(
8 |- x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
8 |+ x: str = ..., # Error: PYI053
9 9 | ) -> None: ...
10 10 | def f3(
11 11 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK

PYI053.pyi:13:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:14:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
11 | ) -> None: ...
12 | def f4(
13 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
12 | ) -> None: ...
13 | def f4(
14 | x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
14 | ) -> None: ...
15 | def f5(
15 | ) -> None: ...
16 | def f5(
|
= help: Replace with `...`

ℹ Safe fix
10 10 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
11 11 | ) -> None: ...
12 12 | def f4(
13 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
13 |+ x: str = ..., # Error: PYI053
14 14 | ) -> None: ...
15 15 | def f5(
16 16 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
11 11 | x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
12 12 | ) -> None: ...
13 13 | def f4(
14 |- x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
14 |+ x: str = ..., # Error: PYI053
15 15 | ) -> None: ...
16 16 | def f5(
17 17 | x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK

PYI053.pyi:25:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:26:16: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
23 | ) -> None: ...
24 | def f8(
25 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
24 | ) -> None: ...
25 | def f8(
26 | x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
26 | ) -> None: ...
27 | ) -> None: ...
|
= help: Replace with `...`

ℹ Safe fix
22 22 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
23 23 | ) -> None: ...
24 24 | def f8(
25 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
25 |+ x: bytes = ..., # Error: PYI053
26 26 | ) -> None: ...
27 27 |
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
23 23 | x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
24 24 | ) -> None: ...
25 25 | def f8(
26 |- x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
26 |+ x: bytes = ..., # Error: PYI053
27 27 | ) -> None: ...
28 28 |
29 29 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK

PYI053.pyi:30:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:31:12: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 |
30 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
29 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
30 |
31 | bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
31 |
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
32 |
33 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`

ℹ Safe fix
27 27 |
28 28 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
29 29 |
30 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
30 |+bar: str = ... # Error: PYI053
31 31 |
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 33 |
28 28 |
29 29 | foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
30 30 |
31 |-bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
31 |+bar: str = ... # Error: PYI053
32 32 |
33 33 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
34 34 |

PYI053.pyi:34:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:35:14: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 |
34 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
33 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
34 |
35 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
35 |
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
36 |
37 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
|
= help: Replace with `...`

ℹ Safe fix
31 31 |
32 32 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
33 33 |
34 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
34 |+qux: bytes = ... # Error: PYI053
35 35 |
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 37 |
32 32 |
33 33 | baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
34 34 |
35 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
35 |+qux: bytes = ... # Error: PYI053
36 36 |
37 37 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
38 38 |

PYI053.pyi:38:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:39:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 |
38 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
37 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
38 |
39 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
39 |
40 | class Demo:
40 |
41 | class Demo:
|
= help: Replace with `...`

ℹ Safe fix
35 35 |
36 36 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
37 37 |
38 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
38 |+fbar: str = ... # Error: PYI053
39 39 |
40 40 | class Demo:
41 41 | """Docstrings are excluded from this rule. Some padding.""" # OK
36 36 |
37 37 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK
38 38 |
39 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
39 |+fbar: str = ... # Error: PYI053
40 40 |
41 41 | class Demo:
42 42 | """Docstrings are excluded from this rule. Some padding.""" # OK

PYI053.pyi:64:5: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:65:5: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
63 | @not_warnings_dot_deprecated(
64 | "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
64 | @not_warnings_dot_deprecated(
65 | "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
65 | )
66 | def not_a_deprecated_function() -> None: ...
66 | )
67 | def not_a_deprecated_function() -> None: ...
|
= help: Replace with `...`

ℹ Safe fix
61 61 | ) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
62 62 |
63 63 | @not_warnings_dot_deprecated(
64 |- "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
64 |+ ... # Error: PYI053
65 65 | )
66 66 | def not_a_deprecated_function() -> None: ...
67 67 |
62 62 | ) -> Callable[[Callable[[], None]], Callable[[], None]]: ...
63 63 |
64 64 | @not_warnings_dot_deprecated(
65 |- "Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
65 |+ ... # Error: PYI053
66 66 | )
67 67 | def not_a_deprecated_function() -> None: ...
68 68 |

PYI053.pyi:68:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
PYI053.pyi:69:13: PYI053 [*] String and bytes literals longer than 50 characters are not permitted
|
66 | def not_a_deprecated_function() -> None: ...
67 |
68 | fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
67 | def not_a_deprecated_function() -> None: ...
68 |
69 | fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053
69 |
70 | # see https://github.com/astral-sh/ruff/issues/12995
70 |
71 | # See https://github.com/astral-sh/ruff/issues/12995
|
= help: Replace with `...`

ℹ Safe fix
65 65 | )
66 66 | def not_a_deprecated_function() -> None: ...
67 67 |
68 |-fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
68 |+fbaz: str = ... # Error: PYI053
69 69 |
70 70 | # see https://github.com/astral-sh/ruff/issues/12995
71 71 | def foo(bar: typing.Literal["a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]):...
66 66 | )
67 67 | def not_a_deprecated_function() -> None: ...
68 68 |
69 |-fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
69 |+fbaz: str = ... # Error: PYI053
70 70 |
71 71 | # See https://github.com/astral-sh/ruff/issues/12995
72 72 | def foo(bar: Literal["a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]):... #OK
20 changes: 18 additions & 2 deletions crates/ruff_python_semantic/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1682,11 +1682,17 @@ impl<'a> SemanticModel<'a> {
self.flags.intersects(SemanticModelFlags::BOOLEAN_TEST)
}

/// Return `true` if the model is in a `typing::Literal` annotation.
/// Return `true` if the model is in a `typing.Literal` annotation.
pub const fn in_typing_literal(&self) -> bool {
self.flags.intersects(SemanticModelFlags::TYPING_LITERAL)
}

/// Return `true` if the model is in a `typing.Annotated` annotation.
pub const fn in_typing_annotated_pep593(&self) -> bool {
self.flags
.intersects(SemanticModelFlags::TYPING_ANNOTATED_PEP593)
}

/// Return `true` if the model is in a subscript expression.
pub const fn in_subscript(&self) -> bool {
self.flags.intersects(SemanticModelFlags::SUBSCRIPT)
Expand Down Expand Up @@ -2019,7 +2025,7 @@ bitflags! {
/// not used, only its truthiness.
const BOOLEAN_TEST = 1 << 9;

/// The model is in a `typing::Literal` annotation.
/// The model is in a `typing.Literal` annotation.
///
/// For example, the model could be visiting any of `"A"`, `"B"`, or `"C"` in:
/// ```python
Expand Down Expand Up @@ -2229,6 +2235,16 @@ bitflags! {
/// [PEP 257]: https://peps.python.org/pep-0257/#what-is-a-docstring
const ATTRIBUTE_DOCSTRING = 1 << 26;

/// The model is in a `typing.Annotated` annotation.
///
/// For example, the model could be visiting `int` or "A" in
/// ```python
/// def f(x: Annotated[int, "A"]): ...
/// ```
///
/// [PEP 593]: https://peps.python.org/pep-0593/
const TYPING_ANNOTATED_PEP593 = 1<<27;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof... but I have been trying to think of a better name for 10 minutes and I've got nothing 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a reasonable name given other variants although I leave this up to you to decide, maybe you come up with a better name in the morning ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside, it would be useful to provide a link to PEP 593 in the documentation above @dylwil3


/// The context is in any type annotation.
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();

Expand Down
Loading