From e2785f3fb690860edadada2a4bf98ed1caeb4848 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 12 Feb 2024 12:06:20 -0500 Subject: [PATCH] [`flake8-pyi`] Ignore 'unused' private type dicts in class scopes (#9952) ## Summary If these are defined within class scopes, they're actually attributes of the class, and can be accessed through the class itself. (We preserve our existing behavior for `.pyi` files.) Closes https://github.com/astral-sh/ruff/issues/9948. --- .../test/fixtures/flake8_pyi/PYI049.py | 14 +++++++++- .../test/fixtures/flake8_pyi/PYI049.pyi | 10 +++++++ .../checkers/ast/analyze/deferred_scopes.rs | 26 +++++++++++-------- ...__flake8_pyi__tests__PYI049_PYI049.py.snap | 8 +++--- ..._flake8_pyi__tests__PYI049_PYI049.pyi.snap | 9 +++++++ 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.py index 237d6bb151f57..d1cd972597a31 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.py @@ -11,13 +11,25 @@ class _UnusedTypedDict2(typing.TypedDict): class _UsedTypedDict(TypedDict): - foo: bytes + foo: bytes class _CustomClass(_UsedTypedDict): bar: list[int] + _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int}) _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes}) + def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ... + + +# In `.py` files, we don't flag unused definitions in class scopes (unlike in `.pyi` +# files). +class _CustomClass3: + class _UnusedTypeDict4(TypedDict): + pass + + def method(self) -> None: + _CustomClass3._UnusedTypeDict4() diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.pyi index 29612a224fed6..8703409eae0e1 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI049.pyi @@ -35,3 +35,13 @@ _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int}) _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes}) def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ... + + +# In `.pyi` files, we flag unused definitions in class scopes as well as in the global +# scope (unlike in `.py` files). +class _CustomClass3: + class _UnusedTypeDict4(TypedDict): + pass + + def method(self) -> None: + _CustomClass3._UnusedTypeDict4() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 64938222d5520..e070ae5adbc0b 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -281,17 +281,21 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { } } - if checker.enabled(Rule::UnusedPrivateTypeVar) { - flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics); - } - if checker.enabled(Rule::UnusedPrivateProtocol) { - flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics); - } - if checker.enabled(Rule::UnusedPrivateTypeAlias) { - flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics); - } - if checker.enabled(Rule::UnusedPrivateTypedDict) { - flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics); + if checker.source_type.is_stub() + || matches!(scope.kind, ScopeKind::Module | ScopeKind::Function(_)) + { + if checker.enabled(Rule::UnusedPrivateTypeVar) { + flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics); + } + if checker.enabled(Rule::UnusedPrivateProtocol) { + flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics); + } + if checker.enabled(Rule::UnusedPrivateTypeAlias) { + flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics); + } + if checker.enabled(Rule::UnusedPrivateTypedDict) { + flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics); + } } if checker.enabled(Rule::AsyncioDanglingTask) { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.py.snap index 577fa93a43c39..c50ecfef7c7b1 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.py.snap @@ -15,13 +15,11 @@ PYI049.py:9:7: PYI049 Private TypedDict `_UnusedTypedDict2` is never used 10 | bar: int | -PYI049.py:20:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used +PYI049.py:21:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used | -18 | bar: list[int] -19 | -20 | _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int}) +21 | _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int}) | ^^^^^^^^^^^^^^^^^ PYI049 -21 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes}) +22 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes}) | diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.pyi.snap index 4235d1fe38191..189b4bade5d8d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI049_PYI049.pyi.snap @@ -24,4 +24,13 @@ PYI049.pyi:34:1: PYI049 Private TypedDict `_UnusedTypedDict3` is never used 35 | _UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes}) | +PYI049.pyi:43:11: PYI049 Private TypedDict `_UnusedTypeDict4` is never used + | +41 | # scope (unlike in `.py` files). +42 | class _CustomClass3: +43 | class _UnusedTypeDict4(TypedDict): + | ^^^^^^^^^^^^^^^^ PYI049 +44 | pass + | +