Skip to content

Commit

Permalink
Implement PYI047 (#6134)
Browse files Browse the repository at this point in the history
## Summary

Checks for the presence of unused private `typing.TypeAlias`
definitions.

ref #848 

## Test Plan

Snapshots and manual runs of flake8
  • Loading branch information
LaBatata101 committed Jul 29, 2023
1 parent 047c211 commit 7838d8c
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 0 deletions.
22 changes: 22 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import typing
import sys
from typing import TypeAlias


_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str

# OK
_UsedPrivateTypeAlias: TypeAlias = int | None

def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...


if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None


def func2(arg: _PrivateTypeAlias) -> None: ...
22 changes: 22 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_pyi/PYI047.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import typing
import sys
from typing import TypeAlias


_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str

# OK
_UsedPrivateTypeAlias: TypeAlias = int | None

def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...


if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None


def func2(arg: _PrivateTypeAlias) -> None: ...
4 changes: 4 additions & 0 deletions crates/ruff/src/checkers/ast/analyze/deferred_scopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
Rule::UnusedLambdaArgument,
Rule::UnusedMethodArgument,
Rule::UnusedPrivateProtocol,
Rule::UnusedPrivateTypeAlias,
Rule::UnusedPrivateTypeVar,
Rule::UnusedStaticMethodArgument,
Rule::UnusedVariable,
Expand Down Expand Up @@ -223,6 +224,9 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
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 matches!(
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
(Flake8Pyi, "046") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateProtocol),
(Flake8Pyi, "047") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeAlias),
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/flake8_pyi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ mod tests {
#[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))]
#[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.py"))]
#[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.pyi"))]
#[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.py"))]
#[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.pyi"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,44 @@ impl Violation for UnusedPrivateProtocol {
}
}

/// ## What it does
/// Checks for the presence of unused private `typing.TypeAlias` definitions.
///
/// ## Why is this bad?
/// A private `typing.TypeAlias` that is defined but not used is likely a
/// mistake, and should either be used, made public, or removed to avoid
/// confusion.
///
/// ## Example
/// ```python
/// import typing
///
/// _UnusedTypeAlias: typing.TypeAlias = int
/// ```
///
/// Use instead:
/// ```python
/// import typing
///
/// _UsedTypeAlias: typing.TypeAlias = int
///
///
/// def func(arg: _UsedTypeAlias) -> _UsedTypeAlias:
/// ...
/// ```
#[violation]
pub struct UnusedPrivateTypeAlias {
name: String,
}

impl Violation for UnusedPrivateTypeAlias {
#[derive_message_formats]
fn message(&self) -> String {
let UnusedPrivateTypeAlias { name } = self;
format!("Private TypeAlias `{name}` is never used")
}
}

/// PYI018
pub(crate) fn unused_private_type_var(
checker: &Checker,
Expand Down Expand Up @@ -157,3 +195,49 @@ pub(crate) fn unused_private_protocol(
));
}
}

/// PYI047
pub(crate) fn unused_private_type_alias(
checker: &Checker,
scope: &Scope,
diagnostics: &mut Vec<Diagnostic>,
) {
for binding in scope
.binding_ids()
.map(|binding_id| checker.semantic().binding(binding_id))
{
if !(binding.kind.is_assignment() && binding.is_private_declaration()) {
continue;
}
if binding.is_used() {
continue;
}

let Some(source) = binding.source else {
continue;
};
let Stmt::AnnAssign(ast::StmtAnnAssign {
target, annotation, ..
}) = checker.semantic().stmts[source]
else {
continue;
};
let Some(ast::ExprName { id, .. }) = target.as_name_expr() else {
continue;
};

if !checker
.semantic()
.match_typing_expr(annotation, "TypeAlias")
{
continue;
}

diagnostics.push(Diagnostic::new(
UnusedPrivateTypeAlias {
name: id.to_string(),
},
binding.range,
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI047.pyi:6:1: PYI047 Private TypeAlias `_UnusedPrivateTypeAlias` is never used
|
6 | _UnusedPrivateTypeAlias: TypeAlias = int | None
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI047
7 | _T: typing.TypeAlias = str
|

PYI047.pyi:7:1: PYI047 Private TypeAlias `_T` is never used
|
6 | _UnusedPrivateTypeAlias: TypeAlias = int | None
7 | _T: typing.TypeAlias = str
| ^^ PYI047
8 |
9 | # OK
|


1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7838d8c

Please sign in to comment.