Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
range(0, 10, step=1)
range(start=0, stop=10)
range(0, stop=10)

# regression test for https://github.com/astral-sh/ruff/pull/18805
range((0), 42)
52 changes: 28 additions & 24 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF056.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,86 +23,86 @@
# Valid

# Using dict.get with a falsy fallback: False
value = my_dict.get("key", False)
value = my_dict.get("key", False)

# Using dict.get with a falsy fallback: empty string
value = my_dict.get("key", "")
value = my_dict.get("key", "")

# Using dict.get with a falsy fallback: empty list
value = my_dict.get("key", [])
value = my_dict.get("key", [])

# Using dict.get with a falsy fallback: empty dict
value = my_dict.get("key", {})
value = my_dict.get("key", {})

# Using dict.get with a falsy fallback: empty set
value = my_dict.get("key", set())
value = my_dict.get("key", set())

# Using dict.get with a falsy fallback: zero integer
value = my_dict.get("key", 0)
value = my_dict.get("key", 0)

# Using dict.get with a falsy fallback: zero float
value = my_dict.get("key", 0.0)
value = my_dict.get("key", 0.0)

# Using dict.get with a falsy fallback: None
value = my_dict.get("key", 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())
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)
return d.get("key", False)

# Multiple dict.get calls with mixed fallbacks
value1 = my_dict.get("key1", "default")
value2 = my_dict.get("key2", 0)
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", {})
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 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)
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 {})
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")
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)
get_fallback = lambda d: d.get("key", False)

# Falsy fallback in a list comprehension
results = [d.get("key", "") for d in dicts]
results = [d.get("key", "") for d in dicts]

# Falsy fallback in a generator expression
results = (d.get("key", None) for d in dicts)
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"
value = my_dict.get("key", 0) if True else "default"


# Falsy fallback with inline comment
Expand Down Expand Up @@ -185,3 +185,7 @@ def inner():
# TypeError is raised at runtime before and after the fix, but we still bail
# out for having an unrecognized number of arguments
not my_dict.get("key", False, foo=...)

