aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/code_model.rs15
-rw-r--r--crates/ra_ide/src/completion.rs32
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs293
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs6
-rw-r--r--crates/ra_ide_db/src/line_index.rs12
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs2
-rw-r--r--crates/ra_syntax/src/ast/generated/nodes.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs18
-rw-r--r--docs/dev/syntax.md42
-rw-r--r--xtask/src/ast_src.rs4
10 files changed, 359 insertions, 67 deletions
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index af59aa1b6..a004363ee 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -19,7 +19,7 @@ use hir_def::{
19use hir_expand::{ 19use hir_expand::{
20 diagnostics::DiagnosticSink, 20 diagnostics::DiagnosticSink,
21 name::{name, AsName}, 21 name::{name, AsName},
22 MacroDefId, 22 MacroDefId, MacroDefKind,
23}; 23};
24use hir_ty::{ 24use hir_ty::{
25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, 25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
@@ -762,13 +762,12 @@ impl MacroDef {
762 762
763 /// Indicate it is a proc-macro 763 /// Indicate it is a proc-macro
764 pub fn is_proc_macro(&self) -> bool { 764 pub fn is_proc_macro(&self) -> bool {
765 match self.id.kind { 765 matches!(self.id.kind, MacroDefKind::CustomDerive(_))
766 hir_expand::MacroDefKind::Declarative => false, 766 }
767 hir_expand::MacroDefKind::BuiltIn(_) => false, 767
768 hir_expand::MacroDefKind::BuiltInDerive(_) => false, 768 /// Indicate it is a derive macro
769 hir_expand::MacroDefKind::BuiltInEager(_) => false, 769 pub fn is_derive_macro(&self) -> bool {
770 hir_expand::MacroDefKind::CustomDerive(_) => true, 770 matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_))
771 }
772 } 771 }
773} 772}
774 773
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 4ca0fdf4f..a0e06faa2 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -65,21 +65,23 @@ pub(crate) fn completions(
65 let ctx = CompletionContext::new(db, position, config)?; 65 let ctx = CompletionContext::new(db, position, config)?;
66 66
67 let mut acc = Completions::default(); 67 let mut acc = Completions::default();
68 68 if ctx.attribute_under_caret.is_some() {
69 complete_fn_param::complete_fn_param(&mut acc, &ctx); 69 complete_attribute::complete_attribute(&mut acc, &ctx);
70 complete_keyword::complete_expr_keyword(&mut acc, &ctx); 70 } else {
71 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); 71 complete_fn_param::complete_fn_param(&mut acc, &ctx);
72 complete_snippet::complete_expr_snippet(&mut acc, &ctx); 72 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
73 complete_snippet::complete_item_snippet(&mut acc, &ctx); 73 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
74 complete_qualified_path::complete_qualified_path(&mut acc, &ctx); 74 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
75 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); 75 complete_snippet::complete_item_snippet(&mut acc, &ctx);
76 complete_dot::complete_dot(&mut acc, &ctx); 76 complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
77 complete_record::complete_record(&mut acc, &ctx); 77 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
78 complete_pattern::complete_pattern(&mut acc, &ctx); 78 complete_dot::complete_dot(&mut acc, &ctx);
79 complete_postfix::complete_postfix(&mut acc, &ctx); 79 complete_record::complete_record(&mut acc, &ctx);
80 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 80 complete_pattern::complete_pattern(&mut acc, &ctx);
81 complete_trait_impl::complete_trait_impl(&mut acc, &ctx); 81 complete_postfix::complete_postfix(&mut acc, &ctx);
82 complete_attribute::complete_attribute(&mut acc, &ctx); 82 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
83 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
84 }
83 85
84 Some(acc) 86 Some(acc)
85} 87}
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
index 8bf952798..20e6edc17 100644
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -5,23 +5,26 @@
5 5
6use super::completion_context::CompletionContext; 6use super::completion_context::CompletionContext;
7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; 7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8use ast::AttrInput;
8use ra_syntax::{ 9use ra_syntax::{
9 ast::{Attr, AttrKind}, 10 ast::{self, AttrKind},
10 AstNode, 11 AstNode, SyntaxKind,
11}; 12};
13use rustc_hash::FxHashSet;
12 14
13pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { 15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
14 if !ctx.is_attribute { 16 let attribute = ctx.attribute_under_caret.as_ref()?;
15 return;
16 }
17 17
18 let is_inner = ctx 18 match (attribute.path(), attribute.input()) {
19 .original_token 19 (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => {
20 .ancestors() 20 complete_derive(acc, ctx, token_tree)
21 .find_map(Attr::cast) 21 }
22 .map(|attr| attr.kind() == AttrKind::Inner) 22 _ => complete_attribute_start(acc, ctx, attribute),
23 .unwrap_or(false); 23 }
24 Some(())
25}
24 26
27fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
25 for attr_completion in ATTRIBUTES { 28 for attr_completion in ATTRIBUTES {
26 let mut item = CompletionItem::new( 29 let mut item = CompletionItem::new(
27 CompletionKind::Attribute, 30 CompletionKind::Attribute,
@@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
37 _ => {} 40 _ => {}
38 } 41 }
39 42
40 if is_inner || !attr_completion.should_be_inner { 43 if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner {
41 acc.add(item); 44 acc.add(item);
42 } 45 }
43 } 46 }
@@ -126,6 +129,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[
126 }, 129 },
127]; 130];
128 131
132fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
133 if let Ok(existing_derives) = parse_derive_input(derive_input) {
134 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
135 .into_iter()
136 .filter(|completion| !existing_derives.contains(completion.label))
137 {
138 let mut label = derive_completion.label.to_owned();
139 for dependency in derive_completion
140 .dependencies
141 .into_iter()
142 .filter(|&&dependency| !existing_derives.contains(dependency))
143 {
144 label.push_str(", ");
145 label.push_str(dependency);
146 }
147 acc.add(
148 CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
149 .kind(CompletionItemKind::Attribute),
150 );
151 }
152
153 for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
154 acc.add(
155 CompletionItem::new(
156 CompletionKind::Attribute,
157 ctx.source_range(),
158 custom_derive_name,
159 )
160 .kind(CompletionItemKind::Attribute),
161 );
162 }
163 }
164}
165
166fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
167 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
168 (Some(left_paren), Some(right_paren))
169 if left_paren.kind() == SyntaxKind::L_PAREN
170 && right_paren.kind() == SyntaxKind::R_PAREN =>
171 {
172 let mut input_derives = FxHashSet::default();
173 let mut current_derive = String::new();
174 for token in derive_input
175 .syntax()
176 .children_with_tokens()
177 .filter_map(|token| token.into_token())
178 .skip_while(|token| token != &left_paren)
179 .skip(1)
180 .take_while(|token| token != &right_paren)
181 {
182 if SyntaxKind::COMMA == token.kind() {
183 if !current_derive.is_empty() {
184 input_derives.insert(current_derive);
185 current_derive = String::new();
186 }
187 } else {
188 current_derive.push_str(token.to_string().trim());
189 }
190 }
191
192 if !current_derive.is_empty() {
193 input_derives.insert(current_derive);
194 }
195 Ok(input_derives)
196 }
197 _ => Err(()),
198 }
199}
200
201fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
202 let mut result = FxHashSet::default();
203 ctx.scope().process_all_names(&mut |name, scope_def| {
204 if let hir::ScopeDef::MacroDef(mac) = scope_def {
205 if mac.is_derive_macro() {
206 result.insert(name.to_string());
207 }
208 }
209 });
210 result
211}
212
213struct DeriveCompletion {
214 label: &'static str,
215 dependencies: &'static [&'static str],
216}
217
218/// Standard Rust derives and the information about their dependencies
219/// (the dependencies are needed so that the main derive don't break the compilation when added)
220const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
221 DeriveCompletion { label: "Clone", dependencies: &[] },
222 DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
223 DeriveCompletion { label: "Debug", dependencies: &[] },
224 DeriveCompletion { label: "Default", dependencies: &[] },
225 DeriveCompletion { label: "Hash", dependencies: &[] },
226 DeriveCompletion { label: "PartialEq", dependencies: &[] },
227 DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
228 DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
229 DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
230];
231
129#[cfg(test)] 232#[cfg(test)]
130mod tests { 233mod tests {
131 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 234 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
@@ -136,6 +239,170 @@ mod tests {
136 } 239 }
137 240
138 #[test] 241 #[test]
242 fn empty_derive_completion() {
243 assert_debug_snapshot!(
244 do_attr_completion(
245 r"
246 #[derive(<|>)]
247 struct Test {}
248 ",
249 ),
250 @r###"
251 [
252 CompletionItem {
253 label: "Clone",
254 source_range: 30..30,
255 delete: 30..30,
256 insert: "Clone",
257 kind: Attribute,
258 },
259 CompletionItem {
260 label: "Copy, Clone",
261 source_range: 30..30,
262 delete: 30..30,
263 insert: "Copy, Clone",
264 kind: Attribute,
265 },
266 CompletionItem {
267 label: "Debug",
268 source_range: 30..30,
269 delete: 30..30,
270 insert: "Debug",
271 kind: Attribute,
272 },
273 CompletionItem {
274 label: "Default",
275 source_range: 30..30,
276 delete: 30..30,
277 insert: "Default",
278 kind: Attribute,
279 },
280 CompletionItem {
281 label: "Eq, PartialEq",
282 source_range: 30..30,
283 delete: 30..30,
284 insert: "Eq, PartialEq",
285 kind: Attribute,
286 },
287 CompletionItem {
288 label: "Hash",
289 source_range: 30..30,
290 delete: 30..30,
291 insert: "Hash",
292 kind: Attribute,
293 },
294 CompletionItem {
295 label: "Ord, PartialOrd, Eq, PartialEq",
296 source_range: 30..30,
297 delete: 30..30,
298 insert: "Ord, PartialOrd, Eq, PartialEq",
299 kind: Attribute,
300 },
301 CompletionItem {
302 label: "PartialEq",
303 source_range: 30..30,
304 delete: 30..30,
305 insert: "PartialEq",
306 kind: Attribute,
307 },
308 CompletionItem {
309 label: "PartialOrd, PartialEq",
310 source_range: 30..30,
311 delete: 30..30,
312 insert: "PartialOrd, PartialEq",
313 kind: Attribute,
314 },
315 ]
316 "###
317 );
318 }
319
320 #[test]
321 fn no_completion_for_incorrect_derive() {
322 assert_debug_snapshot!(
323 do_attr_completion(
324 r"
325 #[derive{<|>)]
326 struct Test {}
327 ",
328 ),
329 @"[]"
330 );
331 }
332
333 #[test]
334 fn derive_with_input_completion() {
335 assert_debug_snapshot!(
336 do_attr_completion(
337 r"
338 #[derive(serde::Serialize, PartialEq, <|>)]
339 struct Test {}
340 ",
341 ),
342 @r###"
343 [
344 CompletionItem {
345 label: "Clone",
346 source_range: 59..59,
347 delete: 59..59,
348 insert: "Clone",
349 kind: Attribute,
350 },
351 CompletionItem {
352 label: "Copy, Clone",
353 source_range: 59..59,
354 delete: 59..59,
355 insert: "Copy, Clone",
356 kind: Attribute,
357 },
358 CompletionItem {
359 label: "Debug",
360 source_range: 59..59,
361 delete: 59..59,
362 insert: "Debug",
363 kind: Attribute,
364 },
365 CompletionItem {
366 label: "Default",
367 source_range: 59..59,
368 delete: 59..59,
369 insert: "Default",
370 kind: Attribute,
371 },
372 CompletionItem {
373 label: "Eq",
374 source_range: 59..59,
375 delete: 59..59,
376 insert: "Eq",
377 kind: Attribute,
378 },
379 CompletionItem {
380 label: "Hash",
381 source_range: 59..59,
382 delete: 59..59,
383 insert: "Hash",
384 kind: Attribute,
385 },
386 CompletionItem {
387 label: "Ord, PartialOrd, Eq",
388 source_range: 59..59,
389 delete: 59..59,
390 insert: "Ord, PartialOrd, Eq",
391 kind: Attribute,
392 },
393 CompletionItem {
394 label: "PartialOrd",
395 source_range: 59..59,
396 delete: 59..59,
397 insert: "PartialOrd",
398 kind: Attribute,
399 },
400 ]
401 "###
402 );
403 }
404
405 #[test]
139 fn test_attribute_completion() { 406 fn test_attribute_completion() {
140 assert_debug_snapshot!( 407 assert_debug_snapshot!(
141 do_attr_completion( 408 do_attr_completion(
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index c529752d4..dd87bd119 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> {
58 pub(super) is_macro_call: bool, 58 pub(super) is_macro_call: bool,
59 pub(super) is_path_type: bool, 59 pub(super) is_path_type: bool,
60 pub(super) has_type_args: bool, 60 pub(super) has_type_args: bool,
61 pub(super) is_attribute: bool, 61 pub(super) attribute_under_caret: Option<ast::Attr>,
62} 62}
63 63
64impl<'a> CompletionContext<'a> { 64impl<'a> CompletionContext<'a> {
@@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> {
116 is_path_type: false, 116 is_path_type: false,
117 has_type_args: false, 117 has_type_args: false,
118 dot_receiver_is_ambiguous_float_literal: false, 118 dot_receiver_is_ambiguous_float_literal: false,
119 is_attribute: false, 119 attribute_under_caret: None,
120 }; 120 };
121 121
122 let mut original_file = original_file.syntax().clone(); 122 let mut original_file = original_file.syntax().clone();
@@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> {
200 Some(ty) 200 Some(ty)
201 }) 201 })
202 .flatten(); 202 .flatten();
203 self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
203 204
204 // First, let's try to complete a reference to some declaration. 205 // First, let's try to complete a reference to some declaration.
205 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { 206 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
@@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> {
318 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) 319 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
319 .is_some(); 320 .is_some();
320 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); 321 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
321 self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
322 322
323 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); 323 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
324 self.has_type_args = segment.type_arg_list().is_some(); 324 self.has_type_args = segment.type_arg_list().is_some();
diff --git a/crates/ra_ide_db/src/line_index.rs b/crates/ra_ide_db/src/line_index.rs
index 00ba95913..212cb7b5b 100644
--- a/crates/ra_ide_db/src/line_index.rs
+++ b/crates/ra_ide_db/src/line_index.rs
@@ -8,7 +8,9 @@ use superslice::Ext;
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct LineIndex { 10pub struct LineIndex {
11 /// Offset the the beginning of each line, zero-based
11 pub(crate) newlines: Vec<TextSize>, 12 pub(crate) newlines: Vec<TextSize>,
13 /// List of non-ASCII characters on each line
12 pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>, 14 pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>,
13} 15}
14 16
@@ -22,7 +24,9 @@ pub struct LineCol {
22 24
23#[derive(Clone, Debug, Hash, PartialEq, Eq)] 25#[derive(Clone, Debug, Hash, PartialEq, Eq)]
24pub(crate) struct Utf16Char { 26pub(crate) struct Utf16Char {
27 /// Start offset of a character inside a line, zero-based
25 pub(crate) start: TextSize, 28 pub(crate) start: TextSize,
29 /// End offset of a character inside a line, zero-based
26 pub(crate) end: TextSize, 30 pub(crate) end: TextSize,
27} 31}
28 32
@@ -120,7 +124,7 @@ impl LineIndex {
120 fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize { 124 fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
121 if let Some(utf16_chars) = self.utf16_lines.get(&line) { 125 if let Some(utf16_chars) = self.utf16_lines.get(&line) {
122 for c in utf16_chars { 126 for c in utf16_chars {
123 if col >= u32::from(c.start) { 127 if col > u32::from(c.start) {
124 col += u32::from(c.len()) - 1; 128 col += u32::from(c.len()) - 1;
125 } else { 129 } else {
126 // From here on, all utf16 characters come *after* the character we are mapping, 130 // From here on, all utf16 characters come *after* the character we are mapping,
@@ -226,8 +230,10 @@ const C: char = \"メ メ\";
226 // UTF-16 to UTF-8 230 // UTF-16 to UTF-8
227 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15)); 231 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
228 232
229 assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); 233 // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1
230 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(23)); 234 assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20
235 assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space
236 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24
231 237
232 assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15)); 238 assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15));
233 } 239 }
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 528c873e0..98c38d009 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -467,7 +467,7 @@ impl ast::TokenTree {
467 467
468 pub fn right_delimiter_token(&self) -> Option<SyntaxToken> { 468 pub fn right_delimiter_token(&self) -> Option<SyntaxToken> {
469 self.syntax().last_child_or_token()?.into_token().filter(|it| match it.kind() { 469 self.syntax().last_child_or_token()?.into_token().filter(|it| match it.kind() {
470 T!['{'] | T!['('] | T!['['] => true, 470 T!['}'] | T![')'] | T![']'] => true,
471 _ => false, 471 _ => false,
472 }) 472 })
473 } 473 }
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs
index 5e844d5ae..c2cc25958 100644
--- a/crates/ra_syntax/src/ast/generated/nodes.rs
+++ b/crates/ra_syntax/src/ast/generated/nodes.rs
@@ -12,6 +12,7 @@ pub struct SourceFile {
12} 12}
13impl ast::ModuleItemOwner for SourceFile {} 13impl ast::ModuleItemOwner for SourceFile {}
14impl ast::AttrsOwner for SourceFile {} 14impl ast::AttrsOwner for SourceFile {}
15impl ast::DocCommentsOwner for SourceFile {}
15impl SourceFile { 16impl SourceFile {
16 pub fn modules(&self) -> AstChildren<Module> { support::children(&self.syntax) } 17 pub fn modules(&self) -> AstChildren<Module> { support::children(&self.syntax) }
17} 18}
@@ -259,6 +260,7 @@ pub struct ImplDef {
259} 260}
260impl ast::TypeParamsOwner for ImplDef {} 261impl ast::TypeParamsOwner for ImplDef {}
261impl ast::AttrsOwner for ImplDef {} 262impl ast::AttrsOwner for ImplDef {}
263impl ast::DocCommentsOwner for ImplDef {}
262impl ImplDef { 264impl ImplDef {
263 pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) } 265 pub fn default_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![default]) }
264 pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) } 266 pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 3bc2e0a46..401fae755 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -666,6 +666,10 @@ fn apply_document_changes(
666 mut line_index: Cow<'_, LineIndex>, 666 mut line_index: Cow<'_, LineIndex>,
667 content_changes: Vec<TextDocumentContentChangeEvent>, 667 content_changes: Vec<TextDocumentContentChangeEvent>,
668) { 668) {
669 // Remove when https://github.com/rust-analyzer/rust-analyzer/issues/4263 is fixed.
670 let backup_text = old_text.clone();
671 let backup_changes = content_changes.clone();
672
669 // The changes we got must be applied sequentially, but can cross lines so we 673 // The changes we got must be applied sequentially, but can cross lines so we
670 // have to keep our line index updated. 674 // have to keep our line index updated.
671 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we 675 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
@@ -693,7 +697,19 @@ fn apply_document_changes(
693 } 697 }
694 index_valid = IndexValid::UpToLine(range.start.line); 698 index_valid = IndexValid::UpToLine(range.start.line);
695 let range = range.conv_with(&line_index); 699 let range = range.conv_with(&line_index);
696 old_text.replace_range(Range::<usize>::from(range), &change.text); 700 let mut text = old_text.to_owned();
701 match std::panic::catch_unwind(move || {
702 text.replace_range(Range::<usize>::from(range), &change.text);
703 text
704 }) {
705 Ok(t) => *old_text = t,
706 Err(e) => {
707 eprintln!("Bug in incremental text synchronization. Please report the following output on https://github.com/rust-analyzer/rust-analyzer/issues/4263");
708 dbg!(&backup_text);
709 dbg!(&backup_changes);
710 std::panic::resume_unwind(e);
711 }
712 }
697 } 713 }
698 None => { 714 None => {
699 *old_text = change.text; 715 *old_text = change.text;
diff --git a/docs/dev/syntax.md b/docs/dev/syntax.md
index 33973ffec..c2864bbbc 100644
--- a/docs/dev/syntax.md
+++ b/docs/dev/syntax.md
@@ -64,7 +64,7 @@ struct Token {
64} 64}
65``` 65```
66 66
67All the difference bettwen the above sketch and the real implementation are strictly due to optimizations. 67All the difference between the above sketch and the real implementation are strictly due to optimizations.
68 68
69Points of note: 69Points of note:
70* The tree is untyped. Each node has a "type tag", `SyntaxKind`. 70* The tree is untyped. Each node has a "type tag", `SyntaxKind`.
@@ -72,7 +72,7 @@ Points of note:
72* Trivia and non-trivia tokens are not distinguished on the type level. 72* Trivia and non-trivia tokens are not distinguished on the type level.
73* Each token carries its full text. 73* Each token carries its full text.
74* The original text can be recovered by concatenating the texts of all tokens in order. 74* The original text can be recovered by concatenating the texts of all tokens in order.
75* Accessing a child of particular type (for example, parameter list of a function) generarly involves linerary traversing the children, looking for a specific `kind`. 75* Accessing a child of particular type (for example, parameter list of a function) generally involves linerary traversing the children, looking for a specific `kind`.
76* Modifying the tree is roughly `O(depth)`. 76* Modifying the tree is roughly `O(depth)`.
77 We don't make special efforts to guarantree that the depth is not liner, but, in practice, syntax trees are branchy and shallow. 77 We don't make special efforts to guarantree that the depth is not liner, but, in practice, syntax trees are branchy and shallow.
78* If mandatory (grammar wise) node is missing from the input, it's just missing from the tree. 78* If mandatory (grammar wise) node is missing from the input, it's just missing from the tree.
@@ -123,7 +123,7 @@ To more compactly store the children, we box *both* interior nodes and tokens, a
123`Either<Arc<Node>, Arc<Token>>` as a single pointer with a tag in the last bit. 123`Either<Arc<Node>, Arc<Token>>` as a single pointer with a tag in the last bit.
124 124
125To avoid allocating EVERY SINGLE TOKEN on the heap, syntax trees use interning. 125To avoid allocating EVERY SINGLE TOKEN on the heap, syntax trees use interning.
126Because the tree is fully imutable, it's valid to structuraly share subtrees. 126Because the tree is fully immutable, it's valid to structurally share subtrees.
127For example, in `1 + 1`, there will be a *single* token for `1` with ref count 2; the same goes for the ` ` whitespace token. 127For example, in `1 + 1`, there will be a *single* token for `1` with ref count 2; the same goes for the ` ` whitespace token.
128Interior nodes are shared as well (for example in `(1 + 1) * (1 + 1)`). 128Interior nodes are shared as well (for example in `(1 + 1) * (1 + 1)`).
129 129
@@ -134,8 +134,8 @@ Currently, the interner is created per-file, but it will be easy to use a per-th
134 134
135We use a `TextSize`, a newtyped `u32`, to store the length of the text. 135We use a `TextSize`, a newtyped `u32`, to store the length of the text.
136 136
137We currently use `SmolStr`, an small object optimized string to store text. 137We currently use `SmolStr`, a small object optimized string to store text.
138This was mostly relevant *before* we implmented tree interning, to avoid allocating common keywords and identifiers. We should switch to storing text data alongside the interned tokens. 138This was mostly relevant *before* we implemented tree interning, to avoid allocating common keywords and identifiers. We should switch to storing text data alongside the interned tokens.
139 139
140#### Alternative designs 140#### Alternative designs
141 141
@@ -162,12 +162,12 @@ Explicit trivia nodes, like in `rowan`, are used by IntelliJ.
162 162
163##### Accessing Children 163##### Accessing Children
164 164
165As noted before, accesing a specific child in the node requires a linear traversal of the children (though we can skip tokens, beacuse the tag is encoded in the pointer itself). 165As noted before, accessing a specific child in the node requires a linear traversal of the children (though we can skip tokens, because the tag is encoded in the pointer itself).
166It is possible to recover O(1) access with another representation. 166It is possible to recover O(1) access with another representation.
167We explicitly store optional and missing (required by the grammar, but not present) nodes. 167We explicitly store optional and missing (required by the grammar, but not present) nodes.
168That is, we use `Option<Node>` for children. 168That is, we use `Option<Node>` for children.
169We also remove trivia tokens from the tree. 169We also remove trivia tokens from the tree.
170This way, each child kind genrerally occupies a fixed position in a parent, and we can use index access to fetch it. 170This way, each child kind generally occupies a fixed position in a parent, and we can use index access to fetch it.
171The cost is that we now need to allocate space for all not-present optional nodes. 171The cost is that we now need to allocate space for all not-present optional nodes.
172So, `fn foo() {}` will have slots for visibility, unsafeness, attributes, abi and return type. 172So, `fn foo() {}` will have slots for visibility, unsafeness, attributes, abi and return type.
173 173
@@ -193,7 +193,7 @@ Modeling this with immutable trees is possible, but annoying.
193### Syntax Nodes 193### Syntax Nodes
194 194
195A function green tree is not super-convenient to use. 195A function green tree is not super-convenient to use.
196The biggest problem is acessing parents (there are no parent pointers!). 196The biggest problem is accessing parents (there are no parent pointers!).
197But there are also "identify" issues. 197But there are also "identify" issues.
198Let's say you want to write a code which builds a list of expressions in a file: `fn collect_exrepssions(file: GreenNode) -> HashSet<GreenNode>`. 198Let's say you want to write a code which builds a list of expressions in a file: `fn collect_exrepssions(file: GreenNode) -> HashSet<GreenNode>`.
199For the input like 199For the input like
@@ -207,7 +207,7 @@ fn main() {
207} 207}
208``` 208```
209 209
210both copies of the `x + 2` expression are representing by equal (and, with interning in mind, actualy the same) green nodes. 210both copies of the `x + 2` expression are representing by equal (and, with interning in mind, actually the same) green nodes.
211Green trees just can't differentiate between the two. 211Green trees just can't differentiate between the two.
212 212
213`SyntaxNode` adds parent pointers and identify semantics to green nodes. 213`SyntaxNode` adds parent pointers and identify semantics to green nodes.
@@ -285,9 +285,9 @@ They also point to the parent (and, consequently, to the root) with an owning `R
285In other words, one needs *one* arc bump when initiating a traversal. 285In other words, one needs *one* arc bump when initiating a traversal.
286 286
287To get rid of allocations, `rowan` takes advantage of `SyntaxNode: !Sync` and uses a thread-local free list of `SyntaxNode`s. 287To get rid of allocations, `rowan` takes advantage of `SyntaxNode: !Sync` and uses a thread-local free list of `SyntaxNode`s.
288In a typical traversal, you only directly hold a few `SyntaxNode`s at a time (and their ancesstors indirectly), so a free list proportional to the depth of the tree removes all allocations in a typical case. 288In a typical traversal, you only directly hold a few `SyntaxNode`s at a time (and their ancestors indirectly), so a free list proportional to the depth of the tree removes all allocations in a typical case.
289 289
290So, while traversal is not exactly incrementing a pointer, it's still prety cheep: tls + rc bump! 290So, while traversal is not exactly incrementing a pointer, it's still pretty cheap: TLS + rc bump!
291 291
292Traversal also yields (cheap) owned nodes, which improves ergonomics quite a bit. 292Traversal also yields (cheap) owned nodes, which improves ergonomics quite a bit.
293 293
@@ -308,15 +308,15 @@ struct SyntaxData {
308} 308}
309``` 309```
310 310
311This allows using true pointer equality for comparision of identities of `SyntaxNodes`. 311This allows using true pointer equality for comparison of identities of `SyntaxNodes`.
312rust-analyzer used to have this design as well, but since we've switch to cursors. 312rust-analyzer used to have this design as well, but we've since switched to cursors.
313The main problem with memoizing the red nodes is that it more than doubles the memory requirenments for fully realized syntax trees. 313The main problem with memoizing the red nodes is that it more than doubles the memory requirements for fully realized syntax trees.
314In contrast, cursors generally retain only a path to the root. 314In contrast, cursors generally retain only a path to the root.
315C# combats increased memory usage by using weak references. 315C# combats increased memory usage by using weak references.
316 316
317### AST 317### AST
318 318
319`GreenTree`s are untyped and homogeneous, because it makes accomodating error nodes, arbitrary whitespace and comments natural, and because it makes possible to write generic tree traversals. 319`GreenTree`s are untyped and homogeneous, because it makes accommodating error nodes, arbitrary whitespace and comments natural, and because it makes possible to write generic tree traversals.
320However, when working with a specific node, like a function definition, one would want a strongly typed API. 320However, when working with a specific node, like a function definition, one would want a strongly typed API.
321 321
322This is what is provided by the AST layer. AST nodes are transparent wrappers over untyped syntax nodes: 322This is what is provided by the AST layer. AST nodes are transparent wrappers over untyped syntax nodes:
@@ -397,7 +397,7 @@ impl HasVisbility for FnDef {
397Points of note: 397Points of note:
398 398
399* Like `SyntaxNode`s, AST nodes are cheap to clone pointer-sized owned values. 399* Like `SyntaxNode`s, AST nodes are cheap to clone pointer-sized owned values.
400* All "fields" are optional, to accomodate incomplete and/or erroneous source code. 400* All "fields" are optional, to accommodate incomplete and/or erroneous source code.
401* It's always possible to go from an ast node to an untyped `SyntaxNode`. 401* It's always possible to go from an ast node to an untyped `SyntaxNode`.
402* It's possible to go in the opposite direction with a checked cast. 402* It's possible to go in the opposite direction with a checked cast.
403* `enum`s allow modeling of arbitrary intersecting subsets of AST types. 403* `enum`s allow modeling of arbitrary intersecting subsets of AST types.
@@ -437,13 +437,13 @@ impl GreenNodeBuilder {
437} 437}
438``` 438```
439 439
440The parser, ultimatelly, needs to invoke the `GreenNodeBuilder`. 440The parser, ultimately, needs to invoke the `GreenNodeBuilder`.
441There are two principal sources of inputs for the parser: 441There are two principal sources of inputs for the parser:
442 * source text, which contains trivia tokens (whitespace and comments) 442 * source text, which contains trivia tokens (whitespace and comments)
443 * token trees from macros, which lack trivia 443 * token trees from macros, which lack trivia
444 444
445Additionaly, input tokens do not correspond 1-to-1 with output tokens. 445Additionally, input tokens do not correspond 1-to-1 with output tokens.
446For example, two consequtive `>` tokens might be glued, by the parser, into a single `>>`. 446For example, two consecutive `>` tokens might be glued, by the parser, into a single `>>`.
447 447
448For these reasons, the parser crate defines a callback interfaces for both input tokens and output trees. 448For these reasons, the parser crate defines a callback interfaces for both input tokens and output trees.
449The explicit glue layer then bridges various gaps. 449The explicit glue layer then bridges various gaps.
@@ -491,7 +491,7 @@ Syntax errors are not stored directly in the tree.
491The primary motivation for this is that syntax tree is not necessary produced by the parser, it may also be assembled manually from pieces (which happens all the time in refactorings). 491The primary motivation for this is that syntax tree is not necessary produced by the parser, it may also be assembled manually from pieces (which happens all the time in refactorings).
492Instead, parser reports errors to an error sink, which stores them in a `Vec`. 492Instead, parser reports errors to an error sink, which stores them in a `Vec`.
493If possible, errors are not reported during parsing and are postponed for a separate validation step. 493If possible, errors are not reported during parsing and are postponed for a separate validation step.
494For example, parser accepts visibility modifiers on trait methods, but then a separate tree traversal flags all such visibilites as erroneous. 494For example, parser accepts visibility modifiers on trait methods, but then a separate tree traversal flags all such visibilities as erroneous.
495 495
496### Macros 496### Macros
497 497
@@ -501,7 +501,7 @@ Specifically, `TreeSink` constructs the tree in lockstep with draining the origi
501In the process, it records which tokens of the tree correspond to which tokens of the input, by using text ranges to identify syntax tokens. 501In the process, it records which tokens of the tree correspond to which tokens of the input, by using text ranges to identify syntax tokens.
502The end result is that parsing an expanded code yields a syntax tree and a mapping of text-ranges of the tree to original tokens. 502The end result is that parsing an expanded code yields a syntax tree and a mapping of text-ranges of the tree to original tokens.
503 503
504To deal with precedence in cases like `$expr * 1`, we use special invisible parenthesis, which are explicitelly handled by the parser 504To deal with precedence in cases like `$expr * 1`, we use special invisible parenthesis, which are explicitly handled by the parser
505 505
506### Whitespace & Comments 506### Whitespace & Comments
507 507
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
index 028f7cbe1..2f8065b73 100644
--- a/xtask/src/ast_src.rs
+++ b/xtask/src/ast_src.rs
@@ -305,7 +305,7 @@ macro_rules! ast_enums {
305pub(crate) const AST_SRC: AstSrc = AstSrc { 305pub(crate) const AST_SRC: AstSrc = AstSrc {
306 tokens: &["Whitespace", "Comment", "String", "RawString"], 306 tokens: &["Whitespace", "Comment", "String", "RawString"],
307 nodes: &ast_nodes! { 307 nodes: &ast_nodes! {
308 struct SourceFile: ModuleItemOwner, AttrsOwner { 308 struct SourceFile: ModuleItemOwner, AttrsOwner, DocCommentsOwner {
309 modules: [Module], 309 modules: [Module],
310 } 310 }
311 311
@@ -401,7 +401,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
401 T![;] 401 T![;]
402 } 402 }
403 403
404 struct ImplDef: TypeParamsOwner, AttrsOwner { 404 struct ImplDef: TypeParamsOwner, AttrsOwner, DocCommentsOwner {
405 T![default], 405 T![default],
406 T![const], 406 T![const],
407 T![unsafe], 407 T![unsafe],