aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ide/src/display/navigation_target.rs8
-rw-r--r--crates/ide/src/references/rename.rs103
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs191
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs51
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html32
-rw-r--r--crates/ide_assists/src/ast_transform.rs33
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs10
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs516
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs208
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs2
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs8
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs20
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs34
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests.rs5
-rw-r--r--crates/ide_assists/src/tests/generated.rs41
-rw-r--r--crates/ide_assists/src/utils.rs3
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs2
-rw-r--r--crates/ide_completion/src/item.rs8
-rw-r--r--crates/ide_completion/src/lib.rs2
-rw-r--r--crates/ide_completion/src/test_utils.rs5
-rw-r--r--crates/ide_db/src/helpers.rs3
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs587
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs42
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs309
-rw-r--r--crates/ide_db/src/search.rs58
-rw-r--r--crates/project_model/src/build_data.rs2
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/build.rs62
-rw-r--r--crates/rust-analyzer/src/config.rs5
-rw-r--r--crates/rust-analyzer/src/main_loop.rs6
-rw-r--r--crates/stdx/src/lib.rs15
-rw-r--r--crates/syntax/src/algo.rs19
-rw-r--r--crates/syntax/src/ast/make.rs27
-rw-r--r--crates/syntax/src/ast/node_ext.rs22
-rw-r--r--crates/syntax/src/ted.rs17
40 files changed, 1616 insertions, 876 deletions
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 364be260c..2079c22a3 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -20,7 +20,7 @@ use syntax::{
20 20
21use crate::FileSymbol; 21use crate::FileSymbol;
22 22
23/// `NavigationTarget` represents and element in the editor's UI which you can 23/// `NavigationTarget` represents an element in the editor's UI which you can
24/// click on to navigate to a particular piece of code. 24/// click on to navigate to a particular piece of code.
25/// 25///
26/// Typically, a `NavigationTarget` corresponds to some element in the source 26/// Typically, a `NavigationTarget` corresponds to some element in the source
@@ -35,12 +35,10 @@ pub struct NavigationTarget {
35 /// Clients should use this range to answer "is the cursor inside the 35 /// Clients should use this range to answer "is the cursor inside the
36 /// element?" question. 36 /// element?" question.
37 pub full_range: TextRange, 37 pub full_range: TextRange,
38 /// A "most interesting" range withing the `full_range`. 38 /// A "most interesting" range within the `full_range`.
39 /// 39 ///
40 /// Typically, `full_range` is the whole syntax node, including doc 40 /// Typically, `full_range` is the whole syntax node, including doc
41 /// comments, and `focus_range` is the range of the identifier. "Most 41 /// comments, and `focus_range` is the range of the identifier.
42 /// interesting" range within the full range, typically the range of
43 /// identifier.
44 /// 42 ///
45 /// Clients should place the cursor on this range when navigating to this target. 43 /// Clients should place the cursor on this range when navigating to this target.
46 pub focus_range: Option<TextRange>, 44 pub focus_range: Option<TextRange>,
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 2408a0181..175e7a31d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -50,6 +50,17 @@ pub(crate) fn prepare_rename(
50 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
51 let source_file = sema.parse(position.file_id); 51 let source_file = sema.parse(position.file_id);
52 let syntax = source_file.syntax(); 52 let syntax = source_file.syntax();
53
54 let def = find_definition(&sema, syntax, position)?;
55 match def {
56 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
57 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"),
58 _ => {}
59 };
60 let nav =
61 def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?;
62 nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?;
63
53 let name_like = sema 64 let name_like = sema
54 .find_node_at_offset_with_descend(&syntax, position.offset) 65 .find_node_at_offset_with_descend(&syntax, position.offset)
55 .ok_or_else(|| format_err!("No references found at position"))?; 66 .ok_or_else(|| format_err!("No references found at position"))?;
@@ -507,7 +518,8 @@ fn source_edit_from_def(
507 def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?; 518 def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?;
508 519
509 let mut replacement_text = String::new(); 520 let mut replacement_text = String::new();
510 let mut repl_range = nav.focus_or_full_range(); 521 let mut repl_range =
522 nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?;
511 if let Definition::Local(local) = def { 523 if let Definition::Local(local) = def {
512 if let Either::Left(pat) = local.source(sema.db).value { 524 if let Either::Left(pat) = local.source(sema.db).value {
513 if matches!( 525 if matches!(
@@ -626,6 +638,49 @@ foo!(Foo$0);",
626 } 638 }
627 639
628 #[test] 640 #[test]
641 fn test_prepare_rename_tuple_field() {
642 check_prepare(
643 r#"
644struct Foo(i32);
645
646fn baz() {
647 let mut x = Foo(4);
648 x.0$0 = 5;
649}
650"#,
651 expect![[r#"No identifier available to rename"#]],
652 );
653 }
654
655 #[test]
656 fn test_prepare_rename_builtin() {
657 check_prepare(
658 r#"
659fn foo() {
660 let x: i32$0 = 0;
661}
662"#,
663 expect![[r#"Cannot rename builtin type"#]],
664 );
665 }
666
667 #[test]
668 fn test_prepare_rename_self() {
669 check_prepare(
670 r#"
671struct Foo {}
672
673impl Foo {
674 fn foo(self) -> Self$0 {
675 self
676 }
677}
678"#,
679 expect![[r#"Cannot rename `Self`"#]],
680 );
681 }
682
683 #[test]
629 fn test_rename_to_underscore() { 684 fn test_rename_to_underscore() {
630 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); 685 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
631 } 686 }
@@ -1787,4 +1842,50 @@ fn foo() {
1787"#, 1842"#,
1788 ) 1843 )
1789 } 1844 }
1845
1846 #[test]
1847 fn test_rename_tuple_field() {
1848 check(
1849 "foo",
1850 r#"
1851struct Foo(i32);
1852
1853fn baz() {
1854 let mut x = Foo(4);
1855 x.0$0 = 5;
1856}
1857"#,
1858 "error: No identifier available to rename",
1859 );
1860 }
1861
1862 #[test]
1863 fn test_rename_builtin() {
1864 check(
1865 "foo",
1866 r#"
1867fn foo() {
1868 let x: i32$0 = 0;
1869}
1870"#,
1871 "error: Cannot rename builtin type",
1872 );
1873 }
1874
1875 #[test]
1876 fn test_rename_self() {
1877 check(
1878 "foo",
1879 r#"
1880struct Foo {}
1881
1882impl Foo {
1883 fn foo(self) -> Self$0 {
1884 self
1885 }
1886}
1887"#,
1888 "error: Cannot rename `Self`",
1889 );
1890 }
1790} 1891}
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 18552459b..b586dcc17 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -1,6 +1,6 @@
1//! Computes color for a single element. 1//! Computes color for a single element.
2 2
3use hir::{AsAssocItem, AssocItemContainer, Semantics, VariantDef}; 3use hir::{AsAssocItem, Semantics};
4use ide_db::{ 4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase, SymbolKind, 6 RootDatabase, SymbolKind,
@@ -45,28 +45,26 @@ pub(super) fn element(
45 }; 45 };
46 46
47 match name_kind { 47 match name_kind {
48 Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(), 48 Some(NameClass::ExternCrate(_)) => SymbolKind::Module.into(),
49 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, 49 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
50 Some(NameClass::ConstReference(def)) => highlight_def(db, def), 50 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
51 Some(NameClass::PatFieldShorthand { field_ref, .. }) => { 51 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
52 let mut h = HlTag::Symbol(SymbolKind::Field).into(); 52 let mut h = HlTag::Symbol(SymbolKind::Field).into();
53 if let Definition::Field(field) = field_ref { 53 if let Definition::Field(field) = field_ref {
54 if let VariantDef::Union(_) = field.parent_def(db) { 54 if let hir::VariantDef::Union(_) = field.parent_def(db) {
55 h |= HlMod::Unsafe; 55 h |= HlMod::Unsafe;
56 } 56 }
57 } 57 }
58
59 h 58 h
60 } 59 }
61 None => highlight_name_by_syntax(name) | HlMod::Definition, 60 None => highlight_name_by_syntax(name) | HlMod::Definition,
62 } 61 }
63 } 62 }
64
65 // Highlight references like the definitions they resolve to 63 // Highlight references like the definitions they resolve to
66 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { 64 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
67 // even though we track whether we are in an attribute or not we still need this special case 65 // even though we track whether we are in an attribute or not we still need this special case
68 // as otherwise we would emit unresolved references for name refs inside attributes 66 // as otherwise we would emit unresolved references for name refs inside attributes
69 Highlight::from(HlTag::Symbol(SymbolKind::Function)) 67 SymbolKind::Function.into()
70 } 68 }
71 NAME_REF => { 69 NAME_REF => {
72 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 70 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
@@ -74,7 +72,7 @@ pub(super) fn element(
74 let is_self = name_ref.self_token().is_some(); 72 let is_self = name_ref.self_token().is_some();
75 let h = match NameRefClass::classify(sema, &name_ref) { 73 let h = match NameRefClass::classify(sema, &name_ref) {
76 Some(name_kind) => match name_kind { 74 Some(name_kind) => match name_kind {
77 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), 75 NameRefClass::ExternCrate(_) => SymbolKind::Module.into(),
78 NameRefClass::Definition(def) => { 76 NameRefClass::Definition(def) => {
79 if let Definition::Local(local) = &def { 77 if let Definition::Local(local) = &def {
80 if let Some(name) = local.name(db) { 78 if let Some(name) = local.name(db) {
@@ -95,7 +93,7 @@ pub(super) fn element(
95 if let Some(parent) = name_ref.syntax().parent() { 93 if let Some(parent) = name_ref.syntax().parent() {
96 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { 94 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
97 if let Definition::Field(field) = def { 95 if let Definition::Field(field) = def {
98 if let VariantDef::Union(_) = field.parent_def(db) { 96 if let hir::VariantDef::Union(_) = field.parent_def(db) {
99 h |= HlMod::Unsafe; 97 h |= HlMod::Unsafe;
100 } 98 }
101 } 99 }
@@ -104,9 +102,7 @@ pub(super) fn element(
104 102
105 h 103 h
106 } 104 }
107 NameRefClass::FieldShorthand { .. } => { 105 NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
108 HlTag::Symbol(SymbolKind::Field).into()
109 }
110 }, 106 },
111 None if syntactic_name_ref_highlighting => { 107 None if syntactic_name_ref_highlighting => {
112 highlight_name_ref_by_syntax(name_ref, sema) 108 highlight_name_ref_by_syntax(name_ref, sema)
@@ -114,7 +110,7 @@ pub(super) fn element(
114 None => HlTag::UnresolvedReference.into(), 110 None => HlTag::UnresolvedReference.into(),
115 }; 111 };
116 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self { 112 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self {
117 HlTag::Symbol(SymbolKind::SelfParam).into() 113 SymbolKind::SelfParam.into()
118 } else { 114 } else {
119 h 115 h
120 } 116 }
@@ -135,7 +131,7 @@ pub(super) fn element(
135 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), 131 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
136 BYTE => HlTag::ByteLiteral.into(), 132 BYTE => HlTag::ByteLiteral.into(),
137 CHAR => HlTag::CharLiteral.into(), 133 CHAR => HlTag::CharLiteral.into(),
138 QUESTION => Highlight::new(HlTag::Operator(HlOperator::Other)) | HlMod::ControlFlow, 134 QUESTION => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
139 LIFETIME => { 135 LIFETIME => {
140 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); 136 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
141 137
@@ -143,44 +139,31 @@ pub(super) fn element(
143 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, 139 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
144 None => match NameRefClass::classify_lifetime(sema, &lifetime) { 140 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
145 Some(NameRefClass::Definition(def)) => highlight_def(db, def), 141 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
146 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), 142 _ => SymbolKind::LifetimeParam.into(),
147 }, 143 },
148 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition, 144 _ => Highlight::from(SymbolKind::LifetimeParam) | HlMod::Definition,
149 } 145 }
150 } 146 }
151 p if p.is_punct() => match p { 147 p if p.is_punct() => match p {
152 T![&] if element.parent().and_then(ast::BinExpr::cast).is_some() => { 148 T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
153 HlTag::Operator(HlOperator::Bitwise).into()
154 }
155 T![&] => { 149 T![&] => {
156 let h = HlTag::Operator(HlOperator::Other).into(); 150 let h = HlTag::Operator(HlOperator::Other).into();
157 let is_unsafe = element 151 let is_unsafe = element
158 .parent() 152 .parent()
159 .and_then(ast::RefExpr::cast) 153 .and_then(ast::RefExpr::cast)
160 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) 154 .map_or(false, |ref_expr| sema.is_unsafe_ref_expr(&ref_expr));
161 .unwrap_or(false);
162 if is_unsafe { 155 if is_unsafe {
163 h | HlMod::Unsafe 156 h | HlMod::Unsafe
164 } else { 157 } else {
165 h 158 h
166 } 159 }
167 } 160 }
168 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => { 161 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlOperator::Other.into(),
169 HlTag::Operator(HlOperator::Other).into() 162 T![!] if parent_matches::<ast::MacroCall>(&element) => SymbolKind::Macro.into(),
170 } 163 T![!] if parent_matches::<ast::NeverType>(&element) => HlTag::BuiltinType.into(),
171 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { 164 T![!] if parent_matches::<ast::PrefixExpr>(&element) => HlOperator::Logical.into(),
172 HlTag::Symbol(SymbolKind::Macro).into() 165 T![*] if parent_matches::<ast::PtrType>(&element) => HlTag::Keyword.into(),
173 } 166 T![*] if parent_matches::<ast::PrefixExpr>(&element) => {
174 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
175 HlTag::BuiltinType.into()
176 }
177 T![!] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
178 HlTag::Operator(HlOperator::Logical).into()
179 }
180 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
181 HlTag::Keyword.into()
182 }
183 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
184 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; 167 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
185 168
186 let expr = prefix_expr.expr()?; 169 let expr = prefix_expr.expr()?;
@@ -188,12 +171,12 @@ pub(super) fn element(
188 if ty.is_raw_ptr() { 171 if ty.is_raw_ptr() {
189 HlTag::Operator(HlOperator::Other) | HlMod::Unsafe 172 HlTag::Operator(HlOperator::Other) | HlMod::Unsafe
190 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { 173 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
191 HlTag::Operator(HlOperator::Other).into() 174 HlOperator::Other.into()
192 } else { 175 } else {
193 HlTag::Punctuation(HlPunct::Other).into() 176 HlPunct::Other.into()
194 } 177 }
195 } 178 }
196 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 179 T![-] if parent_matches::<ast::PrefixExpr>(&element) => {
197 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; 180 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
198 181
199 let expr = prefix_expr.expr()?; 182 let expr = prefix_expr.expr()?;
@@ -203,41 +186,31 @@ pub(super) fn element(
203 } 186 }
204 .into() 187 .into()
205 } 188 }
206 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 189 _ if parent_matches::<ast::PrefixExpr>(&element) => HlOperator::Other.into(),
207 HlTag::Operator(HlOperator::Other).into()
208 }
209 T![+] | T![-] | T![*] | T![/] | T![+=] | T![-=] | T![*=] | T![/=] 190 T![+] | T![-] | T![*] | T![/] | T![+=] | T![-=] | T![*=] | T![/=]
210 if element.parent().and_then(ast::BinExpr::cast).is_some() => 191 if parent_matches::<ast::BinExpr>(&element) =>
211 { 192 {
212 HlTag::Operator(HlOperator::Arithmetic).into() 193 HlOperator::Arithmetic.into()
213 } 194 }
214 T![|] | T![&] | T![!] | T![^] | T![|=] | T![&=] | T![^=] 195 T![|] | T![&] | T![!] | T![^] | T![|=] | T![&=] | T![^=]
215 if element.parent().and_then(ast::BinExpr::cast).is_some() => 196 if parent_matches::<ast::BinExpr>(&element) =>
216 { 197 {
217 HlTag::Operator(HlOperator::Bitwise).into() 198 HlOperator::Bitwise.into()
218 } 199 }
219 T![&&] | T![||] if element.parent().and_then(ast::BinExpr::cast).is_some() => { 200 T![&&] | T![||] if parent_matches::<ast::BinExpr>(&element) => {
220 HlTag::Operator(HlOperator::Logical).into() 201 HlOperator::Logical.into()
221 } 202 }
222 T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=] 203 T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=]
223 if element.parent().and_then(ast::BinExpr::cast).is_some() => 204 if parent_matches::<ast::BinExpr>(&element) =>
224 { 205 {
225 HlTag::Operator(HlOperator::Comparison).into() 206 HlOperator::Comparison.into()
226 }
227 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
228 HlTag::Operator(HlOperator::Other).into()
229 }
230 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
231 HlTag::Operator(HlOperator::Other).into()
232 } 207 }
233 _ if element.parent().and_then(ast::RangePat::cast).is_some() => { 208 _ if parent_matches::<ast::BinExpr>(&element) => HlOperator::Other.into(),
234 HlTag::Operator(HlOperator::Other).into() 209 _ if parent_matches::<ast::RangeExpr>(&element) => HlOperator::Other.into(),
235 } 210 _ if parent_matches::<ast::RangePat>(&element) => HlOperator::Other.into(),
236 _ if element.parent().and_then(ast::RestPat::cast).is_some() => { 211 _ if parent_matches::<ast::RestPat>(&element) => HlOperator::Other.into(),
237 HlTag::Operator(HlOperator::Other).into() 212 _ if parent_matches::<ast::Attr>(&element) => HlTag::Attribute.into(),
238 } 213 kind => match kind {
239 _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(),
240 kind => HlTag::Punctuation(match kind {
241 T!['['] | T![']'] => HlPunct::Bracket, 214 T!['['] | T![']'] => HlPunct::Bracket,
242 T!['{'] | T!['}'] => HlPunct::Brace, 215 T!['{'] | T!['}'] => HlPunct::Brace,
243 T!['('] | T![')'] => HlPunct::Parenthesis, 216 T!['('] | T![')'] => HlPunct::Parenthesis,
@@ -247,22 +220,24 @@ pub(super) fn element(
247 T![;] => HlPunct::Semi, 220 T![;] => HlPunct::Semi,
248 T![.] => HlPunct::Dot, 221 T![.] => HlPunct::Dot,
249 _ => HlPunct::Other, 222 _ => HlPunct::Other,
250 }) 223 }
251 .into(), 224 .into(),
252 }, 225 },
253 226
254 k if k.is_keyword() => { 227 k if k.is_keyword() => {
255 let h = Highlight::new(HlTag::Keyword); 228 let h = Highlight::new(HlTag::Keyword);
256 match k { 229 match k {
257 T![break] 230 T![await]
231 | T![break]
258 | T![continue] 232 | T![continue]
259 | T![else] 233 | T![else]
260 | T![if] 234 | T![if]
235 | T![in]
261 | T![loop] 236 | T![loop]
262 | T![match] 237 | T![match]
263 | T![return] 238 | T![return]
264 | T![while] 239 | T![while]
265 | T![in] => h | HlMod::ControlFlow, 240 | T![yield] => h | HlMod::ControlFlow,
266 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, 241 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
267 T![unsafe] => h | HlMod::Unsafe, 242 T![unsafe] => h | HlMod::Unsafe,
268 T![true] | T![false] => HlTag::BoolLiteral.into(), 243 T![true] | T![false] => HlTag::BoolLiteral.into(),
@@ -301,7 +276,6 @@ pub(super) fn element(
301 hash((name, shadow_count)) 276 hash((name, shadow_count))
302 } 277 }
303} 278}
304
305fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { 279fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
306 match def { 280 match def {
307 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro), 281 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro),
@@ -312,17 +286,22 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
312 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); 286 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
313 if let Some(item) = func.as_assoc_item(db) { 287 if let Some(item) = func.as_assoc_item(db) {
314 h |= HlMod::Associated; 288 h |= HlMod::Associated;
315 if func.self_param(db).is_none() { 289 match func.self_param(db) {
316 h |= HlMod::Static 290 Some(sp) => {
291 if let hir::Access::Exclusive = sp.access(db) {
292 h |= HlMod::Mutable;
293 }
294 }
295 None => h |= HlMod::Static,
317 } 296 }
318 297
319 match item.container(db) { 298 match item.container(db) {
320 AssocItemContainer::Impl(i) => { 299 hir::AssocItemContainer::Impl(i) => {
321 if i.trait_(db).is_some() { 300 if i.trait_(db).is_some() {
322 h |= HlMod::Trait; 301 h |= HlMod::Trait;
323 } 302 }
324 } 303 }
325 AssocItemContainer::Trait(_t) => { 304 hir::AssocItemContainer::Trait(_t) => {
326 h |= HlMod::Trait; 305 h |= HlMod::Trait;
327 } 306 }
328 } 307 }
@@ -342,12 +321,12 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
342 if let Some(item) = konst.as_assoc_item(db) { 321 if let Some(item) = konst.as_assoc_item(db) {
343 h |= HlMod::Associated; 322 h |= HlMod::Associated;
344 match item.container(db) { 323 match item.container(db) {
345 AssocItemContainer::Impl(i) => { 324 hir::AssocItemContainer::Impl(i) => {
346 if i.trait_(db).is_some() { 325 if i.trait_(db).is_some() {
347 h |= HlMod::Trait; 326 h |= HlMod::Trait;
348 } 327 }
349 } 328 }
350 AssocItemContainer::Trait(_t) => { 329 hir::AssocItemContainer::Trait(_t) => {
351 h |= HlMod::Trait; 330 h |= HlMod::Trait;
352 } 331 }
353 } 332 }
@@ -361,12 +340,12 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
361 if let Some(item) = type_.as_assoc_item(db) { 340 if let Some(item) = type_.as_assoc_item(db) {
362 h |= HlMod::Associated; 341 h |= HlMod::Associated;
363 match item.container(db) { 342 match item.container(db) {
364 AssocItemContainer::Impl(i) => { 343 hir::AssocItemContainer::Impl(i) => {
365 if i.trait_(db).is_some() { 344 if i.trait_(db).is_some() {
366 h |= HlMod::Trait; 345 h |= HlMod::Trait;
367 } 346 }
368 } 347 }
369 AssocItemContainer::Trait(_t) => { 348 hir::AssocItemContainer::Trait(_t) => {
370 h |= HlMod::Trait; 349 h |= HlMod::Trait;
371 } 350 }
372 } 351 }
@@ -425,7 +404,7 @@ fn highlight_method_call(
425 method_call: &ast::MethodCallExpr, 404 method_call: &ast::MethodCallExpr,
426) -> Option<Highlight> { 405) -> Option<Highlight> {
427 let func = sema.resolve_method_call(&method_call)?; 406 let func = sema.resolve_method_call(&method_call)?;
428 let mut h = HlTag::Symbol(SymbolKind::Function).into(); 407 let mut h = SymbolKind::Function.into();
429 h |= HlMod::Associated; 408 h |= HlMod::Associated;
430 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 409 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
431 h |= HlMod::Unsafe; 410 h |= HlMod::Unsafe;
@@ -461,20 +440,20 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
461 }; 440 };
462 441
463 let tag = match parent.kind() { 442 let tag = match parent.kind() {
464 STRUCT => HlTag::Symbol(SymbolKind::Struct), 443 STRUCT => SymbolKind::Struct,
465 ENUM => HlTag::Symbol(SymbolKind::Enum), 444 ENUM => SymbolKind::Enum,
466 VARIANT => HlTag::Symbol(SymbolKind::Variant), 445 VARIANT => SymbolKind::Variant,
467 UNION => HlTag::Symbol(SymbolKind::Union), 446 UNION => SymbolKind::Union,
468 TRAIT => HlTag::Symbol(SymbolKind::Trait), 447 TRAIT => SymbolKind::Trait,
469 TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias), 448 TYPE_ALIAS => SymbolKind::TypeAlias,
470 TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam), 449 TYPE_PARAM => SymbolKind::TypeParam,
471 RECORD_FIELD => HlTag::Symbol(SymbolKind::Field), 450 RECORD_FIELD => SymbolKind::Field,
472 MODULE => HlTag::Symbol(SymbolKind::Module), 451 MODULE => SymbolKind::Module,
473 FN => HlTag::Symbol(SymbolKind::Function), 452 FN => SymbolKind::Function,
474 CONST => HlTag::Symbol(SymbolKind::Const), 453 CONST => SymbolKind::Const,
475 STATIC => HlTag::Symbol(SymbolKind::Static), 454 STATIC => SymbolKind::Static,
476 IDENT_PAT => HlTag::Symbol(SymbolKind::Local), 455 IDENT_PAT => SymbolKind::Local,
477 _ => default, 456 _ => return default.into(),
478 }; 457 };
479 458
480 tag.into() 459 tag.into()
@@ -492,20 +471,15 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
492 METHOD_CALL_EXPR => { 471 METHOD_CALL_EXPR => {
493 return ast::MethodCallExpr::cast(parent) 472 return ast::MethodCallExpr::cast(parent)
494 .and_then(|it| highlight_method_call(sema, &it)) 473 .and_then(|it| highlight_method_call(sema, &it))
495 .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into()); 474 .unwrap_or_else(|| SymbolKind::Function.into());
496 } 475 }
497 FIELD_EXPR => { 476 FIELD_EXPR => {
498 let h = HlTag::Symbol(SymbolKind::Field); 477 let h = HlTag::Symbol(SymbolKind::Field);
499 let is_union = ast::FieldExpr::cast(parent) 478 let is_union = ast::FieldExpr::cast(parent)
500 .and_then(|field_expr| { 479 .and_then(|field_expr| sema.resolve_field(&field_expr))
501 let field = sema.resolve_field(&field_expr)?; 480 .map_or(false, |field| {
502 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { 481 matches!(field.parent_def(sema.db), hir::VariantDef::Union(_))
503 true 482 });
504 } else {
505 false
506 })
507 })
508 .unwrap_or(false);
509 if is_union { 483 if is_union {
510 h | HlMod::Unsafe 484 h | HlMod::Unsafe
511 } else { 485 } else {
@@ -522,9 +496,9 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
522 _ => { 496 _ => {
523 // within path, decide whether it is module or adt by checking for uppercase name 497 // within path, decide whether it is module or adt by checking for uppercase name
524 return if name.text().chars().next().unwrap_or_default().is_uppercase() { 498 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
525 HlTag::Symbol(SymbolKind::Struct) 499 SymbolKind::Struct
526 } else { 500 } else {
527 HlTag::Symbol(SymbolKind::Module) 501 SymbolKind::Module
528 } 502 }
529 .into(); 503 .into();
530 } 504 }
@@ -535,11 +509,11 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
535 }; 509 };
536 510
537 match parent.kind() { 511 match parent.kind() {
538 CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(), 512 CALL_EXPR => SymbolKind::Function.into(),
539 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { 513 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
540 HlTag::Symbol(SymbolKind::Struct) 514 SymbolKind::Struct
541 } else { 515 } else {
542 HlTag::Symbol(SymbolKind::Const) 516 SymbolKind::Const
543 } 517 }
544 .into(), 518 .into(),
545 } 519 }
@@ -574,6 +548,11 @@ fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[Sy
574 kinds.len() == 0 548 kinds.len() == 0
575} 549}
576 550
551#[inline]
552fn parent_matches<N: AstNode>(element: &SyntaxElement) -> bool {
553 element.parent().map_or(false, |it| N::can_cast(it.kind()))
554}
555
577fn is_child_of_impl(element: &SyntaxElement) -> bool { 556fn is_child_of_impl(element: &SyntaxElement) -> bool {
578 match element.parent() { 557 match element.parent() {
579 Some(e) => e.kind() == IMPL, 558 Some(e) => e.kind() == IMPL,
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index e58392d67..a304b3250 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -40,28 +40,33 @@ pub enum HlTag {
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
41#[repr(u8)] 41#[repr(u8)]
42pub enum HlMod { 42pub enum HlMod {
43 /// Used for items in traits and impls.
44 Associated = 0,
43 /// Used to differentiate individual elements within attributes. 45 /// Used to differentiate individual elements within attributes.
44 Attribute = 0, 46 Attribute,
47 /// Callable item or value.
48 Callable,
49 /// Value that is being consumed in a function call
50 Consuming,
45 /// Used with keywords like `if` and `break`. 51 /// Used with keywords like `if` and `break`.
46 ControlFlow, 52 ControlFlow,
47 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 53 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
48 /// not. 54 /// not.
49 Definition, 55 Definition,
56 /// Doc-strings like this one.
50 Documentation, 57 Documentation,
58 /// Highlighting injection like rust code in doc strings or ra_fixture.
51 Injected, 59 Injected,
52 Mutable,
53 Consuming,
54 Callable,
55 /// Used for associated functions
56 Static,
57 /// Used for items in impls&traits.
58 Associated,
59 /// Used for intra doc links in doc injection. 60 /// Used for intra doc links in doc injection.
60 IntraDocLink, 61 IntraDocLink,
62 /// Mutable binding.
63 Mutable,
64 /// Used for associated functions.
65 Static,
61 /// Used for items in traits and trait impls. 66 /// Used for items in traits and trait impls.
62 Trait, 67 Trait,
63 68 // Keep this last!
64 /// Keep this last! 69 /// Used for unsafe functions, mutable statics, union accesses and unsafe operations.
65 Unsafe, 70 Unsafe,
66} 71}
67 72
@@ -169,17 +174,17 @@ impl fmt::Display for HlTag {
169 174
170impl HlMod { 175impl HlMod {
171 const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[ 176 const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[
177 HlMod::Associated,
172 HlMod::Attribute, 178 HlMod::Attribute,
179 HlMod::Callable,
180 HlMod::Consuming,
173 HlMod::ControlFlow, 181 HlMod::ControlFlow,
174 HlMod::Definition, 182 HlMod::Definition,
175 HlMod::Documentation, 183 HlMod::Documentation,
176 HlMod::IntraDocLink,
177 HlMod::Injected, 184 HlMod::Injected,
185 HlMod::IntraDocLink,
178 HlMod::Mutable, 186 HlMod::Mutable,
179 HlMod::Consuming,
180 HlMod::Callable,
181 HlMod::Static, 187 HlMod::Static,
182 HlMod::Associated,
183 HlMod::Trait, 188 HlMod::Trait,
184 HlMod::Unsafe, 189 HlMod::Unsafe,
185 ]; 190 ];
@@ -229,6 +234,24 @@ impl From<HlTag> for Highlight {
229 } 234 }
230} 235}
231 236
237impl From<HlOperator> for Highlight {
238 fn from(op: HlOperator) -> Highlight {
239 Highlight::new(HlTag::Operator(op))
240 }
241}
242
243impl From<HlPunct> for Highlight {
244 fn from(punct: HlPunct) -> Highlight {
245 Highlight::new(HlTag::Punctuation(punct))
246 }
247}
248
249impl From<SymbolKind> for Highlight {
250 fn from(sym: SymbolKind) -> Highlight {
251 Highlight::new(HlTag::Symbol(sym))
252 }
253}
254
232impl Highlight { 255impl Highlight {
233 pub(crate) fn new(tag: HlTag) -> Highlight { 256 pub(crate) fn new(tag: HlTag) -> Highlight {
234 Highlight { tag, mods: HlMods::default() } 257 Highlight { tag, mods: HlMods::default() }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index 8cde3906c..a0ea1db34 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -42,17 +42,17 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
42<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span> 42<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span>
43 43
44<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span> 44<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span>
45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration static">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
46 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 46 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
47<span class="brace">}</span> 47<span class="brace">}</span>
48 48
49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span> 49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
50 <span class="keyword">fn</span> <span class="function declaration static associated trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 50 <span class="keyword">fn</span> <span class="function associated declaration static trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51 <span class="keyword">fn</span> <span class="function declaration associated trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 51 <span class="keyword">fn</span> <span class="function associated declaration trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
52<span class="brace">}</span> 52<span class="brace">}</span>
53 53
54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span> 54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration static trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
57<span class="brace">}</span> 57<span class="brace">}</span>
58 </code></pre> \ No newline at end of file 58 </code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 6ee6d85fb..638f42c2f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -50,7 +50,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
50 <span class="comment">// KILLER WHALE</span> 50 <span class="comment">// KILLER WHALE</span>
51 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span> 51 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span>
52 <span class="comment documentation">/// ```</span> 52 <span class="comment documentation">/// ```</span>
53 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span> 53 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant associated declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span>
54 54
55 <span class="comment documentation">/// Constructs a new `Foo`.</span> 55 <span class="comment documentation">/// Constructs a new `Foo`.</span>
56 <span class="comment documentation">///</span> 56 <span class="comment documentation">///</span>
@@ -60,7 +60,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
60 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span> 60 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span>
61 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> 61 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
62 <span class="comment documentation">/// ```</span> 62 <span class="comment documentation">/// ```</span>
63 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span> 63 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated declaration static">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span>
64 <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span> 64 <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span>
65 <span class="brace">}</span> 65 <span class="brace">}</span>
66 66
@@ -94,15 +94,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
94 <span class="comment documentation">/// ```sh</span> 94 <span class="comment documentation">/// ```sh</span>
95 <span class="comment documentation">/// echo 1</span> 95 <span class="comment documentation">/// echo 1</span>
96 <span class="comment documentation">/// ```</span> 96 <span class="comment documentation">/// ```</span>
97 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span> 97 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span>
98 <span class="bool_literal">true</span> 98 <span class="bool_literal">true</span>
99 <span class="brace">}</span> 99 <span class="brace">}</span>
100<span class="brace">}</span> 100<span class="brace">}</span>
101 101
102<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span> 102<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
103<span class="comment documentation">/// This function is &gt; </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span> 103<span class="comment documentation">/// This function is &gt; </span><span class="function documentation injected intra_doc_link">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span>
104<span class="comment documentation">/// [`noop`](noop) is a macro below</span> 104<span class="comment documentation">/// [`noop`](noop) is a macro below</span>
105<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation intra_doc_link injected">[`module`]</span> 105<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation injected intra_doc_link">[`module`]</span>
106<span class="comment documentation">///</span> 106<span class="comment documentation">///</span>
107<span class="comment documentation">/// [`Item`]: module::Item</span> 107<span class="comment documentation">/// [`Item`]: module::Item</span>
108<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span> 108<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 7c6694a27..6202a03ce 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -42,7 +42,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span> 43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span> 44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
45 <span class="keyword">fn</span> <span class="function declaration static associated trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 45 <span class="keyword">fn</span> <span class="function associated declaration static trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> 46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
47 <span class="brace">}</span> 47 <span class="brace">}</span>
48 <span class="brace">}</span><span class="string_literal">"#</span> 48 <span class="brace">}</span><span class="string_literal">"#</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 72910421d..68165bdbf 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -47,7 +47,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
47<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="semicolon">;</span> 47<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="semicolon">;</span>
48 48
49<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="brace">{</span> 49<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="brace">{</span>
50 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration associated unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 50 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51<span class="brace">}</span> 51<span class="brace">}</span>
52 52
53<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="brace">{</span> 53<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="brace">{</span>
@@ -62,11 +62,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62<span class="brace">}</span> 62<span class="brace">}</span>
63 63
64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> 64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
65 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> 65 <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
66<span class="brace">}</span> 66<span class="brace">}</span>
67 67
68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span> 68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span>
69 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 69 <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
70<span class="brace">}</span> 70<span class="brace">}</span>
71 71
72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index c43bcb691..df4192194 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -67,25 +67,25 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
67<span class="brace">}</span> 67<span class="brace">}</span>
68 68
69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> 69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span>
70 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span> 70 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
71<span class="brace">}</span> 71<span class="brace">}</span>
72 72
73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> 73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span>
74 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 74 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
76 <span class="brace">}</span> 76 <span class="brace">}</span>
77<span class="brace">}</span> 77<span class="brace">}</span>
78 78
79<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span> 79<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
80 <span class="keyword">fn</span> <span class="function declaration associated">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 80 <span class="keyword">fn</span> <span class="function associated declaration">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
81 <span class="value_param">f</span><span class="operator">.</span><span class="function consuming associated">baz</span><span class="parenthesis">(</span><span class="self_keyword mutable consuming">self</span><span class="parenthesis">)</span> 81 <span class="value_param">f</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="self_keyword consuming mutable">self</span><span class="parenthesis">)</span>
82 <span class="brace">}</span> 82 <span class="brace">}</span>
83 83
84 <span class="keyword">fn</span> <span class="function declaration associated">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span> 84 <span class="keyword">fn</span> <span class="function associated declaration mutable">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
85 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> 85 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
86 <span class="brace">}</span> 86 <span class="brace">}</span>
87 87
88 <span class="keyword">fn</span> <span class="function declaration associated">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 88 <span class="keyword">fn</span> <span class="function associated declaration">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
89 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 89 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
90 <span class="brace">}</span> 90 <span class="brace">}</span>
91<span class="brace">}</span> 91<span class="brace">}</span>
@@ -96,15 +96,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
96<span class="brace">}</span> 96<span class="brace">}</span>
97 97
98<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span> 98<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span>
99 <span class="keyword">fn</span> <span class="function declaration associated">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span> 99 <span class="keyword">fn</span> <span class="function associated declaration">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
100 <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span> 100 <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span>
101 <span class="brace">}</span> 101 <span class="brace">}</span>
102 102
103 <span class="keyword">fn</span> <span class="function declaration associated">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span> 103 <span class="keyword">fn</span> <span class="function associated declaration mutable">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
104 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> 104 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
105 <span class="brace">}</span> 105 <span class="brace">}</span>
106 106
107 <span class="keyword">fn</span> <span class="function declaration associated">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span> 107 <span class="keyword">fn</span> <span class="function associated declaration">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
108 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 108 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
109 <span class="brace">}</span> 109 <span class="brace">}</span>
110<span class="brace">}</span> 110<span class="brace">}</span>
@@ -128,7 +128,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
128<span class="brace">}</span> 128<span class="brace">}</span>
129 129
130<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="semicolon">;</span> 130<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="semicolon">;</span>
131<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param declaration callable">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span> 131<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
132 <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span> 132 <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
133<span class="brace">}</span> 133<span class="brace">}</span>
134 134
@@ -199,16 +199,16 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
199 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span> 199 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
200 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span> 200 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
201 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 201 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
202 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function mutable associated">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 202 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated mutable">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
203 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function consuming associated">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span> 203 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
204 204
205 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span> 205 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span>
206 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 206 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
207 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function mutable associated">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 207 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated mutable">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
208 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span> 208 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span>
209 209
210 <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="semicolon">;</span> 210 <span class="keyword">let</span> <span class="variable callable declaration">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="semicolon">;</span>
211 <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 associated">baz</span><span class="semicolon">;</span> 211 <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span>
212 212
213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> 213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span>
214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> 214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span>
@@ -228,7 +228,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
228<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span> 228<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span>
229 229
230<span class="keyword">impl</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">T</span><span class="angle">&gt;</span> <span class="brace">{</span> 230<span class="keyword">impl</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">T</span><span class="angle">&gt;</span> <span class="brace">{</span>
231 <span class="keyword">fn</span> <span class="function declaration associated">and</span><span class="angle">&lt;</span><span class="type_param declaration">U</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">U</span><span class="angle">&gt;</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">&gt;</span> <span class="brace">{</span> 231 <span class="keyword">fn</span> <span class="function associated declaration">and</span><span class="angle">&lt;</span><span class="type_param declaration">U</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">U</span><span class="angle">&gt;</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">&gt;</span> <span class="brace">{</span>
232 <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span> 232 <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span>
233 <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="macro">unimplemented!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span> 233 <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="macro">unimplemented!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span> 234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span>
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
index 4a3ed7783..e5ae718c9 100644
--- a/crates/ide_assists/src/ast_transform.rs
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -3,20 +3,27 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast; 3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
5use syntax::{ 5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 6 ast::{self, AstNode},
8 SyntaxNode, 7 ted, SyntaxNode,
9}; 8};
10 9
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
12 SyntaxRewriter::from_fn(|element| match element { 11 let mut skip_to = None;
13 syntax::SyntaxElement::Node(n) => { 12 for event in node.syntax().preorder() {
14 let replacement = transformer.get_substitution(&n, transformer)?; 13 match event {
15 Some(replacement.into()) 14 syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
15 skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
16 }
17 syntax::WalkEvent::Enter(_) => (),
18 syntax::WalkEvent::Leave(node) => match &skip_to {
19 Some((replacement, skip_target)) if *skip_target == node => {
20 ted::replace(node, replacement.clone_for_update());
21 skip_to.take();
22 }
23 _ => (),
24 },
16 } 25 }
17 _ => None, 26 }
18 })
19 .rewrite_ast(&node)
20} 27}
21 28
22/// `AstTransform` helps with applying bulk transformations to syntax nodes. 29/// `AstTransform` helps with applying bulk transformations to syntax nodes.
@@ -191,11 +198,9 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 198 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = mod_path_to_ast(&found_path); 199 let mut path = mod_path_to_ast(&found_path);
193 200
194 let type_args = p 201 let type_args = p.segment().and_then(|s| s.generic_arg_list());
195 .segment()
196 .and_then(|s| s.generic_arg_list())
197 .map(|arg_list| apply(recur, arg_list));
198 if let Some(type_args) = type_args { 202 if let Some(type_args) = type_args {
203 apply(recur, &type_args);
199 let last_segment = path.segment().unwrap(); 204 let last_segment = path.segment().unwrap();
200 path = path.with_segment(last_segment.with_generic_args(type_args)) 205 path = path.with_segment(last_segment.with_generic_args(type_args))
201 } 206 }
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 49aa70f74..a454a2af3 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -93,7 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
93 93
94 let range = ctx.sema.original_range(&syntax_under_caret).range; 94 let range = ctx.sema.original_range(&syntax_under_caret).range;
95 let group_label = group_label(import_assets.import_candidate()); 95 let group_label = group_label(import_assets.import_candidate());
96 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; 96 let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?;
97 for import in proposed_imports { 97 for import in proposed_imports {
98 acc.add_group( 98 acc.add_group(
99 &group_label, 99 &group_label,
@@ -101,9 +101,11 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
101 format!("Import `{}`", import.import_path), 101 format!("Import `{}`", import.import_path),
102 range, 102 range,
103 |builder| { 103 |builder| {
104 let rewriter = 104 let scope = match scope.clone() {
105 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); 105 ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
106 builder.rewrite(rewriter); 106 ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
107 };
108 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
107 }, 109 },
108 ); 110 );
109 } 111 }
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
new file mode 100644
index 000000000..b5b5ada5e
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -0,0 +1,516 @@
1use ide_db::defs::{Definition, NameRefClass};
2use syntax::{
3 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
4 match_ast, SyntaxNode,
5};
6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: convert_tuple_struct_to_named_struct
10//
11// Converts tuple struct to struct with named fields.
12//
13// ```
14// struct Point$0(f32, f32);
15//
16// impl Point {
17// pub fn new(x: f32, y: f32) -> Self {
18// Point(x, y)
19// }
20//
21// pub fn x(&self) -> f32 {
22// self.0
23// }
24//
25// pub fn y(&self) -> f32 {
26// self.1
27// }
28// }
29// ```
30// ->
31// ```
32// struct Point { field1: f32, field2: f32 }
33//
34// impl Point {
35// pub fn new(x: f32, y: f32) -> Self {
36// Point { field1: x, field2: y }
37// }
38//
39// pub fn x(&self) -> f32 {
40// self.field1
41// }
42//
43// pub fn y(&self) -> f32 {
44// self.field2
45// }
46// }
47// ```
48pub(crate) fn convert_tuple_struct_to_named_struct(
49 acc: &mut Assists,
50 ctx: &AssistContext,
51) -> Option<()> {
52 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
53 let tuple_fields = match strukt.field_list()? {
54 ast::FieldList::TupleFieldList(it) => it,
55 ast::FieldList::RecordFieldList(_) => return None,
56 };
57 let strukt_def = ctx.sema.to_def(&strukt)?;
58
59 let target = strukt.syntax().text_range();
60 acc.add(
61 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
62 "Convert to named struct",
63 target,
64 |edit| {
65 let names = generate_names(tuple_fields.fields());
66 edit_field_references(ctx, edit, tuple_fields.fields(), &names);
67 edit_struct_references(ctx, edit, strukt_def, &names);
68 edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
69 },
70 )
71}
72
73fn edit_struct_def(
74 ctx: &AssistContext,
75 edit: &mut AssistBuilder,
76 strukt: &ast::Struct,
77 tuple_fields: ast::TupleFieldList,
78 names: Vec<ast::Name>,
79) {
80 let record_fields = tuple_fields
81 .fields()
82 .zip(names)
83 .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
84 let record_fields = ast::make::record_field_list(record_fields);
85 let tuple_fields_text_range = tuple_fields.syntax().text_range();
86
87 edit.edit_file(ctx.frange.file_id);
88
89 if let Some(w) = strukt.where_clause() {
90 edit.delete(w.syntax().text_range());
91 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
92 edit.insert(tuple_fields_text_range.start(), w.syntax().text());
93 edit.insert(tuple_fields_text_range.start(), ",");
94 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
95 } else {
96 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
97 }
98
99 edit.replace(tuple_fields_text_range, record_fields.to_string());
100 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
101}
102
103fn edit_struct_references(
104 ctx: &AssistContext,
105 edit: &mut AssistBuilder,
106 strukt: hir::Struct,
107 names: &[ast::Name],
108) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
111
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113 match_ast! {
114 match node {
115 ast::TupleStructPat(tuple_struct_pat) => {
116 edit.replace(
117 tuple_struct_pat.syntax().text_range(),
118 ast::make::record_pat_with_fields(
119 tuple_struct_pat.path()?,
120 ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
121 |(pat, name)| {
122 ast::make::record_pat_field(
123 ast::make::name_ref(&name.to_string()),
124 pat,
125 )
126 },
127 )),
128 )
129 .to_string(),
130 );
131 },
132 // for tuple struct creations like Foo(42)
133 ast::CallExpr(call_expr) => {
134 let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
135
136 // this also includes method calls like Foo::new(42), we should skip them
137 if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
138 match NameRefClass::classify(&ctx.sema, &name_ref) {
139 Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
140 Some(NameRefClass::Definition(def)) if def == strukt_def => {},
141 _ => return None,
142 };
143 }
144
145 let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
146
147 edit.replace(
148 call_expr.syntax().text_range(),
149 ast::make::record_expr(
150 path,
151 ast::make::record_expr_field_list(arg_list.args().zip(names).map(
152 |(expr, name)| {
153 ast::make::record_expr_field(
154 ast::make::name_ref(&name.to_string()),
155 Some(expr),
156 )
157 },
158 )),
159 )
160 .to_string(),
161 );
162 },
163 _ => return None,
164 }
165 }
166 Some(())
167 };
168
169 for (file_id, refs) in usages {
170 edit.edit_file(file_id);
171 for r in refs {
172 for node in r.name.syntax().ancestors() {
173 if edit_node(edit, node).is_some() {
174 break;
175 }
176 }
177 }
178 }
179}
180
181fn edit_field_references(
182 ctx: &AssistContext,
183 edit: &mut AssistBuilder,
184 fields: impl Iterator<Item = ast::TupleField>,
185 names: &[ast::Name],
186) {
187 for (field, name) in fields.zip(names) {
188 let field = match ctx.sema.to_def(&field) {
189 Some(it) => it,
190 None => continue,
191 };
192 let def = Definition::Field(field);
193 let usages = def.usages(&ctx.sema).all();
194 for (file_id, refs) in usages {
195 edit.edit_file(file_id);
196 for r in refs {
197 if let Some(name_ref) = r.name.as_name_ref() {
198 edit.replace(name_ref.syntax().text_range(), name.text());
199 }
200 }
201 }
202 }
203}
204
205fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
206 fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
207}
208
209#[cfg(test)]
210mod tests {
211 use crate::tests::{check_assist, check_assist_not_applicable};
212
213 use super::*;
214
215 #[test]
216 fn not_applicable_other_than_tuple_struct() {
217 check_assist_not_applicable(
218 convert_tuple_struct_to_named_struct,
219 r#"struct Foo$0 { bar: u32 };"#,
220 );
221 check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
222 }
223
224 #[test]
225 fn convert_simple_struct() {
226 check_assist(
227 convert_tuple_struct_to_named_struct,
228 r#"
229struct Inner;
230struct A$0(Inner);
231
232impl A {
233 fn new(inner: Inner) -> A {
234 A(inner)
235 }
236
237 fn new_with_default() -> A {
238 A::new(Inner)
239 }
240
241 fn into_inner(self) -> Inner {
242 self.0
243 }
244}"#,
245 r#"
246struct Inner;
247struct A { field1: Inner }
248
249impl A {
250 fn new(inner: Inner) -> A {
251 A { field1: inner }
252 }
253
254 fn new_with_default() -> A {
255 A::new(Inner)
256 }
257
258 fn into_inner(self) -> Inner {
259 self.field1
260 }
261}"#,
262 );
263 }
264
265 #[test]
266 fn convert_struct_referenced_via_self_kw() {
267 check_assist(
268 convert_tuple_struct_to_named_struct,
269 r#"
270struct Inner;
271struct A$0(Inner);
272
273impl A {
274 fn new(inner: Inner) -> Self {
275 Self(inner)
276 }
277
278 fn new_with_default() -> Self {
279 Self::new(Inner)
280 }
281
282 fn into_inner(self) -> Inner {
283 self.0
284 }
285}"#,
286 r#"
287struct Inner;
288struct A { field1: Inner }
289
290impl A {
291 fn new(inner: Inner) -> Self {
292 Self { field1: inner }
293 }
294
295 fn new_with_default() -> Self {
296 Self::new(Inner)
297 }
298
299 fn into_inner(self) -> Inner {
300 self.field1
301 }
302}"#,
303 );
304 }
305
306 #[test]
307 fn convert_destructured_struct() {
308 check_assist(
309 convert_tuple_struct_to_named_struct,
310 r#"
311struct Inner;
312struct A$0(Inner);
313
314impl A {
315 fn into_inner(self) -> Inner {
316 let A(first) = self;
317 first
318 }
319
320 fn into_inner_via_self(self) -> Inner {
321 let Self(first) = self;
322 first
323 }
324}"#,
325 r#"
326struct Inner;
327struct A { field1: Inner }
328
329impl A {
330 fn into_inner(self) -> Inner {
331 let A { field1: first } = self;
332 first
333 }
334
335 fn into_inner_via_self(self) -> Inner {
336 let Self { field1: first } = self;
337 first
338 }
339}"#,
340 );
341 }
342
343 #[test]
344 fn convert_struct_with_visibility() {
345 check_assist(
346 convert_tuple_struct_to_named_struct,
347 r#"
348struct A$0(pub u32, pub(crate) u64);
349
350impl A {
351 fn new() -> A {
352 A(42, 42)
353 }
354
355 fn into_first(self) -> u32 {
356 self.0
357 }
358
359 fn into_second(self) -> u64 {
360 self.1
361 }
362}"#,
363 r#"
364struct A { pub field1: u32, pub(crate) field2: u64 }
365
366impl A {
367 fn new() -> A {
368 A { field1: 42, field2: 42 }
369 }
370
371 fn into_first(self) -> u32 {
372 self.field1
373 }
374
375 fn into_second(self) -> u64 {
376 self.field2
377 }
378}"#,
379 );
380 }
381
382 #[test]
383 fn convert_struct_with_wrapped_references() {
384 check_assist(
385 convert_tuple_struct_to_named_struct,
386 r#"
387struct Inner$0(u32);
388struct Outer(Inner);
389
390impl Outer {
391 fn new() -> Self {
392 Self(Inner(42))
393 }
394
395 fn into_inner(self) -> u32 {
396 (self.0).0
397 }
398
399 fn into_inner_destructed(self) -> u32 {
400 let Outer(Inner(x)) = self;
401 x
402 }
403}"#,
404 r#"
405struct Inner { field1: u32 }
406struct Outer(Inner);
407
408impl Outer {
409 fn new() -> Self {
410 Self(Inner { field1: 42 })
411 }
412
413 fn into_inner(self) -> u32 {
414 (self.0).field1
415 }
416
417 fn into_inner_destructed(self) -> u32 {
418 let Outer(Inner { field1: x }) = self;
419 x
420 }
421}"#,
422 );
423
424 check_assist(
425 convert_tuple_struct_to_named_struct,
426 r#"
427struct Inner(u32);
428struct Outer$0(Inner);
429
430impl Outer {
431 fn new() -> Self {
432 Self(Inner(42))
433 }
434
435 fn into_inner(self) -> u32 {
436 (self.0).0
437 }
438
439 fn into_inner_destructed(self) -> u32 {
440 let Outer(Inner(x)) = self;
441 x
442 }
443}"#,
444 r#"
445struct Inner(u32);
446struct Outer { field1: Inner }
447
448impl Outer {
449 fn new() -> Self {
450 Self { field1: Inner(42) }
451 }
452
453 fn into_inner(self) -> u32 {
454 (self.field1).0
455 }
456
457 fn into_inner_destructed(self) -> u32 {
458 let Outer { field1: Inner(x) } = self;
459 x
460 }
461}"#,
462 );
463 }
464
465 #[test]
466 fn convert_struct_with_multi_file_references() {
467 check_assist(
468 convert_tuple_struct_to_named_struct,
469 r#"
470//- /main.rs
471struct Inner;
472struct A$0(Inner);
473
474mod foo;
475
476//- /foo.rs
477use crate::{A, Inner};
478fn f() {
479 let a = A(Inner);
480}
481"#,
482 r#"
483//- /main.rs
484struct Inner;
485struct A { field1: Inner }
486
487mod foo;
488
489//- /foo.rs
490use crate::{A, Inner};
491fn f() {
492 let a = A { field1: Inner };
493}
494"#,
495 );
496 }
497
498 #[test]
499 fn convert_struct_with_where_clause() {
500 check_assist(
501 convert_tuple_struct_to_named_struct,
502 r#"
503struct Wrap$0<T>(T)
504where
505 T: Display;
506"#,
507 r#"
508struct Wrap<T>
509where
510 T: Display,
511{ field1: T }
512
513"#,
514 );
515 }
516}
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index a8d6355bd..66f274fa7 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -5,7 +5,7 @@ use hir::{Module, ModuleDef, Name, Variant};
5use ide_db::{ 5use ide_db::{
6 defs::Definition, 6 defs::Definition,
7 helpers::{ 7 helpers::{
8 insert_use::{insert_use, ImportScope}, 8 insert_use::{insert_use, ImportScope, InsertUseConfig},
9 mod_path_to_ast, 9 mod_path_to_ast,
10 }, 10 },
11 search::FileReference, 11 search::FileReference,
@@ -13,9 +13,9 @@ use ide_db::{
13}; 13};
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::{ 15use syntax::{
16 algo::{find_node_at_offset, SyntaxRewriter}, 16 algo::find_node_at_offset,
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, 17 ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T, 18 ted, SyntaxNode, T,
19}; 19};
20 20
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 21use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,40 +62,50 @@ pub(crate) fn extract_struct_from_enum_variant(
62 let mut visited_modules_set = FxHashSet::default(); 62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db()); 63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module); 64 visited_modules_set.insert(current_module);
65 let mut def_rewriter = None; 65 // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
66 let mut def_file_references = None;
66 for (file_id, references) in usages { 67 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default();
68 let source_file = ctx.sema.parse(file_id);
69 for reference in references {
70 update_reference(
71 ctx,
72 &mut rewriter,
73 reference,
74 &source_file,
75 &enum_module_def,
76 &variant_hir_name,
77 &mut visited_modules_set,
78 );
79 }
80 if file_id == ctx.frange.file_id { 68 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter); 69 def_file_references = Some(references);
82 continue; 70 continue;
83 } 71 }
84 builder.edit_file(file_id); 72 builder.edit_file(file_id);
85 builder.rewrite(rewriter); 73 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
74 let processed = process_references(
75 ctx,
76 &mut visited_modules_set,
77 source_file.syntax(),
78 &enum_module_def,
79 &variant_hir_name,
80 references,
81 );
82 processed.into_iter().for_each(|(path, node, import)| {
83 apply_references(ctx.config.insert_use, path, node, import)
84 });
86 } 85 }
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
89 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(),
93 &field_list,
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
96 );
97 builder.edit_file(ctx.frange.file_id); 86 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter); 87 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
88 let variant = builder.make_ast_mut(variant.clone());
89 if let Some(references) = def_file_references {
90 let processed = process_references(
91 ctx,
92 &mut visited_modules_set,
93 source_file.syntax(),
94 &enum_module_def,
95 &variant_hir_name,
96 references,
97 );
98 processed.into_iter().for_each(|(path, node, import)| {
99 apply_references(ctx.config.insert_use, path, node, import)
100 });
101 }
102
103 let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
104 let start_offset = &variant.parent_enum().syntax().clone();
105 ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107
108 update_variant(&variant);
99 }, 109 },
100 ) 110 )
101} 111}
@@ -136,34 +146,11 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
136 .any(|(name, _)| name.to_string() == variant_name.to_string()) 146 .any(|(name, _)| name.to_string() == variant_name.to_string())
137} 147}
138 148
139fn insert_import( 149fn create_struct_def(
140 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
143 module: &Module,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
146) -> Option<()> {
147 let db = ctx.db();
148 let mod_path =
149 module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
150 if let Some(mut mod_path) = mod_path {
151 mod_path.pop_segment();
152 mod_path.push_segment(variant_hir_name.clone());
153 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
154 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
155 }
156 Some(())
157}
158
159fn extract_struct_def(
160 rewriter: &mut SyntaxRewriter,
161 enum_: &ast::Enum,
162 variant_name: ast::Name, 150 variant_name: ast::Name,
163 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 151 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
164 start_offset: &SyntaxElement,
165 visibility: Option<ast::Visibility>, 152 visibility: Option<ast::Visibility>,
166) -> Option<()> { 153) -> ast::Struct {
167 let pub_vis = Some(make::visibility_pub()); 154 let pub_vis = Some(make::visibility_pub());
168 let field_list = match field_list { 155 let field_list = match field_list {
169 Either::Left(field_list) => { 156 Either::Left(field_list) => {
@@ -180,65 +167,90 @@ fn extract_struct_def(
180 .into(), 167 .into(),
181 }; 168 };
182 169
183 rewriter.insert_before( 170 make::struct_(visibility, variant_name, None, field_list).clone_for_update()
184 start_offset,
185 make::struct_(visibility, variant_name, None, field_list).syntax(),
186 );
187 rewriter.insert_before(start_offset, &make::tokens::blank_line());
188
189 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
190 rewriter
191 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
192 }
193 Some(())
194} 171}
195 172
196fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { 173fn update_variant(variant: &ast::Variant) -> Option<()> {
197 let name = variant.name()?; 174 let name = variant.name()?;
198 let tuple_field = make::tuple_field(None, make::ty(&name.text())); 175 let tuple_field = make::tuple_field(None, make::ty(&name.text()));
199 let replacement = make::variant( 176 let replacement = make::variant(
200 name, 177 name,
201 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), 178 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
202 ); 179 )
203 rewriter.replace(variant.syntax(), replacement.syntax()); 180 .clone_for_update();
181 ted::replace(variant.syntax(), replacement.syntax());
204 Some(()) 182 Some(())
205} 183}
206 184
207fn update_reference( 185fn apply_references(
186 insert_use_cfg: InsertUseConfig,
187 segment: ast::PathSegment,
188 node: SyntaxNode,
189 import: Option<(ImportScope, hir::ModPath)>,
190) {
191 if let Some((scope, path)) = import {
192 insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
193 }
194 ted::insert_raw(
195 ted::Position::before(segment.syntax()),
196 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
197 );
198 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
199 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
200}
201
202fn process_references(
208 ctx: &AssistContext, 203 ctx: &AssistContext,
209 rewriter: &mut SyntaxRewriter, 204 visited_modules: &mut FxHashSet<Module>,
210 reference: FileReference, 205 source_file: &SyntaxNode,
211 source_file: &SourceFile,
212 enum_module_def: &ModuleDef, 206 enum_module_def: &ModuleDef,
213 variant_hir_name: &Name, 207 variant_hir_name: &Name,
214 visited_modules_set: &mut FxHashSet<Module>, 208 refs: Vec<FileReference>,
215) -> Option<()> { 209) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
210 // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
211 // and corresponding nodes up front
212 refs.into_iter()
213 .flat_map(|reference| {
214 let (segment, scope_node, module) =
215 reference_to_node(&ctx.sema, source_file, reference)?;
216 if !visited_modules.contains(&module) {
217 let mod_path = module.find_use_path_prefixed(
218 ctx.sema.db,
219 *enum_module_def,
220 ctx.config.insert_use.prefix_kind,
221 );
222 if let Some(mut mod_path) = mod_path {
223 mod_path.pop_segment();
224 mod_path.push_segment(variant_hir_name.clone());
225 let scope = ImportScope::find_insert_use_container(&scope_node)?;
226 visited_modules.insert(module);
227 return Some((segment, scope_node, Some((scope, mod_path))));
228 }
229 }
230 Some((segment, scope_node, None))
231 })
232 .collect()
233}
234
235fn reference_to_node(
236 sema: &hir::Semantics<RootDatabase>,
237 source_file: &SyntaxNode,
238 reference: FileReference,
239) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
216 let offset = reference.range.start(); 240 let offset = reference.range.start();
217 let (segment, expr) = if let Some(path_expr) = 241 if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
218 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
219 {
220 // tuple variant 242 // tuple variant
221 (path_expr.path()?.segment()?, path_expr.syntax().parent()?) 243 Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
222 } else if let Some(record_expr) = 244 } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
223 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
224 {
225 // record variant 245 // record variant
226 (record_expr.path()?.segment()?, record_expr.syntax().clone()) 246 Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
227 } else { 247 } else {
228 return None; 248 None
229 };
230
231 let module = ctx.sema.scope(&expr).module()?;
232 if !visited_modules_set.contains(&module) {
233 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
234 {
235 visited_modules_set.insert(module);
236 }
237 } 249 }
238 rewriter.insert_after(segment.syntax(), &make::token(T!['('])); 250 .and_then(|(segment, expr)| {
239 rewriter.insert_after(segment.syntax(), segment.syntax()); 251 let module = sema.scope(&expr).module()?;
240 rewriter.insert_after(&expr, &make::token(T![')'])); 252 Some((segment, expr, module))
241 Some(()) 253 })
242} 254}
243 255
244#[cfg(test)] 256#[cfg(test)]
@@ -345,7 +357,7 @@ mod my_mod {
345 357
346 pub struct MyField(pub u8, pub u8); 358 pub struct MyField(pub u8, pub u8);
347 359
348 pub enum MyEnum { 360pub enum MyEnum {
349 MyField(MyField), 361 MyField(MyField),
350 } 362 }
351 } 363 }
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
index 8e0794218..add7b8e37 100644
--- a/crates/ide_assists/src/handlers/merge_imports.rs
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -1,4 +1,4 @@
1use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior}; 1use ide_db::helpers::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
2use syntax::{algo::neighbor, ast, ted, AstNode}; 2use syntax::{algo::neighbor, ast, ted, AstNode};
3 3
4use crate::{ 4use crate::{
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
index 1a95135ca..e90bbdbcf 100644
--- a/crates/ide_assists/src/handlers/reorder_fields.rs
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -83,11 +83,9 @@ fn replace<T: AstNode + PartialEq>(
83 fields: impl Iterator<Item = T>, 83 fields: impl Iterator<Item = T>,
84 sorted_fields: impl IntoIterator<Item = T>, 84 sorted_fields: impl IntoIterator<Item = T>,
85) { 85) {
86 fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each( 86 fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
87 |(field, sorted_field)| { 87 ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
88 ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()); 88 });
89 },
90 );
91} 89}
92 90
93fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 91fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs
index f976e73ad..72d889248 100644
--- a/crates/ide_assists/src/handlers/reorder_impl.rs
+++ b/crates/ide_assists/src/handlers/reorder_impl.rs
@@ -4,9 +4,8 @@ use rustc_hash::FxHashMap;
4use hir::{PathResolution, Semantics}; 4use hir::{PathResolution, Semantics};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::{ 6use syntax::{
7 algo,
8 ast::{self, NameOwner}, 7 ast::{self, NameOwner},
9 AstNode, 8 ted, AstNode,
10}; 9};
11 10
12use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -75,13 +74,16 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
75 } 74 }
76 75
77 let target = items.syntax().text_range(); 76 let target = items.syntax().text_range();
78 acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| { 77 acc.add(
79 let mut rewriter = algo::SyntaxRewriter::default(); 78 AssistId("reorder_impl", AssistKind::RefactorRewrite),
80 for (old, new) in methods.iter().zip(&sorted) { 79 "Sort methods",
81 rewriter.replace(old.syntax(), new.syntax()); 80 target,
82 } 81 |builder| {
83 edit.rewrite(rewriter); 82 methods.into_iter().zip(sorted).for_each(|(old, new)| {
84 }) 83 ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax())
84 });
85 },
86 )
85} 87}
86 88
87fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 89fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index 36d2e0331..99ba79860 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,5 +1,5 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope}; 1use ide_db::helpers::insert_use::{insert_use, ImportScope};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; 2use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
3 3
4use crate::{AssistContext, AssistId, AssistKind, Assists}; 4use crate::{AssistContext, AssistId, AssistKind, Assists};
5 5
@@ -31,7 +31,7 @@ pub(crate) fn replace_qualified_name_with_use(
31 } 31 }
32 32
33 let target = path.syntax().text_range(); 33 let target = path.syntax().text_range();
34 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?; 34 let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?;
35 let syntax = scope.as_syntax_node(); 35 let syntax = scope.as_syntax_node();
36 acc.add( 36 acc.add(
37 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 37 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
@@ -40,18 +40,17 @@ pub(crate) fn replace_qualified_name_with_use(
40 |builder| { 40 |builder| {
41 // Now that we've brought the name into scope, re-qualify all paths that could be 41 // Now that we've brought the name into scope, re-qualify all paths that could be
42 // affected (that is, all paths inside the node we added the `use` to). 42 // affected (that is, all paths inside the node we added the `use` to).
43 let mut rewriter = SyntaxRewriter::default(); 43 let syntax = builder.make_mut(syntax.clone());
44 shorten_paths(&mut rewriter, syntax.clone(), &path);
45 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { 44 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
46 rewriter += insert_use(import_scope, path, ctx.config.insert_use); 45 shorten_paths(&syntax, &path.clone_for_update());
47 builder.rewrite(rewriter); 46 insert_use(import_scope, path, ctx.config.insert_use);
48 } 47 }
49 }, 48 },
50 ) 49 )
51} 50}
52 51
53/// Adds replacements to `re` that shorten `path` in all descendants of `node`. 52/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
54fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { 53fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
55 for child in node.children() { 54 for child in node.children() {
56 match_ast! { 55 match_ast! {
57 match child { 56 match child {
@@ -60,34 +59,26 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
60 ast::Use(_it) => continue, 59 ast::Use(_it) => continue,
61 // Don't descend into submodules, they don't have the same `use` items in scope. 60 // Don't descend into submodules, they don't have the same `use` items in scope.
62 ast::Module(_it) => continue, 61 ast::Module(_it) => continue,
63 62 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
64 ast::Path(p) => { 63 shorten_paths(p.syntax(), path);
65 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
66 Some(()) => {},
67 None => shorten_paths(rewriter, p.syntax().clone(), path),
68 }
69 }, 64 },
70 _ => shorten_paths(rewriter, child, path), 65 _ => shorten_paths(&child, path),
71 } 66 }
72 } 67 }
73 } 68 }
74} 69}
75 70
76fn maybe_replace_path( 71fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
77 rewriter: &mut SyntaxRewriter<'static>,
78 path: ast::Path,
79 target: ast::Path,
80) -> Option<()> {
81 if !path_eq(path.clone(), target) { 72 if !path_eq(path.clone(), target) {
82 return None; 73 return None;
83 } 74 }
84 75
85 // Shorten `path`, leaving only its last segment. 76 // Shorten `path`, leaving only its last segment.
86 if let Some(parent) = path.qualifier() { 77 if let Some(parent) = path.qualifier() {
87 rewriter.delete(parent.syntax()); 78 ted::remove(parent.syntax());
88 } 79 }
89 if let Some(double_colon) = path.coloncolon_token() { 80 if let Some(double_colon) = path.coloncolon_token() {
90 rewriter.delete(&double_colon); 81 ted::remove(&double_colon);
91 } 82 }
92 83
93 Some(()) 84 Some(())
@@ -150,6 +141,7 @@ Debug
150 ", 141 ",
151 ); 142 );
152 } 143 }
144
153 #[test] 145 #[test]
154 fn test_replace_add_use_no_anchor_with_item_below() { 146 fn test_replace_add_use_no_anchor_with_item_below() {
155 check_assist( 147 check_assist(
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 8996c1b61..88ae5c9a9 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -120,6 +120,7 @@ mod handlers {
120 mod convert_comment_block; 120 mod convert_comment_block;
121 mod convert_iter_for_each_to_for; 121 mod convert_iter_for_each_to_for;
122 mod convert_into_to_from; 122 mod convert_into_to_from;
123 mod convert_tuple_struct_to_named_struct;
123 mod early_return; 124 mod early_return;
124 mod expand_glob_import; 125 mod expand_glob_import;
125 mod extract_function; 126 mod extract_function;
@@ -190,6 +191,7 @@ mod handlers {
190 convert_comment_block::convert_comment_block, 191 convert_comment_block::convert_comment_block,
191 convert_iter_for_each_to_for::convert_iter_for_each_to_for, 192 convert_iter_for_each_to_for::convert_iter_for_each_to_for,
192 convert_into_to_from::convert_into_to_from, 193 convert_into_to_from::convert_into_to_from,
194 convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
193 early_return::convert_to_guarded_return, 195 early_return::convert_to_guarded_return,
194 expand_glob_import::expand_glob_import, 196 expand_glob_import::expand_glob_import,
195 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 197 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 49533e7d2..6f4f97361 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -4,10 +4,7 @@ use expect_test::expect;
4use hir::Semantics; 4use hir::Semantics;
5use ide_db::{ 5use ide_db::{
6 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, 6 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
7 helpers::{ 7 helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap},
8 insert_use::{InsertUseConfig, MergeBehavior},
9 SnippetCap,
10 },
11 source_change::FileSystemEdit, 8 source_change::FileSystemEdit,
12 RootDatabase, 9 RootDatabase,
13}; 10};
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 41559b43a..59bcef8fb 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -292,6 +292,47 @@ fn main() {
292} 292}
293 293
294#[test] 294#[test]
295fn doctest_convert_tuple_struct_to_named_struct() {
296 check_doc_test(
297 "convert_tuple_struct_to_named_struct",
298 r#####"
299struct Point$0(f32, f32);
300
301impl Point {
302 pub fn new(x: f32, y: f32) -> Self {
303 Point(x, y)
304 }
305
306 pub fn x(&self) -> f32 {
307 self.0
308 }
309
310 pub fn y(&self) -> f32 {
311 self.1
312 }
313}
314"#####,
315 r#####"
316struct Point { field1: f32, field2: f32 }
317
318impl Point {
319 pub fn new(x: f32, y: f32) -> Self {
320 Point { field1: x, field2: y }
321 }
322
323 pub fn x(&self) -> f32 {
324 self.field1
325 }
326
327 pub fn y(&self) -> f32 {
328 self.field2
329 }
330}
331"#####,
332 )
333}
334
335#[test]
295fn doctest_expand_glob_import() { 336fn doctest_expand_glob_import() {
296 check_doc_test( 337 check_doc_test(
297 "expand_glob_import", 338 "expand_glob_import",
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index d67524937..5a90ad715 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -140,7 +140,8 @@ pub fn add_trait_assoc_items_to_impl(
140 140
141 let items = items 141 let items = items
142 .into_iter() 142 .into_iter()
143 .map(|it| ast_transform::apply(&*ast_transform, it)) 143 .map(|it| it.clone_for_update())
144 .inspect(|it| ast_transform::apply(&*ast_transform, it))
144 .map(|it| match it { 145 .map(|it| match it {
145 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), 146 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
146 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), 147 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 8e211ae1e..9d5b61562 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -132,7 +132,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
132 132
133 let user_input_lowercased = potential_import_name.to_lowercase(); 133 let user_input_lowercased = potential_import_name.to_lowercase();
134 let import_assets = import_assets(ctx, potential_import_name)?; 134 let import_assets = import_assets(ctx, potential_import_name)?;
135 let import_scope = ImportScope::find_insert_use_container( 135 let import_scope = ImportScope::find_insert_use_container_with_macros(
136 position_for_import(ctx, Some(import_assets.import_candidate()))?, 136 position_for_import(ctx, Some(import_assets.import_candidate()))?,
137 &ctx.sema, 137 &ctx.sema,
138 )?; 138 )?;
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 16991b688..99edb9499 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -377,11 +377,11 @@ impl ImportEdit {
377 pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> { 377 pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
378 let _p = profile::span("ImportEdit::to_text_edit"); 378 let _p = profile::span("ImportEdit::to_text_edit");
379 379
380 let rewriter = 380 let new_ast = self.scope.clone_for_update();
381 insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg); 381 insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), cfg);
382 let old_ast = rewriter.rewrite_root()?;
383 let mut import_insert = TextEdit::builder(); 382 let mut import_insert = TextEdit::builder();
384 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); 383 algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
384 .into_text_edit(&mut import_insert);
385 385
386 Some(import_insert.finish()) 386 Some(import_insert.finish())
387 } 387 }
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 6f3d5c5c5..e32633565 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -179,7 +179,7 @@ pub fn resolve_completion_edits(
179) -> Option<Vec<TextEdit>> { 179) -> Option<Vec<TextEdit>> {
180 let ctx = CompletionContext::new(db, position, config)?; 180 let ctx = CompletionContext::new(db, position, config)?;
181 let position_for_import = position_for_import(&ctx, None)?; 181 let position_for_import = position_for_import(&ctx, None)?;
182 let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; 182 let scope = ImportScope::find_insert_use_container_with_macros(position_for_import, &ctx.sema)?;
183 183
184 let current_module = ctx.sema.scope(position_for_import).module()?; 184 let current_module = ctx.sema.scope(position_for_import).module()?;
185 let current_crate = current_module.krate(); 185 let current_crate = current_module.krate();
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index 9da844031..c9857ec5f 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -3,10 +3,7 @@
3use hir::{PrefixKind, Semantics}; 3use hir::{PrefixKind, Semantics};
4use ide_db::{ 4use ide_db::{
5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, 5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
6 helpers::{ 6 helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap},
7 insert_use::{InsertUseConfig, MergeBehavior},
8 SnippetCap,
9 },
10 RootDatabase, 7 RootDatabase,
11}; 8};
12use itertools::Itertools; 9use itertools::Itertools;
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 720de0d1f..21b48237a 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -1,6 +1,7 @@
1//! A module with ide helpers for high-level ide features. 1//! A module with ide helpers for high-level ide features.
2pub mod insert_use;
3pub mod import_assets; 2pub mod import_assets;
3pub mod insert_use;
4pub mod merge_imports;
4pub mod rust_doc; 5pub mod rust_doc;
5 6
6use std::collections::VecDeque; 7use std::collections::VecDeque;
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index be3a22725..55cdc4da3 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -1,19 +1,17 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2use std::{cmp::Ordering, iter::successors}; 2use std::cmp::Ordering;
3 3
4use hir::Semantics; 4use hir::Semantics;
5use itertools::{EitherOrBoth, Itertools};
6use syntax::{ 5use syntax::{
7 algo::SyntaxRewriter, 6 algo,
8 ast::{ 7 ast::{self, make, AstNode, PathSegmentKind},
9 self, 8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
10 edit::{AstNodeEdit, IndentLevel},
11 make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner,
12 },
13 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
14}; 9};
15 10
16use crate::RootDatabase; 11use crate::{
12 helpers::merge_imports::{try_merge_imports, use_tree_path_cmp, MergeBehavior},
13 RootDatabase,
14};
17 15
18pub use hir::PrefixKind; 16pub use hir::PrefixKind;
19 17
@@ -42,13 +40,18 @@ impl ImportScope {
42 } 40 }
43 41
44 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. 42 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
45 pub fn find_insert_use_container( 43 pub fn find_insert_use_container_with_macros(
46 position: &SyntaxNode, 44 position: &SyntaxNode,
47 sema: &Semantics<'_, RootDatabase>, 45 sema: &Semantics<'_, RootDatabase>,
48 ) -> Option<Self> { 46 ) -> Option<Self> {
49 sema.ancestors_with_macros(position.clone()).find_map(Self::from) 47 sema.ancestors_with_macros(position.clone()).find_map(Self::from)
50 } 48 }
51 49
50 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
51 pub fn find_insert_use_container(position: &SyntaxNode) -> Option<Self> {
52 std::iter::successors(Some(position.clone()), SyntaxNode::parent).find_map(Self::from)
53 }
54
52 pub fn as_syntax_node(&self) -> &SyntaxNode { 55 pub fn as_syntax_node(&self) -> &SyntaxNode {
53 match self { 56 match self {
54 ImportScope::File(file) => file.syntax(), 57 ImportScope::File(file) => file.syntax(),
@@ -56,434 +59,32 @@ impl ImportScope {
56 } 59 }
57 } 60 }
58 61
59 fn indent_level(&self) -> IndentLevel { 62 pub fn clone_for_update(&self) -> Self {
60 match self { 63 match self {
61 ImportScope::File(file) => file.indent_level(), 64 ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
62 ImportScope::Module(item_list) => item_list.indent_level() + 1, 65 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
63 } 66 }
64 } 67 }
65
66 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
67 match self {
68 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
69 // don't insert the imports before the item list's opening curly brace
70 ImportScope::Module(item_list) => item_list
71 .l_curly_token()
72 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
73 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
74 }
75 }
76
77 fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
78 self.as_syntax_node()
79 .children_with_tokens()
80 .filter(|child| match child {
81 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
82 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
83 })
84 .last()
85 .map(|last_inner_element| {
86 (InsertPosition::After(last_inner_element), AddBlankLine::BeforeTwice)
87 })
88 .unwrap_or_else(|| self.first_insert_pos())
89 }
90}
91
92fn is_inner_attribute(node: SyntaxNode) -> bool {
93 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
94}
95
96fn is_inner_comment(token: SyntaxToken) -> bool {
97 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
98 == Some(ast::CommentPlacement::Inner)
99} 68}
100 69
101/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 70/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
102pub fn insert_use<'a>( 71pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
103 scope: &ImportScope,
104 path: ast::Path,
105 cfg: InsertUseConfig,
106) -> SyntaxRewriter<'a> {
107 let _p = profile::span("insert_use"); 72 let _p = profile::span("insert_use");
108 let mut rewriter = SyntaxRewriter::default(); 73 let use_item =
109 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); 74 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
110 // merge into existing imports if possible 75 // merge into existing imports if possible
111 if let Some(mb) = cfg.merge { 76 if let Some(mb) = cfg.merge {
112 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 77 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
113 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 78 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
114 rewriter.replace(existing_use.syntax(), merged.syntax()); 79 ted::replace(existing_use.syntax(), merged.syntax());
115 return rewriter; 80 return;
116 } 81 }
117 } 82 }
118 } 83 }
119 84
120 // either we weren't allowed to merge or there is no import that fits the merge conditions 85 // either we weren't allowed to merge or there is no import that fits the merge conditions
121 // so look for the place we have to insert to 86 // so look for the place we have to insert to
122 let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group); 87 insert_use_(scope, path, cfg.group, use_item);
123
124 let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
125 Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
126 } else {
127 None
128 };
129
130 let to_insert: Vec<SyntaxElement> = {
131 let mut buf = Vec::new();
132
133 match add_blank {
134 AddBlankLine::Before | AddBlankLine::Around => {
135 buf.push(make::tokens::single_newline().into())
136 }
137 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
138 _ => (),
139 }
140
141 if add_blank.has_before() {
142 if let Some(indent) = indent.clone() {
143 cov_mark::hit!(insert_use_indent_before);
144 buf.push(indent);
145 }
146 }
147
148 buf.push(use_item.syntax().clone().into());
149
150 match add_blank {
151 AddBlankLine::After | AddBlankLine::Around => {
152 buf.push(make::tokens::single_newline().into())
153 }
154 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
155 _ => (),
156 }
157
158 // only add indentation *after* our stuff if there's another node directly after it
159 if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
160 if let Some(indent) = indent {
161 cov_mark::hit!(insert_use_indent_after);
162 buf.push(indent);
163 }
164 } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
165 cov_mark::hit!(insert_use_no_indent_after);
166 }
167
168 buf
169 };
170
171 match insert_position {
172 InsertPosition::First => {
173 rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
174 }
175 InsertPosition::Last => return rewriter, // actually unreachable
176 InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
177 InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
178 }
179 rewriter
180}
181
182fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
183 match (vis0, vis1) {
184 (None, None) => true,
185 // FIXME: Don't use the string representation to check for equality
186 // spaces inside of the node would break this comparison
187 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
188 _ => false,
189 }
190}
191
192fn eq_attrs(
193 attrs0: impl Iterator<Item = ast::Attr>,
194 attrs1: impl Iterator<Item = ast::Attr>,
195) -> bool {
196 let attrs0 = attrs0.map(|attr| attr.to_string());
197 let attrs1 = attrs1.map(|attr| attr.to_string());
198 attrs0.eq(attrs1)
199}
200
201pub fn try_merge_imports(
202 lhs: &ast::Use,
203 rhs: &ast::Use,
204 merge_behavior: MergeBehavior,
205) -> Option<ast::Use> {
206 // don't merge imports with different visibilities
207 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
208 return None;
209 }
210 if !eq_attrs(lhs.attrs(), rhs.attrs()) {
211 return None;
212 }
213
214 let lhs_tree = lhs.use_tree()?;
215 let rhs_tree = rhs.use_tree()?;
216 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
217 Some(lhs.with_use_tree(merged).clone_for_update())
218}
219
220pub fn try_merge_trees(
221 lhs: &ast::UseTree,
222 rhs: &ast::UseTree,
223 merge: MergeBehavior,
224) -> Option<ast::UseTree> {
225 let lhs_path = lhs.path()?;
226 let rhs_path = rhs.path()?;
227
228 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
229 let (lhs, rhs) = if is_simple_path(lhs)
230 && is_simple_path(rhs)
231 && lhs_path == lhs_prefix
232 && rhs_path == rhs_prefix
233 {
234 (lhs.clone(), rhs.clone())
235 } else {
236 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
237 };
238 recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
239}
240
241/// Recursively "zips" together lhs and rhs.
242fn recursive_merge(
243 lhs: &ast::UseTree,
244 rhs: &ast::UseTree,
245 merge: MergeBehavior,
246) -> Option<ast::UseTree> {
247 let mut use_trees = lhs
248 .use_tree_list()
249 .into_iter()
250 .flat_map(|list| list.use_trees())
251 // we use Option here to early return from this function(this is not the same as a `filter` op)
252 .map(|tree| match merge.is_tree_allowed(&tree) {
253 true => Some(tree),
254 false => None,
255 })
256 .collect::<Option<Vec<_>>>()?;
257 use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
258 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
259 if !merge.is_tree_allowed(&rhs_t) {
260 return None;
261 }
262 let rhs_path = rhs_t.path();
263 match use_trees.binary_search_by(|lhs_t| {
264 let (lhs_t, rhs_t) = match lhs_t
265 .path()
266 .zip(rhs_path.clone())
267 .and_then(|(lhs, rhs)| common_prefix(&lhs, &rhs))
268 {
269 Some((lhs_p, rhs_p)) => (lhs_t.split_prefix(&lhs_p), rhs_t.split_prefix(&rhs_p)),
270 None => (lhs_t.clone(), rhs_t.clone()),
271 };
272
273 path_cmp_bin_search(lhs_t.path(), rhs_t.path())
274 }) {
275 Ok(idx) => {
276 let lhs_t = &mut use_trees[idx];
277 let lhs_path = lhs_t.path()?;
278 let rhs_path = rhs_path?;
279 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
280 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
281 let tree_is_self = |tree: ast::UseTree| {
282 tree.path().as_ref().map(path_is_self).unwrap_or(false)
283 };
284 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
285 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self`
286 let tree_contains_self = |tree: &ast::UseTree| {
287 tree.use_tree_list()
288 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
289 .unwrap_or(false)
290 };
291 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
292 (true, false) => continue,
293 (false, true) => {
294 *lhs_t = rhs_t;
295 continue;
296 }
297 _ => (),
298 }
299
300 // glob imports arent part of the use-tree lists so we need to special handle them here as well
301 // this special handling is only required for when we merge a module import into a glob import of said module
302 // see the `merge_self_glob` or `merge_mod_into_glob` tests
303 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
304 *lhs_t = make::use_tree(
305 make::path_unqualified(make::path_segment_self()),
306 None,
307 None,
308 false,
309 );
310 use_trees.insert(idx, make::glob_use_tree());
311 continue;
312 }
313
314 if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
315 continue;
316 }
317 }
318 let lhs = lhs_t.split_prefix(&lhs_prefix);
319 let rhs = rhs_t.split_prefix(&rhs_prefix);
320 match recursive_merge(&lhs, &rhs, merge) {
321 Some(use_tree) => use_trees[idx] = use_tree,
322 None => return None,
323 }
324 }
325 Err(_)
326 if merge == MergeBehavior::Last
327 && use_trees.len() > 0
328 && rhs_t.use_tree_list().is_some() =>
329 {
330 return None
331 }
332 Err(idx) => {
333 use_trees.insert(idx, rhs_t);
334 }
335 }
336 }
337 Some(lhs.with_use_tree_list(make::use_tree_list(use_trees)))
338}
339
340/// Traverses both paths until they differ, returning the common prefix of both.
341fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
342 let mut res = None;
343 let mut lhs_curr = first_path(&lhs);
344 let mut rhs_curr = first_path(&rhs);
345 loop {
346 match (lhs_curr.segment(), rhs_curr.segment()) {
347 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
348 _ => break res,
349 }
350 res = Some((lhs_curr.clone(), rhs_curr.clone()));
351
352 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
353 Some((lhs, rhs)) => {
354 lhs_curr = lhs;
355 rhs_curr = rhs;
356 }
357 _ => break res,
358 }
359 }
360}
361
362fn is_simple_path(use_tree: &ast::UseTree) -> bool {
363 use_tree.use_tree_list().is_none() && use_tree.star_token().is_none()
364}
365
366fn path_is_self(path: &ast::Path) -> bool {
367 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
368}
369
370#[inline]
371fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
372 first_path(path).segment()
373}
374
375fn first_path(path: &ast::Path) -> ast::Path {
376 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
377}
378
379fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
380 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
381 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
382}
383
384fn path_len(path: ast::Path) -> usize {
385 segment_iter(&path).count()
386}
387
388/// Orders paths in the following way:
389/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
390// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
391// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
392// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
393fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
394 match (a, b) {
395 (None, None) => Ordering::Equal,
396 (None, Some(_)) => Ordering::Less,
397 (Some(_), None) => Ordering::Greater,
398 (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
399 (true, true) => Ordering::Equal,
400 (true, false) => Ordering::Less,
401 (false, true) => Ordering::Greater,
402 (false, false) => path_cmp_short(a, b),
403 },
404 }
405}
406
407/// Path comparison func for binary searching for merging.
408fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
409 match (lhs.as_ref().and_then(first_segment), rhs.as_ref().and_then(first_segment)) {
410 (None, None) => Ordering::Equal,
411 (None, Some(_)) => Ordering::Less,
412 (Some(_), None) => Ordering::Greater,
413 (Some(ref a), Some(ref b)) => path_segment_cmp(a, b),
414 }
415}
416
417/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
418/// equal
419fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
420 let a = segment_iter(a);
421 let b = segment_iter(b);
422 // cmp_by would be useful for us here but that is currently unstable
423 // cmp doesnt work due the lifetimes on text's return type
424 a.zip(b)
425 .find_map(|(a, b)| match path_segment_cmp(&a, &b) {
426 Ordering::Equal => None,
427 ord => Some(ord),
428 })
429 .unwrap_or(Ordering::Equal)
430}
431
432/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is
433/// greater as a a path that has a tree list should be greater, while one that just ends without
434/// a tree list should be considered less.
435fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering {
436 let a_segments = segment_iter(a);
437 let b_segments = segment_iter(b);
438 // cmp_by would be useful for us here but that is currently unstable
439 // cmp doesnt work due the lifetimes on text's return type
440 a_segments
441 .zip_longest(b_segments)
442 .find_map(|zipped| match zipped {
443 EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
444 Ordering::Equal => None,
445 ord => Some(ord),
446 },
447 EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
448 EitherOrBoth::Left(_) => Some(Ordering::Less),
449 EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
450 EitherOrBoth::Right(_) => Some(Ordering::Greater),
451 })
452 .unwrap_or(Ordering::Equal)
453}
454
455fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
456 let a = a.kind().and_then(|kind| match kind {
457 PathSegmentKind::Name(name_ref) => Some(name_ref),
458 _ => None,
459 });
460 let b = b.kind().and_then(|kind| match kind {
461 PathSegmentKind::Name(name_ref) => Some(name_ref),
462 _ => None,
463 });
464 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
465}
466
467/// What type of merges are allowed.
468#[derive(Copy, Clone, Debug, PartialEq, Eq)]
469pub enum MergeBehavior {
470 /// Merge everything together creating deeply nested imports.
471 Full,
472 /// Only merge the last import level, doesn't allow import nesting.
473 Last,
474}
475
476impl MergeBehavior {
477 #[inline]
478 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
479 match self {
480 MergeBehavior::Full => true,
481 // only simple single segment paths are allowed
482 MergeBehavior::Last => {
483 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
484 }
485 }
486 }
487} 88}
488 89
489#[derive(Eq, PartialEq, PartialOrd, Ord)] 90#[derive(Eq, PartialEq, PartialOrd, Ord)]
@@ -500,7 +101,7 @@ impl ImportGroup {
500 fn new(path: &ast::Path) -> ImportGroup { 101 fn new(path: &ast::Path) -> ImportGroup {
501 let default = ImportGroup::ExternCrate; 102 let default = ImportGroup::ExternCrate;
502 103
503 let first_segment = match first_segment(path) { 104 let first_segment = match path.first_segment() {
504 Some(it) => it, 105 Some(it) => it,
505 None => return default, 106 None => return default,
506 }; 107 };
@@ -520,32 +121,15 @@ impl ImportGroup {
520 } 121 }
521} 122}
522 123
523#[derive(PartialEq, Eq)] 124fn insert_use_(
524enum AddBlankLine {
525 Before,
526 BeforeTwice,
527 Around,
528 After,
529 AfterTwice,
530}
531
532impl AddBlankLine {
533 fn has_before(&self) -> bool {
534 matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around)
535 }
536 fn has_after(&self) -> bool {
537 matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around)
538 }
539}
540
541fn find_insert_position(
542 scope: &ImportScope, 125 scope: &ImportScope,
543 insert_path: ast::Path, 126 insert_path: ast::Path,
544 group_imports: bool, 127 group_imports: bool,
545) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 128 use_item: ast::Use,
129) {
130 let scope_syntax = scope.as_syntax_node();
546 let group = ImportGroup::new(&insert_path); 131 let group = ImportGroup::new(&insert_path);
547 let path_node_iter = scope 132 let path_node_iter = scope_syntax
548 .as_syntax_node()
549 .children() 133 .children()
550 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) 134 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
551 .flat_map(|(use_, node)| { 135 .flat_map(|(use_, node)| {
@@ -557,9 +141,14 @@ fn find_insert_position(
557 141
558 if !group_imports { 142 if !group_imports {
559 if let Some((_, _, node)) = path_node_iter.last() { 143 if let Some((_, _, node)) = path_node_iter.last() {
560 return (InsertPosition::After(node.into()), AddBlankLine::Before); 144 cov_mark::hit!(insert_no_grouping_last);
145 ted::insert(ted::Position::after(node), use_item.syntax());
146 } else {
147 cov_mark::hit!(insert_no_grouping_last2);
148 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
149 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
561 } 150 }
562 return (InsertPosition::First, AddBlankLine::AfterTwice); 151 return;
563 } 152 }
564 153
565 // Iterator that discards anything thats not in the required grouping 154 // Iterator that discards anything thats not in the required grouping
@@ -572,43 +161,91 @@ fn find_insert_position(
572 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place 161 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
573 let mut last = None; 162 let mut last = None;
574 // find the element that would come directly after our new import 163 // find the element that would come directly after our new import
575 let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find( 164 let post_insert: Option<(_, _, SyntaxNode)> = group_iter
576 |&(ref path, has_tl, _)| { 165 .inspect(|(.., node)| last = Some(node.clone()))
166 .find(|&(ref path, has_tl, _)| {
577 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater 167 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
578 }, 168 });
579 );
580 169
581 match post_insert { 170 if let Some((.., node)) = post_insert {
171 cov_mark::hit!(insert_group);
582 // insert our import before that element 172 // insert our import before that element
583 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), 173 return ted::insert(ted::Position::before(node), use_item.syntax());
174 }
175 if let Some(node) = last {
176 cov_mark::hit!(insert_group_last);
584 // there is no element after our new import, so append it to the end of the group 177 // there is no element after our new import, so append it to the end of the group
585 None => match last { 178 return ted::insert(ted::Position::after(node), use_item.syntax());
586 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), 179 }
587 // the group we were looking for actually doesnt exist, so insert 180
181 // the group we were looking for actually doesn't exist, so insert
182
183 let mut last = None;
184 // find the group that comes after where we want to insert
185 let post_group = path_node_iter
186 .inspect(|(.., node)| last = Some(node.clone()))
187 .find(|(p, ..)| ImportGroup::new(p) > group);
188 if let Some((.., node)) = post_group {
189 cov_mark::hit!(insert_group_new_group);
190 ted::insert(ted::Position::before(&node), use_item.syntax());
191 if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
192 ted::insert(ted::Position::after(node), make::tokens::single_newline());
193 }
194 return;
195 }
196 // there is no such group, so append after the last one
197 if let Some(node) = last {
198 cov_mark::hit!(insert_group_no_group);
199 ted::insert(ted::Position::after(&node), use_item.syntax());
200 ted::insert(ted::Position::after(node), make::tokens::single_newline());
201 return;
202 }
203 // there are no imports in this file at all
204 if let Some(last_inner_element) = scope_syntax
205 .children_with_tokens()
206 .filter(|child| match child {
207 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
208 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
209 })
210 .last()
211 {
212 cov_mark::hit!(insert_group_empty_inner_attr);
213 ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
214 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
215 return;
216 }
217 match scope {
218 ImportScope::File(_) => {
219 cov_mark::hit!(insert_group_empty_file);
220 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
221 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
222 }
223 // don't insert the imports before the item list's opening curly brace
224 ImportScope::Module(item_list) => match item_list.l_curly_token() {
225 Some(b) => {
226 cov_mark::hit!(insert_group_empty_module);
227 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
228 ted::insert(ted::Position::after(&b), use_item.syntax());
229 }
588 None => { 230 None => {
589 // similar concept here to the `last` from above 231 // This should never happens, broken module syntax node
590 let mut last = None; 232 ted::insert(
591 // find the group that comes after where we want to insert 233 ted::Position::first_child_of(scope_syntax),
592 let post_group = path_node_iter 234 make::tokens::blank_line(),
593 .inspect(|(.., node)| last = Some(node.clone())) 235 );
594 .find(|(p, ..)| ImportGroup::new(p) > group); 236 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
595 match post_group {
596 Some((.., node)) => {
597 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
598 }
599 // there is no such group, so append after the last one
600 None => match last {
601 Some(node) => {
602 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
603 }
604 // there are no imports in this file at all
605 None => scope.insert_pos_after_last_inner_element(),
606 },
607 }
608 } 237 }
609 }, 238 },
610 } 239 }
611} 240}
612 241
242fn is_inner_attribute(node: SyntaxNode) -> bool {
243 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
244}
245
246fn is_inner_comment(token: SyntaxToken) -> bool {
247 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
248 == Some(ast::CommentPlacement::Inner)
249}
613#[cfg(test)] 250#[cfg(test)]
614mod tests; 251mod tests;
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 3d151e629..048c213e2 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -5,6 +5,7 @@ use test_utils::assert_eq_text;
5 5
6#[test] 6#[test]
7fn insert_not_group() { 7fn insert_not_group() {
8 cov_mark::check!(insert_no_grouping_last);
8 check( 9 check(
9 "use external_crate2::bar::A", 10 "use external_crate2::bar::A",
10 r" 11 r"
@@ -27,6 +28,21 @@ use external_crate2::bar::A;",
27} 28}
28 29
29#[test] 30#[test]
31fn insert_not_group_empty() {
32 cov_mark::check!(insert_no_grouping_last2);
33 check(
34 "use external_crate2::bar::A",
35 r"",
36 r"use external_crate2::bar::A;
37
38",
39 None,
40 false,
41 false,
42 );
43}
44
45#[test]
30fn insert_existing() { 46fn insert_existing() {
31 check_full("std::fs", "use std::fs;", "use std::fs;") 47 check_full("std::fs", "use std::fs;", "use std::fs;")
32} 48}
@@ -51,21 +67,21 @@ use std::bar::G;",
51 67
52#[test] 68#[test]
53fn insert_start_indent() { 69fn insert_start_indent() {
54 cov_mark::check!(insert_use_indent_after);
55 check_none( 70 check_none(
56 "std::bar::AA", 71 "std::bar::AA",
57 r" 72 r"
58 use std::bar::B; 73 use std::bar::B;
59 use std::bar::D;", 74 use std::bar::C;",
60 r" 75 r"
61 use std::bar::AA; 76 use std::bar::AA;
62 use std::bar::B; 77 use std::bar::B;
63 use std::bar::D;", 78 use std::bar::C;",
64 ) 79 );
65} 80}
66 81
67#[test] 82#[test]
68fn insert_middle() { 83fn insert_middle() {
84 cov_mark::check!(insert_group);
69 check_none( 85 check_none(
70 "std::bar::EE", 86 "std::bar::EE",
71 r" 87 r"
@@ -102,6 +118,7 @@ fn insert_middle_indent() {
102 118
103#[test] 119#[test]
104fn insert_end() { 120fn insert_end() {
121 cov_mark::check!(insert_group_last);
105 check_none( 122 check_none(
106 "std::bar::ZZ", 123 "std::bar::ZZ",
107 r" 124 r"
@@ -120,7 +137,6 @@ use std::bar::ZZ;",
120 137
121#[test] 138#[test]
122fn insert_end_indent() { 139fn insert_end_indent() {
123 cov_mark::check!(insert_use_indent_before);
124 check_none( 140 check_none(
125 "std::bar::ZZ", 141 "std::bar::ZZ",
126 r" 142 r"
@@ -201,6 +217,7 @@ fn insert_first_matching_group() {
201 217
202#[test] 218#[test]
203fn insert_missing_group_std() { 219fn insert_missing_group_std() {
220 cov_mark::check!(insert_group_new_group);
204 check_none( 221 check_none(
205 "std::fmt", 222 "std::fmt",
206 r" 223 r"
@@ -216,6 +233,7 @@ fn insert_missing_group_std() {
216 233
217#[test] 234#[test]
218fn insert_missing_group_self() { 235fn insert_missing_group_self() {
236 cov_mark::check!(insert_group_no_group);
219 check_none( 237 check_none(
220 "self::fmt", 238 "self::fmt",
221 r" 239 r"
@@ -242,6 +260,7 @@ fn main() {}",
242 260
243#[test] 261#[test]
244fn insert_empty_file() { 262fn insert_empty_file() {
263 cov_mark::check!(insert_group_empty_file);
245 // empty files will get two trailing newlines 264 // empty files will get two trailing newlines
246 // this is due to the test case insert_no_imports above 265 // this is due to the test case insert_no_imports above
247 check_full( 266 check_full(
@@ -255,7 +274,7 @@ fn insert_empty_file() {
255 274
256#[test] 275#[test]
257fn insert_empty_module() { 276fn insert_empty_module() {
258 cov_mark::check!(insert_use_no_indent_after); 277 cov_mark::check!(insert_group_empty_module);
259 check( 278 check(
260 "foo::bar", 279 "foo::bar",
261 "mod x {}", 280 "mod x {}",
@@ -270,6 +289,7 @@ fn insert_empty_module() {
270 289
271#[test] 290#[test]
272fn insert_after_inner_attr() { 291fn insert_after_inner_attr() {
292 cov_mark::check!(insert_group_empty_inner_attr);
273 check_full( 293 check_full(
274 "foo::bar", 294 "foo::bar",
275 r"#![allow(unused_imports)]", 295 r"#![allow(unused_imports)]",
@@ -615,7 +635,7 @@ fn check(
615 if module { 635 if module {
616 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); 636 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
617 } 637 }
618 let file = super::ImportScope::from(syntax).unwrap(); 638 let file = super::ImportScope::from(syntax.clone_for_update()).unwrap();
619 let path = ast::SourceFile::parse(&format!("use {};", path)) 639 let path = ast::SourceFile::parse(&format!("use {};", path))
620 .tree() 640 .tree()
621 .syntax() 641 .syntax()
@@ -623,12 +643,8 @@ fn check(
623 .find_map(ast::Path::cast) 643 .find_map(ast::Path::cast)
624 .unwrap(); 644 .unwrap();
625 645
626 let rewriter = insert_use( 646 insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group });
627 &file, 647 let result = file.as_syntax_node().to_string();
628 path,
629 InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
630 );
631 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
632 assert_eq_text!(ra_fixture_after, &result); 648 assert_eq_text!(ra_fixture_after, &result);
633} 649}
634 650
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
new file mode 100644
index 000000000..3f5bbef7f
--- /dev/null
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -0,0 +1,309 @@
1//! Handle syntactic aspects of merging UseTrees.
2use std::cmp::Ordering;
3
4use itertools::{EitherOrBoth, Itertools};
5use syntax::ast::{
6 self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner,
7};
8
9/// What type of merges are allowed.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum MergeBehavior {
12 /// Merge everything together creating deeply nested imports.
13 Full,
14 /// Only merge the last import level, doesn't allow import nesting.
15 Last,
16}
17
18impl MergeBehavior {
19 #[inline]
20 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
21 match self {
22 MergeBehavior::Full => true,
23 // only simple single segment paths are allowed
24 MergeBehavior::Last => {
25 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
26 }
27 }
28 }
29}
30
31pub fn try_merge_imports(
32 lhs: &ast::Use,
33 rhs: &ast::Use,
34 merge_behavior: MergeBehavior,
35) -> Option<ast::Use> {
36 // don't merge imports with different visibilities
37 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
38 return None;
39 }
40 if !eq_attrs(lhs.attrs(), rhs.attrs()) {
41 return None;
42 }
43
44 let lhs_tree = lhs.use_tree()?;
45 let rhs_tree = rhs.use_tree()?;
46 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
47 Some(lhs.with_use_tree(merged).clone_for_update())
48}
49
50pub fn try_merge_trees(
51 lhs: &ast::UseTree,
52 rhs: &ast::UseTree,
53 merge: MergeBehavior,
54) -> Option<ast::UseTree> {
55 let lhs_path = lhs.path()?;
56 let rhs_path = rhs.path()?;
57
58 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
59 let (lhs, rhs) = if lhs.is_simple_path()
60 && rhs.is_simple_path()
61 && lhs_path == lhs_prefix
62 && rhs_path == rhs_prefix
63 {
64 (lhs.clone(), rhs.clone())
65 } else {
66 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
67 };
68 recursive_merge(&lhs, &rhs, merge)
69}
70
71/// Recursively "zips" together lhs and rhs.
72fn recursive_merge(
73 lhs: &ast::UseTree,
74 rhs: &ast::UseTree,
75 merge: MergeBehavior,
76) -> Option<ast::UseTree> {
77 let mut use_trees = lhs
78 .use_tree_list()
79 .into_iter()
80 .flat_map(|list| list.use_trees())
81 // we use Option here to early return from this function(this is not the same as a `filter` op)
82 .map(|tree| match merge.is_tree_allowed(&tree) {
83 true => Some(tree),
84 false => None,
85 })
86 .collect::<Option<Vec<_>>>()?;
87 use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
88 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
89 if !merge.is_tree_allowed(&rhs_t) {
90 return None;
91 }
92 let rhs_path = rhs_t.path();
93 match use_trees.binary_search_by(|lhs_t| {
94 let (lhs_t, rhs_t) = match lhs_t
95 .path()
96 .zip(rhs_path.clone())
97 .and_then(|(lhs, rhs)| common_prefix(&lhs, &rhs))
98 {
99 Some((lhs_p, rhs_p)) => (lhs_t.split_prefix(&lhs_p), rhs_t.split_prefix(&rhs_p)),
100 None => (lhs_t.clone(), rhs_t.clone()),
101 };
102
103 path_cmp_bin_search(lhs_t.path(), rhs_t.path())
104 }) {
105 Ok(idx) => {
106 let lhs_t = &mut use_trees[idx];
107 let lhs_path = lhs_t.path()?;
108 let rhs_path = rhs_path?;
109 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
110 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
111 let tree_is_self = |tree: ast::UseTree| {
112 tree.path().as_ref().map(path_is_self).unwrap_or(false)
113 };
114 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
115 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self`
116 let tree_contains_self = |tree: &ast::UseTree| {
117 tree.use_tree_list()
118 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
119 .unwrap_or(false)
120 };
121 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
122 (true, false) => continue,
123 (false, true) => {
124 *lhs_t = rhs_t;
125 continue;
126 }
127 _ => (),
128 }
129
130 // glob imports arent part of the use-tree lists so we need to special handle them here as well
131 // this special handling is only required for when we merge a module import into a glob import of said module
132 // see the `merge_self_glob` or `merge_mod_into_glob` tests
133 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
134 *lhs_t = make::use_tree(
135 make::path_unqualified(make::path_segment_self()),
136 None,
137 None,
138 false,
139 );
140 use_trees.insert(idx, make::glob_use_tree());
141 continue;
142 }
143
144 if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
145 continue;
146 }
147 }
148 let lhs = lhs_t.split_prefix(&lhs_prefix);
149 let rhs = rhs_t.split_prefix(&rhs_prefix);
150 match recursive_merge(&lhs, &rhs, merge) {
151 Some(use_tree) => use_trees[idx] = use_tree,
152 None => return None,
153 }
154 }
155 Err(_)
156 if merge == MergeBehavior::Last
157 && use_trees.len() > 0
158 && rhs_t.use_tree_list().is_some() =>
159 {
160 return None
161 }
162 Err(idx) => {
163 use_trees.insert(idx, rhs_t);
164 }
165 }
166 }
167
168 Some(if let Some(old) = lhs.use_tree_list() {
169 lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update()
170 } else {
171 lhs.clone()
172 })
173}
174
175/// Traverses both paths until they differ, returning the common prefix of both.
176fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
177 let mut res = None;
178 let mut lhs_curr = lhs.first_qualifier_or_self();
179 let mut rhs_curr = rhs.first_qualifier_or_self();
180 loop {
181 match (lhs_curr.segment(), rhs_curr.segment()) {
182 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
183 _ => break res,
184 }
185 res = Some((lhs_curr.clone(), rhs_curr.clone()));
186
187 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
188 Some((lhs, rhs)) => {
189 lhs_curr = lhs;
190 rhs_curr = rhs;
191 }
192 _ => break res,
193 }
194 }
195}
196
197/// Orders paths in the following way:
198/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
199// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
200// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
201// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
202fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
203 match (a, b) {
204 (None, None) => Ordering::Equal,
205 (None, Some(_)) => Ordering::Less,
206 (Some(_), None) => Ordering::Greater,
207 (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
208 (true, true) => Ordering::Equal,
209 (true, false) => Ordering::Less,
210 (false, true) => Ordering::Greater,
211 (false, false) => path_cmp_short(a, b),
212 },
213 }
214}
215
216/// Path comparison func for binary searching for merging.
217fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
218 match (
219 lhs.as_ref().and_then(ast::Path::first_segment),
220 rhs.as_ref().and_then(ast::Path::first_segment),
221 ) {
222 (None, None) => Ordering::Equal,
223 (None, Some(_)) => Ordering::Less,
224 (Some(_), None) => Ordering::Greater,
225 (Some(ref a), Some(ref b)) => path_segment_cmp(a, b),
226 }
227}
228
229/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
230/// equal
231fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
232 let a = a.segments();
233 let b = b.segments();
234 // cmp_by would be useful for us here but that is currently unstable
235 // cmp doesn't work due the lifetimes on text's return type
236 a.zip(b)
237 .find_map(|(a, b)| match path_segment_cmp(&a, &b) {
238 Ordering::Equal => None,
239 ord => Some(ord),
240 })
241 .unwrap_or(Ordering::Equal)
242}
243
244/// Compares two paths, if one ends earlier than the other the has_tl parameters decide which is
245/// greater as a a path that has a tree list should be greater, while one that just ends without
246/// a tree list should be considered less.
247pub(super) fn use_tree_path_cmp(
248 a: &ast::Path,
249 a_has_tl: bool,
250 b: &ast::Path,
251 b_has_tl: bool,
252) -> Ordering {
253 let a_segments = a.segments();
254 let b_segments = b.segments();
255 // cmp_by would be useful for us here but that is currently unstable
256 // cmp doesn't work due the lifetimes on text's return type
257 a_segments
258 .zip_longest(b_segments)
259 .find_map(|zipped| match zipped {
260 EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
261 Ordering::Equal => None,
262 ord => Some(ord),
263 },
264 EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
265 EitherOrBoth::Left(_) => Some(Ordering::Less),
266 EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
267 EitherOrBoth::Right(_) => Some(Ordering::Greater),
268 })
269 .unwrap_or(Ordering::Equal)
270}
271
272fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
273 let a = a.kind().and_then(|kind| match kind {
274 PathSegmentKind::Name(name_ref) => Some(name_ref),
275 _ => None,
276 });
277 let b = b.kind().and_then(|kind| match kind {
278 PathSegmentKind::Name(name_ref) => Some(name_ref),
279 _ => None,
280 });
281 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
282}
283
284fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
285 match (vis0, vis1) {
286 (None, None) => true,
287 // FIXME: Don't use the string representation to check for equality
288 // spaces inside of the node would break this comparison
289 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
290 _ => false,
291 }
292}
293
294fn eq_attrs(
295 attrs0: impl Iterator<Item = ast::Attr>,
296 attrs1: impl Iterator<Item = ast::Attr>,
297) -> bool {
298 let attrs0 = attrs0.map(|attr| attr.to_string());
299 let attrs1 = attrs1.map(|attr| attr.to_string());
300 attrs0.eq(attrs1)
301}
302
303fn path_is_self(path: &ast::Path) -> bool {
304 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
305}
306
307fn path_len(path: ast::Path) -> usize {
308 path.segments().count()
309}
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index b55e3851e..8f899ea56 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -7,7 +7,9 @@
7use std::{convert::TryInto, mem}; 7use std::{convert::TryInto, mem};
8 8
9use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; 9use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
10use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}; 10use hir::{
11 DefWithBody, HasAttrs, HasSource, InFile, ModuleDef, ModuleSource, Semantics, Visibility,
12};
11use once_cell::unsync::Lazy; 13use once_cell::unsync::Lazy;
12use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
13use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; 15use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
@@ -295,7 +297,7 @@ impl Definition {
295 } 297 }
296 298
297 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { 299 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
298 FindUsages { def: self, sema, scope: None } 300 FindUsages { def: self, sema, scope: None, include_self_kw_refs: false }
299 } 301 }
300} 302}
301 303
@@ -303,9 +305,15 @@ pub struct FindUsages<'a> {
303 def: &'a Definition, 305 def: &'a Definition,
304 sema: &'a Semantics<'a, RootDatabase>, 306 sema: &'a Semantics<'a, RootDatabase>,
305 scope: Option<SearchScope>, 307 scope: Option<SearchScope>,
308 include_self_kw_refs: bool,
306} 309}
307 310
308impl<'a> FindUsages<'a> { 311impl<'a> FindUsages<'a> {
312 pub fn include_self_kw_refs(mut self, include: bool) -> FindUsages<'a> {
313 self.include_self_kw_refs = include;
314 self
315 }
316
309 pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> { 317 pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
310 self.set_scope(Some(scope)) 318 self.set_scope(Some(scope))
311 } 319 }
@@ -352,6 +360,8 @@ impl<'a> FindUsages<'a> {
352 }; 360 };
353 361
354 let pat = name.as_str(); 362 let pat = name.as_str();
363 let search_for_self = self.include_self_kw_refs;
364
355 for (file_id, search_range) in search_scope { 365 for (file_id, search_range) in search_scope {
356 let text = sema.db.file_text(file_id); 366 let text = sema.db.file_text(file_id);
357 let search_range = 367 let search_range =
@@ -359,31 +369,47 @@ impl<'a> FindUsages<'a> {
359 369
360 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); 370 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
361 371
362 for (idx, _) in text.match_indices(pat) { 372 let mut handle_match = |idx: usize| -> bool {
363 let offset: TextSize = idx.try_into().unwrap(); 373 let offset: TextSize = idx.try_into().unwrap();
364 if !search_range.contains_inclusive(offset) { 374 if !search_range.contains_inclusive(offset) {
365 continue; 375 return false;
366 } 376 }
367 377
368 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) { 378 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
369 match name { 379 match name {
370 ast::NameLike::NameRef(name_ref) => { 380 ast::NameLike::NameRef(name_ref) => {
371 if self.found_name_ref(&name_ref, sink) { 381 if self.found_name_ref(&name_ref, sink) {
372 return; 382 return true;
373 } 383 }
374 } 384 }
375 ast::NameLike::Name(name) => { 385 ast::NameLike::Name(name) => {
376 if self.found_name(&name, sink) { 386 if self.found_name(&name, sink) {
377 return; 387 return true;
378 } 388 }
379 } 389 }
380 ast::NameLike::Lifetime(lifetime) => { 390 ast::NameLike::Lifetime(lifetime) => {
381 if self.found_lifetime(&lifetime, sink) { 391 if self.found_lifetime(&lifetime, sink) {
382 return; 392 return true;
383 } 393 }
384 } 394 }
385 } 395 }
386 } 396 }
397
398 return false;
399 };
400
401 for (idx, _) in text.match_indices(pat) {
402 if handle_match(idx) {
403 return;
404 }
405 }
406
407 if search_for_self {
408 for (idx, _) in text.match_indices("Self") {
409 if handle_match(idx) {
410 return;
411 }
412 }
387 } 413 }
388 } 414 }
389 } 415 }
@@ -422,6 +448,24 @@ impl<'a> FindUsages<'a> {
422 }; 448 };
423 sink(file_id, reference) 449 sink(file_id, reference)
424 } 450 }
451 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => {
452 let ty = impl_.self_ty(self.sema.db);
453
454 if let Some(adt) = ty.as_adt() {
455 if &Definition::ModuleDef(ModuleDef::Adt(adt)) == self.def {
456 let FileRange { file_id, range } =
457 self.sema.original_range(name_ref.syntax());
458 let reference = FileReference {
459 range,
460 name: ast::NameLike::NameRef(name_ref.clone()),
461 access: None,
462 };
463 return sink(file_id, reference);
464 }
465 }
466
467 false
468 }
425 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 469 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
426 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 470 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
427 let reference = match self.def { 471 let reference = match self.def {
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index faca336de..7b88dca63 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -143,7 +143,7 @@ impl WorkspaceBuildData {
143 cmd.env("RA_RUSTC_WRAPPER", "1"); 143 cmd.env("RA_RUSTC_WRAPPER", "1");
144 } 144 }
145 145
146 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) 146 cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"])
147 .arg(cargo_toml.as_ref()); 147 .arg(cargo_toml.as_ref());
148 148
149 // --all-targets includes tests, benches and examples in addition to the 149 // --all-targets includes tests, benches and examples in addition to the
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 0571a912c..3e8f4bf89 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -33,7 +33,7 @@ serde_path_to_error = "0.1"
33threadpool = "1.7.1" 33threadpool = "1.7.1"
34rayon = "1.5" 34rayon = "1.5"
35mimalloc = { version = "0.1.19", default-features = false, optional = true } 35mimalloc = { version = "0.1.19", default-features = false, optional = true }
36lsp-server = "0.5.0" 36lsp-server = "0.5.1"
37tracing = "0.1" 37tracing = "0.1"
38tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] } 38tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
39tracing-tree = { version = "0.1.4" } 39tracing-tree = { version = "0.1.4" }
diff --git a/crates/rust-analyzer/build.rs b/crates/rust-analyzer/build.rs
index 13b903891..bca6611d6 100644
--- a/crates/rust-analyzer/build.rs
+++ b/crates/rust-analyzer/build.rs
@@ -1,13 +1,10 @@
1//! Just embed git-hash to `--version` 1//! Construct version in the `commit-hash date chanel` format
2 2
3use std::{env, path::PathBuf, process::Command}; 3use std::{env, path::PathBuf, process::Command};
4 4
5fn main() { 5fn main() {
6 set_rerun(); 6 set_rerun();
7 7 println!("cargo:rustc-env=REV={}", rev())
8 let rev =
9 env::var("RUST_ANALYZER_REV").ok().or_else(rev).unwrap_or_else(|| "???????".to_string());
10 println!("cargo:rustc-env=REV={}", rev)
11} 8}
12 9
13fn set_rerun() { 10fn set_rerun() {
@@ -18,29 +15,52 @@ fn set_rerun() {
18 ); 15 );
19 16
20 while manifest_dir.parent().is_some() { 17 while manifest_dir.parent().is_some() {
21 if manifest_dir.join(".git/HEAD").exists() { 18 let head_ref = manifest_dir.join(".git/HEAD");
22 let git_dir = manifest_dir.join(".git"); 19 if head_ref.exists() {
23 20 println!("cargo:rerun-if-changed={}", head_ref.display());
24 println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display());
25 // current branch ref
26 if let Ok(output) =
27 Command::new("git").args(&["rev-parse", "--symbolic-full-name", "HEAD"]).output()
28 {
29 if let Ok(ref_link) = String::from_utf8(output.stdout) {
30 println!("cargo:rerun-if-changed={}", git_dir.join(ref_link).display());
31 }
32 }
33 return; 21 return;
34 } 22 }
35 23
36 manifest_dir.pop(); 24 manifest_dir.pop();
37 } 25 }
26
38 println!("cargo:warning=Could not find `.git/HEAD` from manifest dir!"); 27 println!("cargo:warning=Could not find `.git/HEAD` from manifest dir!");
39} 28}
40 29
41fn rev() -> Option<String> { 30fn rev() -> String {
42 let output = 31 if let Ok(rev) = env::var("RUST_ANALYZER_REV") {
43 Command::new("git").args(&["describe", "--tags", "--exclude", "nightly"]).output().ok()?; 32 return rev;
33 }
34
35 if let Some(commit_hash) = commit_hash() {
36 let mut buf = commit_hash;
37
38 if let Some(date) = build_date() {
39 buf.push(' ');
40 buf.push_str(&date);
41 }
42
43 let channel = env::var("RUST_ANALYZER_CHANNEL").unwrap_or_else(|_| "dev".to_string());
44 buf.push(' ');
45 buf.push_str(&channel);
46
47 return buf;
48 }
49
50 "???????".to_string()
51}
52
53fn commit_hash() -> Option<String> {
54 output_to_string("git rev-parse --short HEAD")
55}
56
57fn build_date() -> Option<String> {
58 output_to_string("date -u +%Y-%m-%d")
59}
60
61fn output_to_string(command: &str) -> Option<String> {
62 let args = command.split_ascii_whitespace().collect::<Vec<_>>();
63 let output = Command::new(args[0]).args(&args[1..]).output().ok()?;
44 let stdout = String::from_utf8(output.stdout).ok()?; 64 let stdout = String::from_utf8(output.stdout).ok()?;
45 Some(stdout) 65 Some(stdout.trim().to_string())
46} 66}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 1109d2daf..28bbbce19 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -12,7 +12,8 @@ use std::{ffi::OsString, iter, path::PathBuf};
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; 13use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
14use ide_db::helpers::{ 14use ide_db::helpers::{
15 insert_use::{InsertUseConfig, MergeBehavior, PrefixKind}, 15 insert_use::{InsertUseConfig, PrefixKind},
16 merge_imports::MergeBehavior,
16 SnippetCap, 17 SnippetCap,
17}; 18};
18use lsp_types::{ClientCapabilities, MarkupKind}; 19use lsp_types::{ClientCapabilities, MarkupKind};
@@ -144,6 +145,8 @@ config_data! {
144 inlayHints_parameterHints: bool = "true", 145 inlayHints_parameterHints: bool = "true",
145 /// Whether to show inlay type hints for variables. 146 /// Whether to show inlay type hints for variables.
146 inlayHints_typeHints: bool = "true", 147 inlayHints_typeHints: bool = "true",
148 /// Whether inlay hints font size should be smaller than editor's font size.
149 inlayHints_smallerHints: bool = "true",
147 150
148 /// Whether to show `Debug` lens. Only applies when 151 /// Whether to show `Debug` lens. Only applies when
149 /// `#rust-analyzer.lens.enable#` is set. 152 /// `#rust-analyzer.lens.enable#` is set.
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 6ea775d68..a766aacad 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -571,6 +571,12 @@ impl GlobalState {
571 this.cancel(id); 571 this.cancel(id);
572 Ok(()) 572 Ok(())
573 })? 573 })?
574 .on::<lsp_types::notification::WorkDoneProgressCancel>(|_this, _params| {
575 // Just ignore this. It is OK to continue sending progress
576 // notifications for this token, as the client can't know when
577 // we accepted notification.
578 Ok(())
579 })?
574 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| { 580 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
575 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 581 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
576 if this 582 if this
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 857567a85..1b6211044 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -14,18 +14,8 @@ pub fn is_ci() -> bool {
14 14
15#[must_use] 15#[must_use]
16pub fn timeit(label: &'static str) -> impl Drop { 16pub fn timeit(label: &'static str) -> impl Drop {
17 struct Guard { 17 let start = Instant::now();
18 label: &'static str, 18 defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
19 start: Instant,
20 }
21
22 impl Drop for Guard {
23 fn drop(&mut self) {
24 eprintln!("{}: {:.2?}", self.label, self.start.elapsed())
25 }
26 }
27
28 Guard { label, start: Instant::now() }
29} 19}
30 20
31/// Prints backtrace to stderr, useful for debugging. 21/// Prints backtrace to stderr, useful for debugging.
@@ -179,6 +169,7 @@ where
179 start..start + len 169 start..start + len
180} 170}
181 171
172#[must_use]
182pub fn defer<F: FnOnce()>(f: F) -> impl Drop { 173pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
183 struct D<F: FnOnce()>(Option<F>); 174 struct D<F: FnOnce()>(Option<F>);
184 impl<F: FnOnce()> Drop for D<F> { 175 impl<F: FnOnce()> Drop for D<F> {
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index a153a9e1c..c9229c4e0 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -342,10 +342,10 @@ enum InsertPos {
342 342
343#[derive(Default)] 343#[derive(Default)]
344pub struct SyntaxRewriter<'a> { 344pub struct SyntaxRewriter<'a> {
345 f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
346 //FIXME: add debug_assertions that all elements are in fact from the same file. 345 //FIXME: add debug_assertions that all elements are in fact from the same file.
347 replacements: FxHashMap<SyntaxElement, Replacement>, 346 replacements: FxHashMap<SyntaxElement, Replacement>,
348 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>, 347 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
348 _pd: std::marker::PhantomData<&'a ()>,
349} 349}
350 350
351impl fmt::Debug for SyntaxRewriter<'_> { 351impl fmt::Debug for SyntaxRewriter<'_> {
@@ -357,14 +357,7 @@ impl fmt::Debug for SyntaxRewriter<'_> {
357 } 357 }
358} 358}
359 359
360impl<'a> SyntaxRewriter<'a> { 360impl SyntaxRewriter<'_> {
361 pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
362 SyntaxRewriter {
363 f: Some(Box::new(f)),
364 replacements: FxHashMap::default(),
365 insertions: IndexMap::default(),
366 }
367 }
368 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { 361 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
369 let what = what.clone().into(); 362 let what = what.clone().into();
370 let replacement = Replacement::Delete; 363 let replacement = Replacement::Delete;
@@ -470,7 +463,7 @@ impl<'a> SyntaxRewriter<'a> {
470 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { 463 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
471 let _p = profile::span("rewrite"); 464 let _p = profile::span("rewrite");
472 465
473 if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() { 466 if self.replacements.is_empty() && self.insertions.is_empty() {
474 return node.clone(); 467 return node.clone();
475 } 468 }
476 let green = self.rewrite_children(node); 469 let green = self.rewrite_children(node);
@@ -495,7 +488,6 @@ impl<'a> SyntaxRewriter<'a> {
495 } 488 }
496 } 489 }
497 490
498 assert!(self.f.is_none());
499 self.replacements 491 self.replacements
500 .keys() 492 .keys()
501 .filter_map(element_to_node_or_parent) 493 .filter_map(element_to_node_or_parent)
@@ -510,10 +502,6 @@ impl<'a> SyntaxRewriter<'a> {
510 } 502 }
511 503
512 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> { 504 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
513 if let Some(f) = &self.f {
514 assert!(self.replacements.is_empty());
515 return f(element).map(Replacement::Single);
516 }
517 self.replacements.get(element).cloned() 505 self.replacements.get(element).cloned()
518 } 506 }
519 507
@@ -574,7 +562,6 @@ fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
574 562
575impl ops::AddAssign for SyntaxRewriter<'_> { 563impl ops::AddAssign for SyntaxRewriter<'_> {
576 fn add_assign(&mut self, rhs: SyntaxRewriter) { 564 fn add_assign(&mut self, rhs: SyntaxRewriter) {
577 assert!(rhs.f.is_none());
578 self.replacements.extend(rhs.replacements); 565 self.replacements.extend(rhs.replacements);
579 for (pos, insertions) in rhs.insertions.into_iter() { 566 for (pos, insertions) in rhs.insertions.into_iter() {
580 match self.insertions.entry(pos) { 567 match self.insertions.entry(pos) {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 4cf6f871e..42da09606 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -137,6 +137,17 @@ pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast:
137 ast_from_text(&format!("{}use {};", visibility, use_tree)) 137 ast_from_text(&format!("{}use {};", visibility, use_tree))
138} 138}
139 139
140pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
141 ast_from_text(&format!("fn f() {{ {} {} }}", path, fields))
142}
143
144pub fn record_expr_field_list(
145 fields: impl IntoIterator<Item = ast::RecordExprField>,
146) -> ast::RecordExprFieldList {
147 let fields = fields.into_iter().join(", ");
148 ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields))
149}
150
140pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { 151pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
141 return match expr { 152 return match expr {
142 Some(expr) => from_text(&format!("{}: {}", name, expr)), 153 Some(expr) => from_text(&format!("{}: {}", name, expr)),
@@ -339,6 +350,21 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) ->
339 } 350 }
340} 351}
341 352
353pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
354 ast_from_text(&format!("fn f({} {}: ()))", path, fields))
355}
356
357pub fn record_pat_field_list(
358 fields: impl IntoIterator<Item = ast::RecordPatField>,
359) -> ast::RecordPatFieldList {
360 let fields = fields.into_iter().join(", ");
361 ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields))
362}
363
364pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
365 ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat))
366}
367
342/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. 368/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
343pub fn path_pat(path: ast::Path) -> ast::Pat { 369pub fn path_pat(path: ast::Path) -> ast::Pat {
344 return from_text(&path.to_string()); 370 return from_text(&path.to_string());
@@ -606,6 +632,7 @@ pub mod tokens {
606 SOURCE_FILE 632 SOURCE_FILE
607 .tree() 633 .tree()
608 .syntax() 634 .syntax()
635 .clone_for_update()
609 .descendants_with_tokens() 636 .descendants_with_tokens()
610 .filter_map(|it| it.into_token()) 637 .filter_map(|it| it.into_token())
611 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n") 638 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n")
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 171099661..492fbc4a0 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -1,7 +1,7 @@
1//! Various extension methods to ast Nodes, which are hard to code-generate. 1//! Various extension methods to ast Nodes, which are hard to code-generate.
2//! Extensions for various expressions live in a sibling `expr_extensions` module. 2//! Extensions for various expressions live in a sibling `expr_extensions` module.
3 3
4use std::fmt; 4use std::{fmt, iter::successors};
5 5
6use itertools::Itertools; 6use itertools::Itertools;
7use parser::SyntaxKind; 7use parser::SyntaxKind;
@@ -237,6 +237,26 @@ impl ast::Path {
237 None => self.segment(), 237 None => self.segment(),
238 } 238 }
239 } 239 }
240
241 pub fn first_qualifier_or_self(&self) -> ast::Path {
242 successors(Some(self.clone()), ast::Path::qualifier).last().unwrap()
243 }
244
245 pub fn first_segment(&self) -> Option<ast::PathSegment> {
246 self.first_qualifier_or_self().segment()
247 }
248
249 pub fn segments(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
250 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
251 successors(self.first_segment(), |p| {
252 p.parent_path().parent_path().and_then(|p| p.segment())
253 })
254 }
255}
256impl ast::UseTree {
257 pub fn is_simple_path(&self) -> bool {
258 self.use_tree_list().is_none() && self.star_token().is_none()
259 }
240} 260}
241 261
242impl ast::UseTreeList { 262impl ast::UseTreeList {
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 450f2e447..91a06101f 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -7,7 +7,7 @@ use std::{mem, ops::RangeInclusive};
7use parser::T; 7use parser::T;
8 8
9use crate::{ 9use crate::{
10 ast::{edit::IndentLevel, make}, 10 ast::{self, edit::IndentLevel, make, AstNode},
11 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, 11 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
12}; 12};
13 13
@@ -147,6 +147,16 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
147fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> { 147fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
148 let prev = match &position.repr { 148 let prev = match &position.repr {
149 PositionRepr::FirstChild(_) => return None, 149 PositionRepr::FirstChild(_) => return None,
150 PositionRepr::After(it) if it.kind() == SyntaxKind::L_CURLY => {
151 if new.kind() == SyntaxKind::USE {
152 if let Some(item_list) = it.parent().and_then(ast::ItemList::cast) {
153 let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
154 indent.0 += 1;
155 return Some(make::tokens::whitespace(&format!("\n{}", indent)));
156 }
157 }
158 it
159 }
150 PositionRepr::After(it) => it, 160 PositionRepr::After(it) => it,
151 }; 161 };
152 ws_between(prev, new) 162 ws_between(prev, new)
@@ -173,7 +183,10 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
173 } 183 }
174 184
175 if right.kind() == SyntaxKind::USE { 185 if right.kind() == SyntaxKind::USE {
176 let indent = IndentLevel::from_element(left); 186 let mut indent = IndentLevel::from_element(left);
187 if left.kind() == SyntaxKind::USE {
188 indent.0 = IndentLevel::from_element(right).0.max(indent.0);
189 }
177 return Some(make::tokens::whitespace(&format!("\n{}", indent))); 190 return Some(make::tokens::whitespace(&format!("\n{}", indent)));
178 } 191 }
179 Some(make::tokens::single_space()) 192 Some(make::tokens::single_space())