Skip to content

Commit af95f6b

Browse files
[pycodestyle] Avoid false positives and negatives related to type parameter default syntax (E225, E251) (astral-sh#15214)
1 parent 79682a2 commit af95f6b

File tree

6 files changed

+34
-11
lines changed

6 files changed

+34
-11
lines changed

crates/ruff_linter/resources/test/fixtures/pycodestyle/E22.py

+5
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,8 @@ def squares(n):
183183

184184
if a == 1:
185185
print(a)
186+
187+
188+
# No E225: `=` is not an operator in this case
189+
def _[T: str=None](): ...
190+
def _(t: str=None): ...

crates/ruff_linter/resources/test/fixtures/pycodestyle/E25.py

+4
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ def pep_696_good[A = int, B: object = str, C:object = memoryview]():
7575
class PEP696Good[A = int, B: object = str, C:object = memoryview]:
7676
def pep_696_good_method[A = int, B: object = str, C:object = memoryview](self):
7777
pass
78+
79+
80+
# https://github.com/astral-sh/ruff/issues/15202
81+
type Coro[T: object = Any] = Coroutine[None, None, T]

crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace_around_operator.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
55

66
use crate::checkers::logical_lines::LogicalLinesContext;
77
use crate::rules::pycodestyle::helpers::is_non_logical_token;
8-
use crate::rules::pycodestyle::rules::logical_lines::LogicalLine;
8+
use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine};
99

1010
/// ## What it does
1111
/// Checks for missing whitespace around all operators.
@@ -146,6 +146,7 @@ pub(crate) fn missing_whitespace_around_operator(
146146
line: &LogicalLine,
147147
context: &mut LogicalLinesContext,
148148
) {
149+
let mut definition_state = DefinitionState::from_tokens(line.tokens());
149150
let mut tokens = line.tokens().iter().peekable();
150151
let first_token = tokens
151152
.by_ref()
@@ -162,6 +163,8 @@ pub(crate) fn missing_whitespace_around_operator(
162163
while let Some(token) = tokens.next() {
163164
let kind = token.kind();
164165

166+
definition_state.visit_token_kind(kind);
167+
165168
if is_non_logical_token(kind) {
166169
continue;
167170
}
@@ -174,8 +177,11 @@ pub(crate) fn missing_whitespace_around_operator(
174177
_ => {}
175178
};
176179

177-
let needs_space = if kind == TokenKind::Equal && (parens > 0 || fstrings > 0) {
180+
let needs_space = if kind == TokenKind::Equal
181+
&& (parens > 0 || fstrings > 0 || definition_state.in_type_params())
182+
{
178183
// Allow keyword args, defaults: foo(bar=None) and f-strings: f'{foo=}'
184+
// Also ignore `foo[T=int]`, which is handled by E251.
179185
NeedsSpace::No
180186
} else if kind == TokenKind::Slash {
181187
// Tolerate the "/" operator in function definition

crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/mod.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,8 @@ struct Line {
480480
enum DefinitionState {
481481
InClass(TypeParamsState),
482482
InFunction(TypeParamsState),
483-
NotInClassOrFunction,
483+
InTypeAlias(TypeParamsState),
484+
NotInDefinition,
484485
}
485486

486487
impl DefinitionState {
@@ -494,11 +495,12 @@ impl DefinitionState {
494495
TokenKind::Async if matches!(token_kinds.next(), Some(TokenKind::Def)) => {
495496
Self::InFunction(TypeParamsState::default())
496497
}
497-
_ => Self::NotInClassOrFunction,
498+
TokenKind::Type => Self::InTypeAlias(TypeParamsState::default()),
499+
_ => Self::NotInDefinition,
498500
};
499501
return state;
500502
}
501-
Self::NotInClassOrFunction
503+
Self::NotInDefinition
502504
}
503505

504506
const fn in_function_definition(self) -> bool {
@@ -507,8 +509,10 @@ impl DefinitionState {
507509

508510
const fn type_params_state(self) -> Option<TypeParamsState> {
509511
match self {
510-
Self::InClass(state) | Self::InFunction(state) => Some(state),
511-
Self::NotInClassOrFunction => None,
512+
Self::InClass(state) | Self::InFunction(state) | Self::InTypeAlias(state) => {
513+
Some(state)
514+
}
515+
Self::NotInDefinition => None,
512516
}
513517
}
514518

@@ -521,10 +525,10 @@ impl DefinitionState {
521525

522526
fn visit_token_kind(&mut self, token_kind: TokenKind) {
523527
let type_params_state_mut = match self {
524-
Self::InClass(type_params_state) | Self::InFunction(type_params_state) => {
525-
type_params_state
526-
}
527-
Self::NotInClassOrFunction => return,
528+
Self::InClass(type_params_state)
529+
| Self::InFunction(type_params_state)
530+
| Self::InTypeAlias(type_params_state) => type_params_state,
531+
Self::NotInDefinition => return,
528532
};
529533
match token_kind {
530534
TokenKind::Lpar if type_params_state_mut.before_type_params() => {

crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E221_E22.py.snap

+2
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,5 @@ E22.py:184:5: E221 [*] Multiple spaces before operator
186186
184 |-if a == 1:
187187
184 |+if a == 1:
188188
185 185 | print(a)
189+
186 186 |
190+
187 187 |

crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E222_E22.py.snap

+2
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,5 @@ E22.py:184:9: E222 [*] Multiple spaces after operator
123123
184 |-if a == 1:
124124
184 |+if a == 1:
125125
185 185 | print(a)
126+
186 186 |
127+
187 187 |

0 commit comments

Comments
 (0)