mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 21:25:08 +00:00
[flake8-pyi
] Expand Optional[A]
to A | None
(PYI016
) (#18572)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
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 (linux, release) (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) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (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 / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
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 (linux, release) (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) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (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 / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary Under preview 🧪 I've expanded rule `PYI016` to also flag type union duplicates containing `None` and `Optional`. ## Test Plan Examples/tests have been added. I've made sure that the existing examples did not change unless preview is enabled. ## Relevant Issues * https://github.com/astral-sh/ruff/issues/18508 (discussing introducing/extending a rule to flag `Optional[None]`) * https://github.com/astral-sh/ruff/issues/18546 (where I discussed this addition with @AlexWaygood) --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
parent
96f3c8d1ab
commit
6802c4702f
13 changed files with 2747 additions and 25 deletions
|
@ -428,12 +428,52 @@ pub fn is_sys_version_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> boo
|
|||
pub fn traverse_union<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr)
|
||||
where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
traverse_union_options(func, semantic, expr, UnionTraversalOptions::default());
|
||||
}
|
||||
|
||||
/// Traverse a "union" type annotation, applying `func` to each union member.
|
||||
///
|
||||
/// Supports traversal of `Union`, `|`, and `Optional` union expressions.
|
||||
///
|
||||
/// The function is called with each expression in the union (excluding declarations of nested
|
||||
/// unions) and the parent expression.
|
||||
pub fn traverse_union_and_optional<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr)
|
||||
where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
traverse_union_options(
|
||||
func,
|
||||
semantic,
|
||||
expr,
|
||||
UnionTraversalOptions {
|
||||
traverse_optional: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
/// Options for traversing union types.
|
||||
///
|
||||
/// See also [`traverse_union_options`].
|
||||
struct UnionTraversalOptions {
|
||||
traverse_optional: bool,
|
||||
}
|
||||
|
||||
fn traverse_union_options<'a, F>(
|
||||
func: &mut F,
|
||||
semantic: &SemanticModel,
|
||||
expr: &'a Expr,
|
||||
options: UnionTraversalOptions,
|
||||
) where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
fn inner<'a, F>(
|
||||
func: &mut F,
|
||||
semantic: &SemanticModel,
|
||||
expr: &'a Expr,
|
||||
parent: Option<&'a Expr>,
|
||||
options: UnionTraversalOptions,
|
||||
) where
|
||||
F: FnMut(&'a Expr, &'a Expr),
|
||||
{
|
||||
|
@ -456,25 +496,31 @@ where
|
|||
// in the order they appear in the source code.
|
||||
|
||||
// Traverse the left then right arms
|
||||
inner(func, semantic, left, Some(expr));
|
||||
inner(func, semantic, right, Some(expr));
|
||||
inner(func, semantic, left, Some(expr), options);
|
||||
inner(func, semantic, right, Some(expr), options);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) `Union[x, y]`
|
||||
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
|
||||
// Ex) `Union[x, y]`
|
||||
if semantic.match_typing_expr(value, "Union") {
|
||||
if let Expr::Tuple(tuple) = &**slice {
|
||||
// Traverse each element of the tuple within the union recursively to handle cases
|
||||
// such as `Union[..., Union[...]]`
|
||||
tuple
|
||||
.iter()
|
||||
.for_each(|elem| inner(func, semantic, elem, Some(expr)));
|
||||
.for_each(|elem| inner(func, semantic, elem, Some(expr), options));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) `Union[Union[a, b]]` and `Union[a | b | c]`
|
||||
inner(func, semantic, slice, Some(expr));
|
||||
inner(func, semantic, slice, Some(expr), options);
|
||||
return;
|
||||
}
|
||||
// Ex) `Optional[x]`
|
||||
if options.traverse_optional && semantic.match_typing_expr(value, "Optional") {
|
||||
inner(func, semantic, value, Some(expr), options);
|
||||
inner(func, semantic, slice, Some(expr), options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -485,7 +531,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
inner(func, semantic, expr, None);
|
||||
inner(func, semantic, expr, None, options);
|
||||
}
|
||||
|
||||
/// Traverse a "literal" type annotation, applying `func` to each literal member.
|
||||
|
|
|
@ -1604,7 +1604,7 @@ impl<'a> SemanticModel<'a> {
|
|||
let mut parent_expressions = self.current_expressions().skip(1);
|
||||
|
||||
match parent_expressions.next() {
|
||||
// The parent expression is of the inner union is a single `typing.Union`.
|
||||
// The parent expression of the inner union is a single `typing.Union`.
|
||||
// Ex) `Union[Union[a, b]]`
|
||||
Some(Expr::Subscript(parent)) => self.match_typing_expr(&parent.value, "Union"),
|
||||
// The parent expression is of the inner union is a tuple with two or more
|
||||
|
@ -1624,6 +1624,18 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the model is directly inside an Optional (e.g., the inner `Union` in
|
||||
/// `Optional[Union[int, str]]`).
|
||||
pub fn inside_optional(&self) -> bool {
|
||||
let mut parent_expressions = self.current_expressions().skip(1);
|
||||
matches!(
|
||||
parent_expressions.next(),
|
||||
// The parent expression is a single `typing.Optional`.
|
||||
// Ex) `Optional[EXPR]`
|
||||
Some(Expr::Subscript(parent)) if self.match_typing_expr(&parent.value, "Optional")
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in
|
||||
/// `Literal[Literal[int, str], float]`).
|
||||
pub fn in_nested_literal(&self) -> bool {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue