Avoid underflow in expected-special-method-signature (#4377)

This commit is contained in:
Charlie Marsh 2023-05-11 12:47:47 -04:00 committed by GitHub
parent 1ccef5150d
commit ffcf0618c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 51 deletions

View File

@ -1,19 +1,16 @@
class TestClass: class TestClass:
def __bool__(self): def __bool__(self):
... ...
def __bool__(self, x): # too many mandatory args def __bool__(self, x): # too many mandatory args
... ...
def __bool__(self, x=1): # additional optional args OK def __bool__(self, x=1): # additional optional args OK
... ...
def __bool__(self, *args): # varargs OK
...
def __bool__(): # ignored; should be caughty by E0211/N805 def __bool__(): # ignored; should be caughty by E0211/N805
... ...
@staticmethod @staticmethod
def __bool__(): def __bool__():
... ...
@ -21,31 +18,58 @@ class TestClass:
@staticmethod @staticmethod
def __bool__(x): # too many mandatory args def __bool__(x): # too many mandatory args
... ...
@staticmethod @staticmethod
def __bool__(x=1): # additional optional args OK def __bool__(x=1): # additional optional args OK
... ...
def __eq__(self, other): # multiple args def __eq__(self, other): # multiple args
... ...
def __eq__(self, other=1): # expected arg is optional def __eq__(self, other=1): # expected arg is optional
... ...
def __eq__(self): # too few mandatory args def __eq__(self): # too few mandatory args
... ...
def __eq__(self, other, other_other): # too many mandatory args def __eq__(self, other, other_other): # too many mandatory args
... ...
def __round__(self): # allow zero additional args. def __round__(self): # allow zero additional args
... ...
def __round__(self, x): # allow one additional args. def __round__(self, x): # allow one additional args
... ...
def __round__(self, x, y): # disallow 2 args def __round__(self, x, y): # disallow 2 args
... ...
def __round__(self, x, y, z=2): # disallow 3 args even when one is optional def __round__(self, x, y, z=2): # disallow 3 args even when one is optional
... ...
def __eq__(self, *args): # ignore *args
...
def __eq__(self, x, *args): # extra *args is ok
...
def __eq__(self, x, y, *args): # too many args with *args
...
def __round__(self, *args): # allow zero additional args
...
def __round__(self, x, *args): # allow one additional args
...
def __round__(self, x, y, *args): # disallow 2 args
...
def __eq__(self, **kwargs): # ignore **kwargs
...
def __eq__(self, /, other=42): # ignore positional-only args
...
def __eq__(self, *, other=42): # ignore positional-only args
...

View File

@ -7,7 +7,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::identifier_range; use ruff_python_ast::helpers::identifier_range;
use ruff_python_ast::source_code::Locator; use ruff_python_ast::source_code::Locator;
use ruff_python_semantic::analyze::visibility::is_staticmethod; use ruff_python_semantic::analyze::visibility::is_staticmethod;
use ruff_python_semantic::scope::ScopeKind;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -109,7 +108,12 @@ pub fn unexpected_special_method_signature(
args: &Arguments, args: &Arguments,
locator: &Locator, locator: &Locator,
) { ) {
if !matches!(checker.ctx.scope().kind, ScopeKind::Class(_)) { if !checker.ctx.scope().kind.is_class() {
return;
}
// Ignore methods with positional-only or keyword-only parameters, or variadic parameters.
if !args.posonlyargs.is_empty() || !args.kwonlyargs.is_empty() || args.kwarg.is_some() {
return; return;
} }
@ -126,18 +130,22 @@ pub fn unexpected_special_method_signature(
return; return;
}; };
let emit = match expected_params { let valid_signature = match expected_params {
ExpectedParams::Range(min, max) => !(min..=max).contains(&actual_params), ExpectedParams::Range(min, max) => {
ExpectedParams::Fixed(expected) => match expected.cmp(&mandatory_params) { if mandatory_params >= min {
Ordering::Less => true, mandatory_params <= max
Ordering::Greater => { } else {
args.vararg.is_none() && optional_params < (expected - mandatory_params) args.vararg.is_some() || actual_params <= max
} }
Ordering::Equal => false, }
ExpectedParams::Fixed(expected) => match expected.cmp(&mandatory_params) {
Ordering::Less => false,
Ordering::Greater => args.vararg.is_some() || actual_params >= expected,
Ordering::Equal => true,
}, },
}; };
if emit { if !valid_signature {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
UnexpectedSpecialMethodSignature { UnexpectedSpecialMethodSignature {
method_name: name.to_owned(), method_name: name.to_owned(),

View File

@ -4,54 +4,72 @@ source: crates/ruff/src/rules/pylint/mod.rs
unexpected_special_method_signature.py:5:9: PLE0302 The special method `__bool__` expects 1 parameter, 2 were given unexpected_special_method_signature.py:5:9: PLE0302 The special method `__bool__` expects 1 parameter, 2 were given
| |
5 | ... 5 | ...
6 | 6 |
7 | def __bool__(self, x): # too many mandatory args 7 | def __bool__(self, x): # too many mandatory args
| ^^^^^^^^ PLE0302 | ^^^^^^^^ PLE0302
8 | ... 8 | ...
| |
unexpected_special_method_signature.py:22:9: PLE0302 The special method `__bool__` expects 0 parameters, 1 was given unexpected_special_method_signature.py:19:9: PLE0302 The special method `__bool__` expects 0 parameters, 1 was given
| |
22 | @staticmethod 19 | @staticmethod
23 | def __bool__(x): # too many mandatory args 20 | def __bool__(x): # too many mandatory args
| ^^^^^^^^ PLE0302 | ^^^^^^^^ PLE0302
24 | ... 21 | ...
| |
unexpected_special_method_signature.py:35:9: PLE0302 The special method `__eq__` expects 2 parameters, 1 was given unexpected_special_method_signature.py:32:9: PLE0302 The special method `__eq__` expects 2 parameters, 1 was given
|
32 | ...
33 |
34 | def __eq__(self): # too few mandatory args
| ^^^^^^ PLE0302
35 | ...
|
unexpected_special_method_signature.py:35:9: PLE0302 The special method `__eq__` expects 2 parameters, 3 were given
| |
35 | ... 35 | ...
36 | 36 |
37 | def __eq__(self): # too few mandatory args 37 | def __eq__(self, other, other_other): # too many mandatory args
| ^^^^^^ PLE0302 | ^^^^^^ PLE0302
38 | ... 38 | ...
| |
unexpected_special_method_signature.py:38:9: PLE0302 The special method `__eq__` expects 2 parameters, 3 were given unexpected_special_method_signature.py:44:9: PLE0302 The special method `__round__` expects between 1 and 2 parameters, 3 were given
| |
38 | ... 44 | ...
39 | 45 |
40 | def __eq__(self, other, other_other): # too many mandatory args 46 | def __round__(self, x, y): # disallow 2 args
| ^^^^^^ PLE0302 | ^^^^^^^^^ PLE0302
41 | ... 47 | ...
| |
unexpected_special_method_signature.py:47:9: PLE0302 The special method `__round__` expects between 1 and 2 parameters, 3 were given unexpected_special_method_signature.py:47:9: PLE0302 The special method `__round__` expects between 1 and 2 parameters, 4 were given
| |
47 | ... 47 | ...
48 | 48 |
49 | def __round__(self, x, y): # disallow 2 args 49 | def __round__(self, x, y, z=2): # disallow 3 args even when one is optional
| ^^^^^^^^^ PLE0302 | ^^^^^^^^^ PLE0302
50 | ... 50 | ...
| |
unexpected_special_method_signature.py:50:9: PLE0302 The special method `__round__` expects between 1 and 2 parameters, 4 were given unexpected_special_method_signature.py:56:9: PLE0302 The special method `__eq__` expects 2 parameters, 3 were given
| |
50 | ... 56 | ...
51 | 57 |
52 | def __round__(self, x, y, z=2): # disallow 3 args even when one is optional 58 | def __eq__(self, x, y, *args): # too many args with *args
| ^^^^^^ PLE0302
59 | ...
|
unexpected_special_method_signature.py:65:9: PLE0302 The special method `__round__` expects between 1 and 2 parameters, 3 were given
|
65 | ...
66 |
67 | def __round__(self, x, y, *args): # disallow 2 args
| ^^^^^^^^^ PLE0302 | ^^^^^^^^^ PLE0302
53 | ... 68 | ...
| |