Skip to content

Commit

Permalink
Avoid stack overflow for non-BitOr binary types (astral-sh#5743)
Browse files Browse the repository at this point in the history
## Summary

Closes astral-sh#5742.
  • Loading branch information
charliermarsh authored and evanrittenhouse committed Jul 19, 2023
1 parent 4d5841f commit 22cdf75
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def f(a: Union[str, bytes]) -> None: ...
def f(a: Optional[str]) -> None: ...
def f(a: Annotated[str, ...]) -> None: ...
def f(a: "Union[str, bytes]") -> None: ...
def f(a: int + int) -> None: ...

# ANN401
def f(a: Any | int) -> None: ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,59 +186,59 @@ annotation_presence.py:134:13: ANN101 Missing type annotation for `self` in meth
135 | pass
|

annotation_presence.py:148:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:149:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
147 | # ANN401
148 | def f(a: Any | int) -> None: ...
148 | # ANN401
149 | def f(a: Any | int) -> None: ...
| ^^^^^^^^^ ANN401
149 | def f(a: int | Any) -> None: ...
150 | def f(a: Union[str, bytes, Any]) -> None: ...
150 | def f(a: int | Any) -> None: ...
151 | def f(a: Union[str, bytes, Any]) -> None: ...
|

annotation_presence.py:149:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:150:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
147 | # ANN401
148 | def f(a: Any | int) -> None: ...
149 | def f(a: int | Any) -> None: ...
148 | # ANN401
149 | def f(a: Any | int) -> None: ...
150 | def f(a: int | Any) -> None: ...
| ^^^^^^^^^ ANN401
150 | def f(a: Union[str, bytes, Any]) -> None: ...
151 | def f(a: Optional[Any]) -> None: ...
151 | def f(a: Union[str, bytes, Any]) -> None: ...
152 | def f(a: Optional[Any]) -> None: ...
|

annotation_presence.py:150:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:151:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
148 | def f(a: Any | int) -> None: ...
149 | def f(a: int | Any) -> None: ...
150 | def f(a: Union[str, bytes, Any]) -> None: ...
149 | def f(a: Any | int) -> None: ...
150 | def f(a: int | Any) -> None: ...
151 | def f(a: Union[str, bytes, Any]) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^ ANN401
151 | def f(a: Optional[Any]) -> None: ...
152 | def f(a: Annotated[Any, ...]) -> None: ...
152 | def f(a: Optional[Any]) -> None: ...
153 | def f(a: Annotated[Any, ...]) -> None: ...
|

annotation_presence.py:151:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:152:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
149 | def f(a: int | Any) -> None: ...
150 | def f(a: Union[str, bytes, Any]) -> None: ...
151 | def f(a: Optional[Any]) -> None: ...
150 | def f(a: int | Any) -> None: ...
151 | def f(a: Union[str, bytes, Any]) -> None: ...
152 | def f(a: Optional[Any]) -> None: ...
| ^^^^^^^^^^^^^ ANN401
152 | def f(a: Annotated[Any, ...]) -> None: ...
153 | def f(a: "Union[str, bytes, Any]") -> None: ...
153 | def f(a: Annotated[Any, ...]) -> None: ...
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
|

annotation_presence.py:152:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:153:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
150 | def f(a: Union[str, bytes, Any]) -> None: ...
151 | def f(a: Optional[Any]) -> None: ...
152 | def f(a: Annotated[Any, ...]) -> None: ...
151 | def f(a: Union[str, bytes, Any]) -> None: ...
152 | def f(a: Optional[Any]) -> None: ...
153 | def f(a: Annotated[Any, ...]) -> None: ...
| ^^^^^^^^^^^^^^^^^^^ ANN401
153 | def f(a: "Union[str, bytes, Any]") -> None: ...
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
|

annotation_presence.py:153:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
annotation_presence.py:154:10: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
151 | def f(a: Optional[Any]) -> None: ...
152 | def f(a: Annotated[Any, ...]) -> None: ...
153 | def f(a: "Union[str, bytes, Any]") -> None: ...
152 | def f(a: Optional[Any]) -> None: ...
153 | def f(a: Annotated[Any, ...]) -> None: ...
154 | def f(a: "Union[str, bytes, Any]") -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^^^ ANN401
|

Expand Down
47 changes: 9 additions & 38 deletions crates/ruff/src/rules/ruff/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,6 @@ use ruff_python_ast::typing::parse_type_annotation;
use ruff_python_semantic::SemanticModel;
use ruff_python_stdlib::sys::is_known_standard_library;

/// Custom iterator to collect all the `|` separated expressions in a PEP 604
/// union type.
struct PEP604UnionIterator<'a> {
stack: Vec<&'a Expr>,
}

impl<'a> PEP604UnionIterator<'a> {
fn new(expr: &'a Expr) -> Self {
Self { stack: vec![expr] }
}
}

impl<'a> Iterator for PEP604UnionIterator<'a> {
type Item = &'a Expr;

fn next(&mut self) -> Option<Self::Item> {
while let Some(expr) = self.stack.pop() {
match expr {
Expr::BinOp(ast::ExprBinOp {
left,
op: Operator::BitOr,
right,
..
}) => {
self.stack.push(left);
self.stack.push(right);
}
_ => return Some(expr),
}
}
None
}
}

/// Returns `true` if the given call path is a known type.
///
/// A known type is either a builtin type, any object from the standard library,
Expand Down Expand Up @@ -80,7 +46,7 @@ enum TypingTarget<'a> {
Union(&'a Expr),

/// A PEP 604 union type e.g., `int | str`.
PEP604Union(&'a Expr),
PEP604Union(&'a Expr, &'a Expr),

/// A `typing.Literal` type e.g., `Literal[1, 2, 3]`.
Literal(&'a Expr),
Expand Down Expand Up @@ -135,7 +101,12 @@ impl<'a> TypingTarget<'a> {
)
}
}
Expr::BinOp(..) => Some(TypingTarget::PEP604Union(expr)),
Expr::BinOp(ast::ExprBinOp {
left,
op: Operator::BitOr,
right,
..
}) => Some(TypingTarget::PEP604Union(left, right)),
Expr::Constant(ast::ExprConstant {
value: Constant::None,
..
Expand Down Expand Up @@ -197,7 +168,7 @@ impl<'a> TypingTarget<'a> {
new_target.contains_none(semantic, locator, minor_version)
})
}),
TypingTarget::PEP604Union(expr) => PEP604UnionIterator::new(expr).any(|element| {
TypingTarget::PEP604Union(left, right) => [left, right].iter().any(|element| {
TypingTarget::try_from_expr(element, semantic, locator, minor_version)
.map_or(true, |new_target| {
new_target.contains_none(semantic, locator, minor_version)
Expand Down Expand Up @@ -239,7 +210,7 @@ impl<'a> TypingTarget<'a> {
new_target.contains_any(semantic, locator, minor_version)
})
}),
TypingTarget::PEP604Union(expr) => PEP604UnionIterator::new(expr).any(|element| {
TypingTarget::PEP604Union(left, right) => [left, right].iter().any(|element| {
TypingTarget::try_from_expr(element, semantic, locator, minor_version)
.map_or(true, |new_target| {
new_target.contains_any(semantic, locator, minor_version)
Expand Down

0 comments on commit 22cdf75

Please sign in to comment.