-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pylint rule invalid-hash-returned (PLE0309) See #970 for rules Test Plan: `cargo test` TBD: from the description: "Strictly speaking `bool` is a subclass of `int`, thus returning `True`/`False` is valid. To be consistent with other rules (e.g. [PLE0305](#10962) invalid-index-returned), ruff will raise, compared to pylint which will not raise."
- Loading branch information
1 parent
5d3c9f2
commit adf63d9
Showing
8 changed files
with
218 additions
and
4 deletions.
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
crates/ruff_linter/resources/test/fixtures/pylint/invalid_return_type_hash.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# These testcases should raise errors | ||
|
||
|
||
class Bool: | ||
def __hash__(self): | ||
return True # [invalid-hash-return] | ||
|
||
|
||
class Float: | ||
def __hash__(self): | ||
return 3.05 # [invalid-hash-return] | ||
|
||
|
||
class Str: | ||
def __hash__(self): | ||
return "ruff" # [invalid-hash-return] | ||
|
||
|
||
class HashNoReturn: | ||
def __hash__(self): | ||
print("ruff") # [invalid-hash-return] | ||
|
||
|
||
# TODO: Once Ruff has better type checking | ||
def return_int(): | ||
return "3" | ||
|
||
|
||
class ComplexReturn: | ||
def __hash__(self): | ||
return return_int() # [invalid-hash-return] | ||
|
||
|
||
# These testcases should NOT raise errors | ||
|
||
|
||
class Hash: | ||
def __hash__(self): | ||
return 7741 | ||
|
||
|
||
class Hash2: | ||
def __hash__(self): | ||
x = 7741 | ||
return x | ||
|
||
|
||
class Hash3: | ||
def __hash__(self): ... | ||
|
||
|
||
class Has4: | ||
def __hash__(self): | ||
pass | ||
|
||
|
||
class Hash5: | ||
def __hash__(self): | ||
raise NotImplementedError | ||
|
||
|
||
class HashWrong6: | ||
def __hash__(self): | ||
print("raise some error") | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
crates/ruff_linter/src/rules/pylint/rules/invalid_hash_return.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use ruff_diagnostics::{Diagnostic, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::helpers::ReturnStatementVisitor; | ||
use ruff_python_ast::identifier::Identifier; | ||
use ruff_python_ast::visitor::Visitor; | ||
use ruff_python_ast::{self as ast}; | ||
use ruff_python_semantic::analyze::function_type::is_stub; | ||
use ruff_python_semantic::analyze::terminal::Terminal; | ||
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType}; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::checkers::ast::Checker; | ||
|
||
/// ## What it does | ||
/// Checks for `__hash__` implementations that return a type other than `integer`. | ||
/// | ||
/// ## Why is this bad? | ||
/// The `__hash__` method should return an `integer`. Returning a different | ||
/// type may cause unexpected behavior. | ||
/// | ||
/// Note: `bool` is a subclass of `int`, so it's technically valid for `__hash__` to | ||
/// return `True` or `False`. However, for consistency with other rules, Ruff will | ||
/// still raise when `__hash__` returns a `bool`. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// class Foo: | ||
/// def __hash__(self): | ||
/// return "2" | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// class Foo: | ||
/// def __hash__(self): | ||
/// return 2 | ||
/// ``` | ||
/// | ||
/// | ||
/// ## References | ||
/// - [Python documentation: The `__hash__` method](https://docs.python.org/3/reference/datamodel.html#object.__hash__) | ||
#[violation] | ||
pub struct InvalidHashReturnType; | ||
|
||
impl Violation for InvalidHashReturnType { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("`__hash__` does not return an integer") | ||
} | ||
} | ||
|
||
/// E0309 | ||
pub(crate) fn invalid_hash_return(checker: &mut Checker, function_def: &ast::StmtFunctionDef) { | ||
if function_def.name.as_str() != "__hash__" { | ||
return; | ||
} | ||
|
||
if !checker.semantic().current_scope().kind.is_class() { | ||
return; | ||
} | ||
|
||
if is_stub(function_def, checker.semantic()) { | ||
return; | ||
} | ||
|
||
// Determine the terminal behavior (i.e., implicit return, no return, etc.). | ||
let terminal = Terminal::from_function(function_def); | ||
|
||
// If every control flow path raises an exception, ignore the function. | ||
if terminal == Terminal::Raise { | ||
return; | ||
} | ||
|
||
// If there are no return statements, add a diagnostic. | ||
if terminal == Terminal::Implicit { | ||
checker.diagnostics.push(Diagnostic::new( | ||
InvalidHashReturnType, | ||
function_def.identifier(), | ||
)); | ||
return; | ||
} | ||
|
||
let returns = { | ||
let mut visitor = ReturnStatementVisitor::default(); | ||
visitor.visit_body(&function_def.body); | ||
visitor.returns | ||
}; | ||
|
||
for stmt in returns { | ||
if let Some(value) = stmt.value.as_deref() { | ||
if !matches!( | ||
ResolvedPythonType::from(value), | ||
ResolvedPythonType::Unknown | ||
| ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) | ||
) { | ||
checker | ||
.diagnostics | ||
.push(Diagnostic::new(InvalidHashReturnType, value.range())); | ||
} | ||
} else { | ||
// Disallow implicit `None`. | ||
checker | ||
.diagnostics | ||
.push(Diagnostic::new(InvalidHashReturnType, stmt.range())); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
...int/snapshots/ruff_linter__rules__pylint__tests__PLE0309_invalid_return_type_hash.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/pylint/mod.rs | ||
--- | ||
invalid_return_type_hash.py:6:16: PLE0309 `__hash__` does not return an integer | ||
| | ||
4 | class Bool: | ||
5 | def __hash__(self): | ||
6 | return True # [invalid-hash-return] | ||
| ^^^^ PLE0309 | ||
| | ||
|
||
invalid_return_type_hash.py:11:16: PLE0309 `__hash__` does not return an integer | ||
| | ||
9 | class Float: | ||
10 | def __hash__(self): | ||
11 | return 3.05 # [invalid-hash-return] | ||
| ^^^^ PLE0309 | ||
| | ||
|
||
invalid_return_type_hash.py:16:16: PLE0309 `__hash__` does not return an integer | ||
| | ||
14 | class Str: | ||
15 | def __hash__(self): | ||
16 | return "ruff" # [invalid-hash-return] | ||
| ^^^^^^ PLE0309 | ||
| | ||
|
||
invalid_return_type_hash.py:20:9: PLE0309 `__hash__` does not return an integer | ||
| | ||
19 | class HashNoReturn: | ||
20 | def __hash__(self): | ||
| ^^^^^^^^ PLE0309 | ||
21 | print("ruff") # [invalid-hash-return] | ||
| |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.