[ruff] Implement falsy-dict-get-fallback (RUF056) (#15160)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Arnav Gupta 2024-12-31 05:40:51 -05:00 committed by GitHub
parent 68d2466832
commit 3c9021ffcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 897 additions and 88 deletions

View file

@ -0,0 +1,171 @@
# Correct
my_dict = {"key": "value", "other_key": "other_value"}
# Using dict.get without a fallback
value = my_dict.get("key")
# Using dict.get with a truthy fallback
value = my_dict.get("key", "default")
value = my_dict.get("key", 42)
value = my_dict.get("key", [1, 2, 3])
value = my_dict.get("key", {"nested": "dict"})
value = my_dict.get("key", set([1, 2]))
value = my_dict.get("key", True)
value = my_dict.get("key", "Non-empty string")
value = my_dict.get("key", 3.14)
value = my_dict.get("key", (1, 2)) # Tuples are truthy
# Chained get calls with truthy fallbacks
value1 = my_dict.get("key1", {'k': 'v'}).get("subkey")
value2 = my_dict.get("key2", [1, 2, 3]).append(4)
# Valid
# Using dict.get with a falsy fallback: False
value = my_dict.get("key", False)
# Using dict.get with a falsy fallback: empty string
value = my_dict.get("key", "")
# Using dict.get with a falsy fallback: empty list
value = my_dict.get("key", [])
# Using dict.get with a falsy fallback: empty dict
value = my_dict.get("key", {})
# Using dict.get with a falsy fallback: empty set
value = my_dict.get("key", set())
# Using dict.get with a falsy fallback: zero integer
value = my_dict.get("key", 0)
# Using dict.get with a falsy fallback: zero float
value = my_dict.get("key", 0.0)
# Using dict.get with a falsy fallback: None
value = my_dict.get("key", None)
# Using dict.get with a falsy fallback via function call
value = my_dict.get("key", list())
value = my_dict.get("key", dict())
value = my_dict.get("key", set())
# Reassigning with falsy fallback
def get_value(d):
return d.get("key", False)
# Multiple dict.get calls with mixed fallbacks
value1 = my_dict.get("key1", "default")
value2 = my_dict.get("key2", 0)
value3 = my_dict.get("key3", "another default")
# Using dict.get in a class with falsy fallback
class MyClass:
def method(self):
return self.data.get("key", {})
# Using dict.get in a nested function with falsy fallback
def outer():
def inner():
return my_dict.get("key", "")
return inner()
# Using dict.get with variable fallback that is falsy
falsy_value = None
value = my_dict.get("key", falsy_value)
# Using dict.get with variable fallback that is truthy
truthy_value = "exists"
value = my_dict.get("key", truthy_value)
# Using dict.get with complex expressions as fallback
value = my_dict.get("key", 0 or "default")
value = my_dict.get("key", [] if condition else {})
# testing dict.get call using kwargs
value = my_dict.get(key="key", default=False)
value = my_dict.get(default=[], key="key")
# Edge Cases
dicts = [my_dict, my_dict, my_dict]
# Falsy fallback in a lambda
get_fallback = lambda d: d.get("key", False)
# Falsy fallback in a list comprehension
results = [d.get("key", "") for d in dicts]
# Falsy fallback in a generator expression
results = (d.get("key", None) for d in dicts)
# Falsy fallback in a ternary expression
value = my_dict.get("key", 0) if True else "default"
# Falsy fallback with inline comment
value = my_dict.get("key", # comment1
[] # comment2
) # comment3
# Invalid
# Invalid falsy fallbacks are when the call to dict.get is used in a boolean context
# dict.get in ternary expression
value = "not found" if my_dict.get("key", False) else "default" # [RUF056]
# dict.get in an if statement
if my_dict.get("key", False): # [RUF056]
pass
# dict.get in compound if statement
if True and my_dict.get("key", False): # [RUF056]
pass
if my_dict.get("key", False) or True: # [RUF056]
pass
# dict.get in an assert statement
assert my_dict.get("key", False) # [RUF056]
# dict.get in a while statement
while my_dict.get("key", False): # [RUF056]
pass
# dict.get in unary not expression
value = not my_dict.get("key", False) # [RUF056]
# testing all falsy fallbacks
value = not my_dict.get("key", False) # [RUF056]
value = not my_dict.get("key", []) # [RUF056]
value = not my_dict.get("key", list()) # [RUF056]
value = not my_dict.get("key", {}) # [RUF056]
value = not my_dict.get("key", dict()) # [RUF056]
value = not my_dict.get("key", set()) # [RUF056]
value = not my_dict.get("key", None) # [RUF056]
value = not my_dict.get("key", 0) # [RUF056]
value = not my_dict.get("key", 0.0) # [RUF056]
value = not my_dict.get("key", "") # [RUF056]
# testing dict.get call using kwargs
value = not my_dict.get(key="key", default=False) # [RUF056]
value = not my_dict.get(default=[], key="key") # [RUF056]
# testing invalid dict.get call with inline comment
value = not my_dict.get("key", # comment1
[] # comment2
) # [RUF056]
# testing invalid dict.get call with kwargs and inline comment
value = not my_dict.get(key="key", # comment1
default=False # comment2
) # [RUF056]
value = not my_dict.get(default=[], # comment1
key="key" # comment2
) # [RUF056]
# testing invalid dict.get calls
value = not my_dict.get(key="key", other="something", default=False)
value = not my_dict.get(default=False, other="something", key="test")

View file

@ -1111,6 +1111,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::PytestRaisesAmbiguousPattern) {
ruff::rules::pytest_raises_ambiguous_pattern(checker, call);
}
if checker.enabled(Rule::FalsyDictGetFallback) {
ruff::rules::falsy_dict_get_fallback(checker, expr);
}
}
Expr::Dict(dict) => {
if checker.any_enabled(&[

View file

@ -991,6 +991,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel),
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),

View file

@ -293,10 +293,10 @@ impl Renamer {
let qualified_name = semantic.resolve_qualified_name(func)?;
let name_argument = match qualified_name.segments() {
["collections", "namedtuple"] => arguments.find_argument("typename", 0),
["collections", "namedtuple"] => arguments.find_argument_value("typename", 0),
["typing" | "typing_extensions", "TypeVar" | "ParamSpec" | "TypeVarTuple" | "NewType" | "TypeAliasType"] => {
arguments.find_argument("name", 0)
arguments.find_argument_value("name", 0)
}
["enum", "Enum" | "IntEnum" | "StrEnum" | "ReprEnum" | "Flag" | "IntFlag"]

View file

@ -62,7 +62,7 @@ pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) {
return;
}
let Some(arg) = call.arguments.find_argument("seconds", 0) else {
let Some(arg) = call.arguments.find_argument_value("seconds", 0) else {
return;
};

View file

@ -148,7 +148,7 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp
}
fn is_p_wait(call: &ast::ExprCall, semantic: &SemanticModel) -> bool {
let Some(arg) = call.arguments.find_argument("mode", 0) else {
let Some(arg) = call.arguments.find_argument_value("mode", 0) else {
return true;
};

View file

@ -67,7 +67,7 @@ pub(crate) fn long_sleep_not_forever(checker: &mut Checker, call: &ExprCall) {
return;
}
let Some(arg) = call.arguments.find_argument("seconds", 0) else {
let Some(arg) = call.arguments.find_argument_value("seconds", 0) else {
return;
};

View file

@ -71,7 +71,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "chmod"]))
{
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
if let Some(mode_arg) = call.arguments.find_argument_value("mode", 1) {
match parse_mask(mode_arg, checker.semantic()) {
// The mask couldn't be determined (e.g., it's dynamic).
Ok(None) => {}

View file

@ -62,7 +62,7 @@ pub(crate) fn django_extra(checker: &mut Checker, call: &ast::ExprCall) {
fn is_call_insecure(call: &ast::ExprCall) -> bool {
for (argument_name, position) in [("select", 0), ("where", 1), ("tables", 3)] {
if let Some(argument) = call.arguments.find_argument(argument_name, position) {
if let Some(argument) = call.arguments.find_argument_value(argument_name, position) {
match argument_name {
"select" => match argument {
Expr::Dict(dict) => {

View file

@ -52,7 +52,7 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
{
if !call
.arguments
.find_argument("sql", 0)
.find_argument_value("sql", 0)
.is_some_and(Expr::is_string_literal_expr)
{
checker

View file

@ -115,7 +115,7 @@ fn detect_insecure_hashlib_calls(
match hashlib_call {
HashlibCall::New => {
let Some(name_arg) = call.arguments.find_argument("name", 0) else {
let Some(name_arg) = call.arguments.find_argument_value("name", 0) else {
return;
};
let Some(hash_func_name) = string_literal(name_arg) else {
@ -159,7 +159,7 @@ fn detect_insecure_crypt_calls(checker: &mut Checker, call: &ast::ExprCall) {
_ => None,
})
.and_then(|(argument_name, position)| {
call.arguments.find_argument(argument_name, position)
call.arguments.find_argument_value(argument_name, position)
})
else {
return;

View file

@ -53,7 +53,7 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
return;
}
let Some(policy_argument) = call.arguments.find_argument("policy", 0) else {
let Some(policy_argument) = call.arguments.find_argument_value("policy", 0) else {
return;
};

View file

@ -906,7 +906,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
["six", "moves", "urllib", "request", "Request"] => {
// If the `url` argument is a string literal or an f-string, allow `http` and `https` schemes.
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
if call.arguments.find_argument("url", 0).and_then(leading_chars).is_some_and(has_http_prefix) {
if call.arguments.find_argument_value("url", 0).and_then(leading_chars).is_some_and(has_http_prefix) {
return None;
}
}
@ -916,11 +916,11 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
["urllib", "request", "urlopen" | "urlretrieve" ] |
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" ] => {
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
match call.arguments.find_argument("url", 0) {
match call.arguments.find_argument_value("url", 0) {
// If the `url` argument is a `urllib.request.Request` object, allow `http` and `https` schemes.
Some(Expr::Call(ExprCall { func, arguments, .. })) => {
if checker.semantic().resolve_qualified_name(func.as_ref()).is_some_and(|name| name.segments() == ["urllib", "request", "Request"]) {
if arguments.find_argument("url", 0).and_then(leading_chars).is_some_and(has_http_prefix) {
if arguments.find_argument_value("url", 0).and_then(leading_chars).is_some_and(has_http_prefix) {
return None;
}
}

View file

@ -65,7 +65,7 @@ pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["yaml", "load"]))
{
if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) {
if let Some(loader_arg) = call.arguments.find_argument_value("Loader", 1) {
if !checker
.semantic()
.resolve_qualified_name(loader_arg)

View file

@ -114,7 +114,7 @@ fn extract_cryptographic_key(
Some((CryptographicKey::Rsa { key_size }, range))
}
"ec" => {
let argument = call.arguments.find_argument("curve", 0)?;
let argument = call.arguments.find_argument_value("curve", 0)?;
let ExprAttribute { attr, value, .. } = argument.as_attribute_expr()?;
let qualified_name = checker.semantic().resolve_qualified_name(value)?;
if matches!(
@ -150,7 +150,7 @@ fn extract_cryptographic_key(
}
fn extract_int_argument(call: &ExprCall, name: &str, position: usize) -> Option<(u16, TextRange)> {
let argument = call.arguments.find_argument(name, position)?;
let argument = call.arguments.find_argument_value(name, position)?;
let Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(i),
..

View file

@ -60,7 +60,10 @@ pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall
return;
}
if call.arguments.find_argument("stacklevel", 2).is_some()
if call
.arguments
.find_argument_value("stacklevel", 2)
.is_some()
|| call
.arguments
.args

View file

@ -91,7 +91,7 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp
return;
}
let antipattern = match call.arguments.find_argument("tz", 1) {
let antipattern = match call.arguments.find_argument_value("tz", 1) {
Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument,
Some(_) => return,
None => DatetimeModuleAntipattern::NoTzArgumentPassed,

View file

@ -86,7 +86,7 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast
return;
}
let antipattern = match call.arguments.find_argument("tz", 0) {
let antipattern = match call.arguments.find_argument_value("tz", 0) {
Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument,
Some(_) => return,
None => DatetimeModuleAntipattern::NoTzArgumentPassed,

View file

@ -80,7 +80,7 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex
return;
}
let antipattern = match call.arguments.find_argument("tzinfo", 7) {
let antipattern = match call.arguments.find_argument_value("tzinfo", 7) {
Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument,
Some(_) => return,
None => DatetimeModuleAntipattern::NoTzArgumentPassed,

View file

@ -59,7 +59,7 @@ pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprC
return;
}
if let Some(argument) = call.arguments.find_argument("context", 2) {
if let Some(argument) = call.arguments.find_argument_value("context", 2) {
if is_locals_call(argument, checker.semantic()) {
checker.diagnostics.push(Diagnostic::new(
DjangoLocalsInRenderFunction,

View file

@ -62,7 +62,8 @@ pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::Exp
return;
}
let Some(Expr::Name(expr @ ast::ExprName { id, .. })) = call.arguments.find_argument("name", 0)
let Some(Expr::Name(expr @ ast::ExprName { id, .. })) =
call.arguments.find_argument_value("name", 0)
else {
return;
};

View file

@ -181,7 +181,7 @@ pub(crate) fn logging_call(checker: &mut Checker, call: &ast::ExprCall) {
// G001 - G004
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
if let Some(format_arg) = call.arguments.find_argument("msg", msg_pos) {
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
check_msg(checker, format_arg);
}

View file

@ -61,8 +61,8 @@ pub(crate) fn fail_call(checker: &mut Checker, call: &ast::ExprCall) {
// `pytest.fail(msg="...")` (deprecated in pytest 7.0)
if call
.arguments
.find_argument("reason", 0)
.or_else(|| call.arguments.find_argument("msg", 0))
.find_argument_value("reason", 0)
.or_else(|| call.arguments.find_argument_value("msg", 0))
.map_or(true, is_empty_or_null_string)
{
checker

View file

@ -864,12 +864,12 @@ pub(crate) fn parametrize(checker: &mut Checker, call: &ExprCall) {
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
let names = if checker.settings.preview.is_enabled() {
call.arguments.find_argument("argnames", 0)
call.arguments.find_argument_value("argnames", 0)
} else {
call.arguments.find_positional(0)
};
let values = if checker.settings.preview.is_enabled() {
call.arguments.find_argument("argvalues", 1)
call.arguments.find_argument_value("argvalues", 1)
} else {
call.arguments.find_positional(1)
};
@ -879,12 +879,12 @@ pub(crate) fn parametrize(checker: &mut Checker, call: &ExprCall) {
}
if checker.enabled(Rule::PytestParametrizeValuesWrongType) {
let names = if checker.settings.preview.is_enabled() {
call.arguments.find_argument("argnames", 0)
call.arguments.find_argument_value("argnames", 0)
} else {
call.arguments.find_positional(0)
};
let values = if checker.settings.preview.is_enabled() {
call.arguments.find_argument("argvalues", 1)
call.arguments.find_argument_value("argvalues", 1)
} else {
call.arguments.find_positional(1)
};
@ -894,7 +894,7 @@ pub(crate) fn parametrize(checker: &mut Checker, call: &ExprCall) {
}
if checker.enabled(Rule::PytestDuplicateParametrizeTestCases) {
if let Some(values) = if checker.settings.preview.is_enabled() {
call.arguments.find_argument("argvalues", 1)
call.arguments.find_argument_value("argvalues", 1)
} else {
call.arguments.find_positional(1)
} {

View file

@ -84,7 +84,7 @@ fn check_patch_call(call: &ast::ExprCall, index: usize) -> Option<Diagnostic> {
range: _,
} = call
.arguments
.find_argument("new", index)?
.find_argument_value("new", index)?
.as_lambda_expr()?;
// Walk the lambda body. If the lambda uses the arguments, then it's valid.

View file

@ -177,7 +177,7 @@ pub(crate) fn raises_call(checker: &mut Checker, call: &ast::ExprCall) {
}
if checker.enabled(Rule::PytestRaisesTooBroad) {
if let Some(exception) = call.arguments.find_argument("expected_exception", 0) {
if let Some(exception) = call.arguments.find_argument_value("expected_exception", 0) {
if call
.arguments
.find_keyword("match")

View file

@ -67,7 +67,7 @@ pub(crate) fn split_static_string(
) {
let ExprCall { arguments, .. } = call;
let maxsplit_arg = arguments.find_argument("maxsplit", 1);
let maxsplit_arg = arguments.find_argument_value("maxsplit", 1);
let Some(maxsplit_value) = get_maxsplit_value(maxsplit_arg) else {
return;
};
@ -79,7 +79,7 @@ pub(crate) fn split_static_string(
Direction::Right
};
let sep_arg = arguments.find_argument("sep", 0);
let sep_arg = arguments.find_argument_value("sep", 0);
let split_replacement = if let Some(sep) = sep_arg {
match sep {
Expr::NoneLiteral(_) => split_default(str_value, maxsplit_value),

View file

@ -89,7 +89,7 @@ pub(crate) fn invalid_pathlib_with_suffix(checker: &mut Checker, call: &ast::Exp
return;
}
let Some(ast::Expr::StringLiteral(string)) = arguments.find_argument("suffix", 0) else {
let Some(ast::Expr::StringLiteral(string)) = arguments.find_argument_value("suffix", 0) else {
return;
};

View file

@ -82,7 +82,7 @@ pub(crate) fn os_sep_split(checker: &mut Checker, call: &ast::ExprCall) {
return;
}
let Some(sep) = call.arguments.find_argument("sep", 0) else {
let Some(sep) = call.arguments.find_argument_value("sep", 0) else {
return;
};

View file

@ -115,7 +115,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, call: &ExprCall) {
// ```
if call
.arguments
.find_argument("closefd", 6)
.find_argument_value("closefd", 6)
.is_some_and(|expr| {
!matches!(
expr,
@ -124,7 +124,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, call: &ExprCall) {
})
|| call
.arguments
.find_argument("opener", 7)
.find_argument_value("opener", 7)
.is_some_and(|expr| !expr.is_none_literal_expr())
|| call
.arguments

View file

@ -121,8 +121,8 @@ pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &Semanti
// Match against, e.g., `apps.get_model("zerver", "Attachment")`.
if let Some(unqualified_name) = UnqualifiedName::from_expr(func.as_ref()) {
if matches!(unqualified_name.segments(), [.., "get_model"]) {
if let Some(argument) =
arguments.find_argument("model_name", arguments.args.len().saturating_sub(1))
if let Some(argument) = arguments
.find_argument_value("model_name", arguments.args.len().saturating_sub(1))
{
if let Some(string_literal) = argument.as_string_literal_expr() {
if string_literal.value.to_str() == name {
@ -141,7 +141,7 @@ pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &Semanti
qualified_name.segments(),
["django", "utils", "module_loading", "import_string"]
) {
if let Some(argument) = arguments.find_argument("dotted_path", 0) {
if let Some(argument) = arguments.find_argument_value("dotted_path", 0) {
if let Some(string_literal) = argument.as_string_literal_expr() {
if let Some((.., model)) = string_literal.value.to_str().rsplit_once('.') {
if model == name {

View file

@ -10,7 +10,7 @@ use crate::settings::LinterSettings;
/// Returns the value of the `name` parameter to, e.g., a `TypeVar` constructor.
pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
// Handle both `TypeVar("T")` and `TypeVar(name="T")`.
let name_param = arguments.find_argument("name", 0)?;
let name_param = arguments.find_argument_value("name", 0)?;
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &name_param {
Some(value.to_str())
} else {

View file

@ -107,7 +107,7 @@ fn is_open(func: &Expr, semantic: &SemanticModel) -> Option<Kind> {
/// Returns the mode argument, if present.
fn extract_mode(call: &ast::ExprCall, kind: Kind) -> Option<&Expr> {
match kind {
Kind::Builtin => call.arguments.find_argument("mode", 1),
Kind::Pathlib => call.arguments.find_argument("mode", 0),
Kind::Builtin => call.arguments.find_argument_value("mode", 1),
Kind::Pathlib => call.arguments.find_argument_value("mode", 0),
}
}

View file

@ -63,7 +63,7 @@ pub(crate) fn invalid_envvar_default(checker: &mut Checker, call: &ast::ExprCall
})
{
// Find the `default` argument, if it exists.
let Some(expr) = call.arguments.find_argument("default", 1) else {
let Some(expr) = call.arguments.find_argument_value("default", 1) else {
return;
};

View file

@ -47,7 +47,7 @@ pub(crate) fn invalid_envvar_value(checker: &mut Checker, call: &ast::ExprCall)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "getenv"]))
{
// Find the `key` argument, if it exists.
let Some(expr) = call.arguments.find_argument("key", 0) else {
let Some(expr) = call.arguments.find_argument_value("key", 0) else {
return;
};

View file

@ -152,7 +152,9 @@ fn enumerate_items<'a>(
};
// If the `enumerate` call has a non-zero `start`, don't omit.
if !arguments.find_argument("start", 1).map_or(true, |expr| {
if !arguments
.find_argument_value("start", 1)
.map_or(true, |expr| {
matches!(
expr,
Expr::NumberLiteral(ast::ExprNumberLiteral {
@ -160,7 +162,8 @@ fn enumerate_items<'a>(
..
})
)
}) {
})
{
return None;
}

View file

@ -203,19 +203,19 @@ fn is_violation(call: &ast::ExprCall, qualified_name: &Callee) -> bool {
match qualified_name {
Callee::Qualified(qualified_name) => match qualified_name.segments() {
["" | "codecs" | "_io", "open"] => {
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
if let Some(mode_arg) = call.arguments.find_argument_value("mode", 1) {
if is_binary_mode(mode_arg).unwrap_or(true) {
// binary mode or unknown mode is no violation
return false;
}
}
// else mode not specified, defaults to text mode
call.arguments.find_argument("encoding", 3).is_none()
call.arguments.find_argument_value("encoding", 3).is_none()
}
["tempfile", tempfile_class @ ("TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile")] =>
{
let mode_pos = usize::from(*tempfile_class == "SpooledTemporaryFile");
if let Some(mode_arg) = call.arguments.find_argument("mode", mode_pos) {
if let Some(mode_arg) = call.arguments.find_argument_value("mode", mode_pos) {
if is_binary_mode(mode_arg).unwrap_or(true) {
// binary mode or unknown mode is no violation
return false;
@ -225,27 +225,27 @@ fn is_violation(call: &ast::ExprCall, qualified_name: &Callee) -> bool {
return false;
}
call.arguments
.find_argument("encoding", mode_pos + 2)
.find_argument_value("encoding", mode_pos + 2)
.is_none()
}
["io" | "_io", "TextIOWrapper"] => {
call.arguments.find_argument("encoding", 1).is_none()
call.arguments.find_argument_value("encoding", 1).is_none()
}
_ => false,
},
Callee::Pathlib(attr) => match *attr {
"open" => {
if let Some(mode_arg) = call.arguments.find_argument("mode", 0) {
if let Some(mode_arg) = call.arguments.find_argument_value("mode", 0) {
if is_binary_mode(mode_arg).unwrap_or(true) {
// binary mode or unknown mode is no violation
return false;
}
}
// else mode not specified, defaults to text mode
call.arguments.find_argument("encoding", 2).is_none()
call.arguments.find_argument_value("encoding", 2).is_none()
}
"read_text" => call.arguments.find_argument("encoding", 0).is_none(),
"write_text" => call.arguments.find_argument("encoding", 1).is_none(),
"read_text" => call.arguments.find_argument_value("encoding", 0).is_none(),
"write_text" => call.arguments.find_argument_value("encoding", 1).is_none(),
_ => false,
},
}

View file

@ -71,7 +71,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, call: &ast::ExprCall)
return;
}
let Some(mode_param) = call.arguments.find_argument("mode", 1) else {
let Some(mode_param) = call.arguments.find_argument_value("mode", 1) else {
return;
};
let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &mode_param else {

View file

@ -178,7 +178,7 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
{
// If the `start` argument is set to something other than the `range` default,
// there's no clear fix.
let start = arguments.find_argument("start", 1);
let start = arguments.find_argument_value("start", 1);
if start.map_or(true, |start| {
matches!(
start,

View file

@ -115,8 +115,8 @@ pub(crate) fn unnecessary_from_float(checker: &mut Checker, call: &ExprCall) {
};
let Some(value) = (match method_name {
MethodName::FromFloat => call.arguments.find_argument("f", 0),
MethodName::FromDecimal => call.arguments.find_argument("dec", 0),
MethodName::FromFloat => call.arguments.find_argument_value("f", 0),
MethodName::FromDecimal => call.arguments.find_argument_value("dec", 0),
}) else {
return;
};

View file

@ -67,7 +67,7 @@ pub(crate) fn verbose_decimal_constructor(checker: &mut Checker, call: &ast::Exp
}
// Decimal accepts arguments of the form: `Decimal(value='0', context=None)`
let Some(value) = call.arguments.find_argument("value", 0) else {
let Some(value) = call.arguments.find_argument_value("value", 0) else {
return;
};

View file

@ -72,6 +72,7 @@ mod tests {
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());

View file

@ -0,0 +1,120 @@
use crate::checkers::ast::Checker;
use crate::fix::edits::{remove_argument, Parentheses};
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::{helpers::Truthiness, Expr, ExprAttribute, ExprName};
use ruff_python_semantic::analyze::typing;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
/// ## What it does
/// Checks for `dict.get(key, falsy_value)` calls in boolean test positions.
///
/// ## Why is this bad?
/// The default fallback `None` is already falsy.
///
/// ## Example
///
/// ```python
/// if dict.get(key, False):
/// ...
/// ```
///
/// Use instead:
///
/// ```python
/// if dict.get(key):
/// ...
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as safe, unless the `dict.get()` call contains comments between arguments.
#[derive(ViolationMetadata)]
pub(crate) struct FalsyDictGetFallback;
impl AlwaysFixableViolation for FalsyDictGetFallback {
#[derive_message_formats]
fn message(&self) -> String {
"Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.".to_string()
}
fn fix_title(&self) -> String {
"Remove falsy fallback from `dict.get()`".to_string()
}
}
pub(crate) fn falsy_dict_get_fallback(checker: &mut Checker, expr: &Expr) {
let semantic = checker.semantic();
// Check if we are in a boolean test
if !semantic.in_boolean_test() {
return;
}
// Check if the expression is a call
let Expr::Call(call) = expr else {
return;
};
// Check if the function being called is an attribute (e.g. `dict.get`)
let Expr::Attribute(ExprAttribute { value, attr, .. }) = &*call.func else {
return;
};
// Ensure the method called is `get`
if attr != "get" {
return;
}
// Check if the object is a dictionary using the semantic model
if !value
.as_name_expr()
.is_some_and(|name| is_known_to_be_of_type_dict(semantic, name))
{
return;
}
// Get the fallback argument
let Some(fallback_arg) = call.arguments.find_argument("default", 1) else {
return;
};
// Check if the fallback is a falsy value
if Truthiness::from_expr(fallback_arg.value(), |id| semantic.has_builtin_binding(id))
.into_bool()
!= Some(false)
{
return;
}
let mut diagnostic = Diagnostic::new(FalsyDictGetFallback, fallback_arg.range());
let comment_ranges = checker.comment_ranges();
// Determine applicability based on the presence of comments
let applicability = if comment_ranges.intersects(call.arguments.range()) {
Applicability::Unsafe
} else {
Applicability::Safe
};
diagnostic.try_set_fix(|| {
remove_argument(
&fallback_arg,
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
)
.map(|edit| Fix::applicable_edit(edit, applicability))
});
checker.diagnostics.push(diagnostic);
}
fn is_known_to_be_of_type_dict(semantic: &SemanticModel, expr: &ExprName) -> bool {
let Some(binding) = semantic.only_binding(expr).map(|id| semantic.binding(id)) else {
return false;
};
typing::is_dict(binding, semantic)
}

View file

@ -103,7 +103,7 @@ fn is_dunder_version_split_dot(expr: &ast::Expr) -> bool {
}
let Some(ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ })) =
arguments.find_argument("sep", 0)
arguments.find_argument_value("sep", 0)
else {
return false;
};

View file

@ -6,6 +6,7 @@ pub(crate) use collection_literal_concatenation::*;
pub(crate) use decimal_from_float_literal::*;
pub(crate) use default_factory_kwarg::*;
pub(crate) use explicit_f_string_type_conversion::*;
pub(crate) use falsy_dict_get_fallback::*;
pub(crate) use function_call_in_dataclass_default::*;
pub(crate) use if_key_in_dict_del::*;
pub(crate) use implicit_optional::*;
@ -54,6 +55,7 @@ mod confusables;
mod decimal_from_float_literal;
mod default_factory_kwarg;
mod explicit_f_string_type_conversion;
mod falsy_dict_get_fallback;
mod function_call_in_dataclass_default;
mod helpers;
mod if_key_in_dict_del;

View file

@ -128,7 +128,7 @@ fn convert_to_reduce(iterable: &Expr, call: &ast::ExprCall, checker: &Checker) -
/// Returns `true` if the `start` argument to a `sum()` call is an empty list.
fn start_is_empty_list(arguments: &Arguments, semantic: &SemanticModel) -> bool {
let Some(start_arg) = arguments.find_argument("start", 1) else {
let Some(start_arg) = arguments.find_argument_value("start", 1) else {
return false;
};

View file

@ -175,8 +175,8 @@ fn round_applicability(checker: &Checker, arguments: &Arguments) -> Option<Appli
return None;
}
let number = arguments.find_argument("number", 0)?;
let ndigits = arguments.find_argument("ndigits", 1);
let number = arguments.find_argument_value("number", 0)?;
let ndigits = arguments.find_argument_value("ndigits", 1);
let number_kind = match number {
Expr::Name(name) => {

View file

@ -173,14 +173,14 @@ impl<'a> ReFunc<'a> {
// have already been filtered out from the `pattern`
("split", 2) => Some(ReFunc {
kind: ReFuncKind::Split,
pattern: call.arguments.find_argument("pattern", 0)?,
string: call.arguments.find_argument("string", 1)?,
pattern: call.arguments.find_argument_value("pattern", 0)?,
string: call.arguments.find_argument_value("string", 1)?,
}),
// `sub` is only safe to fix if `repl` is a string. `re.sub` also
// allows it to be a function, which will *not* work in the str
// version
("sub", 3) => {
let repl = call.arguments.find_argument("repl", 1)?;
let repl = call.arguments.find_argument_value("repl", 1)?;
let lit = resolve_string_literal(repl, semantic)?;
let mut fixable = true;
for (c, next) in lit.value.chars().tuple_windows() {
@ -207,24 +207,24 @@ impl<'a> ReFunc<'a> {
kind: ReFuncKind::Sub {
repl: fixable.then_some(repl),
},
pattern: call.arguments.find_argument("pattern", 0)?,
string: call.arguments.find_argument("string", 2)?,
pattern: call.arguments.find_argument_value("pattern", 0)?,
string: call.arguments.find_argument_value("string", 2)?,
})
}
("match", 2) if in_if_context => Some(ReFunc {
kind: ReFuncKind::Match,
pattern: call.arguments.find_argument("pattern", 0)?,
string: call.arguments.find_argument("string", 1)?,
pattern: call.arguments.find_argument_value("pattern", 0)?,
string: call.arguments.find_argument_value("string", 1)?,
}),
("search", 2) if in_if_context => Some(ReFunc {
kind: ReFuncKind::Search,
pattern: call.arguments.find_argument("pattern", 0)?,
string: call.arguments.find_argument("string", 1)?,
pattern: call.arguments.find_argument_value("pattern", 0)?,
string: call.arguments.find_argument_value("string", 1)?,
}),
("fullmatch", 2) if in_if_context => Some(ReFunc {
kind: ReFuncKind::Fullmatch,
pattern: call.arguments.find_argument("pattern", 0)?,
string: call.arguments.find_argument("string", 1)?,
pattern: call.arguments.find_argument_value("pattern", 0)?,
string: call.arguments.find_argument_value("string", 1)?,
}),
_ => None,
}

View file

@ -0,0 +1,485 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF056.py:117:43: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
116 | # dict.get in ternary expression
117 | value = "not found" if my_dict.get("key", False) else "default" # [RUF056]
| ^^^^^ RUF056
118 |
119 | # dict.get in an if statement
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
114 114 | # Invalid falsy fallbacks are when the call to dict.get is used in a boolean context
115 115 |
116 116 | # dict.get in ternary expression
117 |-value = "not found" if my_dict.get("key", False) else "default" # [RUF056]
117 |+value = "not found" if my_dict.get("key") else "default" # [RUF056]
118 118 |
119 119 | # dict.get in an if statement
120 120 | if my_dict.get("key", False): # [RUF056]
RUF056.py:120:23: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
119 | # dict.get in an if statement
120 | if my_dict.get("key", False): # [RUF056]
| ^^^^^ RUF056
121 | pass
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
117 117 | value = "not found" if my_dict.get("key", False) else "default" # [RUF056]
118 118 |
119 119 | # dict.get in an if statement
120 |-if my_dict.get("key", False): # [RUF056]
120 |+if my_dict.get("key"): # [RUF056]
121 121 | pass
122 122 |
123 123 | # dict.get in compound if statement
RUF056.py:124:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
123 | # dict.get in compound if statement
124 | if True and my_dict.get("key", False): # [RUF056]
| ^^^^^ RUF056
125 | pass
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
121 121 | pass
122 122 |
123 123 | # dict.get in compound if statement
124 |-if True and my_dict.get("key", False): # [RUF056]
124 |+if True and my_dict.get("key"): # [RUF056]
125 125 | pass
126 126 |
127 127 | if my_dict.get("key", False) or True: # [RUF056]
RUF056.py:127:23: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
125 | pass
126 |
127 | if my_dict.get("key", False) or True: # [RUF056]
| ^^^^^ RUF056
128 | pass
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
124 124 | if True and my_dict.get("key", False): # [RUF056]
125 125 | pass
126 126 |
127 |-if my_dict.get("key", False) or True: # [RUF056]
127 |+if my_dict.get("key") or True: # [RUF056]
128 128 | pass
129 129 |
130 130 | # dict.get in an assert statement
RUF056.py:131:27: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
130 | # dict.get in an assert statement
131 | assert my_dict.get("key", False) # [RUF056]
| ^^^^^ RUF056
132 |
133 | # dict.get in a while statement
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
128 128 | pass
129 129 |
130 130 | # dict.get in an assert statement
131 |-assert my_dict.get("key", False) # [RUF056]
131 |+assert my_dict.get("key") # [RUF056]
132 132 |
133 133 | # dict.get in a while statement
134 134 | while my_dict.get("key", False): # [RUF056]
RUF056.py:134:26: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
133 | # dict.get in a while statement
134 | while my_dict.get("key", False): # [RUF056]
| ^^^^^ RUF056
135 | pass
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
131 131 | assert my_dict.get("key", False) # [RUF056]
132 132 |
133 133 | # dict.get in a while statement
134 |-while my_dict.get("key", False): # [RUF056]
134 |+while my_dict.get("key"): # [RUF056]
135 135 | pass
136 136 |
137 137 | # dict.get in unary not expression
RUF056.py:138:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
137 | # dict.get in unary not expression
138 | value = not my_dict.get("key", False) # [RUF056]
| ^^^^^ RUF056
139 |
140 | # testing all falsy fallbacks
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
135 135 | pass
136 136 |
137 137 | # dict.get in unary not expression
138 |-value = not my_dict.get("key", False) # [RUF056]
138 |+value = not my_dict.get("key") # [RUF056]
139 139 |
140 140 | # testing all falsy fallbacks
141 141 | value = not my_dict.get("key", False) # [RUF056]
RUF056.py:141:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
140 | # testing all falsy fallbacks
141 | value = not my_dict.get("key", False) # [RUF056]
| ^^^^^ RUF056
142 | value = not my_dict.get("key", []) # [RUF056]
143 | value = not my_dict.get("key", list()) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
138 138 | value = not my_dict.get("key", False) # [RUF056]
139 139 |
140 140 | # testing all falsy fallbacks
141 |-value = not my_dict.get("key", False) # [RUF056]
141 |+value = not my_dict.get("key") # [RUF056]
142 142 | value = not my_dict.get("key", []) # [RUF056]
143 143 | value = not my_dict.get("key", list()) # [RUF056]
144 144 | value = not my_dict.get("key", {}) # [RUF056]
RUF056.py:142:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
140 | # testing all falsy fallbacks
141 | value = not my_dict.get("key", False) # [RUF056]
142 | value = not my_dict.get("key", []) # [RUF056]
| ^^ RUF056
143 | value = not my_dict.get("key", list()) # [RUF056]
144 | value = not my_dict.get("key", {}) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
139 139 |
140 140 | # testing all falsy fallbacks
141 141 | value = not my_dict.get("key", False) # [RUF056]
142 |-value = not my_dict.get("key", []) # [RUF056]
142 |+value = not my_dict.get("key") # [RUF056]
143 143 | value = not my_dict.get("key", list()) # [RUF056]
144 144 | value = not my_dict.get("key", {}) # [RUF056]
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
RUF056.py:143:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
141 | value = not my_dict.get("key", False) # [RUF056]
142 | value = not my_dict.get("key", []) # [RUF056]
143 | value = not my_dict.get("key", list()) # [RUF056]
| ^^^^^^ RUF056
144 | value = not my_dict.get("key", {}) # [RUF056]
145 | value = not my_dict.get("key", dict()) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
140 140 | # testing all falsy fallbacks
141 141 | value = not my_dict.get("key", False) # [RUF056]
142 142 | value = not my_dict.get("key", []) # [RUF056]
143 |-value = not my_dict.get("key", list()) # [RUF056]
143 |+value = not my_dict.get("key") # [RUF056]
144 144 | value = not my_dict.get("key", {}) # [RUF056]
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
146 146 | value = not my_dict.get("key", set()) # [RUF056]
RUF056.py:144:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
142 | value = not my_dict.get("key", []) # [RUF056]
143 | value = not my_dict.get("key", list()) # [RUF056]
144 | value = not my_dict.get("key", {}) # [RUF056]
| ^^ RUF056
145 | value = not my_dict.get("key", dict()) # [RUF056]
146 | value = not my_dict.get("key", set()) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
141 141 | value = not my_dict.get("key", False) # [RUF056]
142 142 | value = not my_dict.get("key", []) # [RUF056]
143 143 | value = not my_dict.get("key", list()) # [RUF056]
144 |-value = not my_dict.get("key", {}) # [RUF056]
144 |+value = not my_dict.get("key") # [RUF056]
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
146 146 | value = not my_dict.get("key", set()) # [RUF056]
147 147 | value = not my_dict.get("key", None) # [RUF056]
RUF056.py:145:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
143 | value = not my_dict.get("key", list()) # [RUF056]
144 | value = not my_dict.get("key", {}) # [RUF056]
145 | value = not my_dict.get("key", dict()) # [RUF056]
| ^^^^^^ RUF056
146 | value = not my_dict.get("key", set()) # [RUF056]
147 | value = not my_dict.get("key", None) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
142 142 | value = not my_dict.get("key", []) # [RUF056]
143 143 | value = not my_dict.get("key", list()) # [RUF056]
144 144 | value = not my_dict.get("key", {}) # [RUF056]
145 |-value = not my_dict.get("key", dict()) # [RUF056]
145 |+value = not my_dict.get("key") # [RUF056]
146 146 | value = not my_dict.get("key", set()) # [RUF056]
147 147 | value = not my_dict.get("key", None) # [RUF056]
148 148 | value = not my_dict.get("key", 0) # [RUF056]
RUF056.py:146:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
144 | value = not my_dict.get("key", {}) # [RUF056]
145 | value = not my_dict.get("key", dict()) # [RUF056]
146 | value = not my_dict.get("key", set()) # [RUF056]
| ^^^^^ RUF056
147 | value = not my_dict.get("key", None) # [RUF056]
148 | value = not my_dict.get("key", 0) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
143 143 | value = not my_dict.get("key", list()) # [RUF056]
144 144 | value = not my_dict.get("key", {}) # [RUF056]
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
146 |-value = not my_dict.get("key", set()) # [RUF056]
146 |+value = not my_dict.get("key") # [RUF056]
147 147 | value = not my_dict.get("key", None) # [RUF056]
148 148 | value = not my_dict.get("key", 0) # [RUF056]
149 149 | value = not my_dict.get("key", 0.0) # [RUF056]
RUF056.py:147:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
145 | value = not my_dict.get("key", dict()) # [RUF056]
146 | value = not my_dict.get("key", set()) # [RUF056]
147 | value = not my_dict.get("key", None) # [RUF056]
| ^^^^ RUF056
148 | value = not my_dict.get("key", 0) # [RUF056]
149 | value = not my_dict.get("key", 0.0) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
144 144 | value = not my_dict.get("key", {}) # [RUF056]
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
146 146 | value = not my_dict.get("key", set()) # [RUF056]
147 |-value = not my_dict.get("key", None) # [RUF056]
147 |+value = not my_dict.get("key") # [RUF056]
148 148 | value = not my_dict.get("key", 0) # [RUF056]
149 149 | value = not my_dict.get("key", 0.0) # [RUF056]
150 150 | value = not my_dict.get("key", "") # [RUF056]
RUF056.py:148:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
146 | value = not my_dict.get("key", set()) # [RUF056]
147 | value = not my_dict.get("key", None) # [RUF056]
148 | value = not my_dict.get("key", 0) # [RUF056]
| ^ RUF056
149 | value = not my_dict.get("key", 0.0) # [RUF056]
150 | value = not my_dict.get("key", "") # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
145 145 | value = not my_dict.get("key", dict()) # [RUF056]
146 146 | value = not my_dict.get("key", set()) # [RUF056]
147 147 | value = not my_dict.get("key", None) # [RUF056]
148 |-value = not my_dict.get("key", 0) # [RUF056]
148 |+value = not my_dict.get("key") # [RUF056]
149 149 | value = not my_dict.get("key", 0.0) # [RUF056]
150 150 | value = not my_dict.get("key", "") # [RUF056]
151 151 |
RUF056.py:149:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
147 | value = not my_dict.get("key", None) # [RUF056]
148 | value = not my_dict.get("key", 0) # [RUF056]
149 | value = not my_dict.get("key", 0.0) # [RUF056]
| ^^^ RUF056
150 | value = not my_dict.get("key", "") # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
146 146 | value = not my_dict.get("key", set()) # [RUF056]
147 147 | value = not my_dict.get("key", None) # [RUF056]
148 148 | value = not my_dict.get("key", 0) # [RUF056]
149 |-value = not my_dict.get("key", 0.0) # [RUF056]
149 |+value = not my_dict.get("key") # [RUF056]
150 150 | value = not my_dict.get("key", "") # [RUF056]
151 151 |
152 152 | # testing dict.get call using kwargs
RUF056.py:150:32: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
148 | value = not my_dict.get("key", 0) # [RUF056]
149 | value = not my_dict.get("key", 0.0) # [RUF056]
150 | value = not my_dict.get("key", "") # [RUF056]
| ^^ RUF056
151 |
152 | # testing dict.get call using kwargs
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
147 147 | value = not my_dict.get("key", None) # [RUF056]
148 148 | value = not my_dict.get("key", 0) # [RUF056]
149 149 | value = not my_dict.get("key", 0.0) # [RUF056]
150 |-value = not my_dict.get("key", "") # [RUF056]
150 |+value = not my_dict.get("key") # [RUF056]
151 151 |
152 152 | # testing dict.get call using kwargs
153 153 | value = not my_dict.get(key="key", default=False) # [RUF056]
RUF056.py:153:36: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
152 | # testing dict.get call using kwargs
153 | value = not my_dict.get(key="key", default=False) # [RUF056]
| ^^^^^^^^^^^^^ RUF056
154 | value = not my_dict.get(default=[], key="key") # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
150 150 | value = not my_dict.get("key", "") # [RUF056]
151 151 |
152 152 | # testing dict.get call using kwargs
153 |-value = not my_dict.get(key="key", default=False) # [RUF056]
153 |+value = not my_dict.get(key="key") # [RUF056]
154 154 | value = not my_dict.get(default=[], key="key") # [RUF056]
155 155 |
156 156 | # testing invalid dict.get call with inline comment
RUF056.py:154:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
152 | # testing dict.get call using kwargs
153 | value = not my_dict.get(key="key", default=False) # [RUF056]
154 | value = not my_dict.get(default=[], key="key") # [RUF056]
| ^^^^^^^^^^ RUF056
155 |
156 | # testing invalid dict.get call with inline comment
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
151 151 |
152 152 | # testing dict.get call using kwargs
153 153 | value = not my_dict.get(key="key", default=False) # [RUF056]
154 |-value = not my_dict.get(default=[], key="key") # [RUF056]
154 |+value = not my_dict.get(key="key") # [RUF056]
155 155 |
156 156 | # testing invalid dict.get call with inline comment
157 157 | value = not my_dict.get("key", # comment1
RUF056.py:158:22: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
156 | # testing invalid dict.get call with inline comment
157 | value = not my_dict.get("key", # comment1
158 | [] # comment2
| ^^ RUF056
159 | ) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Unsafe fix
154 154 | value = not my_dict.get(default=[], key="key") # [RUF056]
155 155 |
156 156 | # testing invalid dict.get call with inline comment
157 |-value = not my_dict.get("key", # comment1
158 |- [] # comment2
157 |+value = not my_dict.get("key" # comment2
159 158 | ) # [RUF056]
160 159 |
161 160 | # testing invalid dict.get call with kwargs and inline comment
RUF056.py:163:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
161 | # testing invalid dict.get call with kwargs and inline comment
162 | value = not my_dict.get(key="key", # comment1
163 | default=False # comment2
| ^^^^^^^^^^^^^ RUF056
164 | ) # [RUF056]
165 | value = not my_dict.get(default=[], # comment1
|
= help: Remove falsy fallback from `dict.get()`
Unsafe fix
159 159 | ) # [RUF056]
160 160 |
161 161 | # testing invalid dict.get call with kwargs and inline comment
162 |-value = not my_dict.get(key="key", # comment1
163 |- default=False # comment2
162 |+value = not my_dict.get(key="key" # comment2
164 163 | ) # [RUF056]
165 164 | value = not my_dict.get(default=[], # comment1
166 165 | key="key" # comment2
RUF056.py:165:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
163 | default=False # comment2
164 | ) # [RUF056]
165 | value = not my_dict.get(default=[], # comment1
| ^^^^^^^^^^ RUF056
166 | key="key" # comment2
167 | ) # [RUF056]
|
= help: Remove falsy fallback from `dict.get()`
Unsafe fix
162 162 | value = not my_dict.get(key="key", # comment1
163 163 | default=False # comment2
164 164 | ) # [RUF056]
165 |-value = not my_dict.get(default=[], # comment1
165 |+value = not my_dict.get(# comment1
166 166 | key="key" # comment2
167 167 | ) # [RUF056]
168 168 |
RUF056.py:170:55: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
169 | # testing invalid dict.get calls
170 | value = not my_dict.get(key="key", other="something", default=False)
| ^^^^^^^^^^^^^ RUF056
171 | value = not my_dict.get(default=False, other="something", key="test")
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
167 167 | ) # [RUF056]
168 168 |
169 169 | # testing invalid dict.get calls
170 |-value = not my_dict.get(key="key", other="something", default=False)
170 |+value = not my_dict.get(key="key", other="something")
171 171 | value = not my_dict.get(default=False, other="something", key="test")
RUF056.py:171:25: RUF056 [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
|
169 | # testing invalid dict.get calls
170 | value = not my_dict.get(key="key", other="something", default=False)
171 | value = not my_dict.get(default=False, other="something", key="test")
| ^^^^^^^^^^^^^ RUF056
|
= help: Remove falsy fallback from `dict.get()`
Safe fix
168 168 |
169 169 | # testing invalid dict.get calls
170 170 | value = not my_dict.get(key="key", other="something", default=False)
171 |-value = not my_dict.get(default=False, other="something", key="test")
171 |+value = not my_dict.get(other="something", key="test")

View file

@ -3780,6 +3780,15 @@ pub enum ArgOrKeyword<'a> {
Keyword(&'a Keyword),
}
impl<'a> ArgOrKeyword<'a> {
pub const fn value(&self) -> &'a Expr {
match self {
ArgOrKeyword::Arg(argument) => argument,
ArgOrKeyword::Keyword(keyword) => &keyword.value,
}
}
}
impl<'a> From<&'a Expr> for ArgOrKeyword<'a> {
fn from(arg: &'a Expr) -> Self {
Self::Arg(arg)
@ -3828,15 +3837,24 @@ impl Arguments {
.nth(position)
}
/// Return the argument with the given name or at the given position, or `None` if no such
/// argument exists. Used to retrieve arguments that can be provided _either_ as keyword or
/// Return the value for the argument with the given name or at the given position, or `None` if no such
/// argument exists. Used to retrieve argument values that can be provided _either_ as keyword or
/// positional arguments.
pub fn find_argument(&self, name: &str, position: usize) -> Option<&Expr> {
pub fn find_argument_value(&self, name: &str, position: usize) -> Option<&Expr> {
self.find_keyword(name)
.map(|keyword| &keyword.value)
.or_else(|| self.find_positional(position))
}
/// Return the the argument with the given name or at the given position, or `None` if no such
/// argument exists. Used to retrieve arguments that can be provided _either_ as keyword or
/// positional arguments.
pub fn find_argument(&self, name: &str, position: usize) -> Option<ArgOrKeyword> {
self.find_keyword(name)
.map(ArgOrKeyword::from)
.or_else(|| self.find_positional(position).map(ArgOrKeyword::from))
}
/// Return the positional and keyword arguments in the order of declaration.
///
/// Positional arguments are generally before keyword arguments, but star arguments are an

1
ruff.schema.json generated
View file

@ -3865,6 +3865,7 @@
"RUF051",
"RUF052",
"RUF055",
"RUF056",
"RUF1",
"RUF10",
"RUF100",