[flake8-pyi] Always autofix duplicate-union-members (PYI016) (#14270)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz (push) Blocked by required conditions
CI / Fuzz the parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

This commit is contained in:
Simon Brugman 2024-11-13 17:42:06 +01:00 committed by GitHub
parent 0eb36e4345
commit eb55b9b5a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 190 additions and 517 deletions

View file

@ -1,15 +1,19 @@
use std::collections::HashSet;
use anyhow::Result;
use ruff_python_ast::name::Name;
use rustc_hash::FxHashSet;
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_ast::{Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, Operator};
use ruff_python_semantic::analyze::typing::traverse_union;
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
/// ## What it does
/// Checks for duplicate union members.
@ -27,6 +31,12 @@ use crate::checkers::ast::Checker;
/// foo: str
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as safe unless the union contains comments.
///
/// For nested union, the fix will flatten type expressions into a single
/// top-level union.
///
/// ## References
/// - [Python documentation: `typing.Union`](https://docs.python.org/3/library/typing.html#typing.Union)
#[violation]
@ -53,37 +63,152 @@ impl Violation for DuplicateUnionMember {
/// PYI016
pub(crate) fn duplicate_union_member<'a>(checker: &mut Checker, expr: &'a Expr) {
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
let mut unique_nodes: Vec<&Expr> = Vec::new();
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let mut union_type = UnionKind::TypingUnion;
// Adds a member to `literal_exprs` if it is a `Literal` annotation
let mut check_for_duplicate_members = |expr: &'a Expr, parent: &'a Expr| {
if matches!(parent, Expr::BinOp(_)) {
union_type = UnionKind::PEP604;
}
// If we've already seen this union member, raise a violation.
if !seen_nodes.insert(expr.into()) {
let mut diagnostic = Diagnostic::new(
if seen_nodes.insert(expr.into()) {
unique_nodes.push(expr);
} else {
diagnostics.push(Diagnostic::new(
DuplicateUnionMember {
duplicate_name: checker.generator().expr(expr),
},
expr.range(),
);
// Delete the "|" character as well as the duplicate value by reconstructing the
// parent without the duplicate.
// If the parent node is not a `BinOp` we will not perform a fix
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = parent {
// Replace the parent with its non-duplicate child.
let child = if expr == left.as_ref() { right } else { left };
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.locator().slice(child.as_ref()).to_string(),
parent.range(),
)));
}
diagnostics.push(diagnostic);
));
}
};
// Traverse the union, collect all diagnostic members
traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr);
if diagnostics.is_empty() {
return;
}
if checker.settings.preview.is_enabled() {
// Mark [`Fix`] as unsafe when comments are in range.
let applicability = if checker.comment_ranges().intersects(expr.range()) {
Applicability::Unsafe
} else {
Applicability::Safe
};
// Generate the flattened fix once.
let fix = if let &[edit_expr] = unique_nodes.as_slice() {
// Generate a [`Fix`] for a single type expression, e.g. `int`.
Some(Fix::applicable_edit(
Edit::range_replacement(checker.generator().expr(edit_expr), expr.range()),
applicability,
))
} else {
match union_type {
// See redundant numeric union
UnionKind::PEP604 => Some(generate_pep604_fix(
checker,
unique_nodes,
expr,
applicability,
)),
UnionKind::TypingUnion => {
generate_union_fix(checker, unique_nodes, expr, applicability).ok()
}
}
};
if let Some(fix) = fix {
for diagnostic in &mut diagnostics {
diagnostic.set_fix(fix.clone());
}
}
}
// Add all diagnostics to the checker
checker.diagnostics.append(&mut diagnostics);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum UnionKind {
/// E.g., `typing.Union[int, str]`
TypingUnion,
/// E.g., `int | str`
PEP604,
}
// Generate a [`Fix`] for two or more type expressions, e.g. `int | float | complex`.
fn generate_pep604_fix(
checker: &Checker,
nodes: Vec<&Expr>,
annotation: &Expr,
applicability: Applicability,
) -> Fix {
debug_assert!(nodes.len() >= 2, "At least two nodes required");
let new_expr = nodes
.into_iter()
.fold(None, |acc: Option<Expr>, right: &Expr| {
if let Some(left) = acc {
Some(Expr::BinOp(ExprBinOp {
left: Box::new(left),
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
}))
} else {
Some(right.clone())
}
})
.unwrap();
Fix::applicable_edit(
Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()),
applicability,
)
}
// Generate a [`Fix`] for two or more type expresisons, e.g. `typing.Union[int, float, complex]`.
fn generate_union_fix(
checker: &Checker,
nodes: Vec<&Expr>,
annotation: &Expr,
applicability: Applicability,
) -> Result<Fix> {
debug_assert!(nodes.len() >= 2, "At least two nodes required");
// Request `typing.Union`
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from("typing", "Union"),
annotation.start(),
checker.semantic(),
)?;
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
let new_expr = Expr::Subscript(ExprSubscript {
range: TextRange::default(),
value: Box::new(Expr::Name(ExprName {
id: Name::new(binding),
ctx: ExprContext::Store,
range: TextRange::default(),
})),
slice: Box::new(Expr::Tuple(ExprTuple {
elts: nodes.into_iter().cloned().collect(),
range: TextRange::default(),
ctx: ExprContext::Load,
parenthesized: false,
})),
ctx: ExprContext::Load,
});
Ok(Fix::applicable_edits(
Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()),
[import_edit],
applicability,
))
}

