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

@ -8,9 +8,6 @@ class TestClass:
def __bool__(self, x=1): # additional optional args OK
...
def __bool__(self, *args): # varargs OK
...
def __bool__(): # ignored; should be caughty by E0211/N805
...
@ -38,10 +35,10 @@ class TestClass:
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
@ -49,3 +46,30 @@ class TestClass:
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::source_code::Locator;
use ruff_python_semantic::analyze::visibility::is_staticmethod;
use ruff_python_semantic::scope::ScopeKind;
use crate::checkers::ast::Checker;
@ -109,7 +108,12 @@ pub fn unexpected_special_method_signature(
args: &Arguments,
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;
}
@ -126,18 +130,22 @@ pub fn unexpected_special_method_signature(
return;
};
let emit = match expected_params {
ExpectedParams::Range(min, max) => !(min..=max).contains(&actual_params),
ExpectedParams::Fixed(expected) => match expected.cmp(&mandatory_params) {
Ordering::Less => true,
Ordering::Greater => {
args.vararg.is_none() && optional_params < (expected - mandatory_params)
let valid_signature = match expected_params {
ExpectedParams::Range(min, max) => {
if mandatory_params >= min {
mandatory_params <= max
} else {
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(
UnexpectedSpecialMethodSignature {
method_name: name.to_owned(),

View File

@ -10,48 +10,66 @@ unexpected_special_method_signature.py:5:9: PLE0302 The special method `__bool__
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
23 | def __bool__(x): # too many mandatory args
19 | @staticmethod
20 | def __bool__(x): # too many mandatory args
| ^^^^^^^^ 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 | ...
36 |
37 | def __eq__(self): # too few mandatory args
37 | def __eq__(self, other, other_other): # too many mandatory args
| ^^^^^^ PLE0302
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 | ...
39 |
40 | def __eq__(self, other, other_other): # too many mandatory args
| ^^^^^^ PLE0302
41 | ...
44 | ...
45 |
46 | def __round__(self, x, y): # disallow 2 args
| ^^^^^^^^^ PLE0302
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 | ...
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
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 | ...
51 |
52 | def __round__(self, x, y, z=2): # disallow 3 args even when one is optional
56 | ...
57 |
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
53 | ...
68 | ...
|