@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
55
66use ruff_python_ast:: helpers:: from_relative_import;
77use ruff_python_ast:: name:: { QualifiedName , UnqualifiedName } ;
8- use ruff_python_ast:: { self as ast, Expr , ExprContext , Operator , PySourceType , Stmt } ;
8+ use ruff_python_ast:: { self as ast, Expr , ExprContext , PySourceType , Stmt } ;
99use ruff_text_size:: { Ranged , TextRange , TextSize } ;
1010
1111use crate :: binding:: {
@@ -1508,52 +1508,48 @@ impl<'a> SemanticModel<'a> {
15081508 /// Return `true` if the model is in a nested union expression (e.g., the inner `Union` in
15091509 /// `Union[Union[int, str], float]`).
15101510 pub fn in_nested_union ( & self ) -> bool {
1511- // Ex) `Union[Union[int, str], float]`
1512- if self
1513- . current_expression_grandparent ( )
1514- . and_then ( Expr :: as_subscript_expr )
1515- . is_some_and ( |parent| self . match_typing_expr ( & parent . value , " Union" ) )
1516- {
1517- return true ;
1518- }
1519-
1520- // Ex) `int | Union[str, float]`
1521- if self . current_expression_parent ( ) . is_some_and ( |parent| {
1522- matches ! (
1523- parent ,
1524- Expr :: BinOp ( ast :: ExprBinOp {
1525- op : Operator :: BitOr ,
1526- . .
1527- } )
1528- )
1529- } ) {
1530- return true ;
1511+ let mut parent_expressions = self . current_expressions ( ) . skip ( 1 ) ;
1512+
1513+ match parent_expressions . next ( ) {
1514+ // The parent expression is of the inner union is a single `typing.Union`.
1515+ // Ex) ` Union[Union[a, b]]`
1516+ Some ( Expr :: Subscript ( parent ) ) => self . match_typing_expr ( & parent . value , "Union" ) ,
1517+ // The parent expression is of the inner union is a tuple with two or more
1518+ // comma-separated elements and the parent of that tuple is a `typing.Union`.
1519+ // Ex) `Union[Union[a, b], Union[c, d]]`
1520+ Some ( Expr :: Tuple ( _ ) ) => parent_expressions
1521+ . next ( )
1522+ . and_then ( Expr :: as_subscript_expr )
1523+ . is_some_and ( |grandparent| self . match_typing_expr ( & grandparent . value , "Union" ) ) ,
1524+ // The parent expression of the inner union is a PEP604-style union.
1525+ // Ex) `a | b | c` or `Union[a, b] | c`
1526+ // In contrast to `typing.Union`, PEP604-style unions are always binary operations, e.g .
1527+ // the expression `a | b | c` is represented by two binary unions: `(a | b) | c`.
1528+ Some ( Expr :: BinOp ( bin_op ) ) => bin_op . op . is_bit_or ( ) ,
1529+ // Not a nested union otherwise.
1530+ _ => false ,
15311531 }
1532-
1533- // Ex) `Union[Union[int, int]]` or `Union[int | int]`
1534- self . current_expression_parent ( )
1535- . and_then ( Expr :: as_subscript_expr)
1536- . is_some_and ( |parent| self . match_typing_expr ( & parent. value , "Union" ) )
15371532 }
15381533
15391534 /// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in
15401535 /// `Literal[Literal[int, str], float]`).
15411536 pub fn in_nested_literal ( & self ) -> bool {
1542- // Parent is union or tuple
1543- // Ex) `Literal[Literal[int, str], float]`
1544- if self
1545- . current_expression_grandparent ( )
1546- . and_then ( Expr :: as_subscript_expr)
1547- . is_some_and ( |parent| self . match_typing_expr ( & parent. value , "Literal" ) )
1548- {
1549- return true ;
1537+ let mut parent_expressions = self . current_expressions ( ) . skip ( 1 ) ;
1538+
1539+ match parent_expressions. next ( ) {
1540+ // The parent expression of the current `Literal` is a tuple, and the
1541+ // grandparent is a `Literal`.
1542+ // Ex) `Literal[Literal[str], Literal[int]]`
1543+ Some ( Expr :: Tuple ( _) ) => parent_expressions
1544+ . next ( )
1545+ . and_then ( Expr :: as_subscript_expr)
1546+ . is_some_and ( |grandparent| self . match_typing_expr ( & grandparent. value , "Literal" ) ) ,
1547+ // The parent expression of the current `Literal` is also a `Literal`.
1548+ // Ex) `Literal[Literal[str]]`
1549+ Some ( Expr :: Subscript ( parent) ) => self . match_typing_expr ( & parent. value , "Literal" ) ,
1550+ // Not a nested literal otherwise
1551+ _ => false ,
15501552 }
1551-
1552- // Parent is `Literal`
1553- // Ex) `Literal[Literal[int]]`
1554- self . current_expression_parent ( )
1555- . and_then ( Expr :: as_subscript_expr)
1556- . is_some_and ( |parent| self . match_typing_expr ( & parent. value , "Literal" ) )
15571553 }
15581554
15591555 /// Returns `true` if `left` and `right` are in the same branches of an `if`, `match`, or
0 commit comments