mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-31 08:53:47 +00:00
Improve detection of whether a symbol refers to a builtin exception (#13215)
This commit is contained in:
parent
9d517061f2
commit
387af831f9
6 changed files with 150 additions and 95 deletions
|
@ -119,3 +119,10 @@ def func():
|
||||||
def func():
|
def func():
|
||||||
with suppress(AttributeError):
|
with suppress(AttributeError):
|
||||||
raise AttributeError("This is an exception") # OK
|
raise AttributeError("This is an exception") # OK
|
||||||
|
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
|
||||||
|
PythonFinalizationError("Added in Python 3.13")
|
||||||
|
|
|
@ -57,7 +57,7 @@ use ruff_python_semantic::{
|
||||||
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
|
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
|
||||||
StarImport, SubmoduleImport,
|
StarImport, SubmoduleImport,
|
||||||
};
|
};
|
||||||
use ruff_python_stdlib::builtins::{python_builtins, IPYTHON_BUILTINS, MAGIC_GLOBALS};
|
use ruff_python_stdlib::builtins::{python_builtins, MAGIC_GLOBALS};
|
||||||
use ruff_python_trivia::CommentRanges;
|
use ruff_python_trivia::CommentRanges;
|
||||||
use ruff_source_file::{Locator, OneIndexed, SourceRow};
|
use ruff_source_file::{Locator, OneIndexed, SourceRow};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
|
@ -1951,16 +1951,13 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_builtins(&mut self) {
|
fn bind_builtins(&mut self) {
|
||||||
for builtin in python_builtins(self.settings.target_version.minor())
|
let standard_builtins = python_builtins(
|
||||||
|
self.settings.target_version.minor(),
|
||||||
|
self.source_type.is_ipynb(),
|
||||||
|
);
|
||||||
|
for builtin in standard_builtins
|
||||||
.iter()
|
.iter()
|
||||||
.chain(MAGIC_GLOBALS.iter())
|
.chain(MAGIC_GLOBALS.iter())
|
||||||
.chain(
|
|
||||||
self.source_type
|
|
||||||
.is_ipynb()
|
|
||||||
.then_some(IPYTHON_BUILTINS)
|
|
||||||
.into_iter()
|
|
||||||
.flatten(),
|
|
||||||
)
|
|
||||||
.copied()
|
.copied()
|
||||||
.chain(self.settings.builtins.iter().map(String::as_str))
|
.chain(self.settings.builtins.iter().map(String::as_str))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::settings::types::PythonVersion;
|
use crate::settings::types::PythonVersion;
|
||||||
use ruff_python_ast::PySourceType;
|
use ruff_python_ast::PySourceType;
|
||||||
use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin};
|
use ruff_python_stdlib::builtins::is_python_builtin;
|
||||||
|
|
||||||
pub(super) fn shadows_builtin(
|
pub(super) fn shadows_builtin(
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -8,9 +8,7 @@ pub(super) fn shadows_builtin(
|
||||||
ignorelist: &[String],
|
ignorelist: &[String],
|
||||||
python_version: PythonVersion,
|
python_version: PythonVersion,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if is_python_builtin(name, python_version.minor())
|
if is_python_builtin(name, python_version.minor(), source_type.is_ipynb()) {
|
||||||
|| source_type.is_ipynb() && is_ipython_builtin(name)
|
|
||||||
{
|
|
||||||
ignorelist.iter().all(|ignore| ignore != name)
|
ignorelist.iter().all(|ignore| ignore != name)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -6,6 +6,7 @@ use ruff_python_stdlib::builtins;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::settings::types::PythonVersion;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for an exception that is not raised.
|
/// Checks for an exception that is not raised.
|
||||||
|
@ -54,7 +55,7 @@ pub(crate) fn useless_exception_statement(checker: &mut Checker, expr: &ast::Stm
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_builtin_exception(func, checker.semantic()) {
|
if is_builtin_exception(func, checker.semantic(), checker.settings.target_version) {
|
||||||
let mut diagnostic = Diagnostic::new(UselessExceptionStatement, expr.range());
|
let mut diagnostic = Diagnostic::new(UselessExceptionStatement, expr.range());
|
||||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||||
"raise ".to_string(),
|
"raise ".to_string(),
|
||||||
|
@ -65,8 +66,15 @@ pub(crate) fn useless_exception_statement(checker: &mut Checker, expr: &ast::Stm
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the given expression is a builtin exception.
|
/// Returns `true` if the given expression is a builtin exception.
|
||||||
fn is_builtin_exception(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_builtin_exception(
|
||||||
|
expr: &Expr,
|
||||||
|
semantic: &SemanticModel,
|
||||||
|
target_version: PythonVersion,
|
||||||
|
) -> bool {
|
||||||
semantic
|
semantic
|
||||||
.resolve_qualified_name(expr)
|
.resolve_qualified_name(expr)
|
||||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", name] if builtins::is_exception(name)))
|
.is_some_and(|qualified_name| {
|
||||||
|
matches!(qualified_name.segments(), ["" | "builtins", name]
|
||||||
|
if builtins::is_exception(name, target_version.minor()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,3 +213,39 @@ useless_exception_statement.py:71:5: PLW0133 [*] Missing `raise` statement on ex
|
||||||
72 72 |
|
72 72 |
|
||||||
73 73 |
|
73 73 |
|
||||||
74 74 | # Non-violation test cases: PLW0133
|
74 74 | # Non-violation test cases: PLW0133
|
||||||
|
|
||||||
|
useless_exception_statement.py:126:1: PLW0133 [*] Missing `raise` statement on exception
|
||||||
|
|
|
||||||
|
124 | import builtins
|
||||||
|
125 |
|
||||||
|
126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0133
|
||||||
|
127 |
|
||||||
|
128 | PythonFinalizationError("Added in Python 3.13")
|
||||||
|
|
|
||||||
|
= help: Add `raise` keyword
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
123 123 |
|
||||||
|
124 124 | import builtins
|
||||||
|
125 125 |
|
||||||
|
126 |-builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
126 |+raise builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
127 127 |
|
||||||
|
128 128 | PythonFinalizationError("Added in Python 3.13")
|
||||||
|
|
||||||
|
useless_exception_statement.py:128:1: PLW0133 [*] Missing `raise` statement on exception
|
||||||
|
|
|
||||||
|
126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
127 |
|
||||||
|
128 | PythonFinalizationError("Added in Python 3.13")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0133
|
||||||
|
|
|
||||||
|
= help: Add `raise` keyword
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
125 125 |
|
||||||
|
126 126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||||
|
127 127 |
|
||||||
|
128 |-PythonFinalizationError("Added in Python 3.13")
|
||||||
|
128 |+raise PythonFinalizationError("Added in Python 3.13")
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Intended to be kept in sync with [`is_ipython_builtin`].
|
/// Intended to be kept in sync with [`is_ipython_builtin`].
|
||||||
pub const IPYTHON_BUILTINS: &[&str] = &["__IPYTHON__", "display", "get_ipython"];
|
const IPYTHON_BUILTINS: &[&str] = &["__IPYTHON__", "display", "get_ipython"];
|
||||||
|
|
||||||
/// Globally defined names which are not attributes of the builtins module, or
|
/// Globally defined names which are not attributes of the builtins module, or
|
||||||
/// are only present on some platforms.
|
/// are only present on some platforms.
|
||||||
|
@ -26,7 +26,7 @@ pub const MAGIC_GLOBALS: &[&str] = &[
|
||||||
/// Return the list of builtins for the given Python minor version.
|
/// Return the list of builtins for the given Python minor version.
|
||||||
///
|
///
|
||||||
/// Intended to be kept in sync with [`is_python_builtin`].
|
/// Intended to be kept in sync with [`is_python_builtin`].
|
||||||
pub fn python_builtins(minor: u8) -> Vec<&'static str> {
|
pub fn python_builtins(minor_version: u8, is_notebook: bool) -> Vec<&'static str> {
|
||||||
let mut builtins = vec![
|
let mut builtins = vec![
|
||||||
"ArithmeticError",
|
"ArithmeticError",
|
||||||
"AssertionError",
|
"AssertionError",
|
||||||
|
@ -182,16 +182,20 @@ pub fn python_builtins(minor: u8) -> Vec<&'static str> {
|
||||||
"zip",
|
"zip",
|
||||||
];
|
];
|
||||||
|
|
||||||
if minor >= 10 {
|
if minor_version >= 10 {
|
||||||
builtins.extend(vec!["EncodingWarning", "aiter", "anext"]);
|
builtins.extend(&["EncodingWarning", "aiter", "anext"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if minor >= 11 {
|
if minor_version >= 11 {
|
||||||
builtins.extend(vec!["BaseExceptionGroup", "ExceptionGroup"]);
|
builtins.extend(&["BaseExceptionGroup", "ExceptionGroup"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if minor >= 13 {
|
if minor_version >= 13 {
|
||||||
builtins.extend(vec!["PythonFinalizationError"]);
|
builtins.push("PythonFinalizationError");
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_notebook {
|
||||||
|
builtins.extend(IPYTHON_BUILTINS);
|
||||||
}
|
}
|
||||||
|
|
||||||
builtins
|
builtins
|
||||||
|
@ -200,7 +204,10 @@ pub fn python_builtins(minor: u8) -> Vec<&'static str> {
|
||||||
/// Returns `true` if the given name is that of a Python builtin.
|
/// Returns `true` if the given name is that of a Python builtin.
|
||||||
///
|
///
|
||||||
/// Intended to be kept in sync with [`python_builtins`].
|
/// Intended to be kept in sync with [`python_builtins`].
|
||||||
pub fn is_python_builtin(name: &str, minor_version: u8) -> bool {
|
pub fn is_python_builtin(name: &str, minor_version: u8, is_notebook: bool) -> bool {
|
||||||
|
if is_notebook && is_ipython_builtin(name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
matches!(
|
matches!(
|
||||||
(minor_version, name),
|
(minor_version, name),
|
||||||
(
|
(
|
||||||
|
@ -374,7 +381,7 @@ pub fn is_iterator(name: &str) -> bool {
|
||||||
/// Returns `true` if the given name is that of an IPython builtin.
|
/// Returns `true` if the given name is that of an IPython builtin.
|
||||||
///
|
///
|
||||||
/// Intended to be kept in sync with [`IPYTHON_BUILTINS`].
|
/// Intended to be kept in sync with [`IPYTHON_BUILTINS`].
|
||||||
pub fn is_ipython_builtin(name: &str) -> bool {
|
fn is_ipython_builtin(name: &str) -> bool {
|
||||||
// Constructed by converting the `IPYTHON_BUILTINS` slice to a `match` expression.
|
// Constructed by converting the `IPYTHON_BUILTINS` slice to a `match` expression.
|
||||||
matches!(name, "__IPYTHON__" | "display" | "get_ipython")
|
matches!(name, "__IPYTHON__" | "display" | "get_ipython")
|
||||||
}
|
}
|
||||||
|
@ -382,75 +389,77 @@ pub fn is_ipython_builtin(name: &str) -> bool {
|
||||||
/// Returns `true` if the given name is that of a builtin exception.
|
/// Returns `true` if the given name is that of a builtin exception.
|
||||||
///
|
///
|
||||||
/// See: <https://docs.python.org/3/library/exceptions.html#exception-hierarchy>
|
/// See: <https://docs.python.org/3/library/exceptions.html#exception-hierarchy>
|
||||||
pub fn is_exception(name: &str) -> bool {
|
pub fn is_exception(name: &str, minor_version: u8) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
name,
|
(minor_version, name),
|
||||||
"BaseException"
|
(
|
||||||
| "BaseExceptionGroup"
|
_,
|
||||||
| "GeneratorExit"
|
"BaseException"
|
||||||
| "KeyboardInterrupt"
|
| "GeneratorExit"
|
||||||
| "SystemExit"
|
| "KeyboardInterrupt"
|
||||||
| "Exception"
|
| "SystemExit"
|
||||||
| "ArithmeticError"
|
| "Exception"
|
||||||
| "FloatingPointError"
|
| "ArithmeticError"
|
||||||
| "OverflowError"
|
| "FloatingPointError"
|
||||||
| "ZeroDivisionError"
|
| "OverflowError"
|
||||||
| "AssertionError"
|
| "ZeroDivisionError"
|
||||||
| "AttributeError"
|
| "AssertionError"
|
||||||
| "BufferError"
|
| "AttributeError"
|
||||||
| "EOFError"
|
| "BufferError"
|
||||||
| "ExceptionGroup"
|
| "EOFError"
|
||||||
| "ImportError"
|
| "ImportError"
|
||||||
| "ModuleNotFoundError"
|
| "ModuleNotFoundError"
|
||||||
| "LookupError"
|
| "LookupError"
|
||||||
| "IndexError"
|
| "IndexError"
|
||||||
| "KeyError"
|
| "KeyError"
|
||||||
| "MemoryError"
|
| "MemoryError"
|
||||||
| "NameError"
|
| "NameError"
|
||||||
| "UnboundLocalError"
|
| "UnboundLocalError"
|
||||||
| "OSError"
|
| "OSError"
|
||||||
| "BlockingIOError"
|
| "BlockingIOError"
|
||||||
| "ChildProcessError"
|
| "ChildProcessError"
|
||||||
| "ConnectionError"
|
| "ConnectionError"
|
||||||
| "BrokenPipeError"
|
| "BrokenPipeError"
|
||||||
| "ConnectionAbortedError"
|
| "ConnectionAbortedError"
|
||||||
| "ConnectionRefusedError"
|
| "ConnectionRefusedError"
|
||||||
| "ConnectionResetError"
|
| "ConnectionResetError"
|
||||||
| "FileExistsError"
|
| "FileExistsError"
|
||||||
| "FileNotFoundError"
|
| "FileNotFoundError"
|
||||||
| "InterruptedError"
|
| "InterruptedError"
|
||||||
| "IsADirectoryError"
|
| "IsADirectoryError"
|
||||||
| "NotADirectoryError"
|
| "NotADirectoryError"
|
||||||
| "PermissionError"
|
| "PermissionError"
|
||||||
| "ProcessLookupError"
|
| "ProcessLookupError"
|
||||||
| "TimeoutError"
|
| "TimeoutError"
|
||||||
| "ReferenceError"
|
| "ReferenceError"
|
||||||
| "RuntimeError"
|
| "RuntimeError"
|
||||||
| "NotImplementedError"
|
| "NotImplementedError"
|
||||||
| "RecursionError"
|
| "RecursionError"
|
||||||
| "StopAsyncIteration"
|
| "StopAsyncIteration"
|
||||||
| "StopIteration"
|
| "StopIteration"
|
||||||
| "SyntaxError"
|
| "SyntaxError"
|
||||||
| "IndentationError"
|
| "IndentationError"
|
||||||
| "TabError"
|
| "TabError"
|
||||||
| "SystemError"
|
| "SystemError"
|
||||||
| "TypeError"
|
| "TypeError"
|
||||||
| "ValueError"
|
| "ValueError"
|
||||||
| "UnicodeError"
|
| "UnicodeError"
|
||||||
| "UnicodeDecodeError"
|
| "UnicodeDecodeError"
|
||||||
| "UnicodeEncodeError"
|
| "UnicodeEncodeError"
|
||||||
| "UnicodeTranslateError"
|
| "UnicodeTranslateError"
|
||||||
| "Warning"
|
| "Warning"
|
||||||
| "BytesWarning"
|
| "BytesWarning"
|
||||||
| "DeprecationWarning"
|
| "DeprecationWarning"
|
||||||
| "EncodingWarning"
|
| "FutureWarning"
|
||||||
| "FutureWarning"
|
| "ImportWarning"
|
||||||
| "ImportWarning"
|
| "PendingDeprecationWarning"
|
||||||
| "PendingDeprecationWarning"
|
| "ResourceWarning"
|
||||||
| "ResourceWarning"
|
| "RuntimeWarning"
|
||||||
| "RuntimeWarning"
|
| "SyntaxWarning"
|
||||||
| "SyntaxWarning"
|
| "UnicodeWarning"
|
||||||
| "UnicodeWarning"
|
| "UserWarning"
|
||||||
| "UserWarning"
|
) | (10..=13, "EncodingWarning")
|
||||||
|
| (11..=13, "BaseExceptionGroup" | "ExceptionGroup")
|
||||||
|
| (13, "PythonFinalizationError")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue