-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Add transactional semantics to rustfix
#14747
base: master
Are you sure you want to change the base?
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @weihanglo (or someone else) some time within the next two weeks. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
c34580f
to
c738b3e
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.
This is awesome! Thank you for this patch!
pub fn ignore_identical<T: Default>(err: Error) -> Result<T, Error> { | ||
match err { | ||
Error::AlreadyReplaced { | ||
is_identical: true, .. | ||
} => Ok(Default::default()), | ||
_ => Err(err), | ||
} | ||
} |
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.
Probably unnecessary pub
and generics.
pub fn ignore_identical<T: Default>(err: Error) -> Result<T, Error> { | |
match err { | |
Error::AlreadyReplaced { | |
is_identical: true, .. | |
} => Ok(Default::default()), | |
_ => Err(err), | |
} | |
} | |
fn ignore_identical(err: Error) -> Result<(), Error> { | |
match err { | |
Error::AlreadyReplaced { | |
is_identical: true, .. | |
} => Ok(()), | |
_ => Err(err), | |
} | |
} |
Feels like we can just inline this in apply_suggestions, but that doesn't really matter :)
@@ -216,34 +216,34 @@ pub fn collect_suggestions<S: ::std::hash::BuildHasher>( | |||
/// 2. Calls [`CodeFix::apply`] to apply suggestions to the source code. | |||
/// 3. Calls [`CodeFix::finish`] to get the "fixed" code. | |||
#[derive(Clone)] | |||
pub struct CodeFix { | |||
data: replace::Data, | |||
pub struct CodeFix<'orig> { |
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.
nit: I would probably just call it 'a
. Less visual noise and there is no ambiguity.
#[derive(Debug, Clone, Default)] | ||
pub struct Data { | ||
/// Original data. | ||
original: Vec<u8>, | ||
/// [`Span`]s covering the full range of the original data. | ||
/// Important: it's expected that the underlying implementation maintains this in order, |
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.
Although the code looks pretty valid, should we add some debug_assertion for this in to_vec()
?
/// | ||
/// See the module-level documentation for more information on why uncommitted changes are included. | ||
pub fn to_vec(&self) -> Vec<u8> { | ||
let mut prev_end = 0usize; |
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.
nit: This should be sufficient.
let mut prev_end = 0usize; | |
let mut prev_end = 0; |
match fixed.apply(suggestion) { | ||
Ok(()) => fixed_file.fixes_applied += 1, | ||
Err(rustfix::Error::AlreadyReplaced { |
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.
While I don't think the compiler will get this fixed in any near future, we may want to restore this comment in case there is such a future.
(probably ditto in apply_suggestions()
?)
// If this replacement matches exactly the part that we would
// otherwise split then we ignore this for now. This means that you
// can replace the exact same range with the exact same content
// multiple times and we'll process and allow it.
//
// This is currently done to alleviate issues like
// rust-lang/rust#51211 although this clause likely wants to be
// removed if that's fixed deeper in the compiler.
crates/rustfix/src/replace.rs
Outdated
fn insert_same_twice() { | ||
let mut d = Data::new("foo"); | ||
d.replace_range(1..1, "b").unwrap(); | ||
assert_eq!("fboo", str(&d.to_vec())); | ||
assert!(matches!( | ||
d.replace_range(1..1, "b").unwrap_err(), | ||
Error::AlreadyReplaced { | ||
is_identical: true, | ||
.. | ||
}, | ||
)); | ||
assert_eq!("fboo", str(&d.to_vec())); |
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 test change should move to the second commit maybe.
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.
While this has already covered the case of #13027, I feel we should generate a snapshot test from the exact macro diagnostics, like https://github.com/rust-lang/cargo/blob/de5832fe8fe1a05e278eee82bbc801d544ed4bd1/crates/rustfix/tests/everything/E0178.rs, if we aim to close it.
I can help with that if it is too much a burden.
// Keep sorted by start position, with pure inserts before replacements. | ||
let ins_point = self.parts.partition_point(|span| { | ||
span.range.start < range.start | ||
|| (span.range.start == range.start && span.range.end < range.end) |
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 you explain a bit why we need span.range.end < range.end
?
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.
with pure inserts before replacements.
I think got 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.
Looks like the last three commits are mostly refactoring. To make things clearer and more traceable, could we move them to a separate or a follow-up PR?
range.end, | ||
self.parts, | ||
); | ||
return Err(Error::MaybeAlreadyReplaced(range)); |
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.
If this is no longer needed, we should remove it (and maybe add #[mon_exhaustive]
to the error enum as a follow-up).
What does this PR try to resolve?
This PR adds transactional semantics to
rustfix::Data
, enablingrustfix::CodeFix
to applySuggestion
s as atomic units and rollback partially-applied changes when they conflict with existing ones. The basic approach and goals are discussed in a comment on issue 14699. In that comment, I proposed a solution which extended the existingState
enumeration, but described an alternative that simplifies the overall tracking of incoming changes; this PR implements the latter.The PR is broken up into 5 commits:
commit
andrestore
methods, and it changes how replacements are tracked to make this easier.Error::AlreadyReplaced
in order to remove the special-case handling for duplicate replacements.&[u8]
usingAsRef
to make the interface easier to use (since this code is primarily interested in&str
).to_string
method and moves the logic fromlib.rs
for the same reason as above.How should we test and review this PR?
I've added an additional test and updated the existing ones, but it's a good idea to experiment with
cargo clippy --fix
on repos that match the cases described in these open issues.Additional information
This removes special handling for rust-lang/rust#51211.
Fixes #14699
Fixes #13027
Fixes rust-lang/rust-clippy/issues/13549