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 3258eaf736f37..d37f178bbb938 100644 --- a/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py +++ b/crates/ruff/resources/test/fixtures/flake8_annotations/annotation_presence.py @@ -1,4 +1,5 @@ from typing import Any, Type +from typing_extensions import override # Error def foo(a, b): @@ -94,6 +95,31 @@ def foo(self: "Foo", a: int, *params: Any, **options: str) -> int: def foo(self: "Foo", a: int, *params: str, **options: Any) -> int: pass + # ANN401 + @override + def foo(self: "Foo", a: Any, *params: str, **options: str) -> int: + pass + + # ANN401 + @override + def foo(self: "Foo", a: int, *params: str, **options: str) -> Any: + pass + + # ANN401 + @override + def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int: + pass + + # ANN401 + @override + def foo(self: "Foo", a: int, *params: Any, **options: str) -> int: + pass + + # ANN401 + @override + def foo(self: "Foo", a: int, *params: str, **options: Any) -> int: + pass + # OK @classmethod def foo(cls: Type["Foo"], a: int, b: int) -> int: diff --git a/crates/ruff/src/rules/flake8_annotations/helpers.rs b/crates/ruff/src/rules/flake8_annotations/helpers.rs index 4145c13e72d84..6bf9b092d33f7 100644 --- a/crates/ruff/src/rules/flake8_annotations/helpers.rs +++ b/crates/ruff/src/rules/flake8_annotations/helpers.rs @@ -6,13 +6,16 @@ use ruff_python_semantic::definition::{Definition, Member, MemberKind}; use crate::checkers::ast::Checker; -pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr>, &Vec) { +pub(super) fn match_function_def( + stmt: &Stmt, +) -> (&str, &Arguments, Option<&Expr>, &Vec, &Vec) { match &stmt.node { StmtKind::FunctionDef(ast::StmtFunctionDef { name, args, returns, body, + decorator_list, .. }) | StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { @@ -20,8 +23,15 @@ pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr args, returns, body, + decorator_list, .. - }) => (name, args, returns.as_ref().map(|expr| &**expr), body), + }) => ( + name, + args, + returns.as_ref().map(|expr| &**expr), + body, + decorator_list, + ), _ => panic!("Found non-FunctionDef in match_name"), } } diff --git a/crates/ruff/src/rules/flake8_annotations/rules.rs b/crates/ruff/src/rules/flake8_annotations/rules.rs index 5a9f13ff81380..52334fdd21b25 100644 --- a/crates/ruff/src/rules/flake8_annotations/rules.rs +++ b/crates/ruff/src/rules/flake8_annotations/rules.rs @@ -434,10 +434,11 @@ fn check_dynamically_typed( annotation: &Expr, func: F, diagnostics: &mut Vec, + is_overridden: bool, ) where F: FnOnce() -> String, { - if checker.ctx.match_typing_expr(annotation, "Any") { + if !is_overridden && checker.ctx.match_typing_expr(annotation, "Any") { diagnostics.push(Diagnostic::new( AnyType { name: func() }, annotation.range(), @@ -468,7 +469,7 @@ pub(crate) fn definition( _ => return vec![], }; - let (name, args, returns, body) = match_function_def(stmt); + let (name, args, returns, body, decorator_list) = match_function_def(stmt); // Keep track of whether we've seen any typed arguments or return values. let mut has_any_typed_arg = false; // Any argument has been typed? let mut has_typed_return = false; // Return value has been typed? @@ -478,6 +479,8 @@ pub(crate) fn definition( // unless configured to suppress ANN* for declarations that are fully untyped. let mut diagnostics = Vec::new(); + let is_overridden = visibility::is_override(&checker.ctx, decorator_list); + // ANN001, ANN401 for arg in args .posonlyargs @@ -500,6 +503,7 @@ pub(crate) fn definition( annotation, || arg.node.arg.to_string(), &mut diagnostics, + is_overridden, ); } } else { @@ -529,7 +533,13 @@ pub(crate) fn definition( if !checker.settings.flake8_annotations.allow_star_arg_any { if checker.settings.rules.enabled(Rule::AnyType) { let name = &arg.node.arg; - check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics); + check_dynamically_typed( + checker, + expr, + || format!("*{name}"), + &mut diagnostics, + is_overridden, + ); } } } else { @@ -560,6 +570,7 @@ pub(crate) fn definition( expr, || format!("**{name}"), &mut diagnostics, + is_overridden, ); } } @@ -612,7 +623,13 @@ pub(crate) fn definition( if let Some(expr) = &returns { has_typed_return = true; if checker.settings.rules.enabled(Rule::AnyType) { - check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics); + check_dynamically_typed( + checker, + expr, + || name.to_string(), + &mut diagnostics, + is_overridden, + ); } } else if !( // Allow omission of return annotation if the function only returns `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 85337e599e468..61d69da4b50e3 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 @@ -1,189 +1,189 @@ --- source: crates/ruff/src/rules/flake8_annotations/mod.rs --- -annotation_presence.py:4:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:5:5: ANN201 Missing return type annotation for public function `foo` | -4 | # Error -5 | def foo(a, b): +5 | # Error +6 | def foo(a, b): | ^^^ ANN201 -6 | pass +7 | pass | -annotation_presence.py:4:9: ANN001 Missing type annotation for function argument `a` +annotation_presence.py:5:9: ANN001 Missing type annotation for function argument `a` | -4 | # Error -5 | def foo(a, b): +5 | # Error +6 | def foo(a, b): | ^ ANN001 -6 | pass +7 | pass | -annotation_presence.py:4:12: ANN001 Missing type annotation for function argument `b` +annotation_presence.py:5:12: ANN001 Missing type annotation for function argument `b` | -4 | # Error -5 | def foo(a, b): +5 | # Error +6 | def foo(a, b): | ^ ANN001 -6 | pass +7 | pass | -annotation_presence.py:9:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:10:5: ANN201 Missing return type annotation for public function `foo` | - 9 | # Error -10 | def foo(a: int, b): +10 | # Error +11 | def foo(a: int, b): | ^^^ ANN201 -11 | pass +12 | pass | -annotation_presence.py:9:17: ANN001 Missing type annotation for function argument `b` +annotation_presence.py:10:17: ANN001 Missing type annotation for function argument `b` | - 9 | # Error -10 | def foo(a: int, b): +10 | # Error +11 | def foo(a: int, b): | ^ ANN001 -11 | pass +12 | pass | -annotation_presence.py:14:17: ANN001 Missing type annotation for function argument `b` +annotation_presence.py:15:17: ANN001 Missing type annotation for function argument `b` | -14 | # Error -15 | def foo(a: int, b) -> int: +15 | # Error +16 | def foo(a: int, b) -> int: | ^ ANN001 -16 | pass +17 | pass | -annotation_presence.py:19:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:20:5: ANN201 Missing return type annotation for public function `foo` | -19 | # Error -20 | def foo(a: int, b: int): +20 | # Error +21 | def foo(a: int, b: int): | ^^^ ANN201 -21 | pass +22 | pass | -annotation_presence.py:24:5: ANN201 Missing return type annotation for public function `foo` +annotation_presence.py:25:5: ANN201 Missing return type annotation for public function `foo` | -24 | # Error -25 | def foo(): +25 | # Error +26 | def foo(): | ^^^ ANN201 -26 | pass +27 | pass | -annotation_presence.py:44:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a` +annotation_presence.py:45:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a` | -44 | # ANN401 -45 | def foo(a: Any, *args: str, **kwargs: str) -> int: +45 | # ANN401 +46 | def foo(a: Any, *args: str, **kwargs: str) -> int: | ^^^ ANN401 -46 | pass +47 | pass | -annotation_presence.py:49:47: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo` +annotation_presence.py:50:47: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo` | -49 | # ANN401 -50 | def foo(a: int, *args: str, **kwargs: str) -> Any: +50 | # ANN401 +51 | def foo(a: int, *args: str, **kwargs: str) -> Any: | ^^^ ANN401 -51 | pass +52 | pass | -annotation_presence.py:54:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args` +annotation_presence.py:55:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args` | -54 | # ANN401 -55 | def foo(a: int, *args: Any, **kwargs: Any) -> int: +55 | # ANN401 +56 | def foo(a: int, *args: Any, **kwargs: Any) -> int: | ^^^ ANN401 -56 | pass +57 | pass | -annotation_presence.py:54:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs` +annotation_presence.py:55:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs` | -54 | # ANN401 -55 | def foo(a: int, *args: Any, **kwargs: Any) -> int: +55 | # ANN401 +56 | def foo(a: int, *args: Any, **kwargs: Any) -> int: | ^^^ ANN401 -56 | pass +57 | pass | -annotation_presence.py:59:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args` +annotation_presence.py:60:24: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*args` | -59 | # ANN401 -60 | def foo(a: int, *args: Any, **kwargs: str) -> int: +60 | # ANN401 +61 | def foo(a: int, *args: Any, **kwargs: str) -> int: | ^^^ ANN401 -61 | pass +62 | pass | -annotation_presence.py:64:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs` +annotation_presence.py:65:39: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**kwargs` | -64 | # ANN401 -65 | def foo(a: int, *args: str, **kwargs: Any) -> int: +65 | # ANN401 +66 | def foo(a: int, *args: str, **kwargs: Any) -> int: | ^^^ ANN401 -66 | pass +67 | pass | -annotation_presence.py:74:13: ANN101 Missing type annotation for `self` in method +annotation_presence.py:75:13: ANN101 Missing type annotation for `self` in method | -74 | # ANN101 -75 | def foo(self, a: int, b: int) -> int: +75 | # ANN101 +76 | def foo(self, a: int, b: int) -> int: | ^^^^ ANN101 -76 | pass +77 | pass | -annotation_presence.py:78:29: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a` +annotation_presence.py:79:29: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a` | -78 | # ANN401 -79 | def foo(self: "Foo", a: Any, *params: str, **options: str) -> int: +79 | # ANN401 +80 | def foo(self: "Foo", a: Any, *params: str, **options: str) -> int: | ^^^ ANN401 -80 | pass +81 | pass | -annotation_presence.py:82:67: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo` +annotation_presence.py:83:67: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `foo` | -82 | # ANN401 -83 | def foo(self: "Foo", a: int, *params: str, **options: str) -> Any: +83 | # ANN401 +84 | def foo(self: "Foo", a: int, *params: str, **options: str) -> Any: | ^^^ ANN401 -84 | pass +85 | pass | -annotation_presence.py:86:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params` +annotation_presence.py:87:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params` | -86 | # ANN401 -87 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int: +87 | # ANN401 +88 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int: | ^^^ ANN401 -88 | pass +89 | pass | -annotation_presence.py:86:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options` +annotation_presence.py:87:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options` | -86 | # ANN401 -87 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int: +87 | # ANN401 +88 | def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int: | ^^^ ANN401 -88 | pass +89 | pass | -annotation_presence.py:90:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params` +annotation_presence.py:91:43: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `*params` | -90 | # ANN401 -91 | def foo(self: "Foo", a: int, *params: Any, **options: str) -> int: +91 | # ANN401 +92 | def foo(self: "Foo", a: int, *params: Any, **options: str) -> int: | ^^^ ANN401 -92 | pass +93 | pass | -annotation_presence.py:94:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options` +annotation_presence.py:95:59: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `**options` | -94 | # ANN401 -95 | def foo(self: "Foo", a: int, *params: str, **options: Any) -> int: +95 | # ANN401 +96 | def foo(self: "Foo", a: int, *params: str, **options: Any) -> int: | ^^^ ANN401 -96 | pass +97 | pass | -annotation_presence.py:104:13: ANN102 Missing type annotation for `cls` in classmethod +annotation_presence.py:130:13: ANN102 Missing type annotation for `cls` in classmethod | -104 | # ANN102 -105 | @classmethod -106 | def foo(cls, a: int, b: int) -> int: +130 | # ANN102 +131 | @classmethod +132 | def foo(cls, a: int, b: int) -> int: | ^^^ ANN102 -107 | pass +133 | pass | -annotation_presence.py:108:13: ANN101 Missing type annotation for `self` in method +annotation_presence.py:134:13: ANN101 Missing type annotation for `self` in method | -108 | # ANN101 -109 | def foo(self, /, a: int, b: int) -> int: +134 | # ANN101 +135 | def foo(self, /, a: int, b: int) -> int: | ^^^^ ANN101 -110 | pass +136 | pass |