Use truthiness check in auto_attribs detection (#14562)

This commit is contained in:
Charlie Marsh 2024-11-23 22:06:10 -05:00 committed by GitHub
parent d285717da8
commit de62e39eba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 54 additions and 15 deletions

View file

@ -99,3 +99,27 @@ class C:
b = field() b = field()
c: int = foo() c: int = foo()
d = list() d = list()
@attr.s(auto_attribs=False) # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=True) # auto_attribs = True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=[1, 2, 3]) # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()

View file

@ -311,7 +311,8 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
} }
// S603 // S603
Some(ShellKeyword { Some(ShellKeyword {
truthiness: Truthiness::False | Truthiness::Falsey | Truthiness::Unknown, truthiness:
Truthiness::False | Truthiness::Falsey | Truthiness::None | Truthiness::Unknown,
}) => { }) => {
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) { if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(

View file

@ -87,6 +87,6 @@ fn exc_info_arg_is_falsey(call: &ExprCall, checker: &mut Checker) -> bool {
.is_some_and(|value| { .is_some_and(|value| {
let truthiness = let truthiness =
Truthiness::from_expr(value, |id| checker.semantic().has_builtin_binding(id)); Truthiness::from_expr(value, |id| checker.semantic().has_builtin_binding(id));
matches!(truthiness, Truthiness::False | Truthiness::Falsey) truthiness.into_bool() == Some(false)
}) })
} }

View file

@ -493,7 +493,7 @@ fn to_pytest_raises_args<'a>(
/// PT015 /// PT015
pub(crate) fn assert_falsy(checker: &mut Checker, stmt: &Stmt, test: &Expr) { pub(crate) fn assert_falsy(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
let truthiness = Truthiness::from_expr(test, |id| checker.semantic().has_builtin_binding(id)); let truthiness = Truthiness::from_expr(test, |id| checker.semantic().has_builtin_binding(id));
if matches!(truthiness, Truthiness::False | Truthiness::Falsey) { if truthiness.into_bool() == Some(false) {
checker checker
.diagnostics .diagnostics
.push(Diagnostic::new(PytestAssertAlwaysFalse, stmt.range())); .push(Diagnostic::new(PytestAssertAlwaysFalse, stmt.range()));

View file

@ -1,4 +1,4 @@
use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_ast::helpers::{map_callable, map_subscript, Truthiness};
use ruff_python_ast::{self as ast, Expr, ExprCall}; use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{analyze, BindingKind, Modules, SemanticModel}; use ruff_python_semantic::{analyze, BindingKind, Modules, SemanticModel};
@ -148,16 +148,19 @@ pub(super) fn dataclass_kind(
return Some(DataclassKind::Attrs(AttrsAutoAttribs::None)); return Some(DataclassKind::Attrs(AttrsAutoAttribs::None));
}; };
let auto_attribs = match &auto_attribs.value { let auto_attribs = match Truthiness::from_expr(&auto_attribs.value, |id| {
Expr::BooleanLiteral(literal) => { semantic.has_builtin_binding(id)
if literal.value { }) {
AttrsAutoAttribs::True // `auto_attribs` requires an exact `True` to be true
} else { Truthiness::True => AttrsAutoAttribs::True,
AttrsAutoAttribs::False // Or an exact `None` to auto-detect.
} Truthiness::None => AttrsAutoAttribs::None,
// Otherwise, anything else (even a truthy value, like `1`) is considered `False`.
Truthiness::Truthy | Truthiness::False | Truthiness::Falsey => {
AttrsAutoAttribs::False
} }
Expr::NoneLiteral(..) => AttrsAutoAttribs::None, // Unless, of course, we can't determine the value.
_ => AttrsAutoAttribs::Unknown, Truthiness::Unknown => AttrsAutoAttribs::Unknown,
}; };
return Some(DataclassKind::Attrs(auto_attribs)); return Some(DataclassKind::Attrs(auto_attribs));

View file

@ -1,6 +1,5 @@
--- ---
source: crates/ruff_linter/src/rules/ruff/mod.rs source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
--- ---
RUF009_attrs_auto_attribs.py:12:14: RUF009 Do not perform function call `foo` in dataclass defaults RUF009_attrs_auto_attribs.py:12:14: RUF009 Do not perform function call `foo` in dataclass defaults
| |
@ -100,3 +99,12 @@ RUF009_attrs_auto_attribs.py:100:14: RUF009 Do not perform function call `foo` i
| ^^^^^ RUF009 | ^^^^^ RUF009
101 | d = list() 101 | d = list()
| |
RUF009_attrs_auto_attribs.py:116:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
114 | a: str = 0
115 | b = field()
116 | c: int = foo()
| ^^^^^ RUF009
117 | d = list()
|

View file

@ -1118,6 +1118,8 @@ pub enum Truthiness {
Falsey, Falsey,
/// The expression evaluates to a `True`-like value (e.g., `1`, `"foo"`). /// The expression evaluates to a `True`-like value (e.g., `1`, `"foo"`).
Truthy, Truthy,
/// The expression evaluates to `None`.
None,
/// The expression evaluates to an unknown value (e.g., a variable `x` of unknown type). /// The expression evaluates to an unknown value (e.g., a variable `x` of unknown type).
Unknown, Unknown,
} }
@ -1173,7 +1175,7 @@ impl Truthiness {
Self::False Self::False
} }
} }
Expr::NoneLiteral(_) => Self::Falsey, Expr::NoneLiteral(_) => Self::None,
Expr::EllipsisLiteral(_) => Self::Truthy, Expr::EllipsisLiteral(_) => Self::Truthy,
Expr::FString(f_string) => { Expr::FString(f_string) => {
if is_empty_f_string(f_string) { if is_empty_f_string(f_string) {
@ -1247,6 +1249,7 @@ impl Truthiness {
match self { match self {
Self::True | Self::Truthy => Some(true), Self::True | Self::Truthy => Some(true),
Self::False | Self::Falsey => Some(false), Self::False | Self::Falsey => Some(false),
Self::None => Some(false),
Self::Unknown => None, Self::Unknown => None,
} }
} }