Skip to content

Commit ac028cd

Browse files
authored
[numpy] deprecated type aliases (astral-sh#2810)
Closes astral-sh#2455 Used `NPY` as prefix code as agreed in the issue.
1 parent c0eb5c2 commit ac028cd

15 files changed

Lines changed: 344 additions & 9 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
162162
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
163163
1. [Pylint (PL)](#pylint-pl)
164164
1. [tryceratops (TRY)](#tryceratops-try)
165+
1. [NumPy-specific rules (NPY)](#numpy-specific-rules-npy)
165166
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
166167
1. [Editor Integrations](#editor-integrations)
167168
1. [FAQ](#faq)
@@ -1487,6 +1488,12 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI
14871488
| TRY301 | raise-within-try | Abstract `raise` to an inner function | |
14881489
| TRY400 | error-instead-of-exception | Use `logging.exception` instead of `logging.error` | |
14891490

1491+
### NumPy-specific rules (NPY)
1492+
1493+
| Code | Name | Message | Fix |
1494+
| ---- | ---- | ------- | --- |
1495+
| NPY001 | [numpy-deprecated-type-alias](https://beta.ruff.rs/docs/rules/numpy-deprecated-type-alias/) | Type alias `np.{type_name}` is deprecated, replace with builtin type | 🛠 |
1496+
14901497
### Ruff-specific rules (RUF)
14911498

14921499
| Code | Name | Message | Fix |

clippy.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
doc-valid-idents = ["StackOverflow", "CodeQL", "IPython", ".."]
1+
doc-valid-idents = [
2+
"StackOverflow",
3+
"CodeQL",
4+
"IPython",
5+
"NumPy",
6+
"..",
7+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import numpy as npy
2+
import numpy as np
3+
import numpy
4+
5+
# Error
6+
npy.bool
7+
npy.int
8+
9+
if dtype == np.object:
10+
...
11+
12+
result = result.select_dtypes([np.byte, np.ubyte, np.short, np.ushort, np.int, np.long])
13+
14+
pdf = pd.DataFrame(
15+
data=[[1, 2, 3]],
16+
columns=["a", "b", "c"],
17+
dtype=numpy.object,
18+
)
19+
20+
_ = arr.astype(np.int)

crates/ruff/src/checkers/ast.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ use crate::rules::{
3838
flake8_django, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
3939
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise,
4040
flake8_return, flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
41-
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
42-
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
41+
flake8_unused_arguments, flake8_use_pathlib, mccabe, numpy, pandas_vet, pep8_naming,
42+
pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
4343
};
4444
use crate::settings::types::PythonVersion;
4545
use crate::settings::{flags, Settings};
@@ -2145,6 +2145,9 @@ where
21452145
if self.settings.rules.enabled(&Rule::TypingTextStrAlias) {
21462146
pyupgrade::rules::typing_text_str_alias(self, expr);
21472147
}
2148+
if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) {
2149+
numpy::rules::deprecated_type_alias(self, expr);
2150+
}
21482151

21492152
// Ex) List[...]
21502153
if !self.in_deferred_string_type_definition
@@ -2211,6 +2214,9 @@ where
22112214
if self.settings.rules.enabled(&Rule::TypingTextStrAlias) {
22122215
pyupgrade::rules::typing_text_str_alias(self, expr);
22132216
}
2217+
if self.settings.rules.enabled(&Rule::NumpyDeprecatedTypeAlias) {
2218+
numpy::rules::deprecated_type_alias(self, expr);
2219+
}
22142220
if self.settings.rules.enabled(&Rule::RewriteMockImport) {
22152221
pyupgrade::rules::rewrite_mock_attribute(self, expr);
22162222
}

crates/ruff/src/codes.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
587587
// flake8-self
588588
(Flake8Self, "001") => Rule::PrivateMemberAccess,
589589

590+
// numpy
591+
(Numpy, "001") => Rule::NumpyDeprecatedTypeAlias,
592+
590593
// ruff
591594
(Ruff, "001") => Rule::AmbiguousUnicodeCharacterString,
592595
(Ruff, "002") => Rule::AmbiguousUnicodeCharacterDocstring,

crates/ruff/src/registry.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,8 @@ ruff_macros::register_rules!(
550550
rules::flake8_raise::rules::UnnecessaryParenOnRaiseException,
551551
// flake8-self
552552
rules::flake8_self::rules::PrivateMemberAccess,
553+
// numpy
554+
rules::numpy::rules::NumpyDeprecatedTypeAlias,
553555
// ruff
554556
rules::ruff::rules::AmbiguousUnicodeCharacterString,
555557
rules::ruff::rules::AmbiguousUnicodeCharacterDocstring,
@@ -695,6 +697,9 @@ pub enum Linter {
695697
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
696698
#[prefix = "TRY"]
697699
Tryceratops,
700+
/// NumPy-specific rules
701+
#[prefix = "NPY"]
702+
Numpy,
698703
/// Ruff-specific rules
699704
#[prefix = "RUF"]
700705
Ruff,

crates/ruff/src/rules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub mod flake8_unused_arguments;
3333
pub mod flake8_use_pathlib;
3434
pub mod isort;
3535
pub mod mccabe;
36+
pub mod numpy;
3637
pub mod pandas_vet;
3738
pub mod pep8_naming;
3839
pub mod pycodestyle;

crates/ruff/src/rules/numpy/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! NumPy-specific rules.
2+
pub(crate) mod rules;
3+
4+
#[cfg(test)]
5+
mod tests {
6+
use std::convert::AsRef;
7+
use std::path::Path;
8+
9+
use anyhow::Result;
10+
use test_case::test_case;
11+
12+
use crate::registry::Rule;
13+
use crate::test::test_path;
14+
use crate::{assert_yaml_snapshot, settings};
15+
16+
#[test_case(Rule::NumpyDeprecatedTypeAlias, Path::new("NPY001.py"); "NPY001")]
17+
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
18+
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
19+
let diagnostics = test_path(
20+
Path::new("numpy").join(path).as_path(),
21+
&settings::Settings::for_rule(rule_code),
22+
)?;
23+
assert_yaml_snapshot!(snapshot, diagnostics);
24+
Ok(())
25+
}
26+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use ruff_macros::{define_violation, derive_message_formats};
2+
use rustpython_parser::ast::Expr;
3+
4+
use crate::ast::types::Range;
5+
use crate::checkers::ast::Checker;
6+
use crate::fix::Fix;
7+
use crate::registry::Diagnostic;
8+
use crate::violation::AlwaysAutofixableViolation;
9+
10+
define_violation!(
11+
/// ## What it does
12+
/// Checks for deprecated NumPy type aliases.
13+
///
14+
/// ## Why is this bad?
15+
/// NumPy's `np.int` has long been an alias of the builtin `int`. The same
16+
/// goes for `np.float`, `np.bool`, and others. These aliases exist
17+
/// primarily primarily for historic reasons, and have been a cause of
18+
/// frequent confusion for newcomers.
19+
///
20+
/// These aliases were been deprecated in 1.20, and removed in 1.24.
21+
///
22+
/// ## Examples
23+
/// ```python
24+
/// import numpy as np
25+
///
26+
/// np.bool
27+
/// ```
28+
///
29+
/// Use instead:
30+
/// ```python
31+
/// bool
32+
/// ```
33+
pub struct NumpyDeprecatedTypeAlias {
34+
pub type_name: String,
35+
}
36+
);
37+
impl AlwaysAutofixableViolation for NumpyDeprecatedTypeAlias {
38+
#[derive_message_formats]
39+
fn message(&self) -> String {
40+
let NumpyDeprecatedTypeAlias { type_name } = self;
41+
format!("Type alias `np.{type_name}` is deprecated, replace with builtin type")
42+
}
43+
44+
fn autofix_title(&self) -> String {
45+
let NumpyDeprecatedTypeAlias { type_name } = self;
46+
format!("Replace `np.{type_name}` with builtin type")
47+
}
48+
}
49+
50+
/// NPY001
51+
pub fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) {
52+
if let Some(type_name) = checker.resolve_call_path(expr).and_then(|call_path| {
53+
if call_path.as_slice() == ["numpy", "bool"]
54+
|| call_path.as_slice() == ["numpy", "int"]
55+
|| call_path.as_slice() == ["numpy", "float"]
56+
|| call_path.as_slice() == ["numpy", "complex"]
57+
|| call_path.as_slice() == ["numpy", "object"]
58+
|| call_path.as_slice() == ["numpy", "str"]
59+
|| call_path.as_slice() == ["numpy", "long"]
60+
|| call_path.as_slice() == ["numpy", "unicode"]
61+
{
62+
Some(call_path[1])
63+
} else {
64+
None
65+
}
66+
}) {
67+
let mut diagnostic = Diagnostic::new(
68+
NumpyDeprecatedTypeAlias {
69+
type_name: type_name.to_string(),
70+
},
71+
Range::from_located(expr),
72+
);
73+
if checker.patch(diagnostic.kind.rule()) {
74+
diagnostic.amend(Fix::replacement(
75+
match type_name {
76+
"unicode" => "str",
77+
"long" => "int",
78+
_ => type_name,
79+
}
80+
.to_string(),
81+
expr.location,
82+
expr.end_location.unwrap(),
83+
));
84+
}
85+
checker.diagnostics.push(diagnostic);
86+
}
87+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub use deprecated_type_alias::{deprecated_type_alias, NumpyDeprecatedTypeAlias};
2+
3+
mod deprecated_type_alias;

0 commit comments

Comments
 (0)