-
Notifications
You must be signed in to change notification settings - Fork 198
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
Rewriter::Action rewritten #401
Comments
Fixed a few things when testing with |
I really like your new Rewriter, but am unsure of how compatible it actually is. Can you run the RuboCop test suite? |
Oh, awesome, makes sense that I get 18 failures. 9 look like a single small bug in my code. None are cases where the new code raises a Clobering error (like the intersecting replacements of #402). I'm looking forward to check them out in detail tomorrow, really time to sleep for me... As I stated, it wouldn't be too surprising that compatibility would be an issue, I'm proposing to have a switch to use the new rules. |
There's apparently a check that the
My implementation checks that, so that explains 9 of the 18 failures I'm getting with rubocop. I opened an issue as it's a sign that there might be something else going on in this particular case. |
Hmm! Hmm. This is very interesting. Some background: in 2016, I did a complete overhaul of If Your new For example, as you noted, your Since The fact that Other issues:
The fact is that
So what if the user tries to replace the same range twice, with the exact same replacement text? In that case, it's clear what is wanted. We can handle that. What if one range is a proper subset of the other, but both replacements exactly match the length of their replaced ranges, and they agree where they overlap? That is also not a problem. But what if the replacement texts do not match their replaced texts in length, but they can still be aligned in such a way that they agree? Worse still, what if neither range is a subset of the other? In those tricky cases, my Now, you could say that a better rule is that "overlapping replacements always conflict, unless they are replacing the exact same range with the exact same text". That is also a perfectly good rule. It does make sense and I have no objection to it.
If you want to make that one of your rules, it is a good idea. Just make sure that it never conflicts with any of your other rules, in any situation, and it is always applied consistently.
|
@alexdowad Wow, thanks for the detailed feedback.
I agree 👍 and came to the same conclusion when looking at Rubocop's failures. I hope to have a revised proposal and detailed report ready soon. I'll also have more comments on the rest of you post 😄 |
@alexdowad I agree with most of what you wrote 😄 Let me discuss where there is matter for discussion:
I see a Rewriter as sequence of commands, with some that are more local than others. I see no issue if local ones are superseded by more global ones. If the ranges are not exactly the same, I believe that trying to decide if they "are compatible" can not be well defined and can create false positives and false negatives. Or maybe I tell myself that because it would be too hard and I'm not as courageous as you are 😸 ! In any case, I have made no attempt to detect that.
I probably wasn't clear enough, but what I call overlapping ranges are strictly overlapping, i.e. ranges that have non-trivial intersections and non trivial differences. Neither contain the other completely. 1...10 and 4...20 are such ranges, but 1...10 and 1...10 are not, neither are 1...10 and 10...10. So after your comments and checking out Rubocop's failures, I've revised my rule to say that "strictly overlapping ranges that are pure deletions are fused; other cases always raise errors".
I you think of an insertion "at an end point P", then you are thinking of an insertion at P...P, which would not be swallowed, as it is disjoint. If you think of an insert on a range, then the answer depends on if the range is bigger or smaller than the deleted region. If it is smaller, it gets swallowed. Otherwise it won't. That's definitely the biggest mind shift with my proposal: seeing some insertions as wrapping a particular range. |
I like that approach. I tweaked the doc to be more "rule-like", hopefully you'll like it and it remains short enough and understandable. |
I'll need to find a different term than overlap though, especially given |
@marcandre What you say makes a lot of sense and I support it. Trying to merge "compatible" modifications was probably a bad idea. It seems that "overlapping" is the term used in set theory to describe sets with a non-empty intersection. If you want to borrow mathematical terminology, maybe your test for clobbering is:
|
"crossing" seems to make sense, "crosses_bounds" if you want to be as explicit as possible. |
TreeRewriter has been merge 🎆 |
Just fresh out of the 🚿 I thought I had a great idea. Here it is:
Rewriter::Action is currently either:
I think that
Rewriter::Action
should actually be slightly different:This way, we can order theses actions in a sensical way, by placing them in a tree such that an action strictly containing another is an ancestor. (strict inclusion is a semi-order here; empty ranges are not considered contained if they are at either end of another range).
Actions on the same range are always merged.
Clobbering happens iff two ranges cross (i.e. if they have a different non-trivial intersection, like
1...4
and2..5
).Example operations on
'Hello'
:'Hell'
with'<', '>'
'el'
'ell'
'e'
with'[', ']'
'H'
withnil, '*'
These commands can be given in any order; the resulting tree (left-to-right) is always:
In this case, c is a removal and swallows d and b. In which ever order, the result should be '<H*>o':
insert_before(range, text)
is actuallywrap(before, text, nil)
, whileinsert_after(range, text)
iswrap(range, nil, text)
.replace(range, text)
is always equivalent towrap(range, text, nil)
+remove(range)
insert_{before|after}_multi
have no reason to exist IMO.Pros
Rewriting operations would always well defined. Order is dependent only when acting on the exact same range: wrappings gets combined, later replacements takeover previous ones.
When rewriting an AST, care is no longer needed to deal with children first, the rewriting will order things correctly.
In particular,
insert_after_multi(1...4, '>')
andinsert_after_multi(2..4, ')')
are currently order dependent (even after #399), while the_before
versions are order independent. With this tree proposal, the resulting order would always be the same no matter what, since1...4
contains2...4
.This supersedes #399, fixes #400, and removes the need for
_multi
versions.Incompatibilities
Going deeper into the code, it's time for a cold shower as many of my expectations of the rewriter seem wrong.
First, it allows some intersecting replacements (see #402).
Less importantly it is allowed to removing intersecting ranges (say
1...4
and2...5
). I don't see the use for this, feel it's a bug and not than a feature, but it would be trivial to implement if there's a good usecase.OTOH, current behavior doesn't allow some scenarios I don't see a problem with:
I can imagine a rewriter wanting to do a local change (inserting the '*' here), only to realize somewhere else in the code that a whole section containing that local change should be removed. Why not swallow the
insert
in this case? Anyways, that's also easy to implement with my "tree" version by not allowing deleting nodes to have inserting children, but I don't see why.(I hope I'm not abusing the time of contributors to this awesome gem with this idea).
The text was updated successfully, but these errors were encountered: