-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
string_add_assign
: add autofix
#14286
base: master
Are you sure you want to change the base?
Conversation
4baa140
to
f37b683
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am surprised that s += "foobar"
is not a recognized form, only s = s + "foobar"
seems to be.
Also, every test case seems to suggest both .push()
/.push_str()
and s += …
.
f37b683
to
5950201
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, every test case seems to suggest both .push()/.push_str() and s += ….
I don't think so. In previous test cases, assign_op_pattern
was not explicitly allowed, resulting in a warning. OTOH, string_add_assign
suggests only push
or push_str
both before and after this change.
5950201
to
a9aa909
Compare
Yes, I meant that without allowing it, they both trigger at any |
I see. However, it seems non-trivial to decide which lint is preferred (i.e., there could be an implementation that suppresses |
I think the main differences between them are their categories ( |
So it looks like this is an easy choice: if |
I fail to see why it is an easy choice that |
My reasoning is:
You're right in that it doesn't strictly belong to this PR, but this PR adds a second, conflicting autofix suggestion. The following code #![allow(unused)]
fn main() {
let mut s = String::from("hello");
s = s + ", world!";
println!("s = {s}");
} when linted in pedantic mode will get:
Moreover, when applying the #![allow(unused)]
fn main() {
let mut s = String::from("hello");
s += ", world!";
println!("s = {s}");
} Only the second fix seems to have been applied, or more exactly the second fix seems to have overwritten the first one. I just don't find it very clean. |
OK, I'll prepare a patch to inhibit So I think the discussion that has arisen so far has been resolved and can be reviewed. @Centri3 |
a9aa909
to
f812c24
Compare
f812c24
to
5fce72d
Compare
@samueltardieu, you're free to review here if y'want :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The empty string case ought to be treated separately with a proper removal suggestion if it is a statement or the last expression in a block. Or it can be replaced by ()
in other cases.
Also, you can prevent assign_op_pattern
from triggering by inserting something like
if is_type_lang_item(cx, ty.peel_refs(), LangItem::String)
&& !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id)
{
// Do not lint if the more specialized `clippy::string_add_assign` lint will triggger.
return;
}
to assign_op_pattern.rs
.
It would also be great to add a single codepoint test to ensure that single quotes are used, and that there is no regression if the code is changed later.
@@ -208,16 +214,39 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { | |||
is_type_lang_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), LangItem::String) | |||
} | |||
|
|||
fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { | |||
fn sugg_push_or_pushstr(cx: &LateContext<'_>, s: &Expr<'_>) -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should take an app: &mut Applicability
, for use with snippet_with_applicability
instead of snippet
.
Also, this could return an Option<String>
, returning None
if the string to replace is empty, so that the lint can be specialized.
_ => format!(".push_str(\"{symbol}\")"), | ||
} | ||
} else { | ||
format!(".push_str({})", snippet(cx, s.span, "..")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
format!(".push_str({})", snippet(cx, s.span, "..")) | |
format!(".push_str({})", snippet_with_applicability(cx, s.span, "_", &mut app)) |
`String::push_str()` or `String::push()` instead", | ||
"try", | ||
format!("{}{}", snippet(cx, target.span, "_"), sugg_push_or_pushstr(cx, rem)), | ||
Applicability::MachineApplicable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applicability may not always be MachineApplicable
.
`String::push_str()` instead", | ||
`String::push_str()` or `String::push()` instead", | ||
"try", | ||
format!("{}{}", snippet(cx, target.span, "_"), sugg_push_or_pushstr(cx, rem)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use snippet_with_applicability()
.
Also, if sugg_push_or_pushstr
was tested before linting, depending on the result, if it returns an Option
, you could specialize the lint for the empty string, with something like:
let (span, help, sugg) = match cx.tcx.parent_hir_node(e.hir_id) {
Node::Stmt(stmt) => (stmt.span, "remove it", ""),
Node::Block(_) => (e.span, "remove it", ""),
_ => (e.span, "replace it with", "()")
};
span_lint_and_sugg(
cx,
STRING_ADD_ASSIGN,
span,
"adding an empty string to this string has no effect",
help,
String::from(sugg),
Applicability::MachineApplicable,
);
with tests for
x = x + "";
//~^ string_add_assign
{ x = x + "" }
//~^ string_add_assign
let _: () = x = x + "";
//~^ string_add_assign
x = x + ""
//~^ string_add_assign
Also, situations such as |
changelog: [
string_add_assign
]: add autofix