Extend nested union detection to handle bitwise or Union expressions (#6399)

## Summary

We have some logic in the expression analyzer method to avoid
re-checking the inner `Union` in `Union[Union[...]]`, since the methods
that analyze `Union` expressions already recurse. Elsewhere, we have
logic to avoid re-checking the inner `|` in `int | (int | str)`, for the
same reason.

This PR unifies that logic into a single method _and_ ensures that, just
as we recurse over both `Union` and `|`, we also detect that we're in
_either_ kind of nested union.

Closes https://github.com/astral-sh/ruff/issues/6285.

## Test Plan

Added some new snapshots.
This commit is contained in:
Charlie Marsh 2023-08-07 15:17:26 -04:00 committed by GitHub
parent 98d4657961
commit 26098b8d91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 627 additions and 167 deletions

View file

@ -6,7 +6,7 @@ use smallvec::smallvec;
use ruff_python_ast::call_path::{collect_call_path, from_unqualified_name, CallPath};
use ruff_python_ast::helpers::from_relative_import;
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
use ruff_python_ast::{self as ast, Expr, Operator, Ranged, Stmt};
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_python_stdlib::typing::is_typing_extension;
use ruff_text_size::{TextRange, TextSize};
@ -1022,6 +1022,34 @@ impl<'a> SemanticModel<'a> {
false
}
/// Return `true` if the model is in a nested union expression (e.g., the inner `Union` in
/// `Union[Union[int, str], float]`).
pub fn in_nested_union(&self) -> bool {
// Ex) `Union[Union[int, str], float]`
if self
.current_expression_grandparent()
.and_then(Expr::as_subscript_expr)
.is_some_and(|parent| self.match_typing_expr(&parent.value, "Union"))
{
return true;
}
// Ex) `int | Union[str, float]`
if self.current_expression_parent().is_some_and(|parent| {
matches!(
parent,
Expr::BinOp(ast::ExprBinOp {
op: Operator::BitOr,
..
})
)
}) {
return true;
}
false
}
/// Returns `true` if the given [`BindingId`] is used.
pub fn is_used(&self, binding_id: BindingId) -> bool {
self.bindings[binding_id].is_used()