diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/hir/src/code_model.rs | 25 | ||||
-rw-r--r-- | crates/ide/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 3 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tags.rs | 3 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/test_data/highlighting.html | 19 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tests.rs | 19 | ||||
-rw-r--r-- | crates/rust-analyzer/src/semantic_tokens.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 1 | ||||
-rw-r--r-- | crates/syntax/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/syntax/src/algo.rs | 417 | ||||
-rw-r--r-- | editors/code/package.json | 4 |
13 files changed, 469 insertions, 32 deletions
diff --git a/Cargo.lock b/Cargo.lock index 8bafc3895..fe09e4afe 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1608,6 +1608,7 @@ version = "0.0.0" | |||
1608 | dependencies = [ | 1608 | dependencies = [ |
1609 | "arrayvec", | 1609 | "arrayvec", |
1610 | "expect-test", | 1610 | "expect-test", |
1611 | "indexmap", | ||
1611 | "itertools", | 1612 | "itertools", |
1612 | "once_cell", | 1613 | "once_cell", |
1613 | "parser", | 1614 | "parser", |
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 864f9c0c8..63c1a8ebf 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs | |||
@@ -31,8 +31,7 @@ use hir_ty::{ | |||
31 | autoderef, | 31 | autoderef, |
32 | display::{HirDisplayError, HirFormatter}, | 32 | display::{HirDisplayError, HirFormatter}, |
33 | method_resolution, | 33 | method_resolution, |
34 | traits::Solution, | 34 | traits::{FnTrait, Solution, SolutionVariables}, |
35 | traits::SolutionVariables, | ||
36 | ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, | 35 | ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, |
37 | InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty, | 36 | InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty, |
38 | TyDefId, TyKind, TypeCtor, | 37 | TyDefId, TyKind, TypeCtor, |
@@ -1386,6 +1385,28 @@ impl Type { | |||
1386 | ) | 1385 | ) |
1387 | } | 1386 | } |
1388 | 1387 | ||
1388 | /// Checks that particular type `ty` implements `std::ops::FnOnce`. | ||
1389 | /// | ||
1390 | /// This function can be used to check if a particular type is callable, since FnOnce is a | ||
1391 | /// supertrait of Fn and FnMut, so all callable types implements at least FnOnce. | ||
1392 | pub fn impls_fnonce(&self, db: &dyn HirDatabase) -> bool { | ||
1393 | let krate = self.krate; | ||
1394 | |||
1395 | let fnonce_trait = match FnTrait::FnOnce.get_id(db, krate) { | ||
1396 | Some(it) => it, | ||
1397 | None => return false, | ||
1398 | }; | ||
1399 | |||
1400 | let canonical_ty = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) }; | ||
1401 | method_resolution::implements_trait( | ||
1402 | &canonical_ty, | ||
1403 | db, | ||
1404 | self.ty.environment.clone(), | ||
1405 | krate, | ||
1406 | fnonce_trait, | ||
1407 | ) | ||
1408 | } | ||
1409 | |||
1389 | pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { | 1410 | pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { |
1390 | let trait_ref = hir_ty::TraitRef { | 1411 | let trait_ref = hir_ty::TraitRef { |
1391 | trait_: trait_.id, | 1412 | trait_: trait_.id, |
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 63299dc31..76b52fa04 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -11,7 +11,7 @@ doctest = false | |||
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | either = "1.5.3" | 13 | either = "1.5.3" |
14 | indexmap = "1.3.2" | 14 | indexmap = "1.4.0" |
15 | itertools = "0.9.0" | 15 | itertools = "0.9.0" |
16 | log = "0.4.8" | 16 | log = "0.4.8" |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 90574cb35..232074c3d 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -613,7 +613,7 @@ fn main() { | |||
613 | pub struct Foo { pub a: i32, pub b: i32 } | 613 | pub struct Foo { pub a: i32, pub b: i32 } |
614 | "#, | 614 | "#, |
615 | r#" | 615 | r#" |
616 | fn {a:42, b: ()} {} | 616 | fn some(, b: ()} {} |
617 | fn items() {} | 617 | fn items() {} |
618 | fn here() {} | 618 | fn here() {} |
619 | 619 | ||
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index b35c03162..750848467 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -763,6 +763,9 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
763 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | 763 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { |
764 | h |= HighlightModifier::Mutable; | 764 | h |= HighlightModifier::Mutable; |
765 | } | 765 | } |
766 | if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { | ||
767 | h |= HighlightModifier::Callable; | ||
768 | } | ||
766 | return h; | 769 | return h; |
767 | } | 770 | } |
768 | } | 771 | } |
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index c1b817f06..e8f78ad52 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs | |||
@@ -64,6 +64,7 @@ pub enum HighlightModifier { | |||
64 | Mutable, | 64 | Mutable, |
65 | Consuming, | 65 | Consuming, |
66 | Unsafe, | 66 | Unsafe, |
67 | Callable, | ||
67 | } | 68 | } |
68 | 69 | ||
69 | impl HighlightTag { | 70 | impl HighlightTag { |
@@ -122,6 +123,7 @@ impl HighlightModifier { | |||
122 | HighlightModifier::Mutable, | 123 | HighlightModifier::Mutable, |
123 | HighlightModifier::Consuming, | 124 | HighlightModifier::Consuming, |
124 | HighlightModifier::Unsafe, | 125 | HighlightModifier::Unsafe, |
126 | HighlightModifier::Callable, | ||
125 | ]; | 127 | ]; |
126 | 128 | ||
127 | fn as_str(self) -> &'static str { | 129 | fn as_str(self) -> &'static str { |
@@ -134,6 +136,7 @@ impl HighlightModifier { | |||
134 | HighlightModifier::Mutable => "mutable", | 136 | HighlightModifier::Mutable => "mutable", |
135 | HighlightModifier::Consuming => "consuming", | 137 | HighlightModifier::Consuming => "consuming", |
136 | HighlightModifier::Unsafe => "unsafe", | 138 | HighlightModifier::Unsafe => "unsafe", |
139 | HighlightModifier::Callable => "callable", | ||
137 | } | 140 | } |
138 | } | 141 | } |
139 | 142 | ||
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 0bb0928e4..0cb84866d 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -44,6 +44,17 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
44 | <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span> | 44 | <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span> |
45 | <span class="punctuation">}</span> | 45 | <span class="punctuation">}</span> |
46 | 46 | ||
47 | <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">ops</span> <span class="punctuation">{</span> | ||
48 | <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_once"</span><span class="attribute">]</span> | ||
49 | <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnOnce</span><span class="punctuation"><</span><span class="type_param declaration">Args</span><span class="punctuation">></span> <span class="punctuation">{</span><span class="punctuation">}</span> | ||
50 | |||
51 | <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_mut"</span><span class="attribute">]</span> | ||
52 | <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnMut</span><span class="punctuation"><</span><span class="type_param declaration">Args</span><span class="punctuation">></span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation"><</span><span class="type_param">Args</span><span class="punctuation">></span> <span class="punctuation">{</span><span class="punctuation">}</span> | ||
53 | |||
54 | <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn"</span><span class="attribute">]</span> | ||
55 | <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Fn</span><span class="punctuation"><</span><span class="type_param declaration">Args</span><span class="punctuation">></span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation"><</span><span class="type_param">Args</span><span class="punctuation">></span> <span class="punctuation">{</span><span class="punctuation">}</span> | ||
56 | <span class="punctuation">}</span> | ||
57 | |||
47 | 58 | ||
48 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> | 59 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> |
49 | <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> | 60 | <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> |
@@ -99,6 +110,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
99 | <span class="function">foo</span><span class="operator">::</span><span class="punctuation"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> | 110 | <span class="function">foo</span><span class="operator">::</span><span class="punctuation"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> |
100 | <span class="punctuation">}</span> | 111 | <span class="punctuation">}</span> |
101 | 112 | ||
113 | <span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="punctuation">;</span> | ||
114 | <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation"><</span><span class="type_param declaration">F</span><span class="punctuation">:</span> <span class="trait">Fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">></span><span class="punctuation">(</span><span class="value_param declaration callable">f</span><span class="punctuation">:</span> <span class="type_param">F</span><span class="punctuation">)</span> <span class="punctuation">{</span> | ||
115 | <span class="value_param callable">f</span><span class="punctuation">(</span><span class="punctuation">)</span> | ||
116 | <span class="punctuation">}</span> | ||
117 | |||
102 | <span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span> | 118 | <span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span> |
103 | <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span> | 119 | <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span> |
104 | <span class="punctuation">}</span> | 120 | <span class="punctuation">}</span> |
@@ -157,6 +173,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
157 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 173 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
158 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 174 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
159 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> | 175 | <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> |
176 | |||
177 | <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="punctuation">;</span> | ||
178 | <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function">baz</span><span class="punctuation">;</span> | ||
160 | <span class="punctuation">}</span> | 179 | <span class="punctuation">}</span> |
161 | 180 | ||
162 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span> | 181 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span> |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 126363b8b..da20c300e 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -18,6 +18,17 @@ pub mod marker { | |||
18 | pub trait Copy {} | 18 | pub trait Copy {} |
19 | } | 19 | } |
20 | 20 | ||
21 | pub mod ops { | ||
22 | #[lang = "fn_once"] | ||
23 | pub trait FnOnce<Args> {} | ||
24 | |||
25 | #[lang = "fn_mut"] | ||
26 | pub trait FnMut<Args>: FnOnce<Args> {} | ||
27 | |||
28 | #[lang = "fn"] | ||
29 | pub trait Fn<Args>: FnMut<Args> {} | ||
30 | } | ||
31 | |||
21 | 32 | ||
22 | struct Foo { | 33 | struct Foo { |
23 | pub x: i32, | 34 | pub x: i32, |
@@ -73,6 +84,11 @@ fn foo<'a, T>() -> T { | |||
73 | foo::<'a, i32>() | 84 | foo::<'a, i32>() |
74 | } | 85 | } |
75 | 86 | ||
87 | use ops::Fn; | ||
88 | fn baz<F: Fn() -> ()>(f: F) { | ||
89 | f() | ||
90 | } | ||
91 | |||
76 | macro_rules! def_fn { | 92 | macro_rules! def_fn { |
77 | ($($tt:tt)*) => {$($tt)*} | 93 | ($($tt:tt)*) => {$($tt)*} |
78 | } | 94 | } |
@@ -131,6 +147,9 @@ fn main() { | |||
131 | copy.quop(); | 147 | copy.quop(); |
132 | copy.qux(); | 148 | copy.qux(); |
133 | copy.baz(copy); | 149 | copy.baz(copy); |
150 | |||
151 | let a = |x| x; | ||
152 | let bar = Foo::baz; | ||
134 | } | 153 | } |
135 | 154 | ||
136 | enum Option<T> { | 155 | enum Option<T> { |
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs index a6c4d6099..7df28c9dd 100644 --- a/crates/rust-analyzer/src/semantic_tokens.rs +++ b/crates/rust-analyzer/src/semantic_tokens.rs | |||
@@ -77,6 +77,7 @@ define_semantic_token_modifiers![ | |||
77 | (CONSUMING, "consuming"), | 77 | (CONSUMING, "consuming"), |
78 | (UNSAFE, "unsafe"), | 78 | (UNSAFE, "unsafe"), |
79 | (ATTRIBUTE_MODIFIER, "attribute"), | 79 | (ATTRIBUTE_MODIFIER, "attribute"), |
80 | (CALLABLE, "callable"), | ||
80 | ]; | 81 | ]; |
81 | 82 | ||
82 | #[derive(Default)] | 83 | #[derive(Default)] |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index d0fb92f89..0d34970bc 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -425,6 +425,7 @@ fn semantic_token_type_and_modifiers( | |||
425 | HighlightModifier::Mutable => semantic_tokens::MUTABLE, | 425 | HighlightModifier::Mutable => semantic_tokens::MUTABLE, |
426 | HighlightModifier::Consuming => semantic_tokens::CONSUMING, | 426 | HighlightModifier::Consuming => semantic_tokens::CONSUMING, |
427 | HighlightModifier::Unsafe => semantic_tokens::UNSAFE, | 427 | HighlightModifier::Unsafe => semantic_tokens::UNSAFE, |
428 | HighlightModifier::Callable => semantic_tokens::CALLABLE, | ||
428 | }; | 429 | }; |
429 | mods |= modifier; | 430 | mods |= modifier; |
430 | } | 431 | } |
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index c343f2f70..aa39ce554 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml | |||
@@ -17,6 +17,7 @@ rustc_lexer = { version = "683.0.0", package = "rustc-ap-rustc_lexer" } | |||
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | arrayvec = "0.5.1" | 18 | arrayvec = "0.5.1" |
19 | once_cell = "1.3.1" | 19 | once_cell = "1.3.1" |
20 | indexmap = "1.4.0" | ||
20 | # This crate transitively depends on `smol_str` via `rowan`. | 21 | # This crate transitively depends on `smol_str` via `rowan`. |
21 | # ideally, `serde` should be enabled by `rust-analyzer`, but we enable it here | 22 | # ideally, `serde` should be enabled by `rust-analyzer`, but we enable it here |
22 | # to reduce number of compilations | 23 | # to reduce number of compilations |
@@ -26,10 +27,9 @@ serde = { version = "1.0.106", features = ["derive"] } | |||
26 | stdx = { path = "../stdx", version = "0.0.0" } | 27 | stdx = { path = "../stdx", version = "0.0.0" } |
27 | text_edit = { path = "../text_edit", version = "0.0.0" } | 28 | text_edit = { path = "../text_edit", version = "0.0.0" } |
28 | parser = { path = "../parser", version = "0.0.0" } | 29 | parser = { path = "../parser", version = "0.0.0" } |
30 | test_utils = { path = "../test_utils" } | ||
29 | 31 | ||
30 | [dev-dependencies] | 32 | [dev-dependencies] |
31 | walkdir = "2.3.1" | 33 | walkdir = "2.3.1" |
32 | rayon = "1" | 34 | rayon = "1" |
33 | expect-test = "1.0" | 35 | expect-test = "1.0" |
34 | |||
35 | test_utils = { path = "../test_utils" } | ||
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs index ea199f9b8..4f9a7a6e8 100644 --- a/crates/syntax/src/algo.rs +++ b/crates/syntax/src/algo.rs | |||
@@ -2,11 +2,14 @@ | |||
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | fmt, | 4 | fmt, |
5 | hash::BuildHasherDefault, | ||
5 | ops::{self, RangeInclusive}, | 6 | ops::{self, RangeInclusive}, |
6 | }; | 7 | }; |
7 | 8 | ||
9 | use indexmap::IndexMap; | ||
8 | use itertools::Itertools; | 10 | use itertools::Itertools; |
9 | use rustc_hash::FxHashMap; | 11 | use rustc_hash::FxHashMap; |
12 | use test_utils::mark; | ||
10 | use text_edit::TextEditBuilder; | 13 | use text_edit::TextEditBuilder; |
11 | 14 | ||
12 | use crate::{ | 15 | use crate::{ |
@@ -106,42 +109,56 @@ pub enum InsertPosition<T> { | |||
106 | After(T), | 109 | After(T), |
107 | } | 110 | } |
108 | 111 | ||
112 | type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>; | ||
113 | |||
114 | #[derive(Debug)] | ||
109 | pub struct TreeDiff { | 115 | pub struct TreeDiff { |
110 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, | 116 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, |
117 | deletions: Vec<SyntaxElement>, | ||
118 | // the vec as well as the indexmap are both here to preserve order | ||
119 | insertions: FxIndexMap<SyntaxElement, Vec<SyntaxElement>>, | ||
111 | } | 120 | } |
112 | 121 | ||
113 | impl TreeDiff { | 122 | impl TreeDiff { |
114 | pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { | 123 | pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { |
124 | for (anchor, to) in self.insertions.iter() { | ||
125 | to.iter().for_each(|to| builder.insert(anchor.text_range().end(), to.to_string())); | ||
126 | } | ||
115 | for (from, to) in self.replacements.iter() { | 127 | for (from, to) in self.replacements.iter() { |
116 | builder.replace(from.text_range(), to.to_string()) | 128 | builder.replace(from.text_range(), to.to_string()) |
117 | } | 129 | } |
130 | for text_range in self.deletions.iter().map(SyntaxElement::text_range) { | ||
131 | builder.delete(text_range); | ||
132 | } | ||
118 | } | 133 | } |
119 | 134 | ||
120 | pub fn is_empty(&self) -> bool { | 135 | pub fn is_empty(&self) -> bool { |
121 | self.replacements.is_empty() | 136 | self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty() |
122 | } | 137 | } |
123 | } | 138 | } |
124 | 139 | ||
125 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. | 140 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. |
126 | /// | 141 | /// |
127 | /// Specifically, returns a map whose keys are descendants of `from` and values | 142 | /// Specifically, returns a structure that consists of a replacements, insertions and deletions |
128 | /// are descendants of `to`, such that `replace_descendants(from, map) == to`. | 143 | /// such that applying this map on `from` will result in `to`. |
129 | /// | 144 | /// |
130 | /// A trivial solution is a singleton map `{ from: to }`, but this function | 145 | /// This function tries to find a fine-grained diff. |
131 | /// tries to find a more fine-grained diff. | ||
132 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { | 146 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { |
133 | let mut buf = FxHashMap::default(); | 147 | let mut diff = TreeDiff { |
134 | // FIXME: this is both horrible inefficient and gives larger than | 148 | replacements: FxHashMap::default(), |
135 | // necessary diff. I bet there's a cool algorithm to diff trees properly. | 149 | insertions: FxIndexMap::default(), |
136 | go(&mut buf, from.clone().into(), to.clone().into()); | 150 | deletions: Vec::new(), |
137 | return TreeDiff { replacements: buf }; | 151 | }; |
138 | 152 | let (from, to) = (from.clone().into(), to.clone().into()); | |
139 | fn go( | 153 | |
140 | buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, | 154 | // FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly. |
141 | lhs: SyntaxElement, | 155 | if !syntax_element_eq(&from, &to) { |
142 | rhs: SyntaxElement, | 156 | go(&mut diff, from, to); |
143 | ) { | 157 | } |
144 | if lhs.kind() == rhs.kind() | 158 | return diff; |
159 | |||
160 | fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool { | ||
161 | lhs.kind() == rhs.kind() | ||
145 | && lhs.text_range().len() == rhs.text_range().len() | 162 | && lhs.text_range().len() == rhs.text_range().len() |
146 | && match (&lhs, &rhs) { | 163 | && match (&lhs, &rhs) { |
147 | (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { | 164 | (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { |
@@ -150,18 +167,47 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { | |||
150 | (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), | 167 | (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), |
151 | _ => false, | 168 | _ => false, |
152 | } | 169 | } |
153 | { | 170 | } |
154 | return; | 171 | |
155 | } | 172 | fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) { |
156 | if let (Some(lhs), Some(rhs)) = (lhs.as_node(), rhs.as_node()) { | 173 | let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) { |
157 | if lhs.children_with_tokens().count() == rhs.children_with_tokens().count() { | 174 | Some((lhs, rhs)) => (lhs, rhs), |
158 | for (lhs, rhs) in lhs.children_with_tokens().zip(rhs.children_with_tokens()) { | 175 | _ => { |
159 | go(buf, lhs, rhs) | 176 | mark::hit!(diff_node_token_replace); |
160 | } | 177 | diff.replacements.insert(lhs, rhs); |
161 | return; | 178 | return; |
162 | } | 179 | } |
180 | }; | ||
181 | |||
182 | let mut rhs_children = rhs.children_with_tokens(); | ||
183 | let mut lhs_children = lhs.children_with_tokens(); | ||
184 | let mut last_lhs = None; | ||
185 | loop { | ||
186 | let lhs_child = lhs_children.next(); | ||
187 | match (lhs_child.clone(), rhs_children.next()) { | ||
188 | (None, None) => break, | ||
189 | (None, Some(element)) => match last_lhs.clone() { | ||
190 | Some(prev) => { | ||
191 | mark::hit!(diff_insert); | ||
192 | diff.insertions.entry(prev).or_insert_with(Vec::new).push(element); | ||
193 | } | ||
194 | // first iteration, this means we got no anchor element to insert after | ||
195 | // therefor replace the parent node instead | ||
196 | None => { | ||
197 | mark::hit!(diff_replace_parent); | ||
198 | diff.replacements.insert(lhs.clone().into(), rhs.clone().into()); | ||
199 | break; | ||
200 | } | ||
201 | }, | ||
202 | (Some(element), None) => { | ||
203 | mark::hit!(diff_delete); | ||
204 | diff.deletions.push(element); | ||
205 | } | ||
206 | (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {} | ||
207 | (Some(lhs_ele), Some(rhs_ele)) => go(diff, lhs_ele, rhs_ele), | ||
208 | } | ||
209 | last_lhs = lhs_child.or(last_lhs); | ||
163 | } | 210 | } |
164 | buf.insert(lhs, rhs); | ||
165 | } | 211 | } |
166 | } | 212 | } |
167 | 213 | ||
@@ -404,3 +450,322 @@ fn to_green_element(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row | |||
404 | NodeOrToken::Token(it) => it.green().clone().into(), | 450 | NodeOrToken::Token(it) => it.green().clone().into(), |
405 | } | 451 | } |
406 | } | 452 | } |
453 | |||
454 | #[cfg(test)] | ||
455 | mod tests { | ||
456 | use expect_test::{expect, Expect}; | ||
457 | use itertools::Itertools; | ||
458 | use parser::SyntaxKind; | ||
459 | use test_utils::mark; | ||
460 | use text_edit::TextEdit; | ||
461 | |||
462 | use crate::{AstNode, SyntaxElement}; | ||
463 | |||
464 | #[test] | ||
465 | fn replace_node_token() { | ||
466 | mark::check!(diff_node_token_replace); | ||
467 | check_diff( | ||
468 | r#"use node;"#, | ||
469 | r#"ident"#, | ||
470 | expect![[r#" | ||
471 | insertions: | ||
472 | |||
473 | |||
474 | |||
475 | replacements: | ||
476 | |||
477 | Line 0: Token([email protected] "use") -> ident | ||
478 | |||
479 | deletions: | ||
480 | |||
481 | Line 1: " " | ||
482 | Line 1: node | ||
483 | Line 1: ; | ||
484 | "#]], | ||
485 | ); | ||
486 | } | ||
487 | |||
488 | #[test] | ||
489 | fn insert() { | ||
490 | mark::check!(diff_insert); | ||
491 | check_diff( | ||
492 | r#"use foo;"#, | ||
493 | r#"use foo; | ||
494 | use bar;"#, | ||
495 | expect![[r#" | ||
496 | insertions: | ||
497 | |||
498 | Line 0: Node([email protected]) | ||
499 | -> "\n" | ||
500 | -> use bar; | ||
501 | |||
502 | replacements: | ||
503 | |||
504 | |||
505 | |||
506 | deletions: | ||
507 | |||
508 | |||
509 | "#]], | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn replace_parent() { | ||
515 | mark::check!(diff_replace_parent); | ||
516 | check_diff( | ||
517 | r#""#, | ||
518 | r#"use foo::bar;"#, | ||
519 | expect![[r#" | ||
520 | insertions: | ||
521 | |||
522 | |||
523 | |||
524 | replacements: | ||
525 | |||
526 | Line 0: Node([email protected]) -> use foo::bar; | ||
527 | |||
528 | deletions: | ||
529 | |||
530 | |||
531 | "#]], | ||
532 | ); | ||
533 | } | ||
534 | |||
535 | #[test] | ||
536 | fn delete() { | ||
537 | mark::check!(diff_delete); | ||
538 | check_diff( | ||
539 | r#"use foo; | ||
540 | use bar;"#, | ||
541 | r#"use foo;"#, | ||
542 | expect![[r#" | ||
543 | insertions: | ||
544 | |||
545 | |||
546 | |||
547 | replacements: | ||
548 | |||
549 | |||
550 | |||
551 | deletions: | ||
552 | |||
553 | Line 1: "\n " | ||
554 | Line 2: use bar; | ||
555 | "#]], | ||
556 | ); | ||
557 | } | ||
558 | |||
559 | #[test] | ||
560 | fn insert_use() { | ||
561 | check_diff( | ||
562 | r#" | ||
563 | use expect_test::{expect, Expect}; | ||
564 | |||
565 | use crate::AstNode; | ||
566 | "#, | ||
567 | r#" | ||
568 | use expect_test::{expect, Expect}; | ||
569 | use text_edit::TextEdit; | ||
570 | |||
571 | use crate::AstNode; | ||
572 | "#, | ||
573 | expect![[r#" | ||
574 | insertions: | ||
575 | |||
576 | Line 4: Token([email protected] "\n") | ||
577 | -> use crate::AstNode; | ||
578 | -> "\n" | ||
579 | |||
580 | replacements: | ||
581 | |||
582 | Line 2: Token([email protected] "\n\n") -> "\n" | ||
583 | Line 4: Token([email protected] "crate") -> text_edit | ||
584 | Line 4: Token([email protected] "AstNode") -> TextEdit | ||
585 | Line 4: Token([email protected] "\n") -> "\n\n" | ||
586 | |||
587 | deletions: | ||
588 | |||
589 | |||
590 | "#]], | ||
591 | ) | ||
592 | } | ||
593 | |||
594 | #[test] | ||
595 | fn remove_use() { | ||
596 | check_diff( | ||
597 | r#" | ||
598 | use expect_test::{expect, Expect}; | ||
599 | use text_edit::TextEdit; | ||
600 | |||
601 | use crate::AstNode; | ||
602 | "#, | ||
603 | r#" | ||
604 | use expect_test::{expect, Expect}; | ||
605 | |||
606 | use crate::AstNode; | ||
607 | "#, | ||
608 | expect![[r#" | ||
609 | insertions: | ||
610 | |||
611 | |||
612 | |||
613 | replacements: | ||
614 | |||
615 | Line 2: Token([email protected] "\n") -> "\n\n" | ||
616 | Line 3: Node([email protected]) -> crate | ||
617 | Line 3: Token([email protected] "TextEdit") -> AstNode | ||
618 | Line 3: Token([email protected] "\n\n") -> "\n" | ||
619 | |||
620 | deletions: | ||
621 | |||
622 | Line 4: use crate::AstNode; | ||
623 | Line 5: "\n" | ||
624 | "#]], | ||
625 | ) | ||
626 | } | ||
627 | |||
628 | #[test] | ||
629 | fn merge_use() { | ||
630 | check_diff( | ||
631 | r#" | ||
632 | use std::{ | ||
633 | fmt, | ||
634 | hash::BuildHasherDefault, | ||
635 | ops::{self, RangeInclusive}, | ||
636 | }; | ||
637 | "#, | ||
638 | r#" | ||
639 | use std::fmt; | ||
640 | use std::hash::BuildHasherDefault; | ||
641 | use std::ops::{self, RangeInclusive}; | ||
642 | "#, | ||
643 | expect![[r#" | ||
644 | insertions: | ||
645 | |||
646 | Line 2: Node([email protected]) | ||
647 | -> :: | ||
648 | -> fmt | ||
649 | Line 6: Token([email protected] "\n") | ||
650 | -> use std::hash::BuildHasherDefault; | ||
651 | -> "\n" | ||
652 | -> use std::ops::{self, RangeInclusive}; | ||
653 | -> "\n" | ||
654 | |||
655 | replacements: | ||
656 | |||
657 | Line 2: Token([email protected] "std") -> std | ||
658 | |||
659 | deletions: | ||
660 | |||
661 | Line 2: :: | ||
662 | Line 2: { | ||
663 | fmt, | ||
664 | hash::BuildHasherDefault, | ||
665 | ops::{self, RangeInclusive}, | ||
666 | } | ||
667 | "#]], | ||
668 | ) | ||
669 | } | ||
670 | |||
671 | #[test] | ||
672 | fn early_return_assist() { | ||
673 | check_diff( | ||
674 | r#" | ||
675 | fn main() { | ||
676 | if let Ok(x) = Err(92) { | ||
677 | foo(x); | ||
678 | } | ||
679 | } | ||
680 | "#, | ||
681 | r#" | ||
682 | fn main() { | ||
683 | let x = match Err(92) { | ||
684 | Ok(it) => it, | ||
685 | _ => return, | ||
686 | }; | ||
687 | foo(x); | ||
688 | } | ||
689 | "#, | ||
690 | expect![[r#" | ||
691 | insertions: | ||
692 | |||
693 | Line 3: Node([email protected]) | ||
694 | -> " " | ||
695 | -> match Err(92) { | ||
696 | Ok(it) => it, | ||
697 | _ => return, | ||
698 | } | ||
699 | -> ; | ||
700 | Line 5: Token([email protected] "}") | ||
701 | -> "\n" | ||
702 | -> } | ||
703 | |||
704 | replacements: | ||
705 | |||
706 | Line 3: Token([email protected] "if") -> let | ||
707 | Line 3: Token([email protected] "let") -> x | ||
708 | Line 3: Node([email protected]) -> = | ||
709 | Line 5: Token([email protected] "\n") -> "\n " | ||
710 | Line 5: Token([email protected] "}") -> foo(x); | ||
711 | |||
712 | deletions: | ||
713 | |||
714 | Line 3: " " | ||
715 | Line 3: Ok(x) | ||
716 | Line 3: " " | ||
717 | Line 3: = | ||
718 | Line 3: " " | ||
719 | Line 3: Err(92) | ||
720 | "#]], | ||
721 | ) | ||
722 | } | ||
723 | |||
724 | fn check_diff(from: &str, to: &str, expected_diff: Expect) { | ||
725 | let from_node = crate::SourceFile::parse(from).tree().syntax().clone(); | ||
726 | let to_node = crate::SourceFile::parse(to).tree().syntax().clone(); | ||
727 | let diff = super::diff(&from_node, &to_node); | ||
728 | |||
729 | let line_number = | ||
730 | |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count(); | ||
731 | |||
732 | let fmt_syntax = |syn: &SyntaxElement| match syn.kind() { | ||
733 | SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()), | ||
734 | _ => format!("{}", syn), | ||
735 | }; | ||
736 | |||
737 | let insertions = diff.insertions.iter().format_with("\n", |(k, v), f| { | ||
738 | f(&format!( | ||
739 | "Line {}: {:?}\n-> {}", | ||
740 | line_number(k), | ||
741 | k, | ||
742 | v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v))) | ||
743 | )) | ||
744 | }); | ||
745 | |||
746 | let replacements = diff | ||
747 | .replacements | ||
748 | .iter() | ||
749 | .sorted_by_key(|(syntax, _)| syntax.text_range().start()) | ||
750 | .format_with("\n", |(k, v), f| { | ||
751 | f(&format!("Line {}: {:?} -> {}", line_number(k), k, fmt_syntax(v))) | ||
752 | }); | ||
753 | |||
754 | let deletions = diff | ||
755 | .deletions | ||
756 | .iter() | ||
757 | .format_with("\n", |v, f| f(&format!("Line {}: {}", line_number(v), &fmt_syntax(v)))); | ||
758 | |||
759 | let actual = format!( | ||
760 | "insertions:\n\n{}\n\nreplacements:\n\n{}\n\ndeletions:\n\n{}\n", | ||
761 | insertions, replacements, deletions | ||
762 | ); | ||
763 | expected_diff.assert_eq(&actual); | ||
764 | |||
765 | let mut from = from.to_owned(); | ||
766 | let mut text_edit = TextEdit::builder(); | ||
767 | diff.into_text_edit(&mut text_edit); | ||
768 | text_edit.finish().apply(&mut from); | ||
769 | assert_eq!(&*from, to, "diff did not turn `from` to `to`"); | ||
770 | } | ||
771 | } | ||
diff --git a/editors/code/package.json b/editors/code/package.json index 4bd3117fc..af845d7bc 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -929,6 +929,10 @@ | |||
929 | { | 929 | { |
930 | "id": "consuming", | 930 | "id": "consuming", |
931 | "description": "Style for non-Copy lvalues consumed by method/function call" | 931 | "description": "Style for non-Copy lvalues consumed by method/function call" |
932 | }, | ||
933 | { | ||
934 | "id": "callable", | ||
935 | "description": "Style for variables/parameters that can be used in call expressions" | ||
932 | } | 936 | } |
933 | ], | 937 | ], |
934 | "semanticTokenScopes": [ | 938 | "semanticTokenScopes": [ |