# https://github.com/astral-sh/ruff/issues/18798
d = {}
not d.get("key", (False))
14 changes: 12 additions & 2 deletions crates/ruff_linter/src/fix/edits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub(crate) fn remove_argument<T: Ranged>(
arguments: &Arguments,
parentheses: Parentheses,
source: &str,
comment_ranges: &CommentRanges,
) -> Result<Edit> {
// Partition into arguments before and after the argument to remove.
let (before, after): (Vec<_>, Vec<_>) = arguments
Expand All @@ -217,6 +218,15 @@ pub(crate) fn remove_argument<T: Ranged>(
.filter(|range| argument.range() != *range)
.partition(|range| range.start() < argument.start());

let arg = arguments
.arguments_source_order()
.find(|arg| arg.range() == argument.range())
.context("Unable to find argument")?;

let parenthesized_range =
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
.unwrap_or(arg.range());

if !after.is_empty() {
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
Expand All @@ -234,7 +244,7 @@ pub(crate) fn remove_argument<T: Ranged>(
})
.context("Unable to find next token")?;

Ok(Edit::deletion(argument.start(), next.start()))
Ok(Edit::deletion(parenthesized_range.start(), next.start()))
} else if let Some(previous) = before.iter().map(Ranged::end).max() {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
Expand All @@ -245,7 +255,7 @@ pub(crate) fn remove_argument<T: Ranged>(
.find(|token| token.kind == SimpleTokenKind::Comma)
.context("Unable to find trailing comma")?;

Ok(Edit::deletion(comma.start(), argument.end()))
Ok(Edit::deletion(comma.start(), parenthesized_range.end()))
} else {
// Case 3: argument or keyword is the only node, so delete the arguments (but preserve
// parentheses, if needed).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def:
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::unsafe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,13 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
let mut diagnostic = checker.report_diagnostic(ExcInfoOutsideExceptHandler, exc_info.range);

diagnostic.try_set_fix(|| {
let edit = remove_argument(exc_info, arguments, Parentheses::Preserve, source)?;
let edit = remove_argument(
exc_info,
arguments,
Parentheses::Preserve,
source,
checker.comment_ranges(),
)?;
Ok(Fix::unsafe_edit(edit))
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub(crate) fn unnecessary_range_start(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,18 @@ PIE808.py:5:16: PIE808 [*] Unnecessary `start` argument in `range`
6 6 |
7 7 | # OK
8 8 | range(x, 10)

PIE808.py:19:8: PIE808 [*] Unnecessary `start` argument in `range`
|
18 | # regression test for https://github.com/astral-sh/ruff/pull/18805
19 | range((0), 42)
| ^ PIE808
|
= help: Remove `start` argument

ℹ Safe fix
16 16 | range(0, stop=10)
17 17 |
18 18 | # regression test for https://github.com/astral-sh/ruff/pull/18805
19 |-range((0), 42)
19 |+range(42)
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,13 @@ fn generate_fix(
let locator = checker.locator();
let source = locator.contents();

let deletion = remove_argument(generic_base, arguments, Parentheses::Preserve, source)?;
let deletion = remove_argument(
generic_base,
arguments,
Parentheses::Preserve,
source,
checker.comment_ranges(),
)?;
let insertion = add_argument(
locator.slice(generic_base),
arguments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor
arguments,
edits::Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::unsafe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,13 @@ pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprC
diagnostic.set_fix(Fix::applicable_edit(edit, applicability(parent_range)));
}
None => diagnostic.try_set_fix(|| {
let edit = remove_argument(arg, arguments, Parentheses::Preserve, checker.source())?;
let edit = remove_argument(
arg,
arguments,
Parentheses::Preserve,
checker.source(),
checker.comment_ranges(),
)?;
Ok(Fix::applicable_edit(edit, applicability(call.range())))
}),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ fn convert_inplace_argument_to_assignment(
&call.arguments,
Parentheses::Preserve,
locator.contents(),
comment_ranges,
)
.ok()?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub(crate) fn duplicate_bases(checker: &Checker, name: &str, arguments: Option<&
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD
arguments,
Parentheses::Remove,
checker.source(),
checker.comment_ranges(),
)?;
Ok(Fix::unsafe_edits(
Edit::insertion(type_params.to_string(), name.end()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use anyhow::Result;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Keyword};
use ruff_python_semantic::Modules;
use ruff_python_trivia::CommentRanges;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -96,8 +97,15 @@ pub(crate) fn replace_stdout_stderr(checker: &Checker, call: &ast::ExprCall) {

let mut diagnostic = checker.report_diagnostic(ReplaceStdoutStderr, call.range());
if call.arguments.find_keyword("capture_output").is_none() {
diagnostic
.try_set_fix(|| generate_fix(stdout, stderr, call, checker.locator().contents()));
diagnostic.try_set_fix(|| {
generate_fix(
stdout,
stderr,
call,
checker.locator().contents(),
checker.comment_ranges(),
)
});
}
}
}
Expand All @@ -108,6 +116,7 @@ fn generate_fix(
stderr: &Keyword,
call: &ast::ExprCall,
source: &str,
comment_ranges: &CommentRanges,
) -> Result<Fix> {
let (first, second) = if stdout.start() < stderr.start() {
(stdout, stderr)
Expand All @@ -122,6 +131,7 @@ fn generate_fix(
&call.arguments,
Parentheses::Preserve,
source,
comment_ranges,
)?],
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand All @@ -204,6 +205,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand All @@ -228,6 +230,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand All @@ -245,6 +248,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.comment_ranges(),
)?;

let range = edit.range();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.comment_ranges(),
)?;

let range = edit.range();
Expand Down
Loading
Loading