mirror of https://github.com/astral-sh/ruff
Allow `NoReturn`-like functions for `__str__`, `__len__`, etc. (#11017)
## Summary If the method always raises, we shouldn't raise a diagnostic for "returning a value of the wrong type". Closes https://github.com/astral-sh/ruff/issues/11016.
This commit is contained in:
parent
e751b4ea82
commit
33529c049e
|
|
@ -21,12 +21,6 @@ class BytesNoReturn:
|
||||||
print("ruff") # [invalid-bytes-return]
|
print("ruff") # [invalid-bytes-return]
|
||||||
|
|
||||||
|
|
||||||
class BytesWrongRaise:
|
|
||||||
def __bytes__(self):
|
|
||||||
print("raise some error")
|
|
||||||
raise NotImplementedError # [invalid-bytes-return]
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Once Ruff has better type checking
|
# TODO: Once Ruff has better type checking
|
||||||
def return_bytes():
|
def return_bytes():
|
||||||
return "some string"
|
return "some string"
|
||||||
|
|
@ -63,3 +57,9 @@ class Bytes4:
|
||||||
class Bytes5:
|
class Bytes5:
|
||||||
def __bytes__(self):
|
def __bytes__(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Bytes6:
|
||||||
|
def __bytes__(self):
|
||||||
|
print("raise some error")
|
||||||
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,6 @@ class LengthNegative:
|
||||||
return -42 # [invalid-length-return]
|
return -42 # [invalid-length-return]
|
||||||
|
|
||||||
|
|
||||||
class LengthWrongRaise:
|
|
||||||
def __len__(self):
|
|
||||||
print("raise some error")
|
|
||||||
raise NotImplementedError # [invalid-length-return]
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Once Ruff has better type checking
|
# TODO: Once Ruff has better type checking
|
||||||
def return_int():
|
def return_int():
|
||||||
return "3"
|
return "3"
|
||||||
|
|
@ -68,3 +62,9 @@ class Length4:
|
||||||
class Length5:
|
class Length5:
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Length6:
|
||||||
|
def __len__(self):
|
||||||
|
print("raise some error")
|
||||||
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,14 @@ class Str2:
|
||||||
|
|
||||||
class Str3:
|
class Str3:
|
||||||
def __str__(self): ...
|
def __str__(self): ...
|
||||||
|
|
||||||
|
|
||||||
|
class Str4:
|
||||||
|
def __str__(self):
|
||||||
|
raise RuntimeError("__str__ not allowed")
|
||||||
|
|
||||||
|
|
||||||
|
class Str5:
|
||||||
|
def __str__(self): # PLE0307 (returns None if x <= 0)
|
||||||
|
if x > 0:
|
||||||
|
raise RuntimeError("__str__ not allowed")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{self as ast};
|
use ruff_python_ast::{self as ast};
|
||||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
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_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ impl Violation for InvalidBoolReturnType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// E0307
|
/// PLE0304
|
||||||
pub(crate) fn invalid_bool_return(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
pub(crate) fn invalid_bool_return(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
||||||
if function_def.name.as_str() != "__bool__" {
|
if function_def.name.as_str() != "__bool__" {
|
||||||
return;
|
return;
|
||||||
|
|
@ -57,19 +58,29 @@ pub(crate) fn invalid_bool_return(checker: &mut Checker, function_def: &ast::Stm
|
||||||
return;
|
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(
|
||||||
|
InvalidBoolReturnType,
|
||||||
|
function_def.identifier(),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let returns = {
|
let returns = {
|
||||||
let mut visitor = ReturnStatementVisitor::default();
|
let mut visitor = ReturnStatementVisitor::default();
|
||||||
visitor.visit_body(&function_def.body);
|
visitor.visit_body(&function_def.body);
|
||||||
visitor.returns
|
visitor.returns
|
||||||
};
|
};
|
||||||
|
|
||||||
if returns.is_empty() {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
InvalidBoolReturnType,
|
|
||||||
function_def.identifier(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for stmt in returns {
|
for stmt in returns {
|
||||||
if let Some(value) = stmt.value.as_deref() {
|
if let Some(value) = stmt.value.as_deref() {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{self as ast};
|
use ruff_python_ast::{self as ast};
|
||||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
use ruff_python_semantic::analyze::function_type::is_stub;
|
||||||
|
use ruff_python_semantic::analyze::terminal::Terminal;
|
||||||
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
|
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ impl Violation for InvalidBytesReturnType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// E0308
|
/// PLE0308
|
||||||
pub(crate) fn invalid_bytes_return(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
pub(crate) fn invalid_bytes_return(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
||||||
if function_def.name.as_str() != "__bytes__" {
|
if function_def.name.as_str() != "__bytes__" {
|
||||||
return;
|
return;
|
||||||
|
|
@ -57,19 +58,29 @@ pub(crate) fn invalid_bytes_return(checker: &mut Checker, function_def: &ast::St
|
||||||
return;
|
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(
|
||||||
|
InvalidBytesReturnType,
|
||||||
|
function_def.identifier(),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let returns = {
|
let returns = {
|
||||||
let mut visitor = ReturnStatementVisitor::default();
|
let mut visitor = ReturnStatementVisitor::default();
|
||||||
visitor.visit_body(&function_def.body);
|
visitor.visit_body(&function_def.body);
|
||||||
visitor.returns
|
visitor.returns
|
||||||
};
|
};
|
||||||
|
|
||||||
if returns.is_empty() {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
InvalidBytesReturnType,
|
|
||||||
function_def.identifier(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for stmt in returns {
|
for stmt in returns {
|
||||||
if let Some(value) = stmt.value.as_deref() {
|
if let Some(value) = stmt.value.as_deref() {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{self as ast, Expr};
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
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_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
|
@ -63,19 +64,29 @@ pub(crate) fn invalid_length_return(checker: &mut Checker, function_def: &ast::S
|
||||||
return;
|
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(
|
||||||
|
InvalidLengthReturnType,
|
||||||
|
function_def.identifier(),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let returns = {
|
let returns = {
|
||||||
let mut visitor = ReturnStatementVisitor::default();
|
let mut visitor = ReturnStatementVisitor::default();
|
||||||
visitor.visit_body(&function_def.body);
|
visitor.visit_body(&function_def.body);
|
||||||
visitor.returns
|
visitor.returns
|
||||||
};
|
};
|
||||||
|
|
||||||
if returns.is_empty() {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
InvalidLengthReturnType,
|
|
||||||
function_def.identifier(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for stmt in returns {
|
for stmt in returns {
|
||||||
if let Some(value) = stmt.value.as_deref() {
|
if let Some(value) = stmt.value.as_deref() {
|
||||||
if is_negative_integer(value)
|
if is_negative_integer(value)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
use ruff_python_ast::{self as ast};
|
use ruff_python_ast::{self as ast};
|
||||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
use ruff_python_semantic::analyze::function_type::is_stub;
|
||||||
|
use ruff_python_semantic::analyze::terminal::Terminal;
|
||||||
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
|
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
|
@ -57,19 +58,29 @@ pub(crate) fn invalid_str_return(checker: &mut Checker, function_def: &ast::Stmt
|
||||||
return;
|
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(
|
||||||
|
InvalidStrReturnType,
|
||||||
|
function_def.identifier(),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let returns = {
|
let returns = {
|
||||||
let mut visitor = ReturnStatementVisitor::default();
|
let mut visitor = ReturnStatementVisitor::default();
|
||||||
visitor.visit_body(&function_def.body);
|
visitor.visit_body(&function_def.body);
|
||||||
visitor.returns
|
visitor.returns
|
||||||
};
|
};
|
||||||
|
|
||||||
if returns.is_empty() {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
|
||||||
InvalidStrReturnType,
|
|
||||||
function_def.identifier(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for stmt in returns {
|
for stmt in returns {
|
||||||
if let Some(value) = stmt.value.as_deref() {
|
if let Some(value) = stmt.value.as_deref() {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl Violation for NonSlotAssignment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// E0237
|
/// PLE0237
|
||||||
pub(crate) fn non_slot_assignment(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
pub(crate) fn non_slot_assignment(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
||||||
let semantic = checker.semantic();
|
let semantic = checker.semantic();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,3 @@ invalid_return_type_length.py:26:16: PLE0303 `__len__` does not return a non-neg
|
||||||
26 | return -42 # [invalid-length-return]
|
26 | return -42 # [invalid-length-return]
|
||||||
| ^^^ PLE0303
|
| ^^^ PLE0303
|
||||||
|
|
|
|
||||||
|
|
||||||
invalid_return_type_length.py:30:9: PLE0303 `__len__` does not return a non-negative integer
|
|
||||||
|
|
|
||||||
29 | class LengthWrongRaise:
|
|
||||||
30 | def __len__(self):
|
|
||||||
| ^^^^^^^ PLE0303
|
|
||||||
31 | print("raise some error")
|
|
||||||
32 | raise NotImplementedError # [invalid-length-return]
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,12 @@ invalid_return_type_str.py:21:16: PLE0307 `__str__` does not return `str`
|
||||||
21 | return False
|
21 | return False
|
||||||
| ^^^^^ PLE0307
|
| ^^^^^ PLE0307
|
||||||
|
|
|
|
||||||
|
|
||||||
|
invalid_return_type_str.py:58:9: PLE0307 `__str__` does not return `str`
|
||||||
|
|
|
||||||
|
57 | class Str5:
|
||||||
|
58 | def __str__(self): # PLE0307 (returns None if x <= 0)
|
||||||
|
| ^^^^^^^ PLE0307
|
||||||
|
59 | if x > 0:
|
||||||
|
60 | raise RuntimeError("__str__ not allowed")
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,3 @@ invalid_return_type_bytes.py:20:9: PLE0308 `__bytes__` does not return `bytes`
|
||||||
| ^^^^^^^^^ PLE0308
|
| ^^^^^^^^^ PLE0308
|
||||||
21 | print("ruff") # [invalid-bytes-return]
|
21 | print("ruff") # [invalid-bytes-return]
|
||||||
|
|
|
|
||||||
|
|
||||||
invalid_return_type_bytes.py:25:9: PLE0308 `__bytes__` does not return `bytes`
|
|
||||||
|
|
|
||||||
24 | class BytesWrongRaise:
|
|
||||||
25 | def __bytes__(self):
|
|
||||||
| ^^^^^^^^^ PLE0308
|
|
||||||
26 | print("raise some error")
|
|
||||||
27 | raise NotImplementedError # [invalid-bytes-return]
|
|
||||||
|
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue