diff --git a/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py b/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py index 7778c85072..6c51226268 100644 --- a/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py +++ b/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py @@ -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: ... diff --git a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__defaults.snap b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__defaults.snap index 7cd87414d4..54e30dd686 100644 --- a/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__defaults.snap +++ b/crates/ruff/src/rules/flake8_annotations/snapshots/ruff__rules__flake8_annotations__tests__defaults.snap @@ -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 | diff --git a/crates/ruff/src/rules/ruff/typing.rs b/crates/ruff/src/rules/ruff/typing.rs index 35baa3f9ce..e239ada9f1 100644 --- a/crates/ruff/src/rules/ruff/typing.rs +++ b/crates/ruff/src/rules/ruff/typing.rs @@ -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 { - 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)