mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-08 20:58:32 +00:00
[syntax-errors] Store to or delete __debug__
(#16984)
Summary -- Detect setting or deleting `__debug__`. Assigning to `__debug__` was a `SyntaxError` on the earliest version I tested (3.8). Deleting `__debug__` was made a `SyntaxError` in [BPO 45000], which said it was resolved in Python 3.10. However, `del __debug__` was also a runtime error (`NameError`) when I tested in Python 3.9.6, so I thought it was worth including 3.9 in this check. I don't think it was ever a *good* idea to try `del __debug__`, so I think there's also an argument for not making this version-dependent at all. That would only simplify the implementation very slightly, though. [BPO 45000]: https://github.com/python/cpython/issues/89163 Test Plan -- New inline tests. This also required adding a `PythonVersion` field to the `TestContext` that could be taken from the inline `ParseOptions` and making the version field on the options accessible.
This commit is contained in:
parent
8396d7cd63
commit
a0819f0c51
28 changed files with 1372 additions and 7 deletions
|
@ -9,7 +9,8 @@ use std::fmt::Display;
|
|||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
visitor::{walk_expr, Visitor},
|
||||
Expr, IrrefutablePatternKind, Pattern, PythonVersion, Stmt, StmtExpr, StmtImportFrom,
|
||||
Expr, ExprContext, IrrefutablePatternKind, Pattern, PythonVersion, Stmt, StmtExpr,
|
||||
StmtImportFrom,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
@ -74,6 +75,105 @@ impl SemanticSyntaxChecker {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self::debug_shadowing(stmt, ctx);
|
||||
}
|
||||
|
||||
/// Check for [`SemanticSyntaxErrorKind::WriteToDebug`] in `stmt`.
|
||||
fn debug_shadowing<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
name,
|
||||
type_params,
|
||||
parameters,
|
||||
..
|
||||
}) => {
|
||||
// test_err debug_shadow_function
|
||||
// def __debug__(): ... # function name
|
||||
// def f[__debug__](): ... # type parameter name
|
||||
// def f(__debug__): ... # parameter name
|
||||
Self::check_identifier(name, ctx);
|
||||
if let Some(type_params) = type_params {
|
||||
for type_param in type_params.iter() {
|
||||
Self::check_identifier(type_param.name(), ctx);
|
||||
}
|
||||
}
|
||||
for parameter in parameters {
|
||||
Self::check_identifier(parameter.name(), ctx);
|
||||
}
|
||||
}
|
||||
Stmt::ClassDef(ast::StmtClassDef {
|
||||
name, type_params, ..
|
||||
}) => {
|
||||
// test_err debug_shadow_class
|
||||
// class __debug__: ... # class name
|
||||
// class C[__debug__]: ... # type parameter name
|
||||
Self::check_identifier(name, ctx);
|
||||
if let Some(type_params) = type_params {
|
||||
for type_param in type_params.iter() {
|
||||
Self::check_identifier(type_param.name(), ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
type_params: Some(type_params),
|
||||
..
|
||||
}) => {
|
||||
// test_err debug_shadow_type_alias
|
||||
// type __debug__ = list[int] # visited as an Expr but still flagged
|
||||
// type Debug[__debug__] = str
|
||||
for type_param in type_params.iter() {
|
||||
Self::check_identifier(type_param.name(), ctx);
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, .. })
|
||||
| Stmt::ImportFrom(ast::StmtImportFrom { names, .. }) => {
|
||||
// test_err debug_shadow_import
|
||||
// import __debug__
|
||||
// import debug as __debug__
|
||||
// from x import __debug__
|
||||
// from x import debug as __debug__
|
||||
|
||||
// test_ok debug_rename_import
|
||||
// import __debug__ as debug
|
||||
// from __debug__ import Some
|
||||
// from x import __debug__ as debug
|
||||
for name in names {
|
||||
match &name.asname {
|
||||
Some(asname) => Self::check_identifier(asname, ctx),
|
||||
None => Self::check_identifier(&name.name, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry { handlers, .. }) => {
|
||||
// test_err debug_shadow_try
|
||||
// try: ...
|
||||
// except Exception as __debug__: ...
|
||||
for handler in handlers
|
||||
.iter()
|
||||
.filter_map(ast::ExceptHandler::as_except_handler)
|
||||
{
|
||||
if let Some(name) = &handler.name {
|
||||
Self::check_identifier(name, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// test_err debug_shadow_with
|
||||
// with open("foo.txt") as __debug__: ...
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `ident` is equal to `__debug__` and emit a
|
||||
/// [`SemanticSyntaxErrorKind::WriteToDebug`] if so.
|
||||
fn check_identifier<Ctx: SemanticSyntaxContext>(ident: &ast::Identifier, ctx: &Ctx) {
|
||||
if ident.id == "__debug__" {
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::WriteToDebug(WriteToDebugKind::Store),
|
||||
ident.range,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate_type_parameter_name<Ctx: SemanticSyntaxContext>(
|
||||
|
@ -207,6 +307,51 @@ impl SemanticSyntaxChecker {
|
|||
Self::check_generator_expr(key, generators, ctx);
|
||||
Self::check_generator_expr(value, generators, ctx);
|
||||
}
|
||||
Expr::Name(ast::ExprName {
|
||||
range,
|
||||
id,
|
||||
ctx: expr_ctx,
|
||||
}) => {
|
||||
// test_err write_to_debug_expr
|
||||
// del __debug__
|
||||
// del x, y, __debug__, z
|
||||
// __debug__ = 1
|
||||
// x, y, __debug__, z = 1, 2, 3, 4
|
||||
|
||||
// test_err del_debug_py39
|
||||
// # parse_options: {"target-version": "3.9"}
|
||||
// del __debug__
|
||||
|
||||
// test_ok del_debug_py38
|
||||
// # parse_options: {"target-version": "3.8"}
|
||||
// del __debug__
|
||||
|
||||
// test_ok read_from_debug
|
||||
// if __debug__: ...
|
||||
// x = __debug__
|
||||
if id == "__debug__" {
|
||||
match expr_ctx {
|
||||
ExprContext::Store => Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::WriteToDebug(WriteToDebugKind::Store),
|
||||
*range,
|
||||
),
|
||||
ExprContext::Del => {
|
||||
let version = ctx.python_version();
|
||||
if version >= PythonVersion::PY39 {
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::WriteToDebug(
|
||||
WriteToDebugKind::Delete(version),
|
||||
),
|
||||
*range,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -292,6 +437,12 @@ impl Display for SemanticSyntaxError {
|
|||
f.write_str("wildcard makes remaining patterns unreachable")
|
||||
}
|
||||
},
|
||||
SemanticSyntaxErrorKind::WriteToDebug(kind) => match kind {
|
||||
WriteToDebugKind::Store => f.write_str("cannot assign to `__debug__`"),
|
||||
WriteToDebugKind::Delete(python_version) => {
|
||||
write!(f, "cannot delete `__debug__` on Python {python_version} (syntax was removed in 3.9)")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -370,6 +521,30 @@ pub enum SemanticSyntaxErrorKind {
|
|||
///
|
||||
/// [Python reference]: https://docs.python.org/3/reference/compound_stmts.html#irrefutable-case-blocks
|
||||
IrrefutableCasePattern(IrrefutablePatternKind),
|
||||
|
||||
/// Represents a write to `__debug__`. This includes simple assignments and deletions as well
|
||||
/// other kinds of statements that can introduce bindings, such as type parameters in functions,
|
||||
/// classes, and aliases, `match` arms, and imports, among others.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// del __debug__
|
||||
/// __debug__ = False
|
||||
/// def f(__debug__): ...
|
||||
/// class C[__debug__]: ...
|
||||
/// ```
|
||||
///
|
||||
/// See [BPO 45000] for more information.
|
||||
///
|
||||
/// [BPO 45000]: https://github.com/python/cpython/issues/89163
|
||||
WriteToDebug(WriteToDebugKind),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WriteToDebugKind {
|
||||
Store,
|
||||
Delete(PythonVersion),
|
||||
}
|
||||
|
||||
/// Searches for the first named expression (`x := y`) rebinding one of the `iteration_variables` in
|
||||
|
@ -480,6 +655,10 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
|
|||
ident.range(),
|
||||
);
|
||||
}
|
||||
// test_err debug_shadow_match
|
||||
// match x:
|
||||
// case __debug__: ...
|
||||
SemanticSyntaxChecker::check_identifier(ident, self.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue