aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/code_model.rs25
-rw-r--r--crates/ide/Cargo.toml2
-rw-r--r--crates/ide/src/diagnostics.rs2
-rw-r--r--crates/ide/src/syntax_highlighting.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html19
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs19
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs1
-rw-r--r--crates/syntax/Cargo.toml4
-rw-r--r--crates/syntax/src/algo.rs417
11 files changed, 464 insertions, 32 deletions
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]
13either = "1.5.3" 13either = "1.5.3"
14indexmap = "1.3.2" 14indexmap = "1.4.0"
15itertools = "0.9.0" 15itertools = "0.9.0"
16log = "0.4.8" 16log = "0.4.8"
17rustc-hash = "1.1.0" 17rustc-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() {
613pub struct Foo { pub a: i32, pub b: i32 } 613pub struct Foo { pub a: i32, pub b: i32 }
614"#, 614"#,
615 r#" 615 r#"
616fn {a:42, b: ()} {} 616fn some(, b: ()} {}
617fn items() {} 617fn items() {}
618fn here() {} 618fn 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
69impl HighlightTag { 70impl 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">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</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">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</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">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</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">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> 110 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</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">&lt;</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">-&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">&gt;</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">&gt;</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">&gt;</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">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span> 181<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</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
21pub 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
22struct Foo { 33struct 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
87use ops::Fn;
88fn baz<F: Fn() -> ()>(f: F) {
89 f()
90}
91
76macro_rules! def_fn { 92macro_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
136enum Option<T> { 155enum 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" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
20indexmap = "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"] }
26stdx = { path = "../stdx", version = "0.0.0" } 27stdx = { path = "../stdx", version = "0.0.0" }
27text_edit = { path = "../text_edit", version = "0.0.0" } 28text_edit = { path = "../text_edit", version = "0.0.0" }
28parser = { path = "../parser", version = "0.0.0" } 29parser = { path = "../parser", version = "0.0.0" }
30test_utils = { path = "../test_utils" }
29 31
30[dev-dependencies] 32[dev-dependencies]
31walkdir = "2.3.1" 33walkdir = "2.3.1"
32rayon = "1" 34rayon = "1"
33expect-test = "1.0" 35expect-test = "1.0"
34
35test_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
3use std::{ 3use std::{
4 fmt, 4 fmt,
5 hash::BuildHasherDefault,
5 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
6}; 7};
7 8
9use indexmap::IndexMap;
8use itertools::Itertools; 10use itertools::Itertools;
9use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
12use test_utils::mark;
10use text_edit::TextEditBuilder; 13use text_edit::TextEditBuilder;
11 14
12use crate::{ 15use crate::{
@@ -106,42 +109,56 @@ pub enum InsertPosition<T> {
106 After(T), 109 After(T),
107} 110}
108 111
112type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
113
114#[derive(Debug)]
109pub struct TreeDiff { 115pub 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
113impl TreeDiff { 122impl 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.
132pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { 146pub 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)]
455mod 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;
494use 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#"
563use expect_test::{expect, Expect};
564
565use crate::AstNode;
566"#,
567 r#"
568use expect_test::{expect, Expect};
569use text_edit::TextEdit;
570
571use 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#"
598use expect_test::{expect, Expect};
599use text_edit::TextEdit;
600
601use crate::AstNode;
602"#,
603 r#"
604use expect_test::{expect, Expect};
605
606use 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#"
632use std::{
633 fmt,
634 hash::BuildHasherDefault,
635 ops::{self, RangeInclusive},
636};
637"#,
638 r#"
639use std::fmt;
640use std::hash::BuildHasherDefault;
641use 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#"
675fn main() {
676 if let Ok(x) = Err(92) {
677 foo(x);
678 }
679}
680 "#,
681 r#"
682fn 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}