aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs30
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs42
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/ra_assists/src/utils.rs2
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs9
-rw-r--r--crates/ra_flycheck/Cargo.toml3
-rw-r--r--crates/ra_hir/src/code_model.rs15
-rw-r--r--crates/ra_hir_def/src/adt.rs8
-rw-r--r--crates/ra_hir_ty/src/tests.rs27
-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/src/display/navigation_target.rs10
-rw-r--r--crates/ra_ide/src/goto_definition.rs10
-rw-r--r--crates/ra_ide/src/inlay_hints.rs74
-rw-r--r--crates/ra_ide/src/marks.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs13
-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/caps.rs7
-rw-r--r--crates/rust-analyzer/src/main_loop.rs18
-rw-r--r--crates/stdx/src/lib.rs14
-rw-r--r--docs/dev/debugging.md8
-rw-r--r--docs/dev/syntax.md42
-rw-r--r--xtask/src/ast_src.rs4
26 files changed, 564 insertions, 123 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 2fe7c3de3..da2880037 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -105,7 +105,7 @@ impl<'a> AssistCtx<'a> {
105 let mut info = AssistInfo::new(label); 105 let mut info = AssistInfo::new(label);
106 if self.should_compute_edit { 106 if self.should_compute_edit {
107 let action = { 107 let action = {
108 let mut edit = ActionBuilder::default(); 108 let mut edit = ActionBuilder::new(&self);
109 f(&mut edit); 109 f(&mut edit);
110 edit.build() 110 edit.build()
111 }; 111 };
@@ -130,6 +130,12 @@ impl<'a> AssistCtx<'a> {
130 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { 130 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
131 find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) 131 find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
132 } 132 }
133
134 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
135 self.sema
136 .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
137 }
138
133 pub(crate) fn covering_element(&self) -> SyntaxElement { 139 pub(crate) fn covering_element(&self) -> SyntaxElement {
134 find_covering_element(self.source_file.syntax(), self.frange.range) 140 find_covering_element(self.source_file.syntax(), self.frange.range)
135 } 141 }
@@ -156,7 +162,7 @@ impl<'a> AssistGroup<'a> {
156 let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); 162 let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
157 if self.ctx.should_compute_edit { 163 if self.ctx.should_compute_edit {
158 let action = { 164 let action = {
159 let mut edit = ActionBuilder::default(); 165 let mut edit = ActionBuilder::new(&self.ctx);
160 f(&mut edit); 166 f(&mut edit);
161 edit.build() 167 edit.build()
162 }; 168 };
@@ -175,15 +181,29 @@ impl<'a> AssistGroup<'a> {
175 } 181 }
176} 182}
177 183
178#[derive(Default)] 184pub(crate) struct ActionBuilder<'a, 'b> {
179pub(crate) struct ActionBuilder {
180 edit: TextEditBuilder, 185 edit: TextEditBuilder,
181 cursor_position: Option<TextSize>, 186 cursor_position: Option<TextSize>,
182 target: Option<TextRange>, 187 target: Option<TextRange>,
183 file: AssistFile, 188 file: AssistFile,
189 ctx: &'a AssistCtx<'b>,
184} 190}
185 191
186impl ActionBuilder { 192impl<'a, 'b> ActionBuilder<'a, 'b> {
193 fn new(ctx: &'a AssistCtx<'b>) -> Self {
194 Self {
195 edit: TextEditBuilder::default(),
196 cursor_position: None,
197 target: None,
198 file: AssistFile::default(),
199 ctx,
200 }
201 }
202
203 pub(crate) fn ctx(&self) -> &AssistCtx<'b> {
204 &self.ctx
205 }
206
187 /// Replaces specified `range` of text with a given string. 207 /// Replaces specified `range` of text with a given string.
188 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 208 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
189 self.edit.replace(range, replace_with.into()) 209 self.edit.replace(range, replace_with.into())
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 99682e023..db6c4d2fa 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -45,15 +45,12 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
45 return None; 45 return None;
46 } 46 }
47 47
48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
48 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); 49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
49 for import in proposed_imports { 50 for import in proposed_imports {
50 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
51 edit.target(auto_import_assets.syntax_under_caret.text_range()); 52 edit.target(range);
52 insert_use_statement( 53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
53 &auto_import_assets.syntax_under_caret,
54 &import,
55 edit.text_edit_builder(),
56 );
57 }); 54 });
58 } 55 }
59 group.finish() 56 group.finish()
@@ -68,10 +65,10 @@ struct AutoImportAssets {
68 65
69impl AutoImportAssets { 66impl AutoImportAssets {
70 fn new(ctx: &AssistCtx) -> Option<Self> { 67 fn new(ctx: &AssistCtx) -> Option<Self> {
71 if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() { 68 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
72 Self::for_regular_path(path_under_caret, &ctx) 69 Self::for_regular_path(path_under_caret, &ctx)
73 } else { 70 } else {
74 Self::for_method_call(ctx.find_node_at_offset()?, &ctx) 71 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
75 } 72 }
76 } 73 }
77 74
@@ -306,6 +303,35 @@ mod tests {
306 } 303 }
307 304
308 #[test] 305 #[test]
306 fn applicable_when_found_an_import_in_macros() {
307 check_assist(
308 auto_import,
309 r"
310 macro_rules! foo {
311 ($i:ident) => { fn foo(a: $i) {} }
312 }
313 foo!(Pub<|>Struct);
314
315 pub mod PubMod {
316 pub struct PubStruct;
317 }
318 ",
319 r"
320 use PubMod::PubStruct;
321
322 macro_rules! foo {
323 ($i:ident) => { fn foo(a: $i) {} }
324 }
325 foo!(Pub<|>Struct);
326
327 pub mod PubMod {
328 pub struct PubStruct;
329 }
330 ",
331 );
332 }
333
334 #[test]
309 fn auto_imports_are_merged() { 335 fn auto_imports_are_merged() {
310 check_assist( 336 check_assist(
311 auto_import, 337 auto_import,
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index 918e8dd8d..ff2463c77 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -38,7 +38,7 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
38 "Replace qualified path with use", 38 "Replace qualified path with use",
39 |edit| { 39 |edit| {
40 let path_to_import = hir_path.mod_path().clone(); 40 let path_to_import = hir_path.mod_path().clone();
41 insert_use_statement(path.syntax(), &path_to_import, edit.text_edit_builder()); 41 insert_use_statement(path.syntax(), &path_to_import, edit);
42 42
43 if let Some(last) = path.segment() { 43 if let Some(last) = path.segment() {
44 // Here we are assuming the assist will provide a correct use statement 44 // Here we are assuming the assist will provide a correct use statement
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index efd988697..6be704ce3 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11}; 11};
12use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
13 13
14pub use insert_use::insert_use_statement; 14pub(crate) use insert_use::insert_use_statement;
15 15
16pub fn get_missing_impl_items( 16pub fn get_missing_impl_items(
17 sema: &Semantics<RootDatabase>, 17 sema: &Semantics<RootDatabase>,
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index c507e71e0..c1f447efe 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -2,6 +2,7 @@
2// FIXME: rewrite according to the plan, outlined in 2// FIXME: rewrite according to the plan, outlined in
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4 4
5use crate::assist_ctx::ActionBuilder;
5use hir::{self, ModPath}; 6use hir::{self, ModPath};
6use ra_syntax::{ 7use ra_syntax::{
7 ast::{self, NameOwner}, 8 ast::{self, NameOwner},
@@ -14,14 +15,14 @@ use ra_text_edit::TextEditBuilder;
14/// Creates and inserts a use statement for the given path to import. 15/// Creates and inserts a use statement for the given path to import.
15/// The use statement is inserted in the scope most appropriate to the 16/// The use statement is inserted in the scope most appropriate to the
16/// the cursor position given, additionally merged with the existing use imports. 17/// the cursor position given, additionally merged with the existing use imports.
17pub fn insert_use_statement( 18pub(crate) fn insert_use_statement(
18 // Ideally the position of the cursor, used to 19 // Ideally the position of the cursor, used to
19 position: &SyntaxNode, 20 position: &SyntaxNode,
20 path_to_import: &ModPath, 21 path_to_import: &ModPath,
21 edit: &mut TextEditBuilder, 22 edit: &mut ActionBuilder,
22) { 23) {
23 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 24 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
24 let container = position.ancestors().find_map(|n| { 25 let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| {
25 if let Some(module) = ast::Module::cast(n.clone()) { 26 if let Some(module) = ast::Module::cast(n.clone()) {
26 return module.item_list().map(|it| it.syntax().clone()); 27 return module.item_list().map(|it| it.syntax().clone());
27 } 28 }
@@ -30,7 +31,7 @@ pub fn insert_use_statement(
30 31
31 if let Some(container) = container { 32 if let Some(container) = container {
32 let action = best_action_for_target(container, position.clone(), &target); 33 let action = best_action_for_target(container, position.clone(), &target);
33 make_assist(&action, &target, edit); 34 make_assist(&action, &target, edit.text_edit_builder());
34 } 35 }
35} 36}
36 37
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml
index 324c33d9d..3d5093264 100644
--- a/crates/ra_flycheck/Cargo.toml
+++ b/crates/ra_flycheck/Cargo.toml
@@ -4,6 +4,9 @@ name = "ra_flycheck"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6 6
7[lib]
8doctest = false
9
7[dependencies] 10[dependencies]
8crossbeam-channel = "0.4.0" 11crossbeam-channel = "0.4.0"
9lsp-types = { version = "0.74.0", features = ["proposed"] } 12lsp-types = { version = "0.74.0", features = ["proposed"] }
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_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs
index 8eef51828..d0912ddaa 100644
--- a/crates/ra_hir_def/src/adt.rs
+++ b/crates/ra_hir_def/src/adt.rs
@@ -117,7 +117,13 @@ fn lower_enum(
117 ast: &InFile<ast::EnumDef>, 117 ast: &InFile<ast::EnumDef>,
118 module_id: ModuleId, 118 module_id: ModuleId,
119) { 119) {
120 for var in ast.value.variant_list().into_iter().flat_map(|it| it.variants()) { 120 let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
121 let variants =
122 ast.value.variant_list().into_iter().flat_map(|it| it.variants()).filter(|var| {
123 let attrs = expander.parse_attrs(var);
124 expander.is_cfg_enabled(&attrs)
125 });
126 for var in variants {
121 trace.alloc( 127 trace.alloc(
122 || var.clone(), 128 || var.clone(),
123 || EnumVariantData { 129 || EnumVariantData {
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index 588d81282..d60732e19 100644
--- a/crates/ra_hir_ty/src/tests.rs
+++ b/crates/ra_hir_ty/src/tests.rs
@@ -361,6 +361,33 @@ fn no_such_field_with_feature_flag_diagnostics() {
361} 361}
362 362
363#[test] 363#[test]
364fn no_such_field_enum_with_feature_flag_diagnostics() {
365 let diagnostics = TestDB::with_files(
366 r#"
367 //- /lib.rs crate:foo cfg:feature=foo
368 enum Foo {
369 #[cfg(not(feature = "foo"))]
370 Buz,
371 #[cfg(feature = "foo")]
372 Bar,
373 Baz
374 }
375
376 fn test_fn(f: Foo) {
377 match f {
378 Foo::Bar => {},
379 Foo::Baz => {},
380 }
381 }
382 "#,
383 )
384 .diagnostics()
385 .0;
386
387 assert_snapshot!(diagnostics, @r###""###);
388}
389
390#[test]
364fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() { 391fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
365 let diagnostics = TestDB::with_files( 392 let diagnostics = TestDB::with_files(
366 r#" 393 r#"
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/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 914a8b471..de35c6711 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -376,16 +376,20 @@ impl ToNav for hir::Local {
376impl ToNav for hir::TypeParam { 376impl ToNav for hir::TypeParam {
377 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 377 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
378 let src = self.source(db); 378 let src = self.source(db);
379 let range = match src.value { 379 let full_range = match &src.value {
380 Either::Left(it) => it.syntax().text_range(), 380 Either::Left(it) => it.syntax().text_range(),
381 Either::Right(it) => it.syntax().text_range(), 381 Either::Right(it) => it.syntax().text_range(),
382 }; 382 };
383 let focus_range = match &src.value {
384 Either::Left(_) => None,
385 Either::Right(it) => it.name().map(|it| it.syntax().text_range()),
386 };
383 NavigationTarget { 387 NavigationTarget {
384 file_id: src.file_id.original_file(db), 388 file_id: src.file_id.original_file(db),
385 name: self.name(db).to_string().into(), 389 name: self.name(db).to_string().into(),
386 kind: TYPE_PARAM, 390 kind: TYPE_PARAM,
387 full_range: range, 391 full_range,
388 focus_range: None, 392 focus_range,
389 container_name: None, 393 container_name: None,
390 description: None, 394 description: None,
391 docs: None, 395 docs: None,
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 5b9b3eef8..150895abb 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -787,14 +787,14 @@ mod tests {
787 #[test] 787 #[test]
788 fn goto_for_type_param() { 788 fn goto_for_type_param() {
789 check_goto( 789 check_goto(
790 " 790 r#"
791 //- /lib.rs 791 //- /lib.rs
792 struct Foo<T> { 792 struct Foo<T: Clone> {
793 t: <|>T, 793 t: <|>T,
794 } 794 }
795 ", 795 "#,
796 "T TYPE_PARAM FileId(1) 11..12", 796 "T TYPE_PARAM FileId(1) 11..19 11..12",
797 "T", 797 "T: Clone|T",
798 ); 798 );
799 } 799 }
800 800
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 98483df32..b391f903a 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -9,6 +9,7 @@ use ra_syntax::{
9}; 9};
10 10
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
12use stdx::to_lower_snake_case;
12 13
13#[derive(Clone, Debug, PartialEq, Eq)] 14#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsConfig { 15pub struct InlayHintsConfig {
@@ -144,7 +145,7 @@ fn get_param_name_hints(
144 .iter() 145 .iter()
145 .skip(n_params_to_skip) 146 .skip(n_params_to_skip)
146 .zip(args) 147 .zip(args)
147 .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) 148 .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg))
148 .map(|(param_name, arg)| InlayHint { 149 .map(|(param_name, arg)| InlayHint {
149 range: arg.syntax().text_range(), 150 range: arg.syntax().text_range(),
150 kind: InlayKind::ParameterHint, 151 kind: InlayKind::ParameterHint,
@@ -181,7 +182,7 @@ fn get_bind_pat_hints(
181 182
182fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { 183fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool {
183 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { 184 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() {
184 let pat_text = bind_pat.syntax().to_string(); 185 let pat_text = bind_pat.to_string();
185 enum_data 186 enum_data
186 .variants(db) 187 .variants(db)
187 .into_iter() 188 .into_iter()
@@ -198,7 +199,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_
198 } 199 }
199 200
200 if let Some(Adt::Struct(s)) = pat_ty.as_adt() { 201 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
201 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() { 202 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
202 return true; 203 return true;
203 } 204 }
204 } 205 }
@@ -230,15 +231,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_
230 false 231 false
231} 232}
232 233
233fn should_show_param_hint( 234fn should_show_param_name_hint(
235 sema: &Semantics<RootDatabase>,
234 fn_signature: &FunctionSignature, 236 fn_signature: &FunctionSignature,
235 param_name: &str, 237 param_name: &str,
236 argument: &ast::Expr, 238 argument: &ast::Expr,
237) -> bool { 239) -> bool {
240 let param_name = param_name.trim_start_matches('_');
238 if param_name.is_empty() 241 if param_name.is_empty()
239 || is_argument_similar_to_param(argument, param_name) 242 || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_'))
240 || Some(param_name.trim_start_matches('_')) 243 || is_argument_similar_to_param_name(sema, argument, param_name)
241 == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_'))
242 { 244 {
243 return false; 245 return false;
244 } 246 }
@@ -254,20 +256,42 @@ fn should_show_param_hint(
254 parameters_len != 1 || !is_obvious_param(param_name) 256 parameters_len != 1 || !is_obvious_param(param_name)
255} 257}
256 258
257fn is_argument_similar_to_param(argument: &ast::Expr, param_name: &str) -> bool { 259fn is_argument_similar_to_param_name(
258 let argument_string = remove_ref(argument.clone()).syntax().to_string(); 260 sema: &Semantics<RootDatabase>,
259 let param_name = param_name.trim_start_matches('_'); 261 argument: &ast::Expr,
260 let argument_string = argument_string.trim_start_matches('_'); 262 param_name: &str,
261 argument_string.starts_with(&param_name) || argument_string.ends_with(&param_name) 263) -> bool {
264 if is_enum_name_similar_to_param_name(sema, argument, param_name) {
265 return true;
266 }
267 match get_string_representation(argument) {
268 None => false,
269 Some(repr) => {
270 let argument_string = repr.trim_start_matches('_');
271 argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
272 }
273 }
274}
275
276fn is_enum_name_similar_to_param_name(
277 sema: &Semantics<RootDatabase>,
278 argument: &ast::Expr,
279 param_name: &str,
280) -> bool {
281 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
282 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
283 _ => false,
284 }
262} 285}
263 286
264fn remove_ref(expr: ast::Expr) -> ast::Expr { 287fn get_string_representation(expr: &ast::Expr) -> Option<String> {
265 if let ast::Expr::RefExpr(ref_expr) = &expr { 288 match expr {
266 if let Some(inner) = ref_expr.expr() { 289 ast::Expr::MethodCallExpr(method_call_expr) => {
267 return inner; 290 Some(method_call_expr.name_ref()?.to_string())
268 } 291 }
292 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
293 _ => Some(expr.to_string()),
269 } 294 }
270 expr
271} 295}
272 296
273fn is_obvious_param(param_name: &str) -> bool { 297fn is_obvious_param(param_name: &str) -> bool {
@@ -1073,6 +1097,12 @@ struct TestVarContainer {
1073 test_var: i32, 1097 test_var: i32,
1074} 1098}
1075 1099
1100impl TestVarContainer {
1101 fn test_var(&self) -> i32 {
1102 self.test_var
1103 }
1104}
1105
1076struct Test {} 1106struct Test {}
1077 1107
1078impl Test { 1108impl Test {
@@ -1098,10 +1128,15 @@ struct Param {}
1098fn different_order(param: &Param) {} 1128fn different_order(param: &Param) {}
1099fn different_order_mut(param: &mut Param) {} 1129fn different_order_mut(param: &mut Param) {}
1100fn has_underscore(_param: bool) {} 1130fn has_underscore(_param: bool) {}
1131fn enum_matches_param_name(completion_kind: CompletionKind) {}
1101 1132
1102fn twiddle(twiddle: bool) {} 1133fn twiddle(twiddle: bool) {}
1103fn doo(_doo: bool) {} 1134fn doo(_doo: bool) {}
1104 1135
1136enum CompletionKind {
1137 Keyword,
1138}
1139
1105fn main() { 1140fn main() {
1106 let container: TestVarContainer = TestVarContainer { test_var: 42 }; 1141 let container: TestVarContainer = TestVarContainer { test_var: 42 };
1107 let test: Test = Test {}; 1142 let test: Test = Test {};
@@ -1114,18 +1149,21 @@ fn main() {
1114 let test_var: i32 = 55; 1149 let test_var: i32 = 55;
1115 test_processed.no_hints_expected(22, test_var); 1150 test_processed.no_hints_expected(22, test_var);
1116 test_processed.no_hints_expected(33, container.test_var); 1151 test_processed.no_hints_expected(33, container.test_var);
1152 test_processed.no_hints_expected(44, container.test_var());
1117 test_processed.frob(false); 1153 test_processed.frob(false);
1118 1154
1119 twiddle(true); 1155 twiddle(true);
1120 doo(true); 1156 doo(true);
1121 1157
1122 let param_begin: Param = Param {}; 1158 let mut param_begin: Param = Param {};
1123 different_order(&param_begin); 1159 different_order(&param_begin);
1124 different_order(&mut param_begin); 1160 different_order(&mut param_begin);
1125 1161
1126 let param: bool = true; 1162 let param: bool = true;
1127 has_underscore(param); 1163 has_underscore(param);
1128 1164
1165 enum_matches_param_name(CompletionKind::Keyword);
1166
1129 let a: f64 = 7.0; 1167 let a: f64 = 7.0;
1130 let b: f64 = 4.0; 1168 let b: f64 = 4.0;
1131 let _: f64 = a.div_euclid(b); 1169 let _: f64 = a.div_euclid(b);
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
index bea30fe2a..51ca4dde3 100644
--- a/crates/ra_ide/src/marks.rs
+++ b/crates/ra_ide/src/marks.rs
@@ -11,4 +11,6 @@ test_utils::marks!(
11 self_fulfilling_completion 11 self_fulfilling_completion
12 test_struct_field_completion_in_func_call 12 test_struct_field_completion_in_func_call
13 test_struct_field_completion_in_record_lit 13 test_struct_field_completion_in_record_lit
14 test_rename_struct_field_for_shorthand
15 test_rename_local_for_field_shorthand
14); 16);
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index fd17bc9f2..916edaef2 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -7,14 +7,13 @@ use ra_syntax::{
7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, 7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode,
8}; 8};
9use ra_text_edit::TextEdit; 9use ra_text_edit::TextEdit;
10use test_utils::tested_by;
10 11
11use crate::{ 12use crate::{
12 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, 13 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
13 SourceFileEdit, TextRange, 14 SourceChange, SourceFileEdit, TextRange,
14}; 15};
15 16
16use super::find_all_refs;
17
18pub(crate) fn rename( 17pub(crate) fn rename(
19 db: &RootDatabase, 18 db: &RootDatabase,
20 position: FilePosition, 19 position: FilePosition,
@@ -52,11 +51,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
52 let file_id = reference.file_range.file_id; 51 let file_id = reference.file_range.file_id;
53 let range = match reference.kind { 52 let range = match reference.kind {
54 ReferenceKind::FieldShorthandForField => { 53 ReferenceKind::FieldShorthandForField => {
54 tested_by!(test_rename_struct_field_for_shorthand);
55 replacement_text.push_str(new_name); 55 replacement_text.push_str(new_name);
56 replacement_text.push_str(": "); 56 replacement_text.push_str(": ");
57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
58 } 58 }
59 ReferenceKind::FieldShorthandForLocal => { 59 ReferenceKind::FieldShorthandForLocal => {
60 tested_by!(test_rename_local_for_field_shorthand);
60 replacement_text.push_str(": "); 61 replacement_text.push_str(": ");
61 replacement_text.push_str(new_name); 62 replacement_text.push_str(new_name);
62 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 63 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
@@ -147,7 +148,7 @@ fn rename_reference(
147mod tests { 148mod tests {
148 use insta::assert_debug_snapshot; 149 use insta::assert_debug_snapshot;
149 use ra_text_edit::TextEditBuilder; 150 use ra_text_edit::TextEditBuilder;
150 use test_utils::assert_eq_text; 151 use test_utils::{assert_eq_text, covers};
151 152
152 use crate::{ 153 use crate::{
153 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 154 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
@@ -379,6 +380,7 @@ mod tests {
379 380
380 #[test] 381 #[test]
381 fn test_rename_struct_field_for_shorthand() { 382 fn test_rename_struct_field_for_shorthand() {
383 covers!(test_rename_struct_field_for_shorthand);
382 test_rename( 384 test_rename(
383 r#" 385 r#"
384 struct Foo { 386 struct Foo {
@@ -408,6 +410,7 @@ mod tests {
408 410
409 #[test] 411 #[test]
410 fn test_rename_local_for_field_shorthand() { 412 fn test_rename_local_for_field_shorthand() {
413 covers!(test_rename_local_for_field_shorthand);
411 test_rename( 414 test_rename(
412 r#" 415 r#"
413 struct Foo { 416 struct Foo {
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/caps.rs b/crates/rust-analyzer/src/caps.rs
index 44222d8bd..680415cac 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,4 +1,5 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertizes the capabilities of the LSP Server.
2use std::env;
2 3
3use crate::semantic_tokens; 4use crate::semantic_tokens;
4 5
@@ -16,7 +17,11 @@ pub fn server_capabilities() -> ServerCapabilities {
16 ServerCapabilities { 17 ServerCapabilities {
17 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { 18 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
18 open_close: Some(true), 19 open_close: Some(true),
19 change: Some(TextDocumentSyncKind::Incremental), 20 change: Some(if env::var("RA_PROFILE").is_ok() {
21 TextDocumentSyncKind::Incremental
22 } else {
23 TextDocumentSyncKind::Full
24 }),
20 will_save: None, 25 will_save: None,
21 will_save_wait_until: None, 26 will_save_wait_until: None,
22 save: Some(SaveOptions::default()), 27 save: Some(SaveOptions::default()),
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/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 01cdf452c..0f34ce70e 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -102,3 +102,17 @@ pub fn timeit(label: &'static str) -> impl Drop {
102 102
103 Guard { label, start: Instant::now() } 103 Guard { label, start: Instant::now() }
104} 104}
105
106pub fn to_lower_snake_case(s: &str) -> String {
107 let mut buf = String::with_capacity(s.len());
108 let mut prev = false;
109 for c in s.chars() {
110 if c.is_ascii_uppercase() && prev {
111 buf.push('_')
112 }
113 prev = true;
114
115 buf.push(c.to_ascii_lowercase());
116 }
117 buf
118}
diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md
index bece6a572..1aa392935 100644
--- a/docs/dev/debugging.md
+++ b/docs/dev/debugging.md
@@ -26,7 +26,7 @@ where **only** the `rust-analyzer` extension being debugged is enabled.
26- `Run Extension (Dev Server)` - runs extension with the locally built LSP server (`target/debug/rust-analyzer`). 26- `Run Extension (Dev Server)` - runs extension with the locally built LSP server (`target/debug/rust-analyzer`).
27 27
28TypeScript debugging is configured to watch your source edits and recompile. 28TypeScript debugging is configured to watch your source edits and recompile.
29To apply changes to an already running debug process press <kbd>Ctrl+Shift+P</kbd> and run the following command in your `[Extension Development Host]` 29To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</kbd> and run the following command in your `[Extension Development Host]`
30 30
31``` 31```
32> Developer: Reload Window 32> Developer: Reload Window
@@ -76,11 +76,11 @@ Make sure you open a rust file in the `[Extension Development Host]` and try aga
76 76
77Make sure you have run `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`. 77Make sure you have run `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`.
78 78
79By default this should reset back to 1 everytime you log in. 79By default this should reset back to 1 every time you log in.
80 80
81### Breakpoints are never being hit 81### Breakpoints are never being hit
82 82
83Check your version of `lldb` if it's version 6 and lower use the `classic` adapter type. 83Check your version of `lldb`. If it's version 6 and lower, use the `classic` adapter type.
84It's `lldb.adapterType` in settings file. 84It's `lldb.adapterType` in settings file.
85 85
86If you're running `lldb` version 7 change the lldb adapter type to `bundled` or `native`. 86If you're running `lldb` version 7, change the lldb adapter type to `bundled` or `native`.
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],