Skip to content

ssr: Allow replacing expressions with statements #7147

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

Merged
merged 4 commits into from
Jan 4, 2021

Conversation

MarijnS95
Copy link
Contributor

Depends on #6587

Until that is merged, the diff is https://github.com/MarijnS95/rust-analyzer/compare/stmt..replace-expr-with-stmt


Now that statements can be matched and replaced (#6587) some usecases require expressions to be replaced with statements as well. This happens when something that can ambiguously be an expression or statement like if and loop blocks appear in the last position of a block, as trailing expression. In this case a replacement pattern of the form if foo(){$a();}==>>$a(); will only substitute if blocks in the list of statements but not if they (implicitly) end up in the trailing expression, where they are not wrapped by an EXPR_STMT (but the pattern and template are, as parsing only succeeds for the stmt ==>> stmt case).

Instead of adding two rules that match an expression - and emit duplicate matching errors - allow the template for expressions to be a statement if it fails to parse as an expression.


Another gross change that does not seem to break any tests currently, but perhaps a safeguard should be added to only allow this kind of replacement in blocks by "pushing" the replacement template to the statement list and clearing the trailing expression?

CC @davidlattimore

Adjusting `grammar::fragments::stmt` to Optional or Yes will break
original functionality and tests.
Now that statements can be matched and replaced (rust-lang#6587) some usecases
require expressions to be replaced with statements as well. This happens
when something that can ambiguously be an expression or statement like
`if` and loop blocks appear in the last position of a block, as trailing
expression. In this case a replacement pattern of the form `if
foo(){$a();}==>>$a();` will only substitute `if` blocks in the list of
statements but not if they (implicitly) end up in the trailing
expression, where they are not wrapped by an EXPR_STMT (but the pattern
and template are, as parsing only succeeds for the `stmt ==>> stmt`
case).

Instead of adding two rules that match an expression - and emit
duplicate matching errors - allow the template for expressions to be a
statement if it fails to parse as an expression.
@matklad
Copy link
Member

matklad commented Jan 4, 2021

bors d=davidlattimore

@bors
Copy link
Contributor

bors bot commented Jan 4, 2021

✌️ davidlattimore can now approve this pull request. To approve and merge a pull request, simply reply with bors r+. More detailed instructions are available here.

@davidlattimore
Copy link
Contributor

Interesting. What happens if the search pattern matches somewhere a statement isn't valid? In this case the search pattern evaluates to the () type, which I guess is unlikely to be used in many such places. But still, I worry slightly that the rule can result in code that doesn't parse. It's already possible to write rules that produce code that doesn't type check, but so far, although I haven't thought too hard about it, I wasn't aware of being able to write rules that would make code not parse. That said, it might not be worthwhile the added complexity of trying to prevent this from happening.

@MarijnS95
Copy link
Contributor Author

@davidlattimore The pattern in the tests if $a() {$b;} ==>> $b; already "assumes" the LHS is a "statement" without result because of the trailing semicolon after $b. Updating it to if $a() {$b} ==>> $b makes the test work on master already as $b now includes the full EXPR_STMT. For "true" expressions (not evaluating to ()) something like if $a() {$b} else {$c} ==>> $b works in much the same way, optionally allowing semicolons to be consumed by $b and $c. However, this doesn't work for the actual use-case this PR is made for: if $c.at($a) {$d.eat($b);} ==>> $c.eat($a);. Here the trailing semicolon can't be consumed, (not even by if $c.at($a) {$d.eat($b)$optional_semi} ==>> $c.eat($a)$optional_semi).

To answer your question there's nothing in place preventing users from adding or removing semicolons to make resulting code invalid, or replace expressions with something nonsensical entirely like functions or struct definitions. Don't know if we need/want to impose such limits though, in the end it is the user specifying exactly what they want to replace. Other than that it's hard to come up with plausible test cases where this would perform an undesired change - they must exist and it'd be great to cover them.

I haven't invested the time to check a potential implementation that limits such replacement to trailing block expressions only, but it already sounds complicated and error prone :)

@davidlattimore
Copy link
Contributor

bors r+

@bors
Copy link
Contributor

bors bot commented Jan 4, 2021

@bors bors bot merged commit 550c496 into rust-lang:master Jan 4, 2021
@MarijnS95 MarijnS95 deleted the replace-expr-with-stmt branch January 4, 2021 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants