diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.py b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.py index 585e38b4d1..a0eeb627d0 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.py @@ -70,3 +70,11 @@ class FakeEnum10(enum.Enum): A = ... B = ... # PIE796 C = ... # PIE796 + + +from typing import cast + +class FakeEnum11(enum.Enum): + A = cast(SomeType, ...) + B = cast(SomeType, ...) # PIE796 + C = cast(SomeType, ...) # PIE796 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi index ccfb75a284..d3b77adf2e 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE796.pyi @@ -5,3 +5,11 @@ class FakeEnum1(enum.Enum): A = ... B = ... C = ... + + +from typing import cast + +class FakeEnum2(enum.Enum): + A = cast(SomeType, ...) + B = cast(SomeType, ...) + C = cast(SomeType, ...) diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs index 66d488c2b9..d74fb09ca9 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/non_unique_enums.rs @@ -4,7 +4,7 @@ use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::comparable::ComparableExpr; -use ruff_python_ast::{self as ast, Expr, PySourceType, Stmt}; +use ruff_python_ast::{self as ast, Expr, ExprCall, Stmt}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -55,13 +55,14 @@ impl Violation for NonUniqueEnums { /// PIE796 pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stmt]) { + let semantic = checker.semantic(); + let Stmt::ClassDef(parent) = parent else { return; }; if !parent.bases().iter().any(|expr| { - checker - .semantic() + semantic .resolve_qualified_name(expr) .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["enum", "Enum"])) }) { @@ -84,14 +85,12 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm } } - let comparable = ComparableExpr::from(value); - - if checker.source_type == PySourceType::Stub - && comparable == ComparableExpr::EllipsisLiteral - { + if checker.source_type.is_stub() && member_has_unknown_value(checker, value) { continue; } + let comparable = ComparableExpr::from(value); + if !seen_targets.insert(comparable) { let diagnostic = Diagnostic::new( NonUniqueEnums { @@ -103,3 +102,27 @@ pub(crate) fn non_unique_enums(checker: &mut Checker, parent: &Stmt, body: &[Stm } } } + +/// Whether the value is a bare ellipsis literal (`A = ...`) +/// or a casted one (`A = cast(SomeType, ...)`). +fn member_has_unknown_value(checker: &Checker, expr: &Expr) -> bool { + match expr { + Expr::EllipsisLiteral(_) => true, + + Expr::Call(ExprCall { + func, arguments, .. + }) => { + if !checker.semantic().match_typing_expr(func, "cast") { + return false; + } + + if !arguments.keywords.is_empty() { + return false; + } + + matches!(arguments.args.as_ref(), [_, Expr::EllipsisLiteral(_)]) + } + + _ => false, + } +} diff --git a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE796_PIE796.py.snap b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE796_PIE796.py.snap index afb8aefef0..cad6da6d9d 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE796_PIE796.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE796_PIE796.py.snap @@ -74,3 +74,20 @@ PIE796.py:72:5: PIE796 Enum contains duplicate value: `...` 72 | C = ... # PIE796 | ^^^^^^^ PIE796 | + +PIE796.py:79:5: PIE796 Enum contains duplicate value: `cast(SomeType, ...)` + | +77 | class FakeEnum11(enum.Enum): +78 | A = cast(SomeType, ...) +79 | B = cast(SomeType, ...) # PIE796 + | ^^^^^^^^^^^^^^^^^^^^^^^ PIE796 +80 | C = cast(SomeType, ...) # PIE796 + | + +PIE796.py:80:5: PIE796 Enum contains duplicate value: `cast(SomeType, ...)` + | +78 | A = cast(SomeType, ...) +79 | B = cast(SomeType, ...) # PIE796 +80 | C = cast(SomeType, ...) # PIE796 + | ^^^^^^^^^^^^^^^^^^^^^^^ PIE796 + |