View file

@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI016.py:7:15: PYI016 [*] Duplicate union member `str`
PYI016.py:7:15: PYI016 Duplicate union member `str`
|
6 | # Should emit for duplicate field types.
7 | field2: str | str # PYI016: Duplicate union member `str`
@ -11,17 +11,7 @@ PYI016.py:7:15: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
4 4 | field1: str
5 5 |
6 6 | # Should emit for duplicate field types.
7 |-field2: str | str # PYI016: Duplicate union member `str`
7 |+field2: str # PYI016: Duplicate union member `str`
8 8 |
9 9 | # Should emit for union types in arguments.
10 10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
PYI016.py:10:23: PYI016 [*] Duplicate union member `int`
PYI016.py:10:23: PYI016 Duplicate union member `int`
|
9 | # Should emit for union types in arguments.
10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
@ -30,17 +20,7 @@ PYI016.py:10:23: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
7 7 | field2: str | str # PYI016: Duplicate union member `str`
8 8 |
9 9 | # Should emit for union types in arguments.
10 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int`
10 |+def func1(arg1: int): # PYI016: Duplicate union member `int`
11 11 | print(arg1)
12 12 |
13 13 | # Should emit for unions in return types.
PYI016.py:14:22: PYI016 [*] Duplicate union member `str`
PYI016.py:14:22: PYI016 Duplicate union member `str`
|
13 | # Should emit for unions in return types.
14 | def func2() -> str | str: # PYI016: Duplicate union member `str`
@ -49,17 +29,7 @@ PYI016.py:14:22: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
11 11 | print(arg1)
12 12 |
13 13 | # Should emit for unions in return types.
14 |-def func2() -> str | str: # PYI016: Duplicate union member `str`
14 |+def func2() -> str: # PYI016: Duplicate union member `str`
15 15 | return "my string"
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
PYI016.py:18:15: PYI016 [*] Duplicate union member `str`
PYI016.py:18:15: PYI016 Duplicate union member `str`
|
17 | # Should emit in longer unions, even if not directly adjacent.
18 | field3: str | str | int # PYI016: Duplicate union member `str`
@ -69,17 +39,7 @@ PYI016.py:18:15: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
15 15 | return "my string"
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 |-field3: str | str | int # PYI016: Duplicate union member `str`
18 |+field3: str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
PYI016.py:19:15: PYI016 [*] Duplicate union member `int`
PYI016.py:19:15: PYI016 Duplicate union member `int`
|
17 | # Should emit in longer unions, even if not directly adjacent.
18 | field3: str | str | int # PYI016: Duplicate union member `str`
@ -90,17 +50,7 @@ PYI016.py:19:15: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 |-field4: int | int | str # PYI016: Duplicate union member `int`
19 |+field4: int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
22 22 |
PYI016.py:20:21: PYI016 [*] Duplicate union member `str`
PYI016.py:20:21: PYI016 Duplicate union member `str`
|
18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 | field4: int | int | str # PYI016: Duplicate union member `int`
@ -110,17 +60,7 @@ PYI016.py:20:21: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 |-field5: str | int | str # PYI016: Duplicate union member `str`
20 |+field5: str | int # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
22 22 |
23 23 | # Shouldn't emit for non-type unions.
PYI016.py:21:28: PYI016 [*] Duplicate union member `int`
PYI016.py:21:28: PYI016 Duplicate union member `int`
|
19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 | field5: str | int | str # PYI016: Duplicate union member `str`
@ -131,17 +71,7 @@ PYI016.py:21:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 |-field6: int | bool | str | int # PYI016: Duplicate union member `int`
21 |+field6: int | bool | str # PYI016: Duplicate union member `int`
22 22 |
23 23 | # Shouldn't emit for non-type unions.
24 24 | field7 = str | str
PYI016.py:27:22: PYI016 [*] Duplicate union member `int`
PYI016.py:27:22: PYI016 Duplicate union member `int`
|
26 | # Should emit for strangely-bracketed unions.
27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
@ -151,17 +81,7 @@ PYI016.py:27:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
24 24 | field7 = str | str
25 25 |
26 26 | # Should emit for strangely-bracketed unions.
27 |-field8: int | (str | int) # PYI016: Duplicate union member `int`
27 |+field8: int | (str) # PYI016: Duplicate union member `int`
28 28 |
29 29 | # Should handle user brackets when fixing.
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
PYI016.py:30:16: PYI016 [*] Duplicate union member `int`
PYI016.py:30:16: PYI016 Duplicate union member `int`
|
29 | # Should handle user brackets when fixing.
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
@ -170,17 +90,7 @@ PYI016.py:30:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
27 27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
28 28 |
29 29 | # Should handle user brackets when fixing.
30 |-field9: int | (int | str) # PYI016: Duplicate union member `int`
30 |+field9: int | (str) # PYI016: Duplicate union member `int`
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
PYI016.py:31:24: PYI016 [*] Duplicate union member `str`
PYI016.py:31:24: PYI016 Duplicate union member `str`
|
29 | # Should handle user brackets when fixing.
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
@ -191,17 +101,7 @@ PYI016.py:31:24: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
28 28 |
29 29 | # Should handle user brackets when fixing.
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
31 |-field10: (str | int) | str # PYI016: Duplicate union member `str`
31 |+field10: str | int # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
34 34 | field11: dict[int | int, str]
PYI016.py:34:21: PYI016 [*] Duplicate union member `int`
PYI016.py:34:21: PYI016 Duplicate union member `int`
|
33 | # Should emit for nested unions.
34 | field11: dict[int | int, str]
@ -211,17 +111,7 @@ PYI016.py:34:21: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
34 |-field11: dict[int | int, str]
34 |+field11: dict[int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
PYI016.py:37:16: PYI016 [*] Duplicate union member `int`
PYI016.py:37:16: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -230,17 +120,7 @@ PYI016.py:37:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
34 34 | field11: dict[int | int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 |-field12: int | int | int # Error
37 |+field12: int | int # Error
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI016.py:37:22: PYI016 [*] Duplicate union member `int`
PYI016.py:37:22: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -249,17 +129,7 @@ PYI016.py:37:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
34 34 | field11: dict[int | int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 |-field12: int | int | int # Error
37 |+field12: int | int # Error
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI016.py:38:16: PYI016 [*] Duplicate union member `int`
PYI016.py:38:16: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -270,17 +140,7 @@ PYI016.py:38:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.py:38:22: PYI016 [*] Duplicate union member `int`
PYI016.py:38:22: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -291,17 +151,7 @@ PYI016.py:38:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.py:38:28: PYI016 [*] Duplicate union member `int`
PYI016.py:38:28: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -312,17 +162,7 @@ PYI016.py:38:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.py:41:16: PYI016 [*] Duplicate union member `int`
PYI016.py:41:16: PYI016 Duplicate union member `int`
|
40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 | field14: int | int | str | int # Error
@ -332,17 +172,7 @@ PYI016.py:41:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 |-field14: int | int | str | int # Error
41 |+field14: int | str | int # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
PYI016.py:41:28: PYI016 [*] Duplicate union member `int`
PYI016.py:41:28: PYI016 Duplicate union member `int`
|
40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 | field14: int | int | str | int # Error
@ -352,17 +182,7 @@ PYI016.py:41:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 |-field14: int | int | str | int # Error
41 |+field14: int | int | str # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
PYI016.py:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
PYI016.py:44:30: PYI016 Duplicate union member `typing.Literal[1]`
|
43 | # Should emit for duplicate literal types; also covered by PYI030
44 | field15: typing.Literal[1] | typing.Literal[1] # Error
@ -372,16 +192,6 @@ PYI016.py:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
|
= help: Remove duplicate union member `typing.Literal[1]`
Safe fix
41 41 | field14: int | int | str | int # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 |-field15: typing.Literal[1] | typing.Literal[1] # Error
44 |+field15: typing.Literal[1] # Error
45 45 |
46 46 | # Shouldn't emit if in new parent type
47 47 | field16: int | dict[int, str] # OK
PYI016.py:57:5: PYI016 Duplicate union member `set[int]`
|
55 | int # foo
@ -415,7 +225,7 @@ PYI016.py:66:41: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.py:69:28: PYI016 [*] Duplicate union member `int`
PYI016.py:69:28: PYI016 Duplicate union member `int`
|
68 | # Should emit in cases with mixed `typing.Union` and `|`
69 | field21: typing.Union[int, int | str] # Error
@ -425,16 +235,6 @@ PYI016.py:69:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error
67 67 |
68 68 | # Should emit in cases with mixed `typing.Union` and `|`
69 |-field21: typing.Union[int, int | str] # Error
69 |+field21: typing.Union[int, str] # Error
70 70 |
71 71 | # Should emit only once in cases with multiple nested `typing.Union`
72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
PYI016.py:72:41: PYI016 Duplicate union member `int`
|
71 | # Should emit only once in cases with multiple nested `typing.Union`
@ -465,7 +265,7 @@ PYI016.py:72:64: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.py:76:12: PYI016 [*] Duplicate union member `set[int]`
PYI016.py:76:12: PYI016 Duplicate union member `set[int]`
|
74 | # Should emit in cases with newlines
75 | field23: set[ # foo
@ -476,16 +276,6 @@ PYI016.py:76:12: PYI016 [*] Duplicate union member `set[int]`
|
= help: Remove duplicate union member `set[int]`
Safe fix
73 73 |
74 74 | # Should emit in cases with newlines
75 75 | field23: set[ # foo
76 |- int] | set[int]
76 |+ int]
77 77 |
78 78 | # Should emit twice (once for each `int` in the nested union, both of which are
79 79 | # duplicates of the outer `int`), but not three times (which would indicate that
PYI016.py:81:41: PYI016 Duplicate union member `int`
|
79 | # duplicates of the outer `int`), but not three times (which would indicate that
@ -508,7 +298,7 @@ PYI016.py:81:46: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.py:86:28: PYI016 [*] Duplicate union member `int`
PYI016.py:86:28: PYI016 Duplicate union member `int`
|
84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 | # we incorrectly re-checked the nested union).
@ -517,14 +307,7 @@ PYI016.py:86:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 85 | # we incorrectly re-checked the nested union).
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
PYI016.py:86:34: PYI016 [*] Duplicate union member `int`
PYI016.py:86:34: PYI016 Duplicate union member `int`
|
84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 | # we incorrectly re-checked the nested union).
@ -532,12 +315,3 @@ PYI016.py:86:34: PYI016 [*] Duplicate union member `int`
| ^^^ PYI016
|
= help: Remove duplicate union member `int`
Safe fix
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 85 | # we incorrectly re-checked the nested union).
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`

