-
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
Rework the suspicious formatting lints. #12980
base: master
Are you sure you want to change the base?
Conversation
r? @Alexendoo rustbot has assigned @Alexendoo. Use |
fn with_line_indent<T>(sm: &SourceMap, sp: Range<BytePos>, f: impl for<'a> FnOnce(&'a str) -> T) -> T { | ||
let src = get_source_text(sm, sp); | ||
let indent = if let Some(src) = &src | ||
&& let Some(line) = src.sf.lookup_line(src.range.start) | ||
&& let Some(start) = src.sf.lines().get(line) | ||
&& let Some(text) = src.sf.src.as_ref() | ||
{ | ||
let text = if let Some(end) = src.sf.lines().get(line + 1) { | ||
&text[start.to_usize()..end.to_usize()] | ||
} else { | ||
&text[start.to_usize()..] | ||
}; | ||
&text[..text.len() - text.trim_start().len()] | ||
} else { | ||
"" | ||
}; | ||
f(indent) | ||
} |
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 would be our third function that gets the indentation, or forth if you count the one on SourceMap
too
The other SpanRangeExt
methods look to be in a similar situation, could we replace the regular functions e.g. snippet_opt
with ones that return a wrapper (Lrc<String>, Range<usize>)
that has ToString
, derefs to &str
etc.? That would allow us to have a single set of functions that don't allocate without the callbacks which are slightly awkward
Could be Range<*const u8>
instead of Range<usize>
if we wanted to eagerly get rid of the extra dereference
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'm still in the middle of redoing how we handle this so things are still changing. Callbacks can be avoided now via:
if let Some(src) = span.get_source_text(cx)
&& let Some(src) = src.as_str()
{}
The main reason this isn't simple is that the transition from span to source text can fail. Several of the functions are convenience wrappers around this. Going through some of them
check_source_text
: makes it easier to write a short predicate on the text.with_source_text
andwith_source_text_and_range
: basicallyOption::map
for the text.source_text_to_string
:snippet_opt
, but works with more span-like typeswrite_source_text_to
: same, but writes to something instead of creating a string
The remaining functions (map_range
, with_leading_whitespace
, trim_start
) all adjust the span. This requires calculating a delta to apply to the real span so these are actually doing something.
with_line_indent
could be changed to return Option<SomeTypeOwningPartOfTheSource>
, but I would rather that be exposed differently.
Edit: with_line_indent
never fails, so this would not translate to anything else nicely.
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.
get_source_text
already returns an Option
, if we verify there and replace the pub fields with private + accessors we can treat SourceFileRange
as an infallible str-ish
Then ToString
would replace source_text_to_string
, Display
would replace write_source_text_to
. get_source_text().map()
could replace with_source_text
and with_source_text_and_range
, is_some_and
for the check_
versions. Some of these are slightly longer to write but I think it's far clearer
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.
That will add the minor cost of an extra range check and either an unwrap or an Lrc
clone. I don't think this is ever needed on a hot path, but it's something to take into account.
Going function by function again:
check_source_text
: This will be quite heavily used and it's existence can be justified on that alone. The fact that it performs better is another plus since it matters more for this use case.with_source_text
: I don't really care about. Both of it's current uses are questionable. This should really be namedmap_source_text
.source_text_to_string
: This is used frequently enough that a separate function isn't a problem. Speed isn't as important here since we should only be using this to build suggestions. Ideally this would only be called insidespan_lint_and_then
.write_source_text_to
: Having a type which implementsDisplay
is definitely nicer to use.
For future work I want to find a nice way to expose the line spans as well. That's going to wait till I see more of how we would use it.
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 extra check/unwrap could be removed with some unsafe
, but I don't think it's crucial to do so. It would be faster than an allocation and convenient enough to use such that we can replace snippet_opt
's return value with it without changing much code - I think any multipart suggestion where they're used directly would be most of it since that doesn't take impl ToString
I don't hate check_source_text
, but I would still prefer .is_some_and
as it's very clear what is happening and doesn't need a check_source_text_with_x
variant
source_text_to_string
is currently unused, but I assume you mean it to replace snippet_opt
, I think if we get a nice string-like type there would be far fewer places that an owned String
is actually required
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.
Got rid of check_source_text_with_range
since it's unlikely to be used much. The other added functions are range adjustments or have no failure states. Neither fit with returning a string-like type.
ping @Alexendoo. |
@@ -0,0 +1,75 @@ | |||
//@no-rustfix |
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 tests with multiple suggestions should be rustfixable now
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.
Could group them into a tests/ui/formatting
dir too since it will be a good amount of files
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.
Hurray.
&& else_.span.ctxt() == ctxt | ||
&& let is_block = matches!(else_.kind, ExprKind::Block(..)) | ||
&& let else_range = (then.span.hi()..else_.span.lo()) | ||
&& else_range.clone().with_source_text(cx, |src| { |
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.
Can use check_source_text
skip_lf = lf_count != 0; | ||
allow_lf |= skip_lf; | ||
}, | ||
TokenKind::LineComment { .. } => return true, |
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.
Could do with a test for this case
if lf_count == 0 { | ||
lf_count = usize::from(text.contains('\n')); | ||
} | ||
skip_lf = lf_count != 0; |
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.
Could this be skip_lf = lf_count != 0 || text.contains('\n')
?
if let ExprKind::Binary(op, lhs, rhs) = &e.kind | ||
&& e.span.ctxt() == ctxt | ||
{ | ||
if matches!( | ||
op.node, | ||
BinOpKind::And | BinOpKind::Mul | BinOpKind::Sub | BinOpKind::BitAnd | ||
) && let op_data = op.span.data() | ||
&& op_data.ctxt == ctxt |
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.
Can be flattened into the parent if
&& let Some(else_snippet) = snippet_opt(cx, else_span) | ||
&& !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace()) | ||
&& let else_range = (first.span.hi()..second.span.lo()) | ||
&& else_range.clone().with_source_text(cx, |src| { |
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.
Can also be check_source_text
For the utils - I don't think |
An alternative would be to have trait MapRange {
fn map_range(self, r: Range<usize>) -> Range<usize>;
}
impl MapRange for Range<usize> { .. }
struct PosLen(usize, usize);
impl MapRange for PosLen { .. } This would allow other ways of specifying a transformation as well. Could rename |
The extra trait doesn't seem worth the extra complexity to me either, I don't really see |
☔ The latest upstream changes (presumably #13296) made this pull request unmergeable. Please resolve the merge conflicts. |
@rustbot author |
Short overview of the functional changes:
possible_missing_comma
:1 -2 - 3
and1 - 2 -3
should both be detected.suspicious_assignment_formatting
andsuspicious_unary_op_formatting
:suspicious_else_formatting
:else
appears immediately after a block comment, but a different line than the closing brace.All lints now lint in macros and use suggestions.
suspicious_else_formatting
is still missing a suggestion for the case where the else is in a weird spot.changelog: Lint suspicious formatting lints in macros