From 7a05d80fabac72ac3a176fcf8e262d66a8d4340b Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 13:58:06 -0500 Subject: [PATCH 01/16] Implement PYI0303: Unnecessary literal union --- .../test/fixtures/flake8_pyi/PYI030.py | 24 ++ .../test/fixtures/flake8_pyi/PYI030.pyi | 70 +++++ crates/ruff/src/checkers/ast/mod.rs | 4 + crates/ruff/src/codes.rs | 1 + crates/ruff/src/rules/flake8_pyi/mod.rs | 2 + crates/ruff/src/rules/flake8_pyi/rules/mod.rs | 2 + .../rules/unnecessary_literal_union.rs | 92 +++++++ ...__flake8_pyi__tests__PYI030_PYI030.py.snap | 4 + ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 247 ++++++++++++++++++ 9 files changed, 446 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.py create mode 100644 crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi create mode 100644 crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.py.snap create mode 100644 crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.py b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.py new file mode 100644 index 0000000000000..cc199f1480479 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.py @@ -0,0 +1,24 @@ +from typing import Literal +# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi. +# Note that this rule could be applied here in the future. + +field1: Literal[1] # OK +field2: Literal[1] | Literal[2] # OK + +def func1(arg1: Literal[1] | Literal[2]): # OK + print(arg1) + + +def func2() -> Literal[1] | Literal[2]: # OK + return "my Literal[1]ing" + + +field3: Literal[1] | Literal[2] | str # OK +field4: str | Literal[1] | Literal[2] # OK +field5: Literal[1] | str | Literal[2] # OK +field6: Literal[1] | bool | Literal[2] | str # OK +field7 = Literal[1] | Literal[2] # OK +field8: Literal[1] | (Literal[2] | str) # OK +field9: Literal[1] | (Literal[2] | str) # OK +field10: (Literal[1] | str) | Literal[2] # OK +field11: dict[Literal[1] | Literal[2], str] # OK diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi new file mode 100644 index 0000000000000..1588b34d37b96 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -0,0 +1,70 @@ +import typing +from typing import Literal + +# Shouldn't affect non-union field types. +field1: Literal[1] # OK + +# Should emit for duplicate field types. +field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should emit for union types in arguments. +def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + print(arg1) + + +# Should emit for unions in return types. +def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + return "my Literal[1]ing" + + +# Should emit in longer unions, even if not directly adjacent. +field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should emit for non-type unions. +field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should emit for parenthesized unions. +field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should handle user parentheses when fixing. +field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should emit for union in generic parent type. +field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + +# Should emit for unions with more than two cases +field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + +# Should emit for unions with more than two cases, even if not directly adjacent +field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + +# Should emit for unions with mixed literal internal types +field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". + +# Shouldn't emit for duplicate field types with same value; covered by Y016 +field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" + +# Shouldn't emit if in new parent type +field17: Literal[1] | dict[Literal[2], str] # OK + +# Shouldn't emit if not in a union parent +field18: dict[Literal[1], Literal[2]] # OK + +# Should respect name of literal type used +field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]". + +# Should handle newlines +field20: typing.Union[ + Literal[ + 1 # test + ], + Literal[2], +] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]". + +# Should handle multiple unions with multiple members +field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]". \ No newline at end of file diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 7767129c5f5bd..30fad458626a3 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3148,6 +3148,10 @@ where { flake8_pyi::rules::duplicate_union_member(self, expr); } + + if self.enabled(Rule::UnnecessaryLiteralUnion) { + flake8_pyi::rules::unnecessary_literal_union(self, expr); + } } } Expr::UnaryOp(ast::ExprUnaryOp { diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 280007374b7fa..dfe691950c99b 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -630,6 +630,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple), (Flake8Pyi, "025") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnaliasedCollectionsAbcSetImport), (Flake8Pyi, "029") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StrOrReprDefinedInStub), + (Flake8Pyi, "030") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryLiteralUnion), (Flake8Pyi, "032") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::AnyEqNeAnnotation), (Flake8Pyi, "033") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TypeCommentInStub), (Flake8Pyi, "034") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NonSelfReturnType), diff --git a/crates/ruff/src/rules/flake8_pyi/mod.rs b/crates/ruff/src/rules/flake8_pyi/mod.rs index 5575a7ced52dd..dda789c12acd8 100644 --- a/crates/ruff/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/mod.rs @@ -52,6 +52,8 @@ mod tests { #[test_case(Rule::UnassignedSpecialVariableInStub, Path::new("PYI035.pyi"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.pyi"))] + #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.py"))] + #[test_case(Rule::UnnecessaryLiteralUnion, Path::new("PYI030.pyi"))] #[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.py"))] #[test_case(Rule::StubBodyMultipleStatements, Path::new("PYI048.pyi"))] #[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.py"))] diff --git a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs index 612055db37811..ce083d31c3e53 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/mod.rs @@ -22,6 +22,7 @@ pub(crate) use stub_body_multiple_statements::*; pub(crate) use type_alias_naming::*; pub(crate) use type_comment_in_stub::*; pub(crate) use unaliased_collections_abc_set_import::*; +pub(crate) use unnecessary_literal_union::*; pub(crate) use unrecognized_platform::*; pub(crate) use unrecognized_version_info::*; @@ -49,5 +50,6 @@ mod stub_body_multiple_statements; mod type_alias_naming; mod type_comment_in_stub; mod unaliased_collections_abc_set_import; +mod unnecessary_literal_union; mod unrecognized_platform; mod unrecognized_version_info; diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs new file mode 100644 index 0000000000000..cba7648de3781 --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -0,0 +1,92 @@ +use ruff_python_semantic::SemanticModel; +use rustpython_parser::ast::{self, Expr, Operator, Ranged}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for the presence of multiple literal types in a union. +/// +/// ## Why is this bad? +/// Literal types accept multiple arguments and it is clearer to specify them as a single literal. +/// +/// ## Example +/// ```python +/// field: Literal[1] | Literal[2] +/// ``` +/// +/// Use instead: +/// ```python +/// field: Literal[1, 2] +/// ``` +#[violation] +pub struct UnnecessaryLiteralUnion { + members: Vec, +} + +impl Violation for UnnecessaryLiteralUnion { + #[derive_message_formats] + fn message(&self) -> String { + format!( + "Multiple literal members in a union. Use a single literal, e.g. `Literal[{}]`", + self.members.join(", ") + ) + } +} + +/// PYI030 +pub(crate) fn unnecessary_literal_union(checker: &mut Checker, expr: &Expr) { + let mut literal_members = Vec::new(); + collect_literal_members(&mut literal_members, checker.semantic(), expr); + + // Raise a violation if more than one + if literal_members.len() > 1 { + let diagnostic = Diagnostic::new( + UnnecessaryLiteralUnion { + members: literal_members + .into_iter() + .map(|m| checker.locator.slice(m.range()).to_string()) + .collect(), + }, + expr.range(), + ); + + checker.diagnostics.push(diagnostic); + } +} + +/// Collect literal expressions from a union. +fn collect_literal_members<'a>( + literal_members: &mut Vec<&'a Expr>, + model: &SemanticModel, + expr: &'a Expr, +) { + // The union data structure usually looks like this: + // a | b | c -> (a | b) | c + // + // However, parenthesized expressions can coerce it into any structure: + // a | (b | c) + // + // So we have to traverse both branches in order (left, then right), to report members + // in the order they appear in the source code. + if let Expr::BinOp(ast::ExprBinOp { + op: Operator::BitOr, + left, + right, + range: _, + }) = expr + { + // Traverse left subtree, then the right subtree, propagating the previous node. + collect_literal_members(literal_members, model, left); + collect_literal_members(literal_members, model, right); + } + + // If it's a literal expression add it to the members + if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { + if model.match_typing_expr(value, "Literal") { + literal_members.push(slice); + } + } +} diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.py.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.py.snap new file mode 100644 index 0000000000000..d1aa2e9116558 --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +--- + diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap new file mode 100644 index 0000000000000..7427304a9452f --- /dev/null +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -0,0 +1,247 @@ +--- +source: crates/ruff/src/rules/flake8_pyi/mod.rs +--- +PYI030.pyi:8:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | + 7 | # Should emit for duplicate field types. + 8 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 + 9 | +10 | # Should emit for union types in arguments. + | + +PYI030.pyi:11:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +10 | # Should emit for union types in arguments. +11 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +12 | print(arg1) + | + +PYI030.pyi:16:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +15 | # Should emit for unions in return types. +16 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +17 | return "my Literal[1]ing" + | + +PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +20 | # Should emit in longer unions, even if not directly adjacent. +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | + +PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +20 | # Should emit in longer unions, even if not directly adjacent. +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | + +PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +20 | # Should emit in longer unions, even if not directly adjacent. +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | + +PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | + +PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +25 | +26 | # Should emit for non-type unions. + | + +PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +25 | +26 | # Should emit for non-type unions. + | + +PYI030.pyi:27:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +26 | # Should emit for non-type unions. +27 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +28 | +29 | # Should emit for parenthesized unions. + | + +PYI030.pyi:30:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +29 | # Should emit for parenthesized unions. +30 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +31 | +32 | # Should handle user parentheses when fixing. + | + +PYI030.pyi:33:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +32 | # Should handle user parentheses when fixing. +33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | + +PYI030.pyi:34:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +32 | # Should handle user parentheses when fixing. +33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +35 | +36 | # Should emit for union in generic parent type. + | + +PYI030.pyi:37:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +36 | # Should emit for union in generic parent type. +37 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +38 | +39 | # Should emit for unions with more than two cases + | + +PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` + | +39 | # Should emit for unions with more than two cases +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + | + +PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +39 | # Should emit for unions with more than two cases +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + | + +PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` + | +39 | # Should emit for unions with more than two cases +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +42 | +43 | # Should emit for unions with more than two cases, even if not directly adjacent + | + +PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` + | +39 | # Should emit for unions with more than two cases +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +42 | +43 | # Should emit for unions with more than two cases, even if not directly adjacent + | + +PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +39 | # Should emit for unions with more than two cases +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +42 | +43 | # Should emit for unions with more than two cases, even if not directly adjacent + | + +PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` + | +43 | # Should emit for unions with more than two cases, even if not directly adjacent +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +45 | +46 | # Should emit for unions with mixed literal internal types + | + +PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +43 | # Should emit for unions with more than two cases, even if not directly adjacent +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +45 | +46 | # Should emit for unions with mixed literal internal types + | + +PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +43 | # Should emit for unions with more than two cases, even if not directly adjacent +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +45 | +46 | # Should emit for unions with mixed literal internal types + | + +PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` + | +46 | # Should emit for unions with mixed literal internal types +47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +48 | +49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 + | + +PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` + | +46 | # Should emit for unions with mixed literal internal types +47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +48 | +49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 + | + +PYI030.pyi:50:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]` + | +49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 +50 | field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +51 | +52 | # Shouldn't emit if in new parent type + | + +PYI030.pyi:59:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +58 | # Should respect name of literal type used +59 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +60 | +61 | # Should handle newlines + | + +PYI030.pyi:70:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` + | +69 | # Should handle multiple unions with multiple members +70 | field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]". + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 + | + + From fe2dd78741238a8aa786071aeb320c5c397ce629 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 14:03:13 -0500 Subject: [PATCH 02/16] Fix trailing quote on comments --- .../test/fixtures/flake8_pyi/PYI030.pyi | 38 ++++---- ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 94 +++++++++---------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index 1588b34d37b96..a442476b0f109 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -5,46 +5,46 @@ from typing import Literal field1: Literal[1] # OK # Should emit for duplicate field types. -field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should emit for union types in arguments. -def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. print(arg1) # Should emit for unions in return types. -def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. return "my Literal[1]ing" # Should emit in longer unions, even if not directly adjacent. -field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should emit for non-type unions. -field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should emit for parenthesized unions. -field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should handle user parentheses when fixing. -field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should emit for union in generic parent type. -field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. # Should emit for unions with more than two cases -field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". -field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. # Should emit for unions with more than two cases, even if not directly adjacent -field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. # Should emit for unions with mixed literal internal types -field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". +field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. # Shouldn't emit for duplicate field types with same value; covered by Y016 field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" @@ -56,7 +56,7 @@ field17: Literal[1] | dict[Literal[2], str] # OK field18: dict[Literal[1], Literal[2]] # OK # Should respect name of literal type used -field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]". +field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. # Should handle newlines field20: typing.Union[ @@ -64,7 +64,7 @@ field20: typing.Union[ 1 # test ], Literal[2], -] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]". +] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]`. # Should handle multiple unions with multiple members -field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]". \ No newline at end of file +field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. \ No newline at end of file diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 7427304a9452f..9efb3b894fd1f 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -4,7 +4,7 @@ source: crates/ruff/src/rules/flake8_pyi/mod.rs PYI030.pyi:8:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 7 | # Should emit for duplicate field types. - 8 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". + 8 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 9 | 10 | # Should emit for union types in arguments. @@ -13,7 +13,7 @@ PYI030.pyi:8:9: PYI030 Multiple literal members in a union. Use a single literal PYI030.pyi:11:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 10 | # Should emit for union types in arguments. -11 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +11 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 12 | print(arg1) | @@ -21,7 +21,7 @@ PYI030.pyi:11:17: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:16:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 15 | # Should emit for unions in return types. -16 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +16 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 17 | return "my Literal[1]ing" | @@ -29,45 +29,45 @@ PYI030.pyi:16:16: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 25 | 26 | # Should emit for non-type unions. @@ -75,9 +75,9 @@ PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 25 | 26 | # Should emit for non-type unions. @@ -86,7 +86,7 @@ PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:27:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 26 | # Should emit for non-type unions. -27 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +27 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 28 | 29 | # Should emit for parenthesized unions. @@ -95,7 +95,7 @@ PYI030.pyi:27:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:30:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 29 | # Should emit for parenthesized unions. -30 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +30 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 31 | 32 | # Should handle user parentheses when fixing. @@ -104,16 +104,16 @@ PYI030.pyi:30:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:33:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 32 | # Should handle user parentheses when fixing. -33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | PYI030.pyi:34:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 32 | # Should handle user parentheses when fixing. -33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". -34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 35 | 36 | # Should emit for union in generic parent type. @@ -122,7 +122,7 @@ PYI030.pyi:34:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:37:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 36 | # Should emit for union in generic parent type. -37 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]". +37 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 38 | 39 | # Should emit for unions with more than two cases @@ -131,24 +131,24 @@ PYI030.pyi:37:15: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 42 | 43 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -157,8 +157,8 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 42 | 43 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -167,8 +167,8 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]". +40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 42 | 43 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -177,7 +177,7 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 45 | 46 | # Should emit for unions with mixed literal internal types @@ -186,7 +186,7 @@ PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 45 | 46 | # Should emit for unions with mixed literal internal types @@ -195,7 +195,7 @@ PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]". +44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 45 | 46 | # Should emit for unions with mixed literal internal types @@ -204,7 +204,7 @@ PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` | 46 | # Should emit for unions with mixed literal internal types -47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". +47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 48 | 49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 @@ -213,7 +213,7 @@ PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` | 46 | # Should emit for unions with mixed literal internal types -47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]". +47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 48 | 49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 @@ -231,7 +231,7 @@ PYI030.pyi:50:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:59:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 58 | # Should respect name of literal type used -59 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]". +59 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 60 | 61 | # Should handle newlines @@ -240,7 +240,7 @@ PYI030.pyi:59:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:70:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 69 | # Should handle multiple unions with multiple members -70 | field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]". +70 | field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From ca2945747dfbe8d5514f7bca7f7d59bed6fee6c3 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 14:13:56 -0500 Subject: [PATCH 03/16] Add test coverage for `typing_extensions.Literal` and `typing.Union` --- .../test/fixtures/flake8_pyi/PYI030.pyi | 11 +- ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 279 +++++++++--------- 2 files changed, 153 insertions(+), 137 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index a442476b0f109..aea26e3370ba2 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -1,4 +1,5 @@ import typing +import typing_extensions from typing import Literal # Shouldn't affect non-union field types. @@ -58,7 +59,7 @@ field18: dict[Literal[1], Literal[2]] # OK # Should respect name of literal type used field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. -# Should handle newlines +# Should emit in cases with newlines field20: typing.Union[ Literal[ 1 # test @@ -67,4 +68,10 @@ field20: typing.Union[ ] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]`. # Should handle multiple unions with multiple members -field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. \ No newline at end of file +field21: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. + +# Should emit in cases with `typing.Union`` instead of `|` +field22: typing.Union[Literal[1], Literal[2]] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]`. + +# Should emit in cases with `typing_extensions.Literal` +field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing_extensions.Literal[1], typing_extensions.Literal[2]]`. diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 9efb3b894fd1f..20d8b09109b10 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -1,247 +1,256 @@ --- source: crates/ruff/src/rules/flake8_pyi/mod.rs --- -PYI030.pyi:8:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | - 7 | # Should emit for duplicate field types. - 8 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. + 8 | # Should emit for duplicate field types. + 9 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 - 9 | -10 | # Should emit for union types in arguments. +10 | +11 | # Should emit for union types in arguments. | -PYI030.pyi:11:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -10 | # Should emit for union types in arguments. -11 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +11 | # Should emit for union types in arguments. +12 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -12 | print(arg1) +13 | print(arg1) | -PYI030.pyi:16:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -15 | # Should emit for unions in return types. -16 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +16 | # Should emit for unions in return types. +17 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -17 | return "my Literal[1]ing" +18 | return "my Literal[1]ing" | -PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +21 | # Should emit in longer unions, even if not directly adjacent. +22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | -PYI030.pyi:21:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +21 | # Should emit in longer unions, even if not directly adjacent. +22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | -PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -20 | # Should emit in longer unions, even if not directly adjacent. -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +21 | # Should emit in longer unions, even if not directly adjacent. +22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | -PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -21 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | -PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -25 | -26 | # Should emit for non-type unions. +26 | +27 | # Should emit for non-type unions. | -PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -22 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -25 | -26 | # Should emit for non-type unions. +26 | +27 | # Should emit for non-type unions. | -PYI030.pyi:27:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -26 | # Should emit for non-type unions. -27 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +27 | # Should emit for non-type unions. +28 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -28 | -29 | # Should emit for parenthesized unions. +29 | +30 | # Should emit for parenthesized unions. | -PYI030.pyi:30:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -29 | # Should emit for parenthesized unions. -30 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +30 | # Should emit for parenthesized unions. +31 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -31 | -32 | # Should handle user parentheses when fixing. +32 | +33 | # Should handle user parentheses when fixing. | -PYI030.pyi:33:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -32 | # Should handle user parentheses when fixing. -33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +33 | # Should handle user parentheses when fixing. +34 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +35 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | -PYI030.pyi:34:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -32 | # Should handle user parentheses when fixing. -33 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -34 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +33 | # Should handle user parentheses when fixing. +34 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +35 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -35 | -36 | # Should emit for union in generic parent type. +36 | +37 | # Should emit for union in generic parent type. | -PYI030.pyi:37:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -36 | # Should emit for union in generic parent type. -37 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +37 | # Should emit for union in generic parent type. +38 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -38 | -39 | # Should emit for unions with more than two cases +39 | +40 | # Should emit for unions with more than two cases | -PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` +PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | -39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +40 | # Should emit for unions with more than two cases +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | -PYI030.pyi:40:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +40 | # Should emit for unions with more than two cases +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | -PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` +PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | -39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +40 | # Should emit for unions with more than two cases +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | -43 | # Should emit for unions with more than two cases, even if not directly adjacent +43 | +44 | # Should emit for unions with more than two cases, even if not directly adjacent | -PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` +PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | -39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +40 | # Should emit for unions with more than two cases +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | -43 | # Should emit for unions with more than two cases, even if not directly adjacent +43 | +44 | # Should emit for unions with more than two cases, even if not directly adjacent | -PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -39 | # Should emit for unions with more than two cases -40 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -41 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +40 | # Should emit for unions with more than two cases +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | -43 | # Should emit for unions with more than two cases, even if not directly adjacent +43 | +44 | # Should emit for unions with more than two cases, even if not directly adjacent | -PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` +PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | -43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +44 | # Should emit for unions with more than two cases, even if not directly adjacent +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -45 | -46 | # Should emit for unions with mixed literal internal types +46 | +47 | # Should emit for unions with mixed literal internal types | -PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +44 | # Should emit for unions with more than two cases, even if not directly adjacent +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -45 | -46 | # Should emit for unions with mixed literal internal types +46 | +47 | # Should emit for unions with mixed literal internal types | -PYI030.pyi:44:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -43 | # Should emit for unions with more than two cases, even if not directly adjacent -44 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +44 | # Should emit for unions with more than two cases, even if not directly adjacent +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -45 | -46 | # Should emit for unions with mixed literal internal types +46 | +47 | # Should emit for unions with mixed literal internal types | -PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` +PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` | -46 | # Should emit for unions with mixed literal internal types -47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. +47 | # Should emit for unions with mixed literal internal types +48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -48 | -49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 +49 | +50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 | -PYI030.pyi:47:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` +PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` | -46 | # Should emit for unions with mixed literal internal types -47 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. +47 | # Should emit for unions with mixed literal internal types +48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -48 | -49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 +49 | +50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 | -PYI030.pyi:50:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]` +PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]` | -49 | # Shouldn't emit for duplicate field types with same value; covered by Y016 -50 | field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" +50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 +51 | field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -51 | -52 | # Shouldn't emit if in new parent type +52 | +53 | # Shouldn't emit if in new parent type | -PYI030.pyi:59:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` +PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -58 | # Should respect name of literal type used -59 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. +59 | # Should respect name of literal type used +60 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -60 | -61 | # Should handle newlines +61 | +62 | # Should emit in cases with newlines | -PYI030.pyi:70:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` +PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | -69 | # Should handle multiple unions with multiple members -70 | field16: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. +70 | # Should handle multiple unions with multiple members +71 | field21: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +72 | +73 | # Should emit in cases with `typing.Union`` instead of `|` + | + +PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +76 | # Should emit in cases with `typing_extensions.Literal` +77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing_extensions.Literal[1], typing_extensions.Literal[2]]`. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From d7077042542a41d9a77f04f6215431d3ebdddaa7 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 14:19:15 -0500 Subject: [PATCH 04/16] Remove message information from test file for readability --- .../test/fixtures/flake8_pyi/PYI030.pyi | 44 ++++----- ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 98 +++++++++---------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index aea26e3370ba2..875e76477d178 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -6,49 +6,49 @@ from typing import Literal field1: Literal[1] # OK # Should emit for duplicate field types. -field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field2: Literal[1] | Literal[2] # Error # Should emit for union types in arguments. -def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +def func1(arg1: Literal[1] | Literal[2]): # Error print(arg1) # Should emit for unions in return types. -def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +def func2() -> Literal[1] | Literal[2]: # Error return "my Literal[1]ing" # Should emit in longer unions, even if not directly adjacent. -field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field3: Literal[1] | Literal[2] | str # Error +field4: str | Literal[1] | Literal[2] # Error +field5: Literal[1] | str | Literal[2] # Error +field6: Literal[1] | bool | Literal[2] | str # Error # Should emit for non-type unions. -field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field7 = Literal[1] | Literal[2] # Error # Should emit for parenthesized unions. -field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field8: Literal[1] | (Literal[2] | str) # Error # Should handle user parentheses when fixing. -field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field9: Literal[1] | (Literal[2] | str) # Error +field10: (Literal[1] | str) | Literal[2] # Error # Should emit for union in generic parent type. -field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +field11: dict[Literal[1] | Literal[2], str] # Error # Should emit for unions with more than two cases -field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +field12: Literal[1] | Literal[2] | Literal[3] # Error +field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error # Should emit for unions with more than two cases, even if not directly adjacent -field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +field14: Literal[1] | Literal[2] | str | Literal[3] # Error # Should emit for unions with mixed literal internal types -field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. +field15: Literal[1] | Literal["foo"] | Literal[True] # Error # Shouldn't emit for duplicate field types with same value; covered by Y016 -field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" +field16: Literal[1] | Literal[1] # OK # Shouldn't emit if in new parent type field17: Literal[1] | dict[Literal[2], str] # OK @@ -57,7 +57,7 @@ field17: Literal[1] | dict[Literal[2], str] # OK field18: dict[Literal[1], Literal[2]] # OK # Should respect name of literal type used -field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. +field19: typing.Literal[1] | typing.Literal[2] # Error # Should emit in cases with newlines field20: typing.Union[ @@ -65,13 +65,13 @@ field20: typing.Union[ 1 # test ], Literal[2], -] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]`. +] # Error, newline and comment will not be emitted in message # Should handle multiple unions with multiple members -field21: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. +field21: Literal[1, 2] | Literal[3, 4] # Error # Should emit in cases with `typing.Union`` instead of `|` -field22: typing.Union[Literal[1], Literal[2]] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Union[typing.Literal[1], typing.Literal[2]]`. +field22: typing.Union[Literal[1], Literal[2]] # Error # Should emit in cases with `typing_extensions.Literal` -field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing_extensions.Literal[1], typing_extensions.Literal[2]]`. +field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 20d8b09109b10..1485ceaefaf35 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -4,7 +4,7 @@ source: crates/ruff/src/rules/flake8_pyi/mod.rs PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 8 | # Should emit for duplicate field types. - 9 | field2: Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. + 9 | field2: Literal[1] | Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 10 | 11 | # Should emit for union types in arguments. @@ -13,7 +13,7 @@ PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 11 | # Should emit for union types in arguments. -12 | def func1(arg1: Literal[1] | Literal[2]): # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +12 | def func1(arg1: Literal[1] | Literal[2]): # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 13 | print(arg1) | @@ -21,7 +21,7 @@ PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 16 | # Should emit for unions in return types. -17 | def func2() -> Literal[1] | Literal[2]: # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +17 | def func2() -> Literal[1] | Literal[2]: # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 18 | return "my Literal[1]ing" | @@ -29,45 +29,45 @@ PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 21 | # Should emit in longer unions, even if not directly adjacent. -22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field3: Literal[1] | Literal[2] | str # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error +24 | field5: Literal[1] | str | Literal[2] # Error | PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 21 | # Should emit in longer unions, even if not directly adjacent. -22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field3: Literal[1] | Literal[2] | str # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error +24 | field5: Literal[1] | str | Literal[2] # Error | PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 21 | # Should emit in longer unions, even if not directly adjacent. -22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field3: Literal[1] | Literal[2] | str # Error +23 | field4: str | Literal[1] | Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +24 | field5: Literal[1] | str | Literal[2] # Error +25 | field6: Literal[1] | bool | Literal[2] | str # Error | PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -22 | field3: Literal[1] | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +22 | field3: Literal[1] | Literal[2] | str # Error +23 | field4: str | Literal[1] | Literal[2] # Error +24 | field5: Literal[1] | str | Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +25 | field6: Literal[1] | bool | Literal[2] | str # Error | PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error +24 | field5: Literal[1] | str | Literal[2] # Error +25 | field6: Literal[1] | bool | Literal[2] | str # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 26 | 27 | # Should emit for non-type unions. @@ -75,9 +75,9 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | -23 | field4: str | Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -24 | field5: Literal[1] | str | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -25 | field6: Literal[1] | bool | Literal[2] | str # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +23 | field4: str | Literal[1] | Literal[2] # Error +24 | field5: Literal[1] | str | Literal[2] # Error +25 | field6: Literal[1] | bool | Literal[2] | str # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 26 | 27 | # Should emit for non-type unions. @@ -86,7 +86,7 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 27 | # Should emit for non-type unions. -28 | field7 = Literal[1] | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +28 | field7 = Literal[1] | Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 29 | 30 | # Should emit for parenthesized unions. @@ -95,7 +95,7 @@ PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 30 | # Should emit for parenthesized unions. -31 | field8: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +31 | field8: Literal[1] | (Literal[2] | str) # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 32 | 33 | # Should handle user parentheses when fixing. @@ -104,16 +104,16 @@ PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single litera PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 33 | # Should handle user parentheses when fixing. -34 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +34 | field9: Literal[1] | (Literal[2] | str) # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -35 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +35 | field10: (Literal[1] | str) | Literal[2] # Error | PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 33 | # Should handle user parentheses when fixing. -34 | field9: Literal[1] | (Literal[2] | str) # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. -35 | field10: (Literal[1] | str) | Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +34 | field9: Literal[1] | (Literal[2] | str) # Error +35 | field10: (Literal[1] | str) | Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 36 | 37 | # Should emit for union in generic parent type. @@ -122,7 +122,7 @@ PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 37 | # Should emit for union in generic parent type. -38 | field11: dict[Literal[1] | Literal[2], str] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2]`. +38 | field11: dict[Literal[1] | Literal[2], str] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 39 | 40 | # Should emit for unions with more than two cases @@ -131,24 +131,24 @@ PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 43 | 44 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -157,8 +157,8 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 43 | 44 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -167,8 +167,8 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3, 4]`. +41 | field12: Literal[1] | Literal[2] | Literal[3] # Error +42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 43 | 44 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -177,7 +177,7 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 44 | # Should emit for unions with more than two cases, even if not directly adjacent -45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 46 | 47 | # Should emit for unions with mixed literal internal types @@ -186,7 +186,7 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 44 | # Should emit for unions with more than two cases, even if not directly adjacent -45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 46 | 47 | # Should emit for unions with mixed literal internal types @@ -195,7 +195,7 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 44 | # Should emit for unions with more than two cases, even if not directly adjacent -45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, 2, 3]`. +45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 46 | 47 | # Should emit for unions with mixed literal internal types @@ -204,7 +204,7 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` | 47 | # Should emit for unions with mixed literal internal types -48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. +48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 49 | 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 @@ -213,7 +213,7 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` | 47 | # Should emit for unions with mixed literal internal types -48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `Literal[1, "foo", True]`. +48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 49 | 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 @@ -222,7 +222,7 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]` | 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 -51 | field16: Literal[1] | Literal[1] # Error: Y016 Duplicate union member "Literal[1]" +51 | field16: Literal[1] | Literal[1] # OK | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 52 | 53 | # Shouldn't emit if in new parent type @@ -231,7 +231,7 @@ PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 59 | # Should respect name of literal type used -60 | field19: typing.Literal[1] | typing.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2]`. +60 | field19: typing.Literal[1] | typing.Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 61 | 62 | # Should emit in cases with newlines @@ -240,7 +240,7 @@ PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 70 | # Should handle multiple unions with multiple members -71 | field21: Literal[1, 2] | Literal[3, 4] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing.Literal[1, 2, 3, 4]`. +71 | field21: Literal[1, 2] | Literal[3, 4] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 72 | 73 | # Should emit in cases with `typing.Union`` instead of `|` @@ -249,7 +249,7 @@ PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 76 | # Should emit in cases with `typing_extensions.Literal` -77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error: PYI030 Multiple literal members in a union. Use a single literal e.g. `typing_extensions.Literal[1], typing_extensions.Literal[2]]`. +77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From 56f7d09e4adfde8863a381f622192751825ff725 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 14:27:49 -0500 Subject: [PATCH 05/16] Update schema --- ruff.schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.schema.json b/ruff.schema.json index 47e0a9fde04fa..405783eb9cb6f 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2346,6 +2346,7 @@ "PYI025", "PYI029", "PYI03", + "PYI030", "PYI032", "PYI033", "PYI034", From 9c7194aa83dd8cf6e6dbb59c89f4524f063ba848 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 15:19:45 -0500 Subject: [PATCH 06/16] Add support for `typing.Union` --- .../test/fixtures/flake8_pyi/PYI030.pyi | 8 +- crates/ruff/src/checkers/ast/mod.rs | 5 ++ .../rules/unnecessary_literal_union.rs | 75 ++++++++++++------- ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 44 ++++++++++- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index 875e76477d178..e54264d331add 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -70,8 +70,14 @@ field20: typing.Union[ # Should handle multiple unions with multiple members field21: Literal[1, 2] | Literal[3, 4] # Error -# Should emit in cases with `typing.Union`` instead of `|` +# Should emit in cases with `typing.Union` instead of `|` field22: typing.Union[Literal[1], Literal[2]] # Error # Should emit in cases with `typing_extensions.Literal` field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error + +# Should emit in cases with nested `typing.Union` +field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error + +# Should emit in cases with mixed `typing.Union` and `|` +field24: typing.Union[Literal[1], Literal[2] | str] # Error diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 30fad458626a3..3468ac61eed05 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2189,6 +2189,11 @@ where } } + // Ex) Union[...] + if self.enabled(Rule::UnnecessaryLiteralUnion) { + flake8_pyi::rules::unnecessary_literal_union(self, expr); + } + if self.semantic.match_typing_expr(value, "Literal") { self.semantic.flags |= SemanticModelFlags::LITERAL; } diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index cba7648de3781..1aceb1ba22f32 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -10,7 +10,8 @@ use crate::checkers::ast::Checker; /// Checks for the presence of multiple literal types in a union. /// /// ## Why is this bad? -/// Literal types accept multiple arguments and it is clearer to specify them as a single literal. +/// Literal types accept multiple arguments and it is clearer to specify them +/// as a single literal. /// /// ## Example /// ```python @@ -37,15 +38,26 @@ impl Violation for UnnecessaryLiteralUnion { } /// PYI030 -pub(crate) fn unnecessary_literal_union(checker: &mut Checker, expr: &Expr) { - let mut literal_members = Vec::new(); - collect_literal_members(&mut literal_members, checker.semantic(), expr); +pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Expr) { + let mut literal_exprs = Vec::new(); + + // Adds a member to `literal_exprs` if it is a `Literal` annotation + let mut collect_literal_expr = |expr: &'a Expr| { + if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { + if checker.semantic().match_typing_expr(&*value, "Literal") { + literal_exprs.push(slice); + } + } + }; + + // Traverse the union, collect all literal members + traverse_union(&mut collect_literal_expr, expr, checker.semantic()); // Raise a violation if more than one - if literal_members.len() > 1 { + if literal_exprs.len() > 1 { let diagnostic = Diagnostic::new( UnnecessaryLiteralUnion { - members: literal_members + members: literal_exprs .into_iter() .map(|m| checker.locator.slice(m.range()).to_string()) .collect(), @@ -57,20 +69,12 @@ pub(crate) fn unnecessary_literal_union(checker: &mut Checker, expr: &Expr) { } } -/// Collect literal expressions from a union. -fn collect_literal_members<'a>( - literal_members: &mut Vec<&'a Expr>, - model: &SemanticModel, - expr: &'a Expr, -) { - // The union data structure usually looks like this: - // a | b | c -> (a | b) | c - // - // However, parenthesized expressions can coerce it into any structure: - // a | (b | c) - // - // So we have to traverse both branches in order (left, then right), to report members - // in the order they appear in the source code. +/// Traverse a "union" type annotation, calling `func` on each expression in the union. +fn traverse_union<'a, F>(func: &mut F, expr: &'a Expr, semantic: &SemanticModel) +where + F: FnMut(&'a Expr), +{ + // Ex) x | y if let Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, left, @@ -78,15 +82,34 @@ fn collect_literal_members<'a>( range: _, }) = expr { - // Traverse left subtree, then the right subtree, propagating the previous node. - collect_literal_members(literal_members, model, left); - collect_literal_members(literal_members, model, right); + // The union data structure usually looks like this: + // a | b | c -> (a | b) | c + // + // However, parenthesized expressions can coerce it into any structure: + // a | (b | c) + // + // So we have to traverse both branches in order (left, then right), to report members + // in the order they appear in the source code. + + // Traverse the left then right arms + traverse_union(func, left, semantic); + traverse_union(func, right, semantic); + return; } - // If it's a literal expression add it to the members + // Ex) Union[x, y] if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { - if model.match_typing_expr(value, "Literal") { - literal_members.push(slice); + if semantic.match_typing_expr(value, "Union") { + if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() { + // Traverse each element of the tuple within the union recursively to handle cases + // such as `Union[..., Union[...]] + elts.iter() + .for_each(|elt| traverse_union(func, elt, semantic)); + return; + } } } + + // Otherwise, call the function on expression + func(expr) } diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 1485ceaefaf35..74eb4c0cadc6a 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -237,13 +237,37 @@ PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single liter 62 | # Should emit in cases with newlines | +PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +62 | # Should emit in cases with newlines +63 | field20: typing.Union[ + | __________^ +64 | | Literal[ +65 | | 1 # test +66 | | ], +67 | | Literal[2], +68 | | ] # Error, newline and comment will not be emitted in message + | |_^ PYI030 +69 | +70 | # Should handle multiple unions with multiple members + | + PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 70 | # Should handle multiple unions with multiple members 71 | field21: Literal[1, 2] | Literal[3, 4] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 72 | -73 | # Should emit in cases with `typing.Union`` instead of `|` +73 | # Should emit in cases with `typing.Union` instead of `|` + | + +PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +73 | # Should emit in cases with `typing.Union` instead of `|` +74 | field22: typing.Union[Literal[1], Literal[2]] # Error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +75 | +76 | # Should emit in cases with `typing_extensions.Literal` | PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` @@ -251,6 +275,24 @@ PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single liter 76 | # Should emit in cases with `typing_extensions.Literal` 77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +78 | +79 | # Should emit in cases with nested `typing.Union` + | + +PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +79 | # Should emit in cases with nested `typing.Union` +80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +81 | +82 | # Should emit in cases with mixed `typing.Union` and `|` + | + +PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` + | +82 | # Should emit in cases with mixed `typing.Union` and `|` +83 | field24: typing.Union[Literal[1], Literal[2] | str] # Error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From e088b3476eacf657b0b64046ff2ab8333bfddacb Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 16:08:27 -0500 Subject: [PATCH 07/16] `m` -> `literal_expr` --- .../src/rules/flake8_pyi/rules/unnecessary_literal_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index 1aceb1ba22f32..c8f39e95352ee 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -59,7 +59,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp UnnecessaryLiteralUnion { members: literal_exprs .into_iter() - .map(|m| checker.locator.slice(m.range()).to_string()) + .map(|literal_expr| checker.locator.slice(literal_expr.range()).to_string()) .collect(), }, expr.range(), From 8b9d54c82103a562f2bcf6f865bc76f7f40fcd32 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 16:09:29 -0500 Subject: [PATCH 08/16] Clippy --- .../src/rules/flake8_pyi/rules/unnecessary_literal_union.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index c8f39e95352ee..2b16a98a57584 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -44,7 +44,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp // Adds a member to `literal_exprs` if it is a `Literal` annotation let mut collect_literal_expr = |expr: &'a Expr| { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { - if checker.semantic().match_typing_expr(&*value, "Literal") { + if checker.semantic().match_typing_expr(value, "Literal") { literal_exprs.push(slice); } } @@ -111,5 +111,5 @@ where } // Otherwise, call the function on expression - func(expr) + func(expr); } From 7c605c350d9ce7a9b4802b6374c510077e8069bf Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 16:29:45 -0500 Subject: [PATCH 09/16] Update case number to 25 --- crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index e54264d331add..ff816401e7f2d 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -80,4 +80,4 @@ field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error # Should emit in cases with mixed `typing.Union` and `|` -field24: typing.Union[Literal[1], Literal[2] | str] # Error +field25: typing.Union[Literal[1], Literal[2] | str] # Error From 40e4fe313321bedacb1f16b6c61824bac17c06b3 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 16:33:43 -0500 Subject: [PATCH 10/16] Fix duplicate violation reports for `|` --- crates/ruff/src/checkers/ast/mod.rs | 14 +++- ..._flake8_pyi__tests__PYI030_PYI030.pyi.snap | 76 +------------------ 2 files changed, 14 insertions(+), 76 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 3468ac61eed05..cd8df149e2bbf 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3141,6 +3141,7 @@ where if self.is_stub { if self.enabled(Rule::DuplicateUnionMember) && self.semantic.in_type_definition() + // Avoid duplicate checks if the parent is an `|` && self.semantic.expr_parent().map_or(true, |parent| { !matches!( parent, @@ -3154,7 +3155,18 @@ where flake8_pyi::rules::duplicate_union_member(self, expr); } - if self.enabled(Rule::UnnecessaryLiteralUnion) { + if self.enabled(Rule::UnnecessaryLiteralUnion) + // Avoid duplicate checks if the parent is an `|` + && self.semantic.expr_parent().map_or(true, |parent| { + !matches!( + parent, + Expr::BinOp(ast::ExprBinOp { + op: Operator::BitOr, + .. + }) + ) + }) + { flake8_pyi::rules::unnecessary_literal_union(self, expr); } } diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 74eb4c0cadc6a..08a627b70ec9d 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -35,15 +35,6 @@ PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single litera 24 | field5: Literal[1] | str | Literal[2] # Error | -PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -21 | # Should emit in longer unions, even if not directly adjacent. -22 | field3: Literal[1] | Literal[2] | str # Error - | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -23 | field4: str | Literal[1] | Literal[2] # Error -24 | field5: Literal[1] | str | Literal[2] # Error - | - PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 21 | # Should emit in longer unions, even if not directly adjacent. @@ -73,16 +64,6 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera 27 | # Should emit for non-type unions. | -PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -23 | field4: str | Literal[1] | Literal[2] # Error -24 | field5: Literal[1] | str | Literal[2] # Error -25 | field6: Literal[1] | bool | Literal[2] | str # Error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -26 | -27 | # Should emit for non-type unions. - | - PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 27 | # Should emit for non-type unions. @@ -136,14 +117,6 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter 42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error | -PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error - | - PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` | 40 | # Should emit for unions with more than two cases @@ -154,26 +127,6 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter 44 | # Should emit for unions with more than two cases, even if not directly adjacent | -PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` - | -40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -43 | -44 | # Should emit for unions with more than two cases, even if not directly adjacent - | - -PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -40 | # Should emit for unions with more than two cases -41 | field12: Literal[1] | Literal[2] | Literal[3] # Error -42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -43 | -44 | # Should emit for unions with more than two cases, even if not directly adjacent - | - PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]` | 44 | # Should emit for unions with more than two cases, even if not directly adjacent @@ -183,24 +136,6 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter 47 | # Should emit for unions with mixed literal internal types | -PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -44 | # Should emit for unions with more than two cases, even if not directly adjacent -45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -46 | -47 | # Should emit for unions with mixed literal internal types - | - -PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` - | -44 | # Should emit for unions with more than two cases, even if not directly adjacent -45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -46 | -47 | # Should emit for unions with mixed literal internal types - | - PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]` | 47 | # Should emit for unions with mixed literal internal types @@ -210,15 +145,6 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 | -PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo"]` - | -47 | # Should emit for unions with mixed literal internal types -48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 -49 | -50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 - | - PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]` | 50 | # Shouldn't emit for duplicate field types with same value; covered by Y016 @@ -291,7 +217,7 @@ PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single liter PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]` | 82 | # Should emit in cases with mixed `typing.Union` and `|` -83 | field24: typing.Union[Literal[1], Literal[2] | str] # Error +83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From 1759d914456b62dfc479518ce8717949935f6c87 Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 17:21:31 -0500 Subject: [PATCH 11/16] Fix duplicate violation reports for `Union` --- .../resources/test/fixtures/flake8_pyi/PYI030.pyi | 3 +++ crates/ruff/src/checkers/ast/mod.rs | 11 ++++++++++- ...__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi index ff816401e7f2d..e92af925df67d 100644 --- a/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi +++ b/crates/ruff/resources/test/fixtures/flake8_pyi/PYI030.pyi @@ -81,3 +81,6 @@ field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error # Should emit in cases with mixed `typing.Union` and `|` field25: typing.Union[Literal[1], Literal[2] | str] # Error + +# Should emit only once in cases with multiple nested `typing.Union` +field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index cd8df149e2bbf..5b515fb238db4 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2191,7 +2191,16 @@ where // Ex) Union[...] if self.enabled(Rule::UnnecessaryLiteralUnion) { - flake8_pyi::rules::unnecessary_literal_union(self, expr); + // Avoid duplicate checks if the parent is an `Union[...]` + if self.semantic.expr_parent().map_or(true, |parent| { + if let Expr::Subscript(ast::ExprSubscript { value, .. }) = parent { + !self.semantic.match_typing_expr(value, "Union") + } else { + false + } + }) { + flake8_pyi::rules::unnecessary_literal_union(self, expr); + } } if self.semantic.match_typing_expr(value, "Literal") { diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap index 08a627b70ec9d..42a1e51719d9a 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI030_PYI030.pyi.snap @@ -219,6 +219,15 @@ PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single liter 82 | # Should emit in cases with mixed `typing.Union` and `|` 83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 +84 | +85 | # Should emit only once in cases with multiple nested `typing.Union` + | + +PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]` + | +85 | # Should emit only once in cases with multiple nested `typing.Union` +86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030 | From 19f9cbfb13d79c32c501baa2115ac1aa26e1b5db Mon Sep 17 00:00:00 2001 From: Zanie Date: Thu, 6 Jul 2023 17:32:14 -0500 Subject: [PATCH 12/16] Add imports to eamples --- .../src/rules/flake8_pyi/rules/unnecessary_literal_union.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index 2b16a98a57584..33029f9ac4b23 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -15,11 +15,15 @@ use crate::checkers::ast::Checker; /// /// ## Example /// ```python +/// from typing import Literal +/// /// field: Literal[1] | Literal[2] /// ``` /// /// Use instead: /// ```python +/// from typing import Literal +/// /// field: Literal[1, 2] /// ``` #[violation] From 8b068ad689322619587caeb4ae66d539aa3cf766 Mon Sep 17 00:00:00 2001 From: Zanie Date: Fri, 7 Jul 2023 10:47:20 -0500 Subject: [PATCH 13/16] Fix tuple check expression --- crates/ruff/src/checkers/ast/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 5b515fb238db4..b153e7ab912a2 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2191,14 +2191,16 @@ where // Ex) Union[...] if self.enabled(Rule::UnnecessaryLiteralUnion) { + let mut check = true; + // Avoid duplicate checks if the parent is an `Union[...]` - if self.semantic.expr_parent().map_or(true, |parent| { - if let Expr::Subscript(ast::ExprSubscript { value, .. }) = parent { - !self.semantic.match_typing_expr(value, "Union") - } else { - false - } - }) { + if let Some(Expr::Subscript(ast::ExprSubscript { value, .. })) = + self.semantic.expr_grandparent() + { + check = !self.semantic.match_typing_expr(value, "Union") + } + + if check { flake8_pyi::rules::unnecessary_literal_union(self, expr); } } From becf3917e8ec179739ca668c93ee8b4c9042a452 Mon Sep 17 00:00:00 2001 From: Zanie Date: Fri, 7 Jul 2023 10:53:15 -0500 Subject: [PATCH 14/16] Improve match check for parent union --- crates/ruff/src/checkers/ast/mod.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index b153e7ab912a2..ea00e7ac14a3e 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3153,30 +3153,20 @@ where if self.enabled(Rule::DuplicateUnionMember) && self.semantic.in_type_definition() // Avoid duplicate checks if the parent is an `|` - && self.semantic.expr_parent().map_or(true, |parent| { - !matches!( - parent, - Expr::BinOp(ast::ExprBinOp { - op: Operator::BitOr, - .. - }) - ) - }) + && !matches!( + self.semantic.expr_parent(), + Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..})) + ) { flake8_pyi::rules::duplicate_union_member(self, expr); } if self.enabled(Rule::UnnecessaryLiteralUnion) // Avoid duplicate checks if the parent is an `|` - && self.semantic.expr_parent().map_or(true, |parent| { - !matches!( - parent, - Expr::BinOp(ast::ExprBinOp { - op: Operator::BitOr, - .. - }) - ) - }) + && !matches!( + self.semantic.expr_parent(), + Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..})) + ) { flake8_pyi::rules::unnecessary_literal_union(self, expr); } From 80d2269495e9f9d9f967a227a08a6f43a3fbfff9 Mon Sep 17 00:00:00 2001 From: Zanie Date: Fri, 7 Jul 2023 10:57:52 -0500 Subject: [PATCH 15/16] Use a `SmallVec` for storing literal expressions Should reduce the number of heap allocations for uses without violations --- .../src/rules/flake8_pyi/rules/unnecessary_literal_union.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs index 33029f9ac4b23..6d5735ac32a6c 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/unnecessary_literal_union.rs @@ -3,6 +3,7 @@ use rustpython_parser::ast::{self, Expr, Operator, Ranged}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use smallvec::SmallVec; use crate::checkers::ast::Checker; @@ -43,7 +44,7 @@ impl Violation for UnnecessaryLiteralUnion { /// PYI030 pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Expr) { - let mut literal_exprs = Vec::new(); + let mut literal_exprs = SmallVec::<[&Box; 1]>::new(); // Adds a member to `literal_exprs` if it is a `Literal` annotation let mut collect_literal_expr = |expr: &'a Expr| { From 47d8983f79050de62167b5030e5054454dbf7c15 Mon Sep 17 00:00:00 2001 From: Zanie Date: Fri, 7 Jul 2023 11:32:14 -0500 Subject: [PATCH 16/16] Add missing `;` --- crates/ruff/src/checkers/ast/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index ea00e7ac14a3e..4e6060baaaed2 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2197,7 +2197,7 @@ where if let Some(Expr::Subscript(ast::ExprSubscript { value, .. })) = self.semantic.expr_grandparent() { - check = !self.semantic.match_typing_expr(value, "Union") + check = !self.semantic.match_typing_expr(value, "Union"); } if check {