Skip to content
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

StructuralSearchReplace: next steps #3186

Open
2 of 4 tasks
matklad opened this issue Feb 17, 2020 · 30 comments
Open
2 of 4 tasks

StructuralSearchReplace: next steps #3186

matklad opened this issue Feb 17, 2020 · 30 comments
Labels
A-ide general IDE features E-hard fun A technically challenging issue with high impact good first issue S-actionable Someone could pick this issue up and work on it right now

Comments

@matklad
Copy link
Member

matklad commented Feb 17, 2020

#3099 implemented initial version of SSR.

This issue collects things we need to do to make this really production-read

@mikhail-m1 any other things I've missed?

@matklad matklad added good first issue E-hard fun A technically challenging issue with high impact labels Feb 17, 2020
@matklad
Copy link
Member Author

matklad commented Feb 17, 2020

The most important feature here is documentation!

@adamrk
Copy link
Contributor

adamrk commented Feb 22, 2020

What kinds of things are you thinking about for the second item?

@matklad
Copy link
Member Author

matklad commented Feb 22, 2020

White space differences should not matter for natching

@adamrk
Copy link
Contributor

adamrk commented Feb 22, 2020

Made an initial change for the whitespace, but there's two points I'm not certain about:

  1. The Rust reference says whitespace is defined by the Unicode property Pattern_White_Space, but the char method is_whitespace uses the Unicode property White_Space instead and they're not exactly the same. I didn't notice any code in rust-analyzer for checking Pattern_White_Space so I assume we don't really care about the difference and it's fine to use is_whitespace?
  2. It seems like we might want to completely ignore whitespace in certain situations (e.g. match f(x) with f( x ), but I don't think we can do that in general. The current change only matches various forms of whitespace with each other, but doesn't ignore any whitespace. Any opinions on that?

@adamrk
Copy link
Contributor

adamrk commented Feb 22, 2020

Actually I think for my second question it's fine to throw out all whitespace tokens. I can't actually think of any situations where it would be a problem.

@flodiebold
Copy link
Member

Note that there's an is_trivia method on SyntaxKind, you don't need to implement this yourself. E.g. some_token.kind().is_trivia().

@matklad
Copy link
Member Author

matklad commented Feb 23, 2020 via email

bors bot added a commit that referenced this issue Feb 23, 2020
3275: Add Structural Search and Replace doc r=matklad a=adamrk

Addresses the first point of #3186.

Co-authored-by: adamrk <ark.email@gmail.com>
Co-authored-by: Adam Bratschi-Kaye <ark.email@gmail.com>
@adamrk
Copy link
Contributor

adamrk commented Feb 23, 2020

Cool, thanks.

@mikhail-m1
Copy link
Contributor

mikhail-m1 commented Feb 26, 2020

@adamrk sorry, I missed that you started to work on it and created a PR for the second step.

bors bot added a commit that referenced this issue Feb 27, 2020
3285: Handle trivia in Structural Search and Replace r=matklad a=adamrk

Addresses the second point of #3186.

Structural search and replace will now match code that has varies from the pattern in whitespace or comments.

One issue is that it's not clear where comments in the matched code should go in the replacement. With this change they're just tacked on at the end, which can cause some unexpected moving of comments (see the last test example).

Co-authored-by: adamrk <ark.email@gmail.com>
@adamrk
Copy link
Contributor

adamrk commented Feb 27, 2020

my mistake @mikhail-m1 - didn't realize you were working on it either.

bors bot added a commit that referenced this issue Mar 16, 2020
3540: Swtches to rust SSR query check r=matklad a=mikhail-m1

related to #3186 

Co-authored-by: Mikhail Modin <mikhailm1@gmail.com>
@matklad
Copy link
Member Author

matklad commented Mar 18, 2020

Example of semanticly equivalent transformations -- trailing comma:

            // replace_children(self, $what:expr, $where:expr) ==>> self.replace_children($what, $where)
            return replace_children(
                self,
                single_node(old.syntax().clone()),
                iter::once(segment.syntax().clone().into()),
            );

@matklad
Copy link
Member Author

matklad commented Mar 18, 2020

I've actually been using ssr and it works surprisingly well.

One thing I've found is that typing ssr in comment and then copy-pasting it is the most productive workflow. I think we should probably make this the workflow.

Specifcally, I imaging something like this:

  • If the cursor is on a comment with // ssr: tag, it initiates ssr session.
  • The contents of the comment is interpreted as ssr query (we perhaps can replace ==>> with just splitting by lines?)
  • The ssr can be applied via code action
  • (stretch goal) the contents of ssr comment is syntax highlighted
  • (stretch goal) the matching elements in the current file are highlighted

bors bot added a commit that referenced this issue Apr 1, 2020
3765: Adds sort for RecordLit comparison in SSR r=edwin0cheng a=mikhail-m1

an item from #3186 

Co-authored-by: Mikhail Modin <mikhailm1@gmail.com>
bors bot added a commit that referenced this issue Apr 6, 2020
3829: Adds to SSR match for semantically equivalent call and method call r=matklad a=mikhail-m1

#3186 
maybe I've missed some corner cases, but it works in general

Co-authored-by: Mikhail Modin <mikhailm1@gmail.com>
@mikhail-m1
Copy link
Contributor

next steps:

@matklad
Copy link
Member Author

matklad commented Apr 17, 2020

@RReverser no, we currently run on pre-expansion code. Although we should probably run post-expansion eventually. A minimal test-case would be useful!

@RReverser
Copy link

no, we currently run on pre-expansion code. Although we should probably run post-expansion eventually

Hmm, my problem is exactly that it doesn't seem to match pre-expansion code.

I've tried something as simple as:

eprintln!($a:expr, $b:expr) ==>> $a + $b

and it didn't match anything.

@matklad
Copy link
Member Author

matklad commented Jun 16, 2020

Thanks for pointing out the possible slowness with using semantic info.

See also this nice blog post about how semantic code searches are handled in IntelliJ: https://habr.com/en/company/JetBrains/blog/451754/

I think for rust-analyzer we might be able to come up with something even more efficient (salsa allows us to lift the "single file index" constraint), but that is very much unexplored territory.

davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 17, 2020
@flodiebold
Copy link
Member

BTW, it might be nice to be able to apply SSR by running rust-analyzer on the command line as well 🤔

bors bot added a commit that referenced this issue Jun 17, 2020
4919: Remove :expr from placeholders r=matklad a=davidlattimore

Reasoning discussed at #3186 (comment)

Co-authored-by: David Lattimore <dml@google.com>
davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 17, 2020
davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 17, 2020
davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 22, 2020
davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 22, 2020
davidlattimore added a commit to davidlattimore/rust-analyzer that referenced this issue Jun 22, 2020
bors bot added a commit that referenced this issue Jun 22, 2020
4921: Allow SSR to match type references, items, paths and patterns r=davidlattimore a=davidlattimore

Part of #3186

Co-authored-by: David Lattimore <dml@google.com>
bors bot added a commit that referenced this issue Jun 27, 2020
5007: SSR: Allow matching within macro calls r=matklad a=davidlattimore

#3186 

Co-authored-by: David Lattimore <dml@google.com>
@davidlattimore
Copy link
Contributor

Forgot to reference this issue, but #5120 added support for SSR from the rust-analyzer command line

@matklad
Copy link
Member Author

matklad commented Jul 8, 2020

I figred that it migbe be useful to support statements in SSR:

let $var = foo();
$var.bar();
==>>
foo().bar();

@davidlattimore
Copy link
Contributor

I've added path resolution to SSR rules, the main user-visible changes are:

  • SSR now matches paths based on whether they resolve to the same thing instead of whether they're written the same.
    • So foo() won't match foo() if it's a different function foo(), but will match bar::foo() if it's the same foo.
  • Paths in the replacement will now be rendered with appropriate qualification for their context.
    • For example foo::Bar will render as just Bar inside the module foo, but might render as baz::foo::Bar from elsewhere.
  • This means that all paths in the search pattern and replacement template must be able to be resolved.
  • It now also matters where you invoke SSR from, since paths are resolved relative to wherever that is.
  • Search now uses find-uses on paths to locate places to try matching. This means that when a path is present in the pattern, search will generally be pretty fast.
  • Function calls can now match method calls again, but this time only if they resolve to the same function.

Regarding supporting statements, I agree, that would be good to support. It'll probably require some refactoring since currently we only match whole AST nodes, not sequences of nodes. Also, the example you gave would also require allowing a placeholder to be repeated, then checking the equivalence of each occurrence.

I just started looking at making an SSR assist, but I'm slightly unsure of how it should work if there's an SSR rule, but it fails validation. How should the error be presented? It looks like the assist API just does edits, but doesn't offer any scope for interaction or error reporting. Is that correct?

@bjorn3
Copy link
Member

bjorn3 commented Jul 25, 2020

I just started looking at making an SSR assist, but I'm slightly unsure of how it should work if there's an SSR rule, but it fails validation. How should the error be presented? It looks like the assist API just does edits, but doesn't offer any scope for interaction or error reporting. Is that correct?

You could report an error for all invalid ssr comments instead of waiting for the assist to be invoked.

@davidlattimore
Copy link
Contributor

That sounds like it would work.

Another thing that I've been thinking it would be good to support is a way to scope the rule. Sometimes you just want to automate a repetitive edit to the current file, or even within the current function. One way I was considering was by extending the placeholder constraint syntax. So ${:in(fn)} foo ==>> bar would replace foo with bar in the current function. Similarly in(file) for the current file. This has the advantage of not requiring any new syntax. However I'm open to suggestions if people have any ideas for a dedicated syntax for constraints that apply to the whole pattern.

A completely different option would be to just have different commands - e.g. "Structural Search Replace - current file". That's perhaps more discoverable, but less scalable (adding new scopes would be a pain).

A third option would be building a more sophisticated UI for doing SSR - one that could have drop downs, checkboxes etc. I know comparatively little about extending vscode, so I'm not sure how plausible that would be.

@RReverser
Copy link

Another thing that I've been thinking it would be good to support is a way to scope the rule. Sometimes you just want to automate a repetitive edit to the current file, or even within the current function. One way I was considering was by extending the placeholder constraint syntax.

I think a bit more natural option could be "search within selection" which is how regular Find & Replace already limits itself in VSCode. Then user can easily select a single function, or couple of them, or whole module, etc.

bors bot added a commit that referenced this issue Jan 4, 2021
6587: SSR: Support statement matching and replacing r=davidlattimore a=MarijnS95


For #3186

Hi!

This is a smaller initial patchset that came up while working on support for statement lists (and my first time working on RA :grin:). It has me stuck on trailing semicolons for which I hope to receive some feedback. Matching (and replacing) `let` bindings with a trailing semicolon works fine, but trying to omit these (to make patterns more ergonomic) turns out more complex than expected.

The "optional trailing semicolon solution" implemented in this PR is ugly because `Matcher::attempt_match_token` should only consume a trailing `;` when parsing `let` bindings to prevent other code from breaking. That at the same time has a nasty side-effect of `;` ending up in the matched code: any replacements on that should include the trailing semicolon as well even if it was not in the pattern. A better example is in the tests:

https://github.com/rust-analyzer/rust-analyzer/blob/3ae1649c24a689473b874c331f5f176e5839978e/crates/ssr/src/tests.rs#L178-L184

The end result to achieve is (I guess) allowing replacement of let bindings without trailing semicolon like `let x = $a ==>> let x = 1` (but including them on both sides is still fine), and should make replacement in a macro call (where `foo!(let a = 2;)` for a `$x:stmt` is invalid syntax) possible as well. That should allow to enable/fix these tests:

https://github.com/rust-analyzer/rust-analyzer/blob/3ae1649c24a689473b874c331f5f176e5839978e/crates/ssr/src/tests.rs#L201-L214

A possible MVP of this PR might be to drop this optional `;' handling entirely and only allow an SSR pattern/template with semicolons on either side.

Co-authored-by: Marijn Suijten <marijn@traverseresearch.nl>
@lnicola lnicola added the S-actionable Someone could pick this issue up and work on it right now label Jan 22, 2021
bors bot added a commit that referenced this issue Mar 10, 2021
7874: add apply ssr assist r=JoshMcguigan a=JoshMcguigan

This PR adds an `Apply SSR` assist which was briefly mentioned in #3186. It allows writing an ssr rule as a comment, and then applying that SSR via an assist. This workflow is much nicer than the default available via `coc-rust-analyzer` when iterating to find the proper replacement.

As currently implemented, this requires the ssr rule is written as a single line in the comment, and it doesn't require any kind of prefix. Anything which properly parses as a ssr rule will enable the assist. The benefit of requiring the ssr rule be on a single line is it allows for a workflow where the user has several rules written one after the other, possibly to be triggered in order, without having to try to parse multiple lines of text and determine where one rule ends and the next begins. The benefit of not requiring a prefix is less typing 😆  - plus, I think the chance of something accidentally parsing as an ssr rule is minimal.

I think a reasonable extension of this would be to allow either any ssr rule that fits on a single line, or any comment block which in its entirety makes up a single ssr rule (parsing a comment block containing multiple ssr rules and running them all would break the use case that currently works where a user writes multiple ssr rules then runs them each one by one in arbitrary order).

I've marked this as a draft because for some reason I am strugging to make the unit tests pass. It does work when I compile rust-analyzer and test it in my editor though, so I'm not sure what is going on.

Co-authored-by: Josh Mcguigan <joshmcg88@gmail.com>
@crepererum
Copy link

Tried SSR today. One limitation seems to be the power of the matcher, e.g. foo(vec![$a]) ==>> foo(vec![Some($a)]) does not work.

@IndianBoy42
Copy link

IndianBoy42 commented Dec 5, 2021

Another thing that would make SSR a lot more approachable would be incremental search and replace, ie

  • highlighting what is matched by a certain structural search
  • previewing the changes that would be made if the replacement was run
  • seeing the above two points update live while typing (without actually modifying the buffer)
    • the hard part here is probably making SSR run fast enough to keep up with typing (although debouncing is necessary anyways)

Also syntax highlighting in the SSR query+replacement would be nice

Some of this is more about the ide plugin side, but the server needs to have the capabilities. Is everything necessary for this available from the server already?

@Veykril Veykril added the A-ide general IDE features label Dec 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ide general IDE features E-hard fun A technically challenging issue with high impact good first issue S-actionable Someone could pick this issue up and work on it right now
Projects
None yet
Development

No branches or pull requests