View file

@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI016.pyi:7:15: PYI016 [*] Duplicate union member `str`
PYI016.pyi:7:15: PYI016 Duplicate union member `str`
|
6 | # Should emit for duplicate field types.
7 | field2: str | str # PYI016: Duplicate union member `str`
@ -11,17 +11,7 @@ PYI016.pyi:7:15: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
4 4 | field1: str
5 5 |
6 6 | # Should emit for duplicate field types.
7 |-field2: str | str # PYI016: Duplicate union member `str`
7 |+field2: str # PYI016: Duplicate union member `str`
8 8 |
9 9 | # Should emit for union types in arguments.
10 10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
PYI016.pyi:10:23: PYI016 [*] Duplicate union member `int`
PYI016.pyi:10:23: PYI016 Duplicate union member `int`
|
9 | # Should emit for union types in arguments.
10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
@ -30,17 +20,7 @@ PYI016.pyi:10:23: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
7 7 | field2: str | str # PYI016: Duplicate union member `str`
8 8 |
9 9 | # Should emit for union types in arguments.
10 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int`
10 |+def func1(arg1: int): # PYI016: Duplicate union member `int`
11 11 | print(arg1)
12 12 |
13 13 | # Should emit for unions in return types.
PYI016.pyi:14:22: PYI016 [*] Duplicate union member `str`
PYI016.pyi:14:22: PYI016 Duplicate union member `str`
|
13 | # Should emit for unions in return types.
14 | def func2() -> str | str: # PYI016: Duplicate union member `str`
@ -49,17 +29,7 @@ PYI016.pyi:14:22: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
11 11 | print(arg1)
12 12 |
13 13 | # Should emit for unions in return types.
14 |-def func2() -> str | str: # PYI016: Duplicate union member `str`
14 |+def func2() -> str: # PYI016: Duplicate union member `str`
15 15 | return "my string"
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
PYI016.pyi:18:15: PYI016 [*] Duplicate union member `str`
PYI016.pyi:18:15: PYI016 Duplicate union member `str`
|
17 | # Should emit in longer unions, even if not directly adjacent.
18 | field3: str | str | int # PYI016: Duplicate union member `str`
@ -69,17 +39,7 @@ PYI016.pyi:18:15: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
15 15 | return "my string"
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 |-field3: str | str | int # PYI016: Duplicate union member `str`
18 |+field3: str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
PYI016.pyi:19:15: PYI016 [*] Duplicate union member `int`
PYI016.pyi:19:15: PYI016 Duplicate union member `int`
|
17 | # Should emit in longer unions, even if not directly adjacent.
18 | field3: str | str | int # PYI016: Duplicate union member `str`
@ -90,17 +50,7 @@ PYI016.pyi:19:15: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
16 16 |
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 |-field4: int | int | str # PYI016: Duplicate union member `int`
19 |+field4: int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
22 22 |
PYI016.pyi:20:21: PYI016 [*] Duplicate union member `str`
PYI016.pyi:20:21: PYI016 Duplicate union member `str`
|
18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 | field4: int | int | str # PYI016: Duplicate union member `int`
@ -110,17 +60,7 @@ PYI016.pyi:20:21: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
17 17 | # Should emit in longer unions, even if not directly adjacent.
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 |-field5: str | int | str # PYI016: Duplicate union member `str`
20 |+field5: str | int # PYI016: Duplicate union member `str`
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
22 22 |
23 23 | # Shouldn't emit for non-type unions.
PYI016.pyi:21:28: PYI016 [*] Duplicate union member `int`
PYI016.pyi:21:28: PYI016 Duplicate union member `int`
|
19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 | field5: str | int | str # PYI016: Duplicate union member `str`
@ -131,17 +71,7 @@ PYI016.pyi:21:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
21 |-field6: int | bool | str | int # PYI016: Duplicate union member `int`
21 |+field6: int | bool | str # PYI016: Duplicate union member `int`
22 22 |
23 23 | # Shouldn't emit for non-type unions.
24 24 | field7 = str | str
PYI016.pyi:27:22: PYI016 [*] Duplicate union member `int`
PYI016.pyi:27:22: PYI016 Duplicate union member `int`
|
26 | # Should emit for strangely-bracketed unions.
27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
@ -151,17 +81,7 @@ PYI016.pyi:27:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
24 24 | field7 = str | str
25 25 |
26 26 | # Should emit for strangely-bracketed unions.
27 |-field8: int | (str | int) # PYI016: Duplicate union member `int`
27 |+field8: int | (str) # PYI016: Duplicate union member `int`
28 28 |
29 29 | # Should handle user brackets when fixing.
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
PYI016.pyi:30:16: PYI016 [*] Duplicate union member `int`
PYI016.pyi:30:16: PYI016 Duplicate union member `int`
|
29 | # Should handle user brackets when fixing.
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
@ -170,17 +90,7 @@ PYI016.pyi:30:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
27 27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
28 28 |
29 29 | # Should handle user brackets when fixing.
30 |-field9: int | (int | str) # PYI016: Duplicate union member `int`
30 |+field9: int | (str) # PYI016: Duplicate union member `int`
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
PYI016.pyi:31:24: PYI016 [*] Duplicate union member `str`
PYI016.pyi:31:24: PYI016 Duplicate union member `str`
|
29 | # Should handle user brackets when fixing.
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
@ -191,17 +101,7 @@ PYI016.pyi:31:24: PYI016 [*] Duplicate union member `str`
|
= help: Remove duplicate union member `str`
Safe fix
28 28 |
29 29 | # Should handle user brackets when fixing.
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
31 |-field10: (str | int) | str # PYI016: Duplicate union member `str`
31 |+field10: str | int # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
34 34 | field11: dict[int | int, str]
PYI016.pyi:34:21: PYI016 [*] Duplicate union member `int`
PYI016.pyi:34:21: PYI016 Duplicate union member `int`
|
33 | # Should emit for nested unions.
34 | field11: dict[int | int, str]
@ -211,17 +111,7 @@ PYI016.pyi:34:21: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
32 32 |
33 33 | # Should emit for nested unions.
34 |-field11: dict[int | int, str]
34 |+field11: dict[int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
PYI016.pyi:37:16: PYI016 [*] Duplicate union member `int`
PYI016.pyi:37:16: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -230,17 +120,7 @@ PYI016.pyi:37:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
34 34 | field11: dict[int | int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 |-field12: int | int | int # Error
37 |+field12: int | int # Error
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI016.pyi:37:22: PYI016 [*] Duplicate union member `int`
PYI016.pyi:37:22: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -249,17 +129,7 @@ PYI016.pyi:37:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
34 34 | field11: dict[int | int, str]
35 35 |
36 36 | # Should emit for unions with more than two cases
37 |-field12: int | int | int # Error
37 |+field12: int | int # Error
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
PYI016.pyi:38:16: PYI016 [*] Duplicate union member `int`
PYI016.pyi:38:16: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -270,17 +140,7 @@ PYI016.pyi:38:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.pyi:38:22: PYI016 [*] Duplicate union member `int`
PYI016.pyi:38:22: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -291,17 +151,7 @@ PYI016.pyi:38:22: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.pyi:38:28: PYI016 [*] Duplicate union member `int`
PYI016.pyi:38:28: PYI016 Duplicate union member `int`
|
36 | # Should emit for unions with more than two cases
37 | field12: int | int | int # Error
@ -312,17 +162,7 @@ PYI016.pyi:38:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
35 35 |
36 36 | # Should emit for unions with more than two cases
37 37 | field12: int | int | int # Error
38 |-field13: int | int | int | int # Error
38 |+field13: int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 41 | field14: int | int | str | int # Error
PYI016.pyi:41:16: PYI016 [*] Duplicate union member `int`
PYI016.pyi:41:16: PYI016 Duplicate union member `int`
|
40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 | field14: int | int | str | int # Error
@ -332,17 +172,7 @@ PYI016.pyi:41:16: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 |-field14: int | int | str | int # Error
41 |+field14: int | str | int # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
PYI016.pyi:41:28: PYI016 [*] Duplicate union member `int`
PYI016.pyi:41:28: PYI016 Duplicate union member `int`
|
40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 | field14: int | int | str | int # Error
@ -352,17 +182,7 @@ PYI016.pyi:41:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
38 38 | field13: int | int | int | int # Error
39 39 |
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
41 |-field14: int | int | str | int # Error
41 |+field14: int | int | str # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
PYI016.pyi:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
PYI016.pyi:44:30: PYI016 Duplicate union member `typing.Literal[1]`
|
43 | # Should emit for duplicate literal types; also covered by PYI030
44 | field15: typing.Literal[1] | typing.Literal[1] # Error
@ -372,16 +192,6 @@ PYI016.pyi:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
|
= help: Remove duplicate union member `typing.Literal[1]`
Safe fix
41 41 | field14: int | int | str | int # Error
42 42 |
43 43 | # Should emit for duplicate literal types; also covered by PYI030
44 |-field15: typing.Literal[1] | typing.Literal[1] # Error
44 |+field15: typing.Literal[1] # Error
45 45 |
46 46 | # Shouldn't emit if in new parent type
47 47 | field16: int | dict[int, str] # OK
PYI016.pyi:57:5: PYI016 Duplicate union member `set[int]`
|
55 | int # foo
@ -415,7 +225,7 @@ PYI016.pyi:66:41: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.pyi:69:28: PYI016 [*] Duplicate union member `int`
PYI016.pyi:69:28: PYI016 Duplicate union member `int`
|
68 | # Should emit in cases with mixed `typing.Union` and `|`
69 | field21: typing.Union[int, int | str] # Error
@ -425,16 +235,6 @@ PYI016.pyi:69:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error
67 67 |
68 68 | # Should emit in cases with mixed `typing.Union` and `|`
69 |-field21: typing.Union[int, int | str] # Error
69 |+field21: typing.Union[int, str] # Error
70 70 |
71 71 | # Should emit only once in cases with multiple nested `typing.Union`
72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
PYI016.pyi:72:41: PYI016 Duplicate union member `int`
|
71 | # Should emit only once in cases with multiple nested `typing.Union`
@ -465,7 +265,7 @@ PYI016.pyi:72:64: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.pyi:76:12: PYI016 [*] Duplicate union member `set[int]`
PYI016.pyi:76:12: PYI016 Duplicate union member `set[int]`
|
74 | # Should emit in cases with newlines
75 | field23: set[ # foo
@ -476,16 +276,6 @@ PYI016.pyi:76:12: PYI016 [*] Duplicate union member `set[int]`
|
= help: Remove duplicate union member `set[int]`
Safe fix
73 73 |
74 74 | # Should emit in cases with newlines
75 75 | field23: set[ # foo
76 |- int] | set[int]
76 |+ int]
77 77 |
78 78 | # Should emit twice (once for each `int` in the nested union, both of which are
79 79 | # duplicates of the outer `int`), but not three times (which would indicate that
PYI016.pyi:81:41: PYI016 Duplicate union member `int`
|
79 | # duplicates of the outer `int`), but not three times (which would indicate that
@ -508,7 +298,7 @@ PYI016.pyi:81:46: PYI016 Duplicate union member `int`
|
= help: Remove duplicate union member `int`
PYI016.pyi:86:28: PYI016 [*] Duplicate union member `int`
PYI016.pyi:86:28: PYI016 Duplicate union member `int`
|
84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 | # we incorrectly re-checked the nested union).
@ -517,14 +307,7 @@ PYI016.pyi:86:28: PYI016 [*] Duplicate union member `int`
|
= help: Remove duplicate union member `int`
Safe fix
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 85 | # we incorrectly re-checked the nested union).
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
PYI016.pyi:86:34: PYI016 [*] Duplicate union member `int`
PYI016.pyi:86:34: PYI016 Duplicate union member `int`
|
84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 | # we incorrectly re-checked the nested union).
@ -532,12 +315,3 @@ PYI016.pyi:86:34: PYI016 [*] Duplicate union member `int`
| ^^^ PYI016
|
= help: Remove duplicate union member `int`
Safe fix
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
85 85 | # we incorrectly re-checked the nested union).
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`