Skip to content

Commit

Permalink
[flake8-pyi] Fix PYI047 false negatives on PEP-695 type aliases (#9566)
Browse files Browse the repository at this point in the history
## Summary

Fixes one of the issues listed in
#8771. Fairly straightforward!

## Test Plan

`cargo test` / `cargo insta review`
  • Loading branch information
AlexWaygood authored Jan 18, 2024
1 parent 368e279 commit 848e473
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:


def func2(arg: _PrivateTypeAlias) -> None: ...

type _UnusedPEP695 = int
type _UnusedGeneric695[T] = list[T]

type _UsedPEP695 = str
type _UsedGeneric695[T] = tuple[T, ...]

def func4(arg: _UsedPEP695) -> None: ...
def func5(arg: _UsedGeneric695[bytes]) -> None: ...
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ else:


def func2(arg: _PrivateTypeAlias) -> None: ...

type _UnusedPEP695 = int
type _UnusedGeneric695[T] = list[T]

type _UsedPEP695 = str
type _UsedGeneric695[T] = tuple[T, ...]

def func4(arg: _UsedPEP695) -> None: ...
def func5(arg: _UsedGeneric695[bytes]) -> None: ...
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::Scope;
use ruff_python_semantic::{Scope, SemanticModel};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -267,9 +267,11 @@ pub(crate) fn unused_private_type_alias(
scope: &Scope,
diagnostics: &mut Vec<Diagnostic>,
) {
let semantic = checker.semantic();

for binding in scope
.binding_ids()
.map(|binding_id| checker.semantic().binding(binding_id))
.map(|binding_id| semantic.binding(binding_id))
{
if !(binding.kind.is_assignment() && binding.is_private_declaration()) {
continue;
Expand All @@ -281,32 +283,40 @@ pub(crate) fn unused_private_type_alias(
let Some(source) = binding.source else {
continue;
};
let Stmt::AnnAssign(ast::StmtAnnAssign {
target, annotation, ..
}) = checker.semantic().statement(source)
else {
continue;
};
let Some(ast::ExprName { id, .. }) = target.as_name_expr() else {
continue;
};

if !checker
.semantic()
.match_typing_expr(annotation, "TypeAlias")
{
let Some(alias_name) = extract_type_alias_name(semantic.statement(source), semantic) else {
continue;
}
};

diagnostics.push(Diagnostic::new(
UnusedPrivateTypeAlias {
name: id.to_string(),
name: alias_name.to_string(),
},
binding.range(),
));
}
}

fn extract_type_alias_name<'a>(stmt: &'a ast::Stmt, semantic: &SemanticModel) -> Option<&'a str> {
match stmt {
ast::Stmt::AnnAssign(ast::StmtAnnAssign {
target, annotation, ..
}) => {
let ast::ExprName { id, .. } = target.as_name_expr()?;
if semantic.match_typing_expr(annotation, "TypeAlias") {
Some(id)
} else {
None
}
}
ast::Stmt::TypeAlias(ast::StmtTypeAlias { name, .. }) => {
let ast::ExprName { id, .. } = name.as_name_expr()?;
Some(id)
}
_ => None,
}
}

/// PYI049
pub(crate) fn unused_private_typed_dict(
checker: &Checker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,22 @@ PYI047.py:7:1: PYI047 Private TypeAlias `_T` is never used
9 | # OK
|

PYI047.py:24:6: PYI047 Private TypeAlias `_UnusedPEP695` is never used
|
22 | def func2(arg: _PrivateTypeAlias) -> None: ...
23 |
24 | type _UnusedPEP695 = int
| ^^^^^^^^^^^^^ PYI047
25 | type _UnusedGeneric695[T] = list[T]
|

PYI047.py:25:6: PYI047 Private TypeAlias `_UnusedGeneric695` is never used
|
24 | type _UnusedPEP695 = int
25 | type _UnusedGeneric695[T] = list[T]
| ^^^^^^^^^^^^^^^^^ PYI047
26 |
27 | type _UsedPEP695 = str
|


Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,22 @@ PYI047.pyi:7:1: PYI047 Private TypeAlias `_T` is never used
9 | # OK
|

PYI047.pyi:24:6: PYI047 Private TypeAlias `_UnusedPEP695` is never used
|
22 | def func2(arg: _PrivateTypeAlias) -> None: ...
23 |
24 | type _UnusedPEP695 = int
| ^^^^^^^^^^^^^ PYI047
25 | type _UnusedGeneric695[T] = list[T]
|

PYI047.pyi:25:6: PYI047 Private TypeAlias `_UnusedGeneric695` is never used
|
24 | type _UnusedPEP695 = int
25 | type _UnusedGeneric695[T] = list[T]
| ^^^^^^^^^^^^^^^^^ PYI047
26 |
27 | type _UsedPEP695 = str
|


0 comments on commit 848e473

Please sign in to comment.