diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs index 9d0dd1e7c1..f9e37c7840 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs @@ -119,22 +119,32 @@ pub(crate) fn redundant_none_literal<'a>(checker: &mut Checker, literal_expr: &' } } +/// If possible, return a [`Fix`] for a violation of this rule. +/// +/// Avoid producing code that would raise an exception when +/// `Literal[None] | None` would be fixed to `None | None`. +/// Instead, do not provide a fix. We don't need to worry about unions +/// that use [`typing.Union`], as `Union[None, None]` is valid Python. +/// See . +/// +/// [`typing.Union`]: https://docs.python.org/3/library/typing.html#typing.Union fn create_fix_edit(semantic: &SemanticModel, literal_expr: &Expr) -> Option { - let mut enclosing_union = None; - for expr in semantic.current_expressions().skip(1).take_while(|expr| { - matches!( - expr, - Expr::BinOp(ExprBinOp { - op: Operator::BitOr, - .. - }) - ) - }) { - enclosing_union = Some(expr); - } + let enclosing_pep604_union = semantic + .current_expressions() + .skip(1) + .take_while(|expr| { + matches!( + expr, + Expr::BinOp(ExprBinOp { + op: Operator::BitOr, + .. + }) + ) + }) + .last(); let mut is_fixable = true; - if let Some(enclosing_union) = enclosing_union { + if let Some(enclosing_pep604_union) = enclosing_pep604_union { traverse_union( &mut |expr, _| { if matches!(expr, Expr::NoneLiteral(_)) { @@ -151,14 +161,9 @@ fn create_fix_edit(semantic: &SemanticModel, literal_expr: &Expr) -> Option bool { - let mut enclosing_union = None; - let mut expression_ancestors = semantic.current_expressions().skip(1); - let mut parent_expr = expression_ancestors.next(); - while let Some(Expr::BinOp(ExprBinOp { - op: Operator::BitOr, - .. - })) = parent_expr - { - enclosing_union = parent_expr; - parent_expr = expression_ancestors.next(); - } +/// Return `true` if this union is a [PEP 604 union] that contains `None`, +/// e.g. `int | Never | None`. +/// +/// Autofixing these unions can be dangerous, +/// as `None | None` results in a runtime exception in Python. +/// +/// [PEP 604 union]: https://docs.python.org/3/library/stdtypes.html#types-union +fn is_pep604_union_with_bare_none(semantic: &SemanticModel) -> bool { + let enclosing_pep604_union = semantic + .current_expressions() + .skip(1) + .take_while(|expr| { + matches!( + expr, + Expr::BinOp(ExprBinOp { + op: Operator::BitOr, + .. + }) + ) + }) + .last(); - let mut is_union_with_bare_none = false; - if let Some(enclosing_union) = enclosing_union { - traverse_union( - &mut |expr, _| { - if matches!(expr, Expr::NoneLiteral(_)) { - is_union_with_bare_none = true; - } - }, - semantic, - enclosing_union, - ); - } + let Some(enclosing_pep604_union) = enclosing_pep604_union else { + return false; + }; - is_union_with_bare_none + let mut union_contains_bare_none = false; + + traverse_union( + &mut |expr, _| { + if matches!(expr, Expr::NoneLiteral(_)) { + union_contains_bare_none = true; + } + }, + semantic, + enclosing_pep604_union, + ); + + union_contains_bare_none }