Avoid stack overflow for non-BitOr binary types (#5743)

## Summary

Closes #5742.
This commit is contained in:
Charlie Marsh 2023-07-13 14:23:40 -04:00 committed by GitHub
parent 48309cad08
commit 51a313cca4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 72 deletions

View file

@ -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: ...

View file

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

View file

@ -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,
@ -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),
@ -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,
..
@ -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)
@ -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)