mirror of https://github.com/astral-sh/ruff
[`flake8-pyi`] Ensure `Literal[None,] | Literal[None,]` is not autofixed to `None | None` (`PYI061`) (#17659)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
f521358033
commit
ceb2bf1168
|
|
@ -4980,6 +4980,53 @@ fn flake8_import_convention_unused_aliased_import_no_conflict() {
|
||||||
.pass_stdin("1"));
|
.pass_stdin("1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See: https://github.com/astral-sh/ruff/issues/16177
|
||||||
|
#[test]
|
||||||
|
fn flake8_pyi_redundant_none_literal() {
|
||||||
|
let snippet = r#"
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# For each of these expressions, Ruff provides a fix for one of the `Literal[None]` elements
|
||||||
|
# but not both, as if both were autofixed it would result in `None | None`,
|
||||||
|
# which leads to a `TypeError` at runtime.
|
||||||
|
a: Literal[None,] | Literal[None,]
|
||||||
|
b: Literal[None] | Literal[None]
|
||||||
|
c: Literal[None] | Literal[None,]
|
||||||
|
d: Literal[None,] | Literal[None]
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.args(STDIN_BASE_OPTIONS)
|
||||||
|
.args(["--select", "PYI061"])
|
||||||
|
.args(["--stdin-filename", "test.py"])
|
||||||
|
.arg("--preview")
|
||||||
|
.arg("--diff")
|
||||||
|
.arg("-")
|
||||||
|
.pass_stdin(snippet), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
--- test.py
|
||||||
|
+++ test.py
|
||||||
|
@@ -4,7 +4,7 @@
|
||||||
|
# For each of these expressions, Ruff provides a fix for one of the `Literal[None]` elements
|
||||||
|
# but not both, as if both were autofixed it would result in `None | None`,
|
||||||
|
# which leads to a `TypeError` at runtime.
|
||||||
|
-a: Literal[None,] | Literal[None,]
|
||||||
|
-b: Literal[None] | Literal[None]
|
||||||
|
-c: Literal[None] | Literal[None,]
|
||||||
|
-d: Literal[None,] | Literal[None]
|
||||||
|
+a: None | Literal[None,]
|
||||||
|
+b: None | Literal[None]
|
||||||
|
+c: None | Literal[None,]
|
||||||
|
+d: None | Literal[None]
|
||||||
|
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Would fix 4 errors.
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
/// Test that private, old-style `TypeVar` generics
|
/// Test that private, old-style `TypeVar` generics
|
||||||
/// 1. Get replaced with PEP 695 type parameters (UP046, UP047)
|
/// 1. Get replaced with PEP 695 type parameters (UP046, UP047)
|
||||||
/// 2. Get renamed to remove leading underscores (UP049)
|
/// 2. Get renamed to remove leading underscores (UP049)
|
||||||
|
|
|
||||||
|
|
@ -78,4 +78,3 @@ b: None | Literal[None] | None
|
||||||
c: (None | Literal[None]) | None
|
c: (None | Literal[None]) | None
|
||||||
d: None | (Literal[None] | None)
|
d: None | (Literal[None] | None)
|
||||||
e: None | ((None | Literal[None]) | None) | None
|
e: None | ((None | Literal[None]) | None) | None
|
||||||
f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
|
|
@ -53,4 +53,3 @@ b: None | Literal[None] | None
|
||||||
c: (None | Literal[None]) | None
|
c: (None | Literal[None]) | None
|
||||||
d: None | (Literal[None] | None)
|
d: None | (Literal[None] | None)
|
||||||
e: None | ((None | Literal[None]) | None) | None
|
e: None | ((None | Literal[None]) | None) | None
|
||||||
f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ruff_python_ast::{
|
||||||
self as ast,
|
self as ast,
|
||||||
helpers::{pep_604_union, typing_optional},
|
helpers::{pep_604_union, typing_optional},
|
||||||
name::Name,
|
name::Name,
|
||||||
Expr, ExprBinOp, ExprContext, ExprNoneLiteral, ExprSubscript, Operator, PythonVersion,
|
Expr, ExprBinOp, ExprContext, ExprNoneLiteral, Operator, PythonVersion,
|
||||||
};
|
};
|
||||||
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
|
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
@ -130,6 +130,12 @@ pub(crate) fn redundant_none_literal<'a>(checker: &Checker, literal_expr: &'a Ex
|
||||||
literal_elements.clone(),
|
literal_elements.clone(),
|
||||||
union_kind,
|
union_kind,
|
||||||
)
|
)
|
||||||
|
// Isolate the fix to ensure multiple fixes on the same expression (like
|
||||||
|
// `Literal[None,] | Literal[None,]` -> `None | None`) happen across separate passes,
|
||||||
|
// preventing the production of invalid code.
|
||||||
|
.map(|fix| {
|
||||||
|
fix.map(|fix| fix.isolate(Checker::isolation(semantic.current_statement_id())))
|
||||||
|
})
|
||||||
});
|
});
|
||||||
checker.report_diagnostic(diagnostic);
|
checker.report_diagnostic(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
@ -172,18 +178,9 @@ fn create_fix(
|
||||||
|
|
||||||
traverse_union(
|
traverse_union(
|
||||||
&mut |expr, _| {
|
&mut |expr, _| {
|
||||||
if matches!(expr, Expr::NoneLiteral(_)) {
|
if expr.is_none_literal_expr() {
|
||||||
is_fixable = false;
|
is_fixable = false;
|
||||||
}
|
}
|
||||||
if expr != literal_expr {
|
|
||||||
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr {
|
|
||||||
if semantic.match_typing_expr(value, "Literal")
|
|
||||||
&& matches!(**slice, Expr::NoneLiteral(_))
|
|
||||||
{
|
|
||||||
is_fixable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
semantic,
|
semantic,
|
||||||
enclosing_pep604_union,
|
enclosing_pep604_union,
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,6 @@ PYI061.py:79:20: PYI061 Use `None` rather than `Literal[None]`
|
||||||
79 | d: None | (Literal[None] | None)
|
79 | d: None | (Literal[None] | None)
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
80 | e: None | ((None | Literal[None]) | None) | None
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
||||||
|
|
@ -432,24 +431,5 @@ PYI061.py:80:28: PYI061 Use `None` rather than `Literal[None]`
|
||||||
79 | d: None | (Literal[None] | None)
|
79 | d: None | (Literal[None] | None)
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
80 | e: None | ((None | Literal[None]) | None) | None
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.py:81:12: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
79 | d: None | (Literal[None] | None)
|
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.py:81:28: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
79 | d: None | (Literal[None] | None)
|
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,6 @@ PYI061.pyi:54:20: PYI061 Use `None` rather than `Literal[None]`
|
||||||
54 | d: None | (Literal[None] | None)
|
54 | d: None | (Literal[None] | None)
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
55 | e: None | ((None | Literal[None]) | None) | None
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
||||||
|
|
@ -301,24 +300,5 @@ PYI061.pyi:55:28: PYI061 Use `None` rather than `Literal[None]`
|
||||||
54 | d: None | (Literal[None] | None)
|
54 | d: None | (Literal[None] | None)
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
55 | e: None | ((None | Literal[None]) | None) | None
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.pyi:56:12: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
54 | d: None | (Literal[None] | None)
|
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.pyi:56:28: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
54 | d: None | (Literal[None] | None)
|
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,6 @@ PYI061.py:79:20: PYI061 Use `None` rather than `Literal[None]`
|
||||||
79 | d: None | (Literal[None] | None)
|
79 | d: None | (Literal[None] | None)
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
80 | e: None | ((None | Literal[None]) | None) | None
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
||||||
|
|
@ -474,24 +473,5 @@ PYI061.py:80:28: PYI061 Use `None` rather than `Literal[None]`
|
||||||
79 | d: None | (Literal[None] | None)
|
79 | d: None | (Literal[None] | None)
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
80 | e: None | ((None | Literal[None]) | None) | None
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.py:81:12: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
79 | d: None | (Literal[None] | None)
|
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.py:81:28: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
79 | d: None | (Literal[None] | None)
|
|
||||||
80 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
81 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,6 @@ PYI061.pyi:54:20: PYI061 Use `None` rather than `Literal[None]`
|
||||||
54 | d: None | (Literal[None] | None)
|
54 | d: None | (Literal[None] | None)
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
55 | e: None | ((None | Literal[None]) | None) | None
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
||||||
|
|
@ -301,24 +300,5 @@ PYI061.pyi:55:28: PYI061 Use `None` rather than `Literal[None]`
|
||||||
54 | d: None | (Literal[None] | None)
|
54 | d: None | (Literal[None] | None)
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
55 | e: None | ((None | Literal[None]) | None) | None
|
||||||
| ^^^^ PYI061
|
| ^^^^ PYI061
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.pyi:56:12: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
54 | d: None | (Literal[None] | None)
|
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
||||||
= help: Replace with `None`
|
|
||||||
|
|
||||||
PYI061.pyi:56:28: PYI061 Use `None` rather than `Literal[None]`
|
|
||||||
|
|
|
||||||
54 | d: None | (Literal[None] | None)
|
|
||||||
55 | e: None | ((None | Literal[None]) | None) | None
|
|
||||||
56 | f: Literal[None] | Literal[None]
|
|
||||||
| ^^^^ PYI061
|
|
||||||
|
|
|
|
||||||
= help: Replace with `None`
|
= help: Replace with `None`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue