This story begins in #8384, where we added a smart test for our syntax
highting, which run the algorithm on synthetic files of varying length
in order to guesstimate if the complexity is O(N^2) or O(N)-ish.
The test turned out to be pretty effective, and flagged #9031 as a
change that makes syntax highlighting accidentally quadratic. There was
much rejoicing, for the time being.
Then, lnicola asked an ominous question[1]: "Are we sure that the time
is linear right now?"
Of course it turned out that our sophisticated non-linearity detector
*was* broken, and that our syntax highlighting *was* quadratic.
Investigating that, many brave hearts dug deeper and deeper into the
guts of rust-analyzer, only to get lost in a maze of traits delegating
to traits delegating to macros.
Eventually, matklad managed to peel off all layers of abstraction one by
one, until almost nothing was left. In fact, the issue was discovered in
the very foundation of the rust-analyzer -- in the syntax trees.
Worse, it was not a new problem, but rather a well-know, well-understood
and event (almost) well-fixed (!) performance bug.
The problem lies within `SyntaxNodePtr` type -- a light-weight "address"
of a node in a syntax tree [3]. Such pointers are used by rust-analyzer all
other the place to record relationships between IR nodes and the
original syntax.
Internally, the pointer to a syntax node is represented by node's range.
To "dereference" the pointer, you traverse the syntax tree from the
root, looking for the node with the right range. The inner loop of this
search is finding a node's child whose range contains the specified
range. This inner loop was implemented by naive linear search over all
the children. For wide trees, dereferencing a single `SyntaxNodePtr` was
linear. The problem with wide trees though is that they contain a lot of
nodes! And dereferencing pointers to all the nodes is quadratic in the
size of the file!
The solution to this problem is to speed up the children search --
rather than doing a linear lookup, we can use binary search to locate
the child with the desired interval.
Doing this optimization was one of the motivations (or rather, side
effects) of #6857. That's why `rowan` grew the useful
`child_or_token_at_range` method which does exactly this binary search.
But looks like we've never actually switch to this method? Oups.
Lesson learned: do not leave broken windows in the fundamental infra.
Otherwise, you'll have to repeatedly re-investigate the issue, by
digging from the top of the Everest down to the foundation!
[1]: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/.60syntax_highlighting_not_quadratic.60.20failure/near/240811501
[2]: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/Syntax.20highlighting.20is.20quadratic
[3]: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/Syntax.20highlighting.20is.20quadratic/near/243412392
8813: Get some more array lengths! r=lf- a=lf-
This is built on #8799 and thus contains its changes. I'll rebase it onto master when that one gets merged. It adds support for r-a understanding the length of:
* `let a: [u8; 2] = ...`
* `let a = b"aaa"`
* `let a = [0u8; 4]`
I have added support for getting the values of byte strings, which was not previously there. I am least confident in the correctness of this part and it probably needs some more tests, as we currently have only one test that exercised that part (!).
Fixes#2922.
Co-authored-by: Jade <software@lfcode.ca>
There's a tension between keeping a well-architectured minimal
orthogonal set of constructs, and providing convenience functions.
Relieve this pressure by introducing an dedicated module for
non-orthogonal shortcuts.
This is inspired by the django.shortcuts module which serves a similar
purpose architecturally.
8591: Remove SyntaxRewriter usage in insert_use in favor of mutable syntax trees r=matklad a=Veykril
Unfortunately changing `insert_use` to not use `SyntaxRewriter` creates a lot of changes since so much relies on that. But on the other hand this should be the biggest usage of `SyntaxRewriter` I believe.
8638: Remove SyntaxRewriter::from_fn r=Veykril a=Veykril
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
8524: Fix extract function with partial block selection r=matklad a=brandondong
**Reproduction:**
```rust
fn foo() {
let n = 1;
let mut v = $0n * n;$0
v += 1;
}
```
1. Select the snippet ($0) and use the "Extract into function" assist.
2. Extracted function is incorrect and does not compile:
```rust
fn foo() {
let n = 1;
let mut v = fun_name(n);
v += 1;
}
fn fun_name(n: i32) {}
```
3. Omitting the ending semicolon from the selection fixes the extracted function:
```rust
fn fun_name(n: i32) -> i32 {
n * n
}
```
**Cause:**
- When `extraction_target` uses a block extraction (semicolon case) instead of an expression extraction (no semicolon case), the user selection is directly used as the TextRange.
- However, the existing function extraction logic for blocks requires that the TextRange spans from start to end of complete statements to work correctly.
- For example:
```rust
fn foo() {
let m = 2;
let n = 1;
let mut v = m $0* n;
let mut w = 3;$0
v += 1;
w += 1;
}
```
produces
```rust
fn foo() {
let m = 2;
let n = 1;
let mut v = m let mut w = fun_name(n);
v += 1;
w += 1;
}
fn fun_name(n: i32) -> i32 {
let mut w = 3;
w
}
```
- The user selected TextRange is directly replaced by the function call which is now in the middle of another statement. The extracted function body only contains statements that were fully covered by the TextRange and so the `* n` code is deleted. The logic for calculating variable usage and outlived variables for the function parameters and return type respectively search within the TextRange and so do not include `m` or `v`.
**Fix:**
- Only extract full statements when using block extraction. If a user selected part of a statement, extract that full statement.
8527: Switch introduce_named_lifetime assist to use mutable syntax tree r=matklad a=iDawer
This extends `GenericParamsOwnerEdit` trait with `get_or_create_generic_param_list` method
Co-authored-by: Brandon <brandondong604@hotmail.com>
Co-authored-by: Dawer <7803845+iDawer@users.noreply.github.com>
7620: Support control flow in `extract_function` assist r=matklad a=cpud36
Support `return`ing from outer function, `break`ing and `continue`ing outer loops when extracting function.
# Example
Transforms
```rust
fn foo() -> i32 {
let items = [1,2,3];
let mut sum = 0;
for &item in items {
<|>if item == 42 {
break;
}<|>
sum += item;
}
sum
}
```
Into
```rust
fn foo() -> i32 {
let items = [1,2,3];
let mut sum = 0;
for &item in items {
if fun_name(item) {
break;
}
sum += item;
}
sum
}
fn fun_name(item: i32) -> bool {
if item == 42 {
return true;
}
false
}
```
![add_explicit_type_infer_type](https://user-images.githubusercontent.com/4218373/107544222-0fadf280-6bdb-11eb-9625-ed6194ba92c0.gif)
# Features
Supported variants
- break and function does not return => uses `bool` and plain if
- break and function does return => uses `Option<T>` and matches on it
- break with value and function does not return => uses `Option<T>` and if let
- break with value and function does return => uses `Result<T, U>` and matches on t
- same for `return` and `continue`(but we can't continue with value)
Assist does handle nested loops and nested items(like functions, modules, impls)
Try `expr?` operator is allowed together with `return Err(_)` and `return None`.
`return expr` is not allowed.
# Not supported
## Mixing `return` with `break` or `continue`
If we have e.g. a `return` and a `break` in the selected code, it is unclear what the produced code should look like.
We can try `Result<T, Option<U>>` or something like that, but it isn't idiomatic, nor it is established. Otherwise, implementation
is relatively simple.
## `break` with label
Not sure how to handle different labels for multiple `break`s.
[edit] implemented try `expr?`
Co-authored-by: Vladyslav Katasonov <cpud47@gmail.com>
7291: Wrap remaining self/super/crate in Name{Ref} r=matklad a=Veykril
That should be the remaining special casing for `self` 🎉
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
7218: Fix typos r=Veykril a=regexident
Apart from the very last commit on this PR (which fixes a public type's name) all changes are non-breaking.
Co-authored-by: Vincent Esche <regexident@gmail.com>
7010: Update ungrammar for const block patterns r=matklad a=Veykril
Fixes#6848
Adds const blocks and const block patterns to the AST and parses them.
Blocked on https://github.com/rust-analyzer/ungrammar/pull/17/, will merge that PR there once this one gets the OK so I can remove the local ungrammar dependency path and fix the Cargo.lock.
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
6896: Node-ify lifetimes r=jonas-schievink a=Veykril
Let's see if this passes the tests 🤞
Depends on https://github.com/rust-analyzer/ungrammar/pull/15
Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
6897: Basic support for macros 2.0 r=jonas-schievink a=jonas-schievink
This adds support for (built-in-only) macros 2.0, and removes some hacks used for builtin derives, which are declared via macros 2.0 in libcore.
First steps for https://github.com/rust-analyzer/rust-analyzer/issues/2248.
Blocked on https://github.com/rust-analyzer/ungrammar/pull/16.
Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>