[pycodestyle] Avoid false positives and negatives related to type parameter default syntax (E225, E251) (#15214)

This commit is contained in:
InSync 2025-01-01 17:28:25 +07:00 committed by GitHub
parent 79682a28b8
commit af95f6b577
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 34 additions and 11 deletions

View file

@ -183,3 +183,8 @@ if i == -1:
if a == 1: if a == 1:
print(a) print(a)
# No E225: `=` is not an operator in this case
def _[T: str=None](): ...
def _(t: str=None): ...

View file

@ -75,3 +75,7 @@ def pep_696_good[A = int, B: object = str, C:object = memoryview]():
class PEP696Good[A = int, B: object = str, C:object = memoryview]: class PEP696Good[A = int, B: object = str, C:object = memoryview]:
def pep_696_good_method[A = int, B: object = str, C:object = memoryview](self): def pep_696_good_method[A = int, B: object = str, C:object = memoryview](self):
pass pass
# https://github.com/astral-sh/ruff/issues/15202
type Coro[T: object = Any] = Coroutine[None, None, T]

View file

@ -5,7 +5,7 @@ use ruff_text_size::Ranged;
use crate::checkers::logical_lines::LogicalLinesContext; use crate::checkers::logical_lines::LogicalLinesContext;
use crate::rules::pycodestyle::helpers::is_non_logical_token; use crate::rules::pycodestyle::helpers::is_non_logical_token;
use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; use crate::rules::pycodestyle::rules::logical_lines::{DefinitionState, LogicalLine};
/// ## What it does /// ## What it does
/// Checks for missing whitespace around all operators. /// Checks for missing whitespace around all operators.
@ -146,6 +146,7 @@ pub(crate) fn missing_whitespace_around_operator(
line: &LogicalLine, line: &LogicalLine,
context: &mut LogicalLinesContext, context: &mut LogicalLinesContext,
) { ) {
let mut definition_state = DefinitionState::from_tokens(line.tokens());
let mut tokens = line.tokens().iter().peekable(); let mut tokens = line.tokens().iter().peekable();
let first_token = tokens let first_token = tokens
.by_ref() .by_ref()
@ -162,6 +163,8 @@ pub(crate) fn missing_whitespace_around_operator(
while let Some(token) = tokens.next() { while let Some(token) = tokens.next() {
let kind = token.kind(); let kind = token.kind();
definition_state.visit_token_kind(kind);
if is_non_logical_token(kind) { if is_non_logical_token(kind) {
continue; continue;
} }
@ -174,8 +177,11 @@ pub(crate) fn missing_whitespace_around_operator(
_ => {} _ => {}
}; };
let needs_space = if kind == TokenKind::Equal && (parens > 0 || fstrings > 0) { let needs_space = if kind == TokenKind::Equal
&& (parens > 0 || fstrings > 0 || definition_state.in_type_params())
{
// Allow keyword args, defaults: foo(bar=None) and f-strings: f'{foo=}' // Allow keyword args, defaults: foo(bar=None) and f-strings: f'{foo=}'
// Also ignore `foo[T=int]`, which is handled by E251.
NeedsSpace::No NeedsSpace::No
} else if kind == TokenKind::Slash { } else if kind == TokenKind::Slash {
// Tolerate the "/" operator in function definition // Tolerate the "/" operator in function definition

View file

@ -480,7 +480,8 @@ struct Line {
enum DefinitionState { enum DefinitionState {
InClass(TypeParamsState), InClass(TypeParamsState),
InFunction(TypeParamsState), InFunction(TypeParamsState),
NotInClassOrFunction, InTypeAlias(TypeParamsState),
NotInDefinition,
} }
impl DefinitionState { impl DefinitionState {
@ -494,11 +495,12 @@ impl DefinitionState {
TokenKind::Async if matches!(token_kinds.next(), Some(TokenKind::Def)) => { TokenKind::Async if matches!(token_kinds.next(), Some(TokenKind::Def)) => {
Self::InFunction(TypeParamsState::default()) Self::InFunction(TypeParamsState::default())
} }
_ => Self::NotInClassOrFunction, TokenKind::Type => Self::InTypeAlias(TypeParamsState::default()),
_ => Self::NotInDefinition,
}; };
return state; return state;
} }
Self::NotInClassOrFunction Self::NotInDefinition
} }
const fn in_function_definition(self) -> bool { const fn in_function_definition(self) -> bool {
@ -507,8 +509,10 @@ impl DefinitionState {
const fn type_params_state(self) -> Option<TypeParamsState> { const fn type_params_state(self) -> Option<TypeParamsState> {
match self { match self {
Self::InClass(state) | Self::InFunction(state) => Some(state), Self::InClass(state) | Self::InFunction(state) | Self::InTypeAlias(state) => {
Self::NotInClassOrFunction => None, Some(state)
}
Self::NotInDefinition => None,
} }
} }
@ -521,10 +525,10 @@ impl DefinitionState {
fn visit_token_kind(&mut self, token_kind: TokenKind) { fn visit_token_kind(&mut self, token_kind: TokenKind) {
let type_params_state_mut = match self { let type_params_state_mut = match self {
Self::InClass(type_params_state) | Self::InFunction(type_params_state) => { Self::InClass(type_params_state)
type_params_state | Self::InFunction(type_params_state)
} | Self::InTypeAlias(type_params_state) => type_params_state,
Self::NotInClassOrFunction => return, Self::NotInDefinition => return,
}; };
match token_kind { match token_kind {
TokenKind::Lpar if type_params_state_mut.before_type_params() => { TokenKind::Lpar if type_params_state_mut.before_type_params() => {

View file

@ -186,3 +186,5 @@ E22.py:184:5: E221 [*] Multiple spaces before operator
184 |-if a == 1: 184 |-if a == 1:
184 |+if a == 1: 184 |+if a == 1:
185 185 | print(a) 185 185 | print(a)
186 186 |
187 187 |

View file

@ -123,3 +123,5 @@ E22.py:184:9: E222 [*] Multiple spaces after operator
184 |-if a == 1: 184 |-if a == 1:
184 |+if a == 1: 184 |+if a == 1:
185 185 | print(a) 185 185 | print(a)
186 186 |
187 187 |