aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md5
-rw-r--r--crates/base_db/src/fixture.rs8
-rw-r--r--crates/hir/src/lib.rs12
-rw-r--r--crates/hir_def/src/body/lower.rs8
-rw-r--r--crates/hir_def/src/body/tests.rs2
-rw-r--r--crates/hir_def/src/diagnostics.rs5
-rw-r--r--crates/hir_def/src/find_path.rs23
-rw-r--r--crates/hir_def/src/lib.rs29
-rw-r--r--crates/hir_def/src/nameres.rs13
-rw-r--r--crates/hir_def/src/nameres/collector.rs9
-rw-r--r--crates/hir_def/src/nameres/path_resolution.rs8
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs4
-rw-r--r--crates/hir_def/src/test_db.rs51
-rw-r--r--crates/hir_def/src/visibility.rs10
-rw-r--r--crates/hir_expand/src/builtin_macro.rs10
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/hir_ty/src/lib.rs1
-rw-r--r--crates/hir_ty/src/method_resolution.rs42
-rw-r--r--crates/hir_ty/src/tests/method_resolution.rs37
-rw-r--r--crates/hir_ty/src/tests/simple.rs18
-rw-r--r--crates/hir_ty/src/utils.rs2
-rw-r--r--crates/ide/src/diagnostics.rs2
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs10
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs227
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests.rs7
-rw-r--r--crates/ide_assists/src/tests/generated.rs27
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs23
-rw-r--r--crates/ide_db/src/call_info.rs10
-rw-r--r--crates/ide_db/src/call_info/tests.rs27
-rw-r--r--crates/ide_db/src/helpers.rs4
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs8
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs22
-rw-r--r--crates/mbe/src/syntax_bridge.rs2
-rw-r--r--crates/mbe/src/tests/expand.rs42
-rw-r--r--crates/parser/src/grammar.rs37
-rw-r--r--crates/parser/src/grammar/attributes.rs28
-rw-r--r--crates/rust-analyzer/src/config.rs11
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt1
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt1
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt1
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt1
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt1
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs1
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs6
-rw-r--r--crates/rust-analyzer/src/markdown.rs18
-rw-r--r--crates/rust-analyzer/src/to_proto.rs101
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--docs/dev/lsp-extensions.md3
50 files changed, 785 insertions, 144 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index b5160eaa3..060a05d41 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -14,8 +14,9 @@ Forum for questions: https://users.rust-lang.org/c/ide/14
14Before submitting, please make sure that you're not running into one of these known issues: 14Before submitting, please make sure that you're not running into one of these known issues:
15 15
16 1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file) 16 1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file)
17 2. some settings are required for procedural macro and build script support (`rust-analyzer.cargo.loadOutDirsFromCheck`, `rust-analyzer.procMacro.enable`): #6448 17 2. some platform-specific imports are not resolved: #6038
18 3. some platform-specific imports are not resolved: #6038 18 3. attribute proc macros are not supported: #6029
19 4. the version string is misleading (includes the previous week): #8571
19 20
20Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3. 21Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
21--> 22-->
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 04e2be390..0132565e4 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -35,7 +35,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
35 fn with_position(ra_fixture: &str) -> (Self, FilePosition) { 35 fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
36 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); 36 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
37 let offset = match range_or_offset { 37 let offset = match range_or_offset {
38 RangeOrOffset::Range(_) => panic!(), 38 RangeOrOffset::Range(_) => panic!("Expected a cursor position, got a range instead"),
39 RangeOrOffset::Offset(it) => it, 39 RangeOrOffset::Offset(it) => it,
40 }; 40 };
41 (db, FilePosition { file_id, offset }) 41 (db, FilePosition { file_id, offset })
@@ -45,7 +45,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
45 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); 45 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
46 let range = match range_or_offset { 46 let range = match range_or_offset {
47 RangeOrOffset::Range(it) => it, 47 RangeOrOffset::Range(it) => it,
48 RangeOrOffset::Offset(_) => panic!(), 48 RangeOrOffset::Offset(_) => panic!("Expected a cursor range, got a position instead"),
49 }; 49 };
50 (db, FileRange { file_id, range }) 50 (db, FileRange { file_id, range })
51 } 51 }
@@ -54,7 +54,9 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
54 let fixture = ChangeFixture::parse(ra_fixture); 54 let fixture = ChangeFixture::parse(ra_fixture);
55 let mut db = Self::default(); 55 let mut db = Self::default();
56 fixture.change.apply(&mut db); 56 fixture.change.apply(&mut db);
57 let (file_id, range_or_offset) = fixture.file_position.unwrap(); 57 let (file_id, range_or_offset) = fixture
58 .file_position
59 .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
58 (db, file_id, range_or_offset) 60 (db, file_id, range_or_offset)
59 } 61 }
60 62
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index eba46a056..0acfa582a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2066,6 +2066,18 @@ impl Type {
2066 self.ty.dyn_trait().map(Into::into) 2066 self.ty.dyn_trait().map(Into::into)
2067 } 2067 }
2068 2068
2069 /// If a type can be represented as `dyn Trait`, returns all traits accessible via this type,
2070 /// or an empty iterator otherwise.
2071 pub fn applicable_inherent_traits<'a>(
2072 &'a self,
2073 db: &'a dyn HirDatabase,
2074 ) -> impl Iterator<Item = Trait> + 'a {
2075 self.autoderef(db)
2076 .filter_map(|derefed_type| derefed_type.ty.dyn_trait())
2077 .flat_map(move |dyn_trait_id| hir_ty::all_super_traits(db.upcast(), dyn_trait_id))
2078 .map(Trait::from)
2079 }
2080
2069 pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option<Vec<Trait>> { 2081 pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option<Vec<Trait>> {
2070 self.ty.impl_trait_bounds(db).map(|it| { 2082 self.ty.impl_trait_bounds(db).map(|it| {
2071 it.into_iter() 2083 it.into_iter()
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index ed07d6928..c0b0b7841 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -568,9 +568,13 @@ impl ExprCollector<'_> {
568 568
569 let res = match res { 569 let res = match res {
570 Ok(res) => res, 570 Ok(res) => res,
571 Err(UnresolvedMacro) => { 571 Err(UnresolvedMacro { path }) => {
572 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall( 572 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall(
573 UnresolvedMacroCall { file: outer_file, node: syntax_ptr.cast().unwrap() }, 573 UnresolvedMacroCall {
574 file: outer_file,
575 node: syntax_ptr.cast().unwrap(),
576 path,
577 },
574 )); 578 ));
575 collector(self, None); 579 collector(self, None);
576 return; 580 return;
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index c1d3e998f..63f5fe88d 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -180,7 +180,7 @@ fn unresolved_macro_diag() {
180 r#" 180 r#"
181fn f() { 181fn f() {
182 m!(); 182 m!();
183 //^^^^ unresolved macro call 183 //^^^^ unresolved macro `m!`
184} 184}
185 "#, 185 "#,
186 ); 186 );
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index 97abf8653..a71ae2668 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -8,7 +8,7 @@ use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
8use hir_expand::{HirFileId, InFile}; 8use hir_expand::{HirFileId, InFile};
9use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; 9use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
10 10
11use crate::{db::DefDatabase, DefWithBodyId}; 11use crate::{db::DefDatabase, path::ModPath, DefWithBodyId};
12 12
13pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { 13pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
14 let source_map = db.body_with_source_map(owner).1; 14 let source_map = db.body_with_source_map(owner).1;
@@ -103,6 +103,7 @@ impl Diagnostic for UnresolvedImport {
103pub struct UnresolvedMacroCall { 103pub struct UnresolvedMacroCall {
104 pub file: HirFileId, 104 pub file: HirFileId,
105 pub node: AstPtr<ast::MacroCall>, 105 pub node: AstPtr<ast::MacroCall>,
106 pub path: ModPath,
106} 107}
107 108
108impl Diagnostic for UnresolvedMacroCall { 109impl Diagnostic for UnresolvedMacroCall {
@@ -110,7 +111,7 @@ impl Diagnostic for UnresolvedMacroCall {
110 DiagnosticCode("unresolved-macro-call") 111 DiagnosticCode("unresolved-macro-call")
111 } 112 }
112 fn message(&self) -> String { 113 fn message(&self) -> String {
113 "unresolved macro call".to_string() 114 format!("unresolved macro `{}!`", self.path)
114 } 115 }
115 fn display_source(&self) -> InFile<SyntaxNodePtr> { 116 fn display_source(&self) -> InFile<SyntaxNodePtr> {
116 InFile::new(self.file, self.node.clone().into()) 117 InFile::new(self.file, self.node.clone().into())
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 41da3bc2d..2c4bbe585 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -955,6 +955,29 @@ fn main() {
955 } 955 }
956 956
957 #[test] 957 #[test]
958 fn from_inside_module() {
959 // This worked correctly, but the test suite logic was broken.
960 cov_mark::check!(submodule_in_testdb);
961 check_found_path(
962 r#"
963mod baz {
964 pub struct Foo {}
965}
966
967mod bar {
968 fn bar() {
969 $0
970 }
971}
972 "#,
973 "crate::baz::Foo",
974 "crate::baz::Foo",
975 "crate::baz::Foo",
976 "crate::baz::Foo",
977 )
978 }
979
980 #[test]
958 fn recursive_pub_mod_reexport() { 981 fn recursive_pub_mod_reexport() {
959 cov_mark::check!(recursive_imports); 982 cov_mark::check!(recursive_imports);
960 check_found_path( 983 check_found_path(
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index ffee05500..5ac1670b5 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -66,6 +66,7 @@ use hir_expand::{
66}; 66};
67use la_arena::Idx; 67use la_arena::Idx;
68use nameres::DefMap; 68use nameres::DefMap;
69use path::ModPath;
69use syntax::ast; 70use syntax::ast;
70 71
71use crate::builtin_type::BuiltinType; 72use crate::builtin_type::BuiltinType;
@@ -107,6 +108,18 @@ impl ModuleId {
107 pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> { 108 pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
108 self.def_map(db).containing_module(self.local_id) 109 self.def_map(db).containing_module(self.local_id)
109 } 110 }
111
112 /// Returns `true` if this module represents a block expression.
113 ///
114 /// Returns `false` if this module is a submodule *inside* a block expression
115 /// (eg. `m` in `{ mod m {} }`).
116 pub fn is_block_root(&self, db: &dyn db::DefDatabase) -> bool {
117 if self.block.is_none() {
118 return false;
119 }
120
121 self.def_map(db)[self.local_id].parent.is_none()
122 }
110} 123}
111 124
112/// An ID of a module, **local** to a specific crate 125/// An ID of a module, **local** to a specific crate
@@ -675,7 +688,9 @@ impl<T: ast::AstNode> AstIdWithPath<T> {
675 } 688 }
676} 689}
677 690
678pub struct UnresolvedMacro; 691pub struct UnresolvedMacro {
692 pub path: ModPath,
693}
679 694
680fn macro_call_as_call_id( 695fn macro_call_as_call_id(
681 call: &AstIdWithPath<ast::MacroCall>, 696 call: &AstIdWithPath<ast::MacroCall>,
@@ -684,7 +699,8 @@ fn macro_call_as_call_id(
684 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, 699 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
685 error_sink: &mut dyn FnMut(mbe::ExpandError), 700 error_sink: &mut dyn FnMut(mbe::ExpandError),
686) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> { 701) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
687 let def: MacroDefId = resolver(call.path.clone()).ok_or(UnresolvedMacro)?; 702 let def: MacroDefId =
703 resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
688 704
689 let res = if let MacroDefKind::BuiltInEager(..) = def.kind { 705 let res = if let MacroDefKind::BuiltInEager(..) = def.kind {
690 let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast())); 706 let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast()));
@@ -714,8 +730,13 @@ fn derive_macro_as_call_id(
714 krate: CrateId, 730 krate: CrateId,
715 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, 731 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
716) -> Result<MacroCallId, UnresolvedMacro> { 732) -> Result<MacroCallId, UnresolvedMacro> {
717 let def: MacroDefId = resolver(item_attr.path.clone()).ok_or(UnresolvedMacro)?; 733 let def: MacroDefId = resolver(item_attr.path.clone())
718 let last_segment = item_attr.path.segments().last().ok_or(UnresolvedMacro)?; 734 .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
735 let last_segment = item_attr
736 .path
737 .segments()
738 .last()
739 .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
719 let res = def 740 let res = def
720 .as_lazy_macro( 741 .as_lazy_macro(
721 db.upcast(), 742 db.upcast(),
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 9e181751c..542f190a1 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -481,7 +481,7 @@ mod diagnostics {
481 481
482 UnresolvedProcMacro { ast: MacroCallKind }, 482 UnresolvedProcMacro { ast: MacroCallKind },
483 483
484 UnresolvedMacroCall { ast: AstId<ast::MacroCall> }, 484 UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
485 485
486 MacroError { ast: MacroCallKind, message: String }, 486 MacroError { ast: MacroCallKind, message: String },
487 } 487 }
@@ -546,8 +546,9 @@ mod diagnostics {
546 pub(super) fn unresolved_macro_call( 546 pub(super) fn unresolved_macro_call(
547 container: LocalModuleId, 547 container: LocalModuleId,
548 ast: AstId<ast::MacroCall>, 548 ast: AstId<ast::MacroCall>,
549 path: ModPath,
549 ) -> Self { 550 ) -> Self {
550 Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast } } 551 Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast, path } }
551 } 552 }
552 553
553 pub(super) fn add_to( 554 pub(super) fn add_to(
@@ -662,9 +663,13 @@ mod diagnostics {
662 }); 663 });
663 } 664 }
664 665
665 DiagnosticKind::UnresolvedMacroCall { ast } => { 666 DiagnosticKind::UnresolvedMacroCall { ast, path } => {
666 let node = ast.to_node(db.upcast()); 667 let node = ast.to_node(db.upcast());
667 sink.push(UnresolvedMacroCall { file: ast.file_id, node: AstPtr::new(&node) }); 668 sink.push(UnresolvedMacroCall {
669 file: ast.file_id,
670 node: AstPtr::new(&node),
671 path: path.clone(),
672 });
668 } 673 }
669 674
670 DiagnosticKind::MacroError { ast, message } => { 675 DiagnosticKind::MacroError { ast, message } => {
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index fb4ddff5e..05ceb1efb 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -829,7 +829,7 @@ impl DefCollector<'_> {
829 res = ReachedFixedPoint::No; 829 res = ReachedFixedPoint::No;
830 return false; 830 return false;
831 } 831 }
832 Err(UnresolvedMacro) | Ok(Err(_)) => {} 832 Err(UnresolvedMacro { .. }) | Ok(Err(_)) => {}
833 } 833 }
834 } 834 }
835 MacroDirectiveKind::Derive { ast_id, derive_attr } => { 835 MacroDirectiveKind::Derive { ast_id, derive_attr } => {
@@ -845,7 +845,7 @@ impl DefCollector<'_> {
845 res = ReachedFixedPoint::No; 845 res = ReachedFixedPoint::No;
846 return false; 846 return false;
847 } 847 }
848 Err(UnresolvedMacro) => (), 848 Err(UnresolvedMacro { .. }) => (),
849 } 849 }
850 } 850 }
851 } 851 }
@@ -943,10 +943,11 @@ impl DefCollector<'_> {
943 &mut |_| (), 943 &mut |_| (),
944 ) { 944 ) {
945 Ok(_) => (), 945 Ok(_) => (),
946 Err(UnresolvedMacro) => { 946 Err(UnresolvedMacro { path }) => {
947 self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call( 947 self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
948 directive.module_id, 948 directive.module_id,
949 ast_id.ast_id, 949 ast_id.ast_id,
950 path,
950 )); 951 ));
951 } 952 }
952 }, 953 },
@@ -1530,7 +1531,7 @@ impl ModCollector<'_, '_> {
1530 )); 1531 ));
1531 return; 1532 return;
1532 } 1533 }
1533 Err(UnresolvedMacro) => (), 1534 Err(UnresolvedMacro { .. }) => (),
1534 } 1535 }
1535 1536
1536 // Case 2: resolve in module scope, expand during name resolution. 1537 // Case 2: resolve in module scope, expand during name resolution.
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs
index ccc9f22eb..c984148c3 100644
--- a/crates/hir_def/src/nameres/path_resolution.rs
+++ b/crates/hir_def/src/nameres/path_resolution.rs
@@ -387,7 +387,13 @@ impl DefMap {
387 .get_legacy_macro(name) 387 .get_legacy_macro(name)
388 .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public)); 388 .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public));
389 let from_scope = self[module].scope.get(name); 389 let from_scope = self[module].scope.get(name);
390 let from_builtin = BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none); 390 let from_builtin = match self.block {
391 Some(_) => {
392 // Only resolve to builtins in the root `DefMap`.
393 PerNs::none()
394 }
395 None => BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none),
396 };
391 let from_scope_or_builtin = match shadow { 397 let from_scope_or_builtin = match shadow {
392 BuiltinShadowMode::Module => from_scope.or(from_builtin), 398 BuiltinShadowMode::Module => from_scope.or(from_builtin),
393 BuiltinShadowMode::Other => { 399 BuiltinShadowMode::Other => {
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 1ac88fc89..543975e07 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -170,7 +170,7 @@ fn unresolved_legacy_scope_macro() {
170 170
171 m!(); 171 m!();
172 m2!(); 172 m2!();
173 //^^^^^^ unresolved macro call 173 //^^^^^^ unresolved macro `self::m2!`
174 "#, 174 "#,
175 ); 175 );
176} 176}
@@ -187,7 +187,7 @@ fn unresolved_module_scope_macro() {
187 187
188 self::m!(); 188 self::m!();
189 self::m2!(); 189 self::m2!();
190 //^^^^^^^^^^^^ unresolved macro call 190 //^^^^^^^^^^^^ unresolved macro `self::m2!`
191 "#, 191 "#,
192 ); 192 );
193} 193}
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index dd36106f8..8fa703a57 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -15,7 +15,12 @@ use rustc_hash::FxHashSet;
15use syntax::{algo, ast, AstNode, TextRange, TextSize}; 15use syntax::{algo, ast, AstNode, TextRange, TextSize};
16use test_utils::extract_annotations; 16use test_utils::extract_annotations;
17 17
18use crate::{db::DefDatabase, nameres::DefMap, src::HasSource, Lookup, ModuleDefId, ModuleId}; 18use crate::{
19 db::DefDatabase,
20 nameres::{DefMap, ModuleSource},
21 src::HasSource,
22 LocalModuleId, Lookup, ModuleDefId, ModuleId,
23};
19 24
20#[salsa::database( 25#[salsa::database(
21 base_db::SourceDatabaseExtStorage, 26 base_db::SourceDatabaseExtStorage,
@@ -87,10 +92,11 @@ impl TestDB {
87 pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId { 92 pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId {
88 let file_module = self.module_for_file(position.file_id); 93 let file_module = self.module_for_file(position.file_id);
89 let mut def_map = file_module.def_map(self); 94 let mut def_map = file_module.def_map(self);
95 let module = self.mod_at_position(&def_map, position);
90 96
91 def_map = match self.block_at_position(&def_map, position) { 97 def_map = match self.block_at_position(&def_map, position) {
92 Some(it) => it, 98 Some(it) => it,
93 None => return file_module, 99 None => return def_map.module_id(module),
94 }; 100 };
95 loop { 101 loop {
96 let new_map = self.block_at_position(&def_map, position); 102 let new_map = self.block_at_position(&def_map, position);
@@ -106,6 +112,47 @@ impl TestDB {
106 } 112 }
107 } 113 }
108 114
115 /// Finds the smallest/innermost module in `def_map` containing `position`.
116 fn mod_at_position(&self, def_map: &DefMap, position: FilePosition) -> LocalModuleId {
117 let mut size = None;
118 let mut res = def_map.root();
119 for (module, data) in def_map.modules() {
120 let src = data.definition_source(self);
121 if src.file_id != position.file_id.into() {
122 continue;
123 }
124
125 let range = match src.value {
126 ModuleSource::SourceFile(it) => it.syntax().text_range(),
127 ModuleSource::Module(it) => it.syntax().text_range(),
128 ModuleSource::BlockExpr(it) => it.syntax().text_range(),
129 };
130
131 if !range.contains(position.offset) {
132 continue;
133 }
134
135 let new_size = match size {
136 None => range.len(),
137 Some(size) => {
138 if range.len() < size {
139 range.len()
140 } else {
141 size
142 }
143 }
144 };
145
146 if size != Some(new_size) {
147 cov_mark::hit!(submodule_in_testdb);
148 size = Some(new_size);
149 res = module;
150 }
151 }
152
153 res
154 }
155
109 fn block_at_position(&self, def_map: &DefMap, position: FilePosition) -> Option<Arc<DefMap>> { 156 fn block_at_position(&self, def_map: &DefMap, position: FilePosition) -> Option<Arc<DefMap>> {
110 // Find the smallest (innermost) function in `def_map` containing the cursor. 157 // Find the smallest (innermost) function in `def_map` containing the cursor.
111 let mut size = None; 158 let mut size = None;
diff --git a/crates/hir_def/src/visibility.rs b/crates/hir_def/src/visibility.rs
index 9908cd926..d4b7c9970 100644
--- a/crates/hir_def/src/visibility.rs
+++ b/crates/hir_def/src/visibility.rs
@@ -123,11 +123,19 @@ impl Visibility {
123 def_map: &DefMap, 123 def_map: &DefMap,
124 mut from_module: crate::LocalModuleId, 124 mut from_module: crate::LocalModuleId,
125 ) -> bool { 125 ) -> bool {
126 let to_module = match self { 126 let mut to_module = match self {
127 Visibility::Module(m) => m, 127 Visibility::Module(m) => m,
128 Visibility::Public => return true, 128 Visibility::Public => return true,
129 }; 129 };
130 130
131 // `to_module` might be the root module of a block expression. Those have the same
132 // visibility as the containing module (even though no items are directly nameable from
133 // there, getting this right is important for method resolution).
134 // In that case, we adjust the visibility of `to_module` to point to the containing module.
135 if to_module.is_block_root(db) {
136 to_module = to_module.containing_module(db).unwrap();
137 }
138
131 // from_module needs to be a descendant of to_module 139 // from_module needs to be a descendant of to_module
132 let mut def_map = def_map; 140 let mut def_map = def_map;
133 let mut parent_arc; 141 let mut parent_arc;
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index 80365fc16..179de61f9 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -110,6 +110,7 @@ register_builtin! {
110 (format_args_nl, FormatArgsNl) => format_args_expand, 110 (format_args_nl, FormatArgsNl) => format_args_expand,
111 (llvm_asm, LlvmAsm) => asm_expand, 111 (llvm_asm, LlvmAsm) => asm_expand,
112 (asm, Asm) => asm_expand, 112 (asm, Asm) => asm_expand,
113 (global_asm, GlobalAsm) => global_asm_expand,
113 (cfg, Cfg) => cfg_expand, 114 (cfg, Cfg) => cfg_expand,
114 (core_panic, CorePanic) => panic_expand, 115 (core_panic, CorePanic) => panic_expand,
115 (std_panic, StdPanic) => panic_expand, 116 (std_panic, StdPanic) => panic_expand,
@@ -274,6 +275,15 @@ fn asm_expand(
274 ExpandResult::ok(expanded) 275 ExpandResult::ok(expanded)
275} 276}
276 277
278fn global_asm_expand(
279 _db: &dyn AstDatabase,
280 _id: LazyMacroId,
281 _tt: &tt::Subtree,
282) -> ExpandResult<tt::Subtree> {
283 // Expand to nothing (at item-level)
284 ExpandResult::ok(quote! {})
285}
286
277fn cfg_expand( 287fn cfg_expand(
278 db: &dyn AstDatabase, 288 db: &dyn AstDatabase,
279 id: LazyMacroId, 289 id: LazyMacroId,
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index a0f8766b0..bcfd3e524 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -221,6 +221,7 @@ pub mod known {
221 option_env, 221 option_env,
222 llvm_asm, 222 llvm_asm,
223 asm, 223 asm,
224 global_asm,
224 // Builtin derives 225 // Builtin derives
225 Copy, 226 Copy,
226 Clone, 227 Clone,
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 113234fa4..0505fa4ae 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -56,6 +56,7 @@ pub use mapping::{
56 to_foreign_def_id, to_placeholder_idx, 56 to_foreign_def_id, to_placeholder_idx,
57}; 57};
58pub use traits::TraitEnvironment; 58pub use traits::TraitEnvironment;
59pub use utils::all_super_traits;
59pub use walk::TypeWalk; 60pub use walk::TypeWalk;
60 61
61pub use chalk_ir::{ 62pub use chalk_ir::{
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index 3693e3284..48bbcfd9f 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -246,29 +246,39 @@ pub struct InherentImpls {
246 246
247impl InherentImpls { 247impl InherentImpls {
248 pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> { 248 pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> {
249 let mut map: FxHashMap<_, Vec<_>> = FxHashMap::default(); 249 let mut impls = Self { map: FxHashMap::default() };
250 250
251 let crate_def_map = db.crate_def_map(krate); 251 let crate_def_map = db.crate_def_map(krate);
252 for (_module_id, module_data) in crate_def_map.modules() { 252 collect_def_map(db, &crate_def_map, &mut impls);
253 for impl_id in module_data.scope.impls() { 253
254 let data = db.impl_data(impl_id); 254 return Arc::new(impls);
255 if data.target_trait.is_some() { 255
256 continue; 256 fn collect_def_map(db: &dyn HirDatabase, def_map: &DefMap, impls: &mut InherentImpls) {
257 for (_module_id, module_data) in def_map.modules() {
258 for impl_id in module_data.scope.impls() {
259 let data = db.impl_data(impl_id);
260 if data.target_trait.is_some() {
261 continue;
262 }
263
264 let self_ty = db.impl_self_ty(impl_id);
265 let fp = TyFingerprint::for_inherent_impl(self_ty.skip_binders());
266 if let Some(fp) = fp {
267 impls.map.entry(fp).or_default().push(impl_id);
268 }
269 // `fp` should only be `None` in error cases (either erroneous code or incomplete name resolution)
257 } 270 }
258 271
259 let self_ty = db.impl_self_ty(impl_id); 272 // To better support custom derives, collect impls in all unnamed const items.
260 let fp = TyFingerprint::for_inherent_impl(self_ty.skip_binders()); 273 // const _: () = { ... };
261 if let Some(fp) = fp { 274 for konst in module_data.scope.unnamed_consts() {
262 map.entry(fp).or_default().push(impl_id); 275 let body = db.body(konst.into());
276 for (_, block_def_map) in body.blocks(db.upcast()) {
277 collect_def_map(db, &block_def_map, impls);
278 }
263 } 279 }
264 // `fp` should only be `None` in error cases (either erroneous code or incomplete name resolution)
265 } 280 }
266 } 281 }
267
268 // NOTE: We're not collecting inherent impls from unnamed consts here, we intentionally only
269 // support trait impls there.
270
271 Arc::new(Self { map })
272 } 282 }
273 283
274 pub fn for_self_ty(&self, self_ty: &Ty) -> &[ImplId] { 284 pub fn for_self_ty(&self, self_ty: &Ty) -> &[ImplId] {
diff --git a/crates/hir_ty/src/tests/method_resolution.rs b/crates/hir_ty/src/tests/method_resolution.rs
index 4b2c82b41..a4c132bc5 100644
--- a/crates/hir_ty/src/tests/method_resolution.rs
+++ b/crates/hir_ty/src/tests/method_resolution.rs
@@ -1294,7 +1294,7 @@ mod b {
1294} 1294}
1295 1295
1296#[test] 1296#[test]
1297fn impl_in_unnamed_const() { 1297fn trait_impl_in_unnamed_const() {
1298 check_types( 1298 check_types(
1299 r#" 1299 r#"
1300struct S; 1300struct S;
@@ -1314,3 +1314,38 @@ fn f() {
1314 "#, 1314 "#,
1315 ); 1315 );
1316} 1316}
1317
1318#[test]
1319fn inherent_impl_in_unnamed_const() {
1320 check_types(
1321 r#"
1322struct S;
1323
1324const _: () = {
1325 impl S {
1326 fn method(&self) -> u16 { 0 }
1327
1328 pub(super) fn super_method(&self) -> u16 { 0 }
1329
1330 pub(crate) fn crate_method(&self) -> u16 { 0 }
1331
1332 pub fn pub_method(&self) -> u16 { 0 }
1333 }
1334};
1335
1336fn f() {
1337 S.method();
1338 //^^^^^^^^^^ u16
1339
1340 S.super_method();
1341 //^^^^^^^^^^^^^^^^ u16
1342
1343 S.crate_method();
1344 //^^^^^^^^^^^^^^^^ u16
1345
1346 S.pub_method();
1347 //^^^^^^^^^^^^^^ u16
1348}
1349 "#,
1350 );
1351}
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index 84c5c05fd..5948d0bc2 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -1765,6 +1765,24 @@ fn main() {
1765} 1765}
1766 1766
1767#[test] 1767#[test]
1768fn shadowing_primitive_with_inner_items() {
1769 check_types(
1770 r#"
1771struct i32;
1772struct Foo;
1773
1774impl i32 { fn foo(&self) -> Foo { Foo } }
1775
1776fn main() {
1777 fn inner() {}
1778 let x: i32 = i32;
1779 x.foo();
1780 //^ Foo
1781}"#,
1782 );
1783}
1784
1785#[test]
1768fn not_shadowing_primitive_by_module() { 1786fn not_shadowing_primitive_by_module() {
1769 check_types( 1787 check_types(
1770 r#" 1788 r#"
diff --git a/crates/hir_ty/src/utils.rs b/crates/hir_ty/src/utils.rs
index 5f6cb052a..2f04ee57a 100644
--- a/crates/hir_ty/src/utils.rs
+++ b/crates/hir_ty/src/utils.rs
@@ -78,7 +78,7 @@ fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef) -> Vec<Tr
78 78
79/// Returns an iterator over the whole super trait hierarchy (including the 79/// Returns an iterator over the whole super trait hierarchy (including the
80/// trait itself). 80/// trait itself).
81pub(super) fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> Vec<TraitId> { 81pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> Vec<TraitId> {
82 // we need to take care a bit here to avoid infinite loops in case of cycles 82 // we need to take care a bit here to avoid infinite loops in case of cycles
83 // (i.e. if we have `trait A: B; trait B: A;`) 83 // (i.e. if we have `trait A: B; trait B: A;`)
84 let mut result = vec![trait_]; 84 let mut result = vec![trait_];
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 9a883acb9..1c911a8b2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -725,7 +725,7 @@ fn test_fn() {
725 expect![[r#" 725 expect![[r#"
726 [ 726 [
727 Diagnostic { 727 Diagnostic {
728 message: "unresolved macro call", 728 message: "unresolved macro `foo::bar!`",
729 range: 5..8, 729 range: 5..8,
730 severity: Error, 730 severity: Error,
731 fix: None, 731 fix: None,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index b24c664ba..99e45633e 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -244,6 +244,12 @@ impl Analysis {
244 self.with_db(|db| db.parse(file_id).tree()) 244 self.with_db(|db| db.parse(file_id).tree())
245 } 245 }
246 246
247 /// Returns true if this file belongs to an immutable library.
248 pub fn is_library_file(&self, file_id: FileId) -> Cancelable<bool> {
249 use ide_db::base_db::SourceDatabaseExt;
250 self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library)
251 }
252
247 /// Gets the file's `LineIndex`: data structure to convert between absolute 253 /// Gets the file's `LineIndex`: data structure to convert between absolute
248 /// offsets and line/column representation. 254 /// offsets and line/column representation.
249 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> { 255 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 04fafd244..855c7fba8 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -90,6 +90,13 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
90 "edition2021", 90 "edition2021",
91]; 91];
92 92
93fn is_rustdoc_fence_token(token: &str) -> bool {
94 if RUSTDOC_FENCE_TOKENS.contains(&token) {
95 return true;
96 }
97 token.starts_with('E') && token.len() == 5 && token[1..].parse::<u32>().is_ok()
98}
99
93/// Injection of syntax highlighting of doctests. 100/// Injection of syntax highlighting of doctests.
94pub(super) fn doc_comment( 101pub(super) fn doc_comment(
95 hl: &mut Highlights, 102 hl: &mut Highlights,
@@ -174,8 +181,7 @@ pub(super) fn doc_comment(
174 is_codeblock = !is_codeblock; 181 is_codeblock = !is_codeblock;
175 // Check whether code is rust by inspecting fence guards 182 // Check whether code is rust by inspecting fence guards
176 let guards = &line[idx + RUSTDOC_FENCE.len()..]; 183 let guards = &line[idx + RUSTDOC_FENCE.len()..];
177 let is_rust = 184 let is_rust = guards.split(',').all(|sub| is_rustdoc_fence_token(sub.trim()));
178 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
179 is_doctest = is_codeblock && is_rust; 185 is_doctest = is_codeblock && is_rust;
180 continue; 186 continue;
181 } 187 }
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs
new file mode 100644
index 000000000..4998ff7a4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,227 @@
1use std::fmt::Display;
2
3use ide_db::{helpers::FamousDefs, RootDatabase};
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SyntaxNode,
7};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 utils::generate_trait_impl_text,
12 AssistId, AssistKind,
13};
14
15// Assist: generate_deref
16//
17// Generate `Deref` impl using the given struct field.
18//
19// ```
20// struct A;
21// struct B {
22// $0a: A
23// }
24// ```
25// ->
26// ```
27// struct A;
28// struct B {
29// a: A
30// }
31//
32// impl std::ops::Deref for B {
33// type Target = A;
34//
35// fn deref(&self) -> &Self::Target {
36// &self.a
37// }
38// }
39// ```
40pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
42}
43
44fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
46 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
47
48 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
49 cov_mark::hit!(test_add_record_deref_impl_already_exists);
50 return None;
51 }
52
53 let field_type = field.ty()?;
54 let field_name = field.name()?;
55 let target = field.syntax().text_range();
56 acc.add(
57 AssistId("generate_deref", AssistKind::Generate),
58 format!("Generate `Deref` impl using `{}`", field_name),
59 target,
60 |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()),
61 )
62}
63
64fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
65 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
66 let field = ctx.find_node_at_offset::<ast::TupleField>()?;
67 let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
68 let field_list_index =
69 field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?;
70
71 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
72 cov_mark::hit!(test_add_field_deref_impl_already_exists);
73 return None;
74 }
75
76 let field_type = field.ty()?;
77 let target = field.syntax().text_range();
78 acc.add(
79 AssistId("generate_deref", AssistKind::Generate),
80 format!("Generate `Deref` impl using `{}`", field.syntax()),
81 target,
82 |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index),
83 )
84}
85
86fn generate_edit(
87 edit: &mut AssistBuilder,
88 strukt: ast::Struct,
89 field_type_syntax: &SyntaxNode,
90 field_name: impl Display,
91) {
92 let start_offset = strukt.syntax().text_range().end();
93 let impl_code = format!(
94 r#" type Target = {0};
95
96 fn deref(&self) -> &Self::Target {{
97 &self.{1}
98 }}"#,
99 field_type_syntax, field_name
100 );
101 let strukt_adt = ast::Adt::Struct(strukt);
102 let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code);
103 edit.insert(start_offset, deref_impl);
104}
105
106fn existing_deref_impl(
107 sema: &'_ hir::Semantics<'_, RootDatabase>,
108 strukt: &ast::Struct,
109) -> Option<()> {
110 let strukt = sema.to_def(strukt)?;
111 let krate = strukt.module(sema.db).krate();
112
113 let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
114 let strukt_type = strukt.ty(sema.db);
115
116 if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
117 Some(())
118 } else {
119 None
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn test_generate_record_deref() {
131 check_assist(
132 generate_deref,
133 r#"struct A { }
134struct B { $0a: A }"#,
135 r#"struct A { }
136struct B { a: A }
137
138impl std::ops::Deref for B {
139 type Target = A;
140
141 fn deref(&self) -> &Self::Target {
142 &self.a
143 }
144}"#,
145 );
146 }
147
148 #[test]
149 fn test_generate_field_deref_idx_0() {
150 check_assist(
151 generate_deref,
152 r#"struct A { }
153struct B($0A);"#,
154 r#"struct A { }
155struct B(A);
156
157impl std::ops::Deref for B {
158 type Target = A;
159
160 fn deref(&self) -> &Self::Target {
161 &self.0
162 }
163}"#,
164 );
165 }
166 #[test]
167 fn test_generate_field_deref_idx_1() {
168 check_assist(
169 generate_deref,
170 r#"struct A { }
171struct B(u8, $0A);"#,
172 r#"struct A { }
173struct B(u8, A);
174
175impl std::ops::Deref for B {
176 type Target = A;
177
178 fn deref(&self) -> &Self::Target {
179 &self.1
180 }
181}"#,
182 );
183 }
184
185 fn check_not_applicable(ra_fixture: &str) {
186 let fixture = format!(
187 "//- /main.rs crate:main deps:core,std\n{}\n{}",
188 ra_fixture,
189 FamousDefs::FIXTURE
190 );
191 check_assist_not_applicable(generate_deref, &fixture)
192 }
193
194 #[test]
195 fn test_generate_record_deref_not_applicable_if_already_impl() {
196 cov_mark::check!(test_add_record_deref_impl_already_exists);
197 check_not_applicable(
198 r#"struct A { }
199struct B { $0a: A }
200
201impl std::ops::Deref for B {
202 type Target = A;
203
204 fn deref(&self) -> &Self::Target {
205 &self.a
206 }
207}"#,
208 )
209 }
210
211 #[test]
212 fn test_generate_field_deref_not_applicable_if_already_impl() {
213 cov_mark::check!(test_add_field_deref_impl_already_exists);
214 check_not_applicable(
215 r#"struct A { }
216struct B($0A)
217
218impl std::ops::Deref for B {
219 type Target = A;
220
221 fn deref(&self) -> &Self::Target {
222 &self.0
223 }
224}"#,
225 )
226 }
227}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3694f468f..8996c1b61 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -134,6 +134,7 @@ mod handlers {
134 mod generate_default_from_enum_variant; 134 mod generate_default_from_enum_variant;
135 mod generate_default_from_new; 135 mod generate_default_from_new;
136 mod generate_is_empty_from_len; 136 mod generate_is_empty_from_len;
137 mod generate_deref;
137 mod generate_derive; 138 mod generate_derive;
138 mod generate_enum_is_method; 139 mod generate_enum_is_method;
139 mod generate_enum_projection_method; 140 mod generate_enum_projection_method;
@@ -201,6 +202,7 @@ mod handlers {
201 generate_default_from_enum_variant::generate_default_from_enum_variant, 202 generate_default_from_enum_variant::generate_default_from_enum_variant,
202 generate_default_from_new::generate_default_from_new, 203 generate_default_from_new::generate_default_from_new,
203 generate_is_empty_from_len::generate_is_empty_from_len, 204 generate_is_empty_from_len::generate_is_empty_from_len,
205 generate_deref::generate_deref,
204 generate_derive::generate_derive, 206 generate_derive::generate_derive,
205 generate_enum_is_method::generate_enum_is_method, 207 generate_enum_is_method::generate_enum_is_method,
206 generate_enum_projection_method::generate_enum_as_method, 208 generate_enum_projection_method::generate_enum_as_method,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index a7a923beb..49533e7d2 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -84,7 +84,8 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
84 }); 84 });
85 85
86 let actual = { 86 let actual = {
87 let source_change = assist.source_change.unwrap(); 87 let source_change =
88 assist.source_change.expect("Assist did not contain any source changes");
88 let mut actual = before; 89 let mut actual = before;
89 if let Some(source_file_edit) = source_change.get_source_edit(file_id) { 90 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
90 source_file_edit.apply(&mut actual); 91 source_file_edit.apply(&mut actual);
@@ -121,7 +122,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
121 122
122 match (assist, expected) { 123 match (assist, expected) {
123 (Some(assist), ExpectedResult::After(after)) => { 124 (Some(assist), ExpectedResult::After(after)) => {
124 let source_change = assist.source_change.unwrap(); 125 let source_change =
126 assist.source_change.expect("Assist did not contain any source changes");
125 assert!(!source_change.source_file_edits.is_empty()); 127 assert!(!source_change.source_file_edits.is_empty());
126 let skip_header = source_change.source_file_edits.len() == 1 128 let skip_header = source_change.source_file_edits.len() == 1
127 && source_change.file_system_edits.len() == 0; 129 && source_change.file_system_edits.len() == 0;
@@ -191,6 +193,7 @@ fn assist_order_field_struct() {
191 let mut assists = assists.iter(); 193 let mut assists = assists.iter();
192 194
193 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); 195 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`");
194 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); 197 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); 198 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); 199 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 27a22ca10..41559b43a 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -552,6 +552,33 @@ impl Default for Example {
552} 552}
553 553
554#[test] 554#[test]
555fn doctest_generate_deref() {
556 check_doc_test(
557 "generate_deref",
558 r#####"
559struct A;
560struct B {
561 $0a: A
562}
563"#####,
564 r#####"
565struct A;
566struct B {
567 a: A
568}
569
570impl std::ops::Deref for B {
571 type Target = A;
572
573 fn deref(&self) -> &Self::Target {
574 &self.a
575 }
576}
577"#####,
578 )
579}
580
581#[test]
555fn doctest_generate_derive() { 582fn doctest_generate_derive() {
556 check_doc_test( 583 check_doc_test(
557 "generate_derive", 584 "generate_derive",
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 5e61ecb4d..8e211ae1e 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -1127,4 +1127,27 @@ impl Bar for Foo {
1127 expect![[r#""#]], 1127 expect![[r#""#]],
1128 ); 1128 );
1129 } 1129 }
1130
1131 #[test]
1132 fn no_inherent_candidates_proposed() {
1133 check(
1134 r#"
1135mod baz {
1136 pub trait DefDatabase {
1137 fn method1(&self);
1138 }
1139 pub trait HirDatabase: DefDatabase {
1140 fn method2(&self);
1141 }
1142}
1143
1144mod bar {
1145 fn test(db: &dyn crate::baz::HirDatabase) {
1146 db.metho$0
1147 }
1148}
1149 "#,
1150 expect![[r#""#]],
1151 );
1152 }
1130} 1153}
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs
index e583a52f4..bad277a95 100644
--- a/crates/ide_db/src/call_info.rs
+++ b/crates/ide_db/src/call_info.rs
@@ -4,8 +4,9 @@ use either::Either;
4use hir::{HasAttrs, HirDisplay, Semantics, Type}; 4use hir::{HasAttrs, HirDisplay, Semantics, Type};
5use stdx::format_to; 5use stdx::format_to;
6use syntax::{ 6use syntax::{
7 algo,
7 ast::{self, ArgListOwner, NameOwner}, 8 ast::{self, ArgListOwner, NameOwner},
8 match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize, 9 match_ast, AstNode, Direction, SyntaxNode, SyntaxToken, TextRange, TextSize,
9}; 10};
10 11
11use crate::RootDatabase; 12use crate::RootDatabase;
@@ -43,7 +44,12 @@ pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo>
43 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
44 let file = sema.parse(position.file_id); 45 let file = sema.parse(position.file_id);
45 let file = file.syntax(); 46 let file = file.syntax();
46 let token = file.token_at_offset(position.offset).next()?; 47 let token = file
48 .token_at_offset(position.offset)
49 .left_biased()
50 // if the cursor is sandwiched between two space tokens and the call is unclosed
51 // this prevents us from leaving the CallExpression
52 .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
47 let token = sema.descend_into_macros(token); 53 let token = sema.descend_into_macros(token);
48 54
49 let (callable, active_parameter) = call_info_impl(&sema, token)?; 55 let (callable, active_parameter) = call_info_impl(&sema, token)?;
diff --git a/crates/ide_db/src/call_info/tests.rs b/crates/ide_db/src/call_info/tests.rs
index 281a081a3..be1cc12de 100644
--- a/crates/ide_db/src/call_info/tests.rs
+++ b/crates/ide_db/src/call_info/tests.rs
@@ -522,3 +522,30 @@ fn main(f: fn(i32, f64) -> char) {
522 "#]], 522 "#]],
523 ) 523 )
524} 524}
525
526#[test]
527fn call_info_for_unclosed_call() {
528 check(
529 r#"
530fn foo(foo: u32, bar: u32) {}
531fn main() {
532 foo($0
533}"#,
534 expect![[r#"
535 fn foo(foo: u32, bar: u32)
536 (<foo: u32>, bar: u32)
537 "#]],
538 );
539 // check with surrounding space
540 check(
541 r#"
542fn foo(foo: u32, bar: u32) {}
543fn main() {
544 foo( $0
545}"#,
546 expect![[r#"
547 fn foo(foo: u32, bar: u32)
548 (<foo: u32>, bar: u32)
549 "#]],
550 )
551}
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 66798ea3a..83a665b37 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -113,6 +113,10 @@ impl FamousDefs<'_, '_> {
113 self.find_module("core:iter") 113 self.find_module("core:iter")
114 } 114 }
115 115
116 pub fn core_ops_Deref(&self) -> Option<Trait> {
117 self.find_trait("core:ops:Deref")
118 }
119
116 fn find_trait(&self, path: &str) -> Option<Trait> { 120 fn find_trait(&self, path: &str) -> Option<Trait> {
117 match self.find_def(path)? { 121 match self.find_def(path)? {
118 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), 122 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs
index 4d79e064e..29ae12dcf 100644
--- a/crates/ide_db/src/helpers/famous_defs_fixture.rs
+++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs
@@ -112,6 +112,12 @@ pub mod ops {
112 type Output; 112 type Output;
113 extern "rust-call" fn call_once(self, args: Args) -> Self::Output; 113 extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
114 } 114 }
115
116 #[lang = "deref"]
117 pub trait Deref {
118 type Target: ?Sized;
119 fn deref(&self) -> &Self::Target;
120 }
115} 121}
116 122
117pub mod option { 123pub mod option {
@@ -141,3 +147,5 @@ mod return_keyword {}
141 147
142/// Docs for prim_str 148/// Docs for prim_str
143mod prim_str {} 149mod prim_str {}
150
151pub use core::ops; \ No newline at end of file
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index 8ce648367..91d6a4665 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -436,6 +436,8 @@ fn trait_applicable_items(
436 }) 436 })
437 .collect(); 437 .collect();
438 438
439 let related_dyn_traits =
440 trait_candidate.receiver_ty.applicable_inherent_traits(db).collect::<FxHashSet<_>>();
439 let mut located_imports = FxHashSet::default(); 441 let mut located_imports = FxHashSet::default();
440 442
441 if trait_assoc_item { 443 if trait_assoc_item {
@@ -451,12 +453,16 @@ fn trait_applicable_items(
451 return None; 453 return None;
452 } 454 }
453 } 455 }
456 let located_trait = assoc.containing_trait(db)?;
457 if related_dyn_traits.contains(&located_trait) {
458 return None;
459 }
454 460
455 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?)); 461 let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
456 let original_item = assoc_to_item(assoc); 462 let original_item = assoc_to_item(assoc);
457 located_imports.insert(LocatedImport::new( 463 located_imports.insert(LocatedImport::new(
458 mod_path(item)?, 464 mod_path(trait_item)?,
459 item, 465 trait_item,
460 original_item, 466 original_item,
461 mod_path(original_item), 467 mod_path(original_item),
462 )); 468 ));
@@ -473,11 +479,15 @@ fn trait_applicable_items(
473 |_, function| { 479 |_, function| {
474 let assoc = function.as_assoc_item(db)?; 480 let assoc = function.as_assoc_item(db)?;
475 if required_assoc_items.contains(&assoc) { 481 if required_assoc_items.contains(&assoc) {
476 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?)); 482 let located_trait = assoc.containing_trait(db)?;
483 if related_dyn_traits.contains(&located_trait) {
484 return None;
485 }
486 let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
477 let original_item = assoc_to_item(assoc); 487 let original_item = assoc_to_item(assoc);
478 located_imports.insert(LocatedImport::new( 488 located_imports.insert(LocatedImport::new(
479 mod_path(item)?, 489 mod_path(trait_item)?,
480 item, 490 trait_item,
481 original_item, 491 original_item,
482 mod_path(original_item), 492 mod_path(original_item),
483 )); 493 ));
diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs
index 9ba98f7fb..a7c8c13c6 100644
--- a/crates/mbe/src/syntax_bridge.rs
+++ b/crates/mbe/src/syntax_bridge.rs
@@ -213,7 +213,7 @@ fn doc_comment_text(comment: &ast::Comment) -> SmolStr {
213 213
214 // Quote the string 214 // Quote the string
215 // Note that `tt::Literal` expect an escaped string 215 // Note that `tt::Literal` expect an escaped string
216 let text = format!("{:?}", text.escape_debug().to_string()); 216 let text = format!("\"{}\"", text.escape_debug());
217 text.into() 217 text.into()
218} 218}
219 219
diff --git a/crates/mbe/src/tests/expand.rs b/crates/mbe/src/tests/expand.rs
index e02d038b6..3a1d840ea 100644
--- a/crates/mbe/src/tests/expand.rs
+++ b/crates/mbe/src/tests/expand.rs
@@ -936,7 +936,25 @@ fn test_meta_doc_comments() {
936 MultiLines Doc 936 MultiLines Doc
937 */ 937 */
938 }"#, 938 }"#,
939 "# [doc = \" Single Line Doc 1\"] # [doc = \"\\\\n MultiLines Doc\\\\n \"] fn bar () {}", 939 "# [doc = \" Single Line Doc 1\"] # [doc = \"\\n MultiLines Doc\\n \"] fn bar () {}",
940 );
941}
942
943#[test]
944fn test_meta_extended_key_value_attributes() {
945 parse_macro(
946 r#"
947macro_rules! foo {
948 (#[$i:meta]) => (
949 #[$ i]
950 fn bar() {}
951 )
952}
953"#,
954 )
955 .assert_expand_items(
956 r#"foo! { #[doc = concat!("The `", "bla", "` lang item.")] }"#,
957 r#"# [doc = concat ! ("The `" , "bla" , "` lang item.")] fn bar () {}"#,
940 ); 958 );
941} 959}
942 960
@@ -959,7 +977,27 @@ fn test_meta_doc_comments_non_latin() {
959 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 977 莊生曉夢迷蝴蝶,望帝春心託杜鵑。
960 */ 978 */
961 }"#, 979 }"#,
962 "# [doc = \" 錦瑟無端五十弦,一弦一柱思華年。\"] # [doc = \"\\\\n 莊生曉夢迷蝴蝶,望帝春心託杜鵑。\\\\n \"] fn bar () {}", 980 "# [doc = \" 錦瑟無端五十弦,一弦一柱思華年。\"] # [doc = \"\\n 莊生曉夢迷蝴蝶,望帝春心託杜鵑。\\n \"] fn bar () {}",
981 );
982}
983
984#[test]
985fn test_meta_doc_comments_escaped_characters() {
986 parse_macro(
987 r#"
988 macro_rules! foo {
989 ($(#[$ i:meta])+) => (
990 $(#[$ i])+
991 fn bar() {}
992 )
993 }
994"#,
995 )
996 .assert_expand_items(
997 r#"foo! {
998 /// \ " '
999 }"#,
1000 r#"# [doc = " \\ \" \'"] fn bar () {}"#,
963 ); 1001 );
964} 1002}
965 1003
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index cebb8f400..9bdf0b5fa 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -76,42 +76,7 @@ pub(crate) mod fragments {
76 76
77 // Parse a meta item , which excluded [], e.g : #[ MetaItem ] 77 // Parse a meta item , which excluded [], e.g : #[ MetaItem ]
78 pub(crate) fn meta_item(p: &mut Parser) { 78 pub(crate) fn meta_item(p: &mut Parser) {
79 fn is_delimiter(p: &mut Parser) -> bool { 79 attributes::meta(p);
80 matches!(p.current(), T!['{'] | T!['('] | T!['['])
81 }
82
83 if is_delimiter(p) {
84 items::token_tree(p);
85 return;
86 }
87
88 let m = p.start();
89 while !p.at(EOF) {
90 if is_delimiter(p) {
91 items::token_tree(p);
92 break;
93 } else {
94 // https://doc.rust-lang.org/reference/attributes.html
95 // https://doc.rust-lang.org/reference/paths.html#simple-paths
96 // The start of an meta must be a simple path
97 match p.current() {
98 IDENT | T![super] | T![self] | T![crate] => p.bump_any(),
99 T![=] => {
100 p.bump_any();
101 match p.current() {
102 c if c.is_literal() => p.bump_any(),
103 T![true] | T![false] => p.bump_any(),
104 _ => {}
105 }
106 break;
107 }
108 _ if p.at(T![::]) => p.bump(T![::]),
109 _ => break,
110 }
111 }
112 }
113
114 m.complete(p, TOKEN_TREE);
115 } 80 }
116 81
117 pub(crate) fn item(p: &mut Parser) { 82 pub(crate) fn item(p: &mut Parser) {
diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs
index 96791ffc2..124a10eb2 100644
--- a/crates/parser/src/grammar/attributes.rs
+++ b/crates/parser/src/grammar/attributes.rs
@@ -14,6 +14,21 @@ pub(super) fn outer_attrs(p: &mut Parser) {
14 } 14 }
15} 15}
16 16
17pub(super) fn meta(p: &mut Parser) {
18 paths::use_path(p);
19
20 match p.current() {
21 T![=] => {
22 p.bump(T![=]);
23 if expressions::expr(p).0.is_none() {
24 p.error("expected expression");
25 }
26 }
27 T!['('] | T!['['] | T!['{'] => items::token_tree(p),
28 _ => {}
29 }
30}
31
17fn attr(p: &mut Parser, inner: bool) { 32fn attr(p: &mut Parser, inner: bool) {
18 let attr = p.start(); 33 let attr = p.start();
19 assert!(p.at(T![#])); 34 assert!(p.at(T![#]));
@@ -25,18 +40,7 @@ fn attr(p: &mut Parser, inner: bool) {
25 } 40 }
26 41
27 if p.eat(T!['[']) { 42 if p.eat(T!['[']) {
28 paths::use_path(p); 43 meta(p);
29
30 match p.current() {
31 T![=] => {
32 p.bump(T![=]);
33 if expressions::expr(p).0.is_none() {
34 p.error("expected expression");
35 }
36 }
37 T!['('] | T!['['] | T!['{'] => items::token_tree(p),
38 _ => {}
39 }
40 44
41 if !p.eat(T![']']) { 45 if !p.eat(T![']']) {
42 p.error("expected `]`"); 46 p.error("expected `]`");
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 1edaa394a..7ddea22c8 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -400,6 +400,17 @@ impl Config {
400 pub fn will_rename(&self) -> bool { 400 pub fn will_rename(&self) -> bool {
401 try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false) 401 try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false)
402 } 402 }
403 pub fn change_annotation_support(&self) -> bool {
404 try_!(self
405 .caps
406 .workspace
407 .as_ref()?
408 .workspace_edit
409 .as_ref()?
410 .change_annotation_support
411 .as_ref()?)
412 .is_some()
413 }
403 pub fn code_action_resolve(&self) -> bool { 414 pub fn code_action_resolve(&self) -> bool {
404 try_or!( 415 try_or!(
405 self.caps 416 self.caps
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
index 23ec2efba..227d96d51 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
@@ -326,6 +326,7 @@
326 }, 326 },
327 ), 327 ),
328 document_changes: None, 328 document_changes: None,
329 change_annotations: None,
329 }, 330 },
330 ), 331 ),
331 is_preferred: Some( 332 is_preferred: Some(
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index b6acb5f42..f8adfad3b 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -179,6 +179,7 @@
179 }, 179 },
180 ), 180 ),
181 document_changes: None, 181 document_changes: None,
182 change_annotations: None,
182 }, 183 },
183 ), 184 ),
184 is_preferred: Some( 185 is_preferred: Some(
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index d765257c4..5a70d2ed7 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -179,6 +179,7 @@
179 }, 179 },
180 ), 180 ),
181 document_changes: None, 181 document_changes: None,
182 change_annotations: None,
182 }, 183 },
183 ), 184 ),
184 is_preferred: Some( 185 is_preferred: Some(
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index 6b0d94878..04ca0c9c2 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -179,6 +179,7 @@
179 }, 179 },
180 ), 180 ),
181 document_changes: None, 181 document_changes: None,
182 change_annotations: None,
182 }, 183 },
183 ), 184 ),
184 is_preferred: Some( 185 is_preferred: Some(
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index a0cfb8d33..57d2f1ae3 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -339,6 +339,7 @@
339 }, 339 },
340 ), 340 ),
341 document_changes: None, 341 document_changes: None,
342 change_annotations: None,
342 }, 343 },
343 ), 344 ),
344 is_preferred: Some( 345 is_preferred: Some(
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index e2f319f6b..ca18997e4 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -136,6 +136,7 @@ fn map_rust_child_diagnostic(
136 // FIXME: there's no good reason to use edit_map here.... 136 // FIXME: there's no good reason to use edit_map here....
137 changes: Some(edit_map), 137 changes: Some(edit_map),
138 document_changes: None, 138 document_changes: None,
139 change_annotations: None,
139 }), 140 }),
140 is_preferred: Some(true), 141 is_preferred: Some(true),
141 data: None, 142 data: None,
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index d648cda32..b8835a534 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -312,6 +312,9 @@ pub struct SnippetWorkspaceEdit {
312 pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>, 312 pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>,
313 #[serde(skip_serializing_if = "Option::is_none")] 313 #[serde(skip_serializing_if = "Option::is_none")]
314 pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>, 314 pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>,
315 #[serde(skip_serializing_if = "Option::is_none")]
316 pub change_annotations:
317 Option<HashMap<lsp_types::ChangeAnnotationIdentifier, lsp_types::ChangeAnnotation>>,
315} 318}
316 319
317#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] 320#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
@@ -335,6 +338,9 @@ pub struct SnippetTextEdit {
335 pub new_text: String, 338 pub new_text: String,
336 #[serde(skip_serializing_if = "Option::is_none")] 339 #[serde(skip_serializing_if = "Option::is_none")]
337 pub insert_text_format: Option<lsp_types::InsertTextFormat>, 340 pub insert_text_format: Option<lsp_types::InsertTextFormat>,
341 /// The annotation id if this is an annotated
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub annotation_id: Option<lsp_types::ChangeAnnotationIdentifier>,
338} 344}
339 345
340pub enum HoverRequest {} 346pub enum HoverRequest {}
diff --git a/crates/rust-analyzer/src/markdown.rs b/crates/rust-analyzer/src/markdown.rs
index 865eaae9b..a51ff89e4 100644
--- a/crates/rust-analyzer/src/markdown.rs
+++ b/crates/rust-analyzer/src/markdown.rs
@@ -27,9 +27,8 @@ pub(crate) fn format_docs(src: &str) -> String {
27 in_code_block ^= true; 27 in_code_block ^= true;
28 28
29 if in_code_block { 29 if in_code_block {
30 is_rust = header 30 is_rust =
31 .split(',') 31 header.split(',').all(|sub| is_rust_specific_code_block_attribute(sub.trim()));
32 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC.contains(&sub.trim()));
33 32
34 if is_rust { 33 if is_rust {
35 line = "```rust"; 34 line = "```rust";
@@ -42,6 +41,13 @@ pub(crate) fn format_docs(src: &str) -> String {
42 processed_lines.join("\n") 41 processed_lines.join("\n")
43} 42}
44 43
44fn is_rust_specific_code_block_attribute(attr: &str) -> bool {
45 if RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC.contains(&attr) {
46 return true;
47 }
48 attr.starts_with('E') && attr.len() == 5 && attr[1..].parse::<u32>().is_ok()
49}
50
45fn code_line_ignored_by_rustdoc(line: &str) -> bool { 51fn code_line_ignored_by_rustdoc(line: &str) -> bool {
46 let trimmed = line.trim(); 52 let trimmed = line.trim();
47 trimmed == "#" || trimmed.starts_with("# ") || trimmed.starts_with("#\t") 53 trimmed == "#" || trimmed.starts_with("# ") || trimmed.starts_with("#\t")
@@ -82,6 +88,12 @@ mod tests {
82 } 88 }
83 89
84 #[test] 90 #[test]
91 fn test_format_docs_handles_error_codes() {
92 let comment = "```compile_fail,E0641\nlet b = 0 as *const _;\n```";
93 assert_eq!(format_docs(comment), "```rust\nlet b = 0 as *const _;\n```");
94 }
95
96 #[test]
85 fn test_format_docs_skips_comments_in_rust_block() { 97 fn test_format_docs_skips_comments_in_rust_block() {
86 let comment = 98 let comment =
87 "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```"; 99 "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```";
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 1a1f65f3b..fe4d0733d 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1,15 +1,16 @@
1//! Conversion of rust-analyzer specific types to lsp_types equivalents. 1//! Conversion of rust-analyzer specific types to lsp_types equivalents.
2use std::{ 2use std::{
3 iter::once,
3 path::{self, Path}, 4 path::{self, Path},
4 sync::atomic::{AtomicU32, Ordering}, 5 sync::atomic::{AtomicU32, Ordering},
5}; 6};
6 7
7use ide::{ 8use ide::{
8 Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, 9 Annotation, AnnotationKind, Assist, AssistKind, CallInfo, Cancelable, CompletionItem,
9 CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, 10 CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
10 Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayKind, 11 Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
11 InsertTextFormat, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, 12 InlayKind, InsertTextFormat, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable,
12 SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, 13 Severity, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
13}; 14};
14use itertools::Itertools; 15use itertools::Itertools;
15use serde_json::to_value; 16use serde_json::to_value;
@@ -174,6 +175,7 @@ pub(crate) fn snippet_text_edit(
174 range: text_edit.range, 175 range: text_edit.range,
175 new_text: text_edit.new_text, 176 new_text: text_edit.new_text,
176 insert_text_format, 177 insert_text_format,
178 annotation_id: None,
177 } 179 }
178} 180}
179 181
@@ -688,6 +690,10 @@ pub(crate) fn goto_definition_response(
688 } 690 }
689} 691}
690 692
693fn outside_workspace_annotation_id() -> String {
694 String::from("OutsideWorkspace")
695}
696
691pub(crate) fn snippet_text_document_edit( 697pub(crate) fn snippet_text_document_edit(
692 snap: &GlobalStateSnapshot, 698 snap: &GlobalStateSnapshot,
693 is_snippet: bool, 699 is_snippet: bool,
@@ -696,14 +702,21 @@ pub(crate) fn snippet_text_document_edit(
696) -> Result<lsp_ext::SnippetTextDocumentEdit> { 702) -> Result<lsp_ext::SnippetTextDocumentEdit> {
697 let text_document = optional_versioned_text_document_identifier(snap, file_id); 703 let text_document = optional_versioned_text_document_identifier(snap, file_id);
698 let line_index = snap.file_line_index(file_id)?; 704 let line_index = snap.file_line_index(file_id)?;
699 let edits = edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect(); 705 let mut edits: Vec<_> =
706 edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
707
708 if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
709 for edit in &mut edits {
710 edit.annotation_id = Some(outside_workspace_annotation_id())
711 }
712 }
700 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) 713 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
701} 714}
702 715
703pub(crate) fn snippet_text_document_ops( 716pub(crate) fn snippet_text_document_ops(
704 snap: &GlobalStateSnapshot, 717 snap: &GlobalStateSnapshot,
705 file_system_edit: FileSystemEdit, 718 file_system_edit: FileSystemEdit,
706) -> Vec<lsp_ext::SnippetDocumentChangeOperation> { 719) -> Cancelable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
707 let mut ops = Vec::new(); 720 let mut ops = Vec::new();
708 match file_system_edit { 721 match file_system_edit {
709 FileSystemEdit::CreateFile { dst, initial_contents } => { 722 FileSystemEdit::CreateFile { dst, initial_contents } => {
@@ -721,6 +734,7 @@ pub(crate) fn snippet_text_document_ops(
721 range: lsp_types::Range::default(), 734 range: lsp_types::Range::default(),
722 new_text: initial_contents, 735 new_text: initial_contents,
723 insert_text_format: Some(lsp_types::InsertTextFormat::PlainText), 736 insert_text_format: Some(lsp_types::InsertTextFormat::PlainText),
737 annotation_id: None,
724 }; 738 };
725 let edit_file = 739 let edit_file =
726 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] }; 740 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
@@ -730,16 +744,19 @@ pub(crate) fn snippet_text_document_ops(
730 FileSystemEdit::MoveFile { src, dst } => { 744 FileSystemEdit::MoveFile { src, dst } => {
731 let old_uri = snap.file_id_to_url(src); 745 let old_uri = snap.file_id_to_url(src);
732 let new_uri = snap.anchored_path(&dst); 746 let new_uri = snap.anchored_path(&dst);
733 let rename_file = lsp_types::ResourceOp::Rename(lsp_types::RenameFile { 747 let mut rename_file =
734 old_uri, 748 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
735 new_uri, 749 if snap.analysis.is_library_file(src) == Ok(true)
736 options: None, 750 && snap.config.change_annotation_support()
737 annotation_id: None, 751 {
738 }); 752 rename_file.annotation_id = Some(outside_workspace_annotation_id())
739 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(rename_file)) 753 }
754 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
755 rename_file,
756 )))
740 } 757 }
741 } 758 }
742 ops 759 Ok(ops)
743} 760}
744 761
745pub(crate) fn snippet_workspace_edit( 762pub(crate) fn snippet_workspace_edit(
@@ -747,16 +764,35 @@ pub(crate) fn snippet_workspace_edit(
747 source_change: SourceChange, 764 source_change: SourceChange,
748) -> Result<lsp_ext::SnippetWorkspaceEdit> { 765) -> Result<lsp_ext::SnippetWorkspaceEdit> {
749 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); 766 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
767
750 for op in source_change.file_system_edits { 768 for op in source_change.file_system_edits {
751 let ops = snippet_text_document_ops(snap, op); 769 let ops = snippet_text_document_ops(snap, op)?;
752 document_changes.extend_from_slice(&ops); 770 document_changes.extend_from_slice(&ops);
753 } 771 }
754 for (file_id, edit) in source_change.source_file_edits { 772 for (file_id, edit) in source_change.source_file_edits {
755 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; 773 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?;
756 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); 774 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
757 } 775 }
758 let workspace_edit = 776 let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
759 lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) }; 777 changes: None,
778 document_changes: Some(document_changes),
779 change_annotations: None,
780 };
781 if snap.config.change_annotation_support() {
782 workspace_edit.change_annotations = Some(
783 once((
784 outside_workspace_annotation_id(),
785 lsp_types::ChangeAnnotation {
786 label: String::from("Edit outside of the workspace"),
787 needs_confirmation: Some(true),
788 description: Some(String::from(
789 "This edit lies outside of the workspace and may affect dependencies",
790 )),
791 },
792 ))
793 .collect(),
794 )
795 }
760 Ok(workspace_edit) 796 Ok(workspace_edit)
761} 797}
762 798
@@ -784,16 +820,7 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
784 lsp_types::DocumentChangeOperation::Edit( 820 lsp_types::DocumentChangeOperation::Edit(
785 lsp_types::TextDocumentEdit { 821 lsp_types::TextDocumentEdit {
786 text_document: edit.text_document, 822 text_document: edit.text_document,
787 edits: edit 823 edits: edit.edits.into_iter().map(From::from).collect(),
788 .edits
789 .into_iter()
790 .map(|edit| {
791 lsp_types::OneOf::Left(lsp_types::TextEdit {
792 range: edit.range,
793 new_text: edit.new_text,
794 })
795 })
796 .collect(),
797 }, 824 },
798 ) 825 )
799 } 826 }
@@ -801,7 +828,23 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
801 .collect(), 828 .collect(),
802 ) 829 )
803 }), 830 }),
804 change_annotations: None, 831 change_annotations: snippet_workspace_edit.change_annotations,
832 }
833 }
834}
835
836impl From<lsp_ext::SnippetTextEdit>
837 for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
838{
839 fn from(
840 lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
841 ) -> Self {
842 match annotation_id {
843 Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
844 text_edit: lsp_types::TextEdit { range, new_text },
845 annotation_id,
846 }),
847 None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
805 } 848 }
806 } 849 }
807} 850}
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index f09cdf28a..a8c1a8075 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13[dependencies] 13[dependencies]
14cov-mark = { version = "1.1", features = ["thread-local"] } 14cov-mark = { version = "1.1", features = ["thread-local"] }
15itertools = "0.10.0" 15itertools = "0.10.0"
16rowan = "0.13.0-pre.3" 16rowan = "=0.13.0-pre.3"
17rustc_lexer = { version = "714.0.0", package = "rustc-ap-rustc_lexer" } 17rustc_lexer = { version = "714.0.0", package = "rustc-ap-rustc_lexer" }
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19arrayvec = "0.7" 19arrayvec = "0.7"
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index a4d92242b..a112477de 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: b19ddc3ab8767af9 2lsp_ext.rs hash: 28a9d5a24b7ca396
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -46,6 +46,7 @@ If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests
46```typescript 46```typescript
47interface SnippetTextEdit extends TextEdit { 47interface SnippetTextEdit extends TextEdit {
48 insertTextFormat?: InsertTextFormat; 48 insertTextFormat?: InsertTextFormat;
49 annotationId?: ChangeAnnotationIdentifier;
49} 50}
50``` 51```
51 52