aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/base_db/src/fixture.rs8
-rw-r--r--crates/hir_def/src/lib.rs12
-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/method_resolution.rs42
-rw-r--r--crates/hir_ty/src/tests/method_resolution.rs37
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs16
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs2
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs128
-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_db/src/helpers.rs5
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs8
-rw-r--r--crates/ide_db/src/helpers/rust_doc.rs34
-rw-r--r--crates/mbe/src/syntax_bridge.rs2
-rw-r--r--crates/mbe/src/tests/expand.rs24
-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.rs22
-rw-r--r--crates/rust-analyzer/src/to_proto.rs101
30 files changed, 642 insertions, 112 deletions
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_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 059724daa..25694f037 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -108,6 +108,18 @@ impl ModuleId {
108 pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> { 108 pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
109 self.def_map(db).containing_module(self.local_id) 109 self.def_map(db).containing_module(self.local_id)
110 } 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 }
111} 123}
112 124
113/// An ID of a module, **local** to a specific crate 125/// An ID of a module, **local** to a specific crate
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/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/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..bc221d599 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -4,7 +4,7 @@ use std::mem;
4 4
5use either::Either; 5use either::Either;
6use hir::{InFile, Semantics}; 6use hir::{InFile, Semantics};
7use ide_db::{call_info::ActiveParameter, SymbolKind}; 7use ide_db::{call_info::ActiveParameter, helpers::rust_doc::is_rust_fence, SymbolKind};
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode}, 9 ast::{self, AstNode},
10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, 10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -78,17 +78,6 @@ pub(super) fn ra_fixture(
78} 78}
79 79
80const RUSTDOC_FENCE: &'static str = "```"; 80const RUSTDOC_FENCE: &'static str = "```";
81const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
82 "",
83 "rust",
84 "should_panic",
85 "ignore",
86 "no_run",
87 "compile_fail",
88 "edition2015",
89 "edition2018",
90 "edition2021",
91];
92 81
93/// Injection of syntax highlighting of doctests. 82/// Injection of syntax highlighting of doctests.
94pub(super) fn doc_comment( 83pub(super) fn doc_comment(
@@ -174,8 +163,7 @@ pub(super) fn doc_comment(
174 is_codeblock = !is_codeblock; 163 is_codeblock = !is_codeblock;
175 // Check whether code is rust by inspecting fence guards 164 // Check whether code is rust by inspecting fence guards
176 let guards = &line[idx + RUSTDOC_FENCE.len()..]; 165 let guards = &line[idx + RUSTDOC_FENCE.len()..];
177 let is_rust = 166 let is_rust = is_rust_fence(guards);
178 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
179 is_doctest = is_codeblock && is_rust; 167 is_doctest = is_codeblock && is_rust;
180 continue; 168 continue;
181 } 169 }
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 933cfa6f3..17cc6334b 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -307,7 +307,7 @@ fn benchmark_syntax_highlighting_parser() {
307 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) 307 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
308 .count() 308 .count()
309 }; 309 };
310 assert_eq!(hash, 1629); 310 assert_eq!(hash, 1632);
311} 311}
312 312
313#[test] 313#[test]
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index 80bd1b7e8..a30c4d04e 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -1,5 +1,6 @@
1use std::iter; 1use std::iter;
2 2
3use either::Either;
3use hir::{Adt, HasSource, ModuleDef, Semantics}; 4use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs}; 5use ide_db::helpers::{mod_path_to_ast, FamousDefs};
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
@@ -7,7 +8,7 @@ use itertools::Itertools;
7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 8use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8 9
9use crate::{ 10use crate::{
10 utils::{does_pat_match_variant, render_snippet, Cursor}, 11 utils::{self, render_snippet, Cursor},
11 AssistContext, AssistId, AssistKind, Assists, 12 AssistContext, AssistId, AssistKind, Assists,
12}; 13};
13 14
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
48 } 49 }
49 } 50 }
50 51
52 let top_lvl_pats: Vec<_> = arms
53 .iter()
54 .filter_map(ast::MatchArm::pat)
55 .flat_map(|pat| match pat {
56 // Special casee OrPat as separate top-level pats
57 Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
58 _ => Either::Right(iter::once(pat)),
59 })
60 // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
61 .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
62 .collect();
63
51 let module = ctx.sema.scope(expr.syntax()).module()?; 64 let module = ctx.sema.scope(expr.syntax()).module()?;
52 65
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 66 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
@@ -56,7 +69,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
56 let mut variants = variants 69 let mut variants = variants
57 .into_iter() 70 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db(), module, variant)) 71 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>(); 74 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { 75 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
@@ -66,11 +79,6 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
66 } 79 }
67 variants 80 variants
68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { 81 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
69 // Partial fill not currently supported for tuple of enums.
70 if !arms.is_empty() {
71 return None;
72 }
73
74 // When calculating the match arms for a tuple of enums, we want 82 // When calculating the match arms for a tuple of enums, we want
75 // to create a match arm for each possible combination of enum 83 // to create a match arm for each possible combination of enum
76 // values. The `multi_cartesian_product` method transforms 84 // values. The `multi_cartesian_product` method transforms
@@ -85,7 +93,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
85 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); 93 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
86 ast::Pat::from(make::tuple_pat(patterns)) 94 ast::Pat::from(make::tuple_pat(patterns))
87 }) 95 })
88 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 96 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
89 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 97 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
90 .collect() 98 .collect()
91 } else { 99 } else {
@@ -128,16 +136,19 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
128 ) 136 )
129} 137}
130 138
131fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { 139fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
132 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { 140 !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
133 // Special casee OrPat as separate top-level pats 141}
134 let top_level_pats: Vec<Pat> = match pat {
135 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
136 _ => vec![pat],
137 };
138 142
139 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) 143// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
140 }) 144fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
145 match (pat, var) {
146 (Pat::WildcardPat(_), _) => true,
147 (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
148 tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
149 }
150 _ => utils::does_pat_match_variant(pat, var),
151 }
141} 152}
142 153
143fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 154fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
@@ -467,20 +478,81 @@ fn main() {
467 478
468 #[test] 479 #[test]
469 fn fill_match_arms_tuple_of_enum_partial() { 480 fn fill_match_arms_tuple_of_enum_partial() {
470 check_assist_not_applicable( 481 check_assist(
471 fill_match_arms, 482 fill_match_arms,
472 r#" 483 r#"
473 enum A { One, Two } 484enum A { One, Two }
474 enum B { One, Two } 485enum B { One, Two }
475 486
476 fn main() { 487fn main() {
477 let a = A::One; 488 let a = A::One;
478 let b = B::One; 489 let b = B::One;
479 match (a$0, b) { 490 match (a$0, b) {
480 (A::Two, B::One) => {} 491 (A::Two, B::One) => {}
481 } 492 }
482 } 493}
483 "#, 494"#,
495 r#"
496enum A { One, Two }
497enum B { One, Two }
498
499fn main() {
500 let a = A::One;
501 let b = B::One;
502 match (a, b) {
503 (A::Two, B::One) => {}
504 $0(A::One, B::One) => {}
505 (A::One, B::Two) => {}
506 (A::Two, B::Two) => {}
507 }
508}
509"#,
510 );
511 }
512
513 #[test]
514 fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
515 let ra_fixture = r#"
516fn main() {
517 let a = Some(1);
518 let b = Some(());
519 match (a$0, b) {
520 (Some(_), _) => {}
521 (None, Some(_)) => {}
522 }
523}
524"#;
525 check_assist(
526 fill_match_arms,
527 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
528 r#"
529fn main() {
530 let a = Some(1);
531 let b = Some(());
532 match (a, b) {
533 (Some(_), _) => {}
534 (None, Some(_)) => {}
535 $0(None, None) => {}
536 }
537}
538"#,
539 );
540 }
541
542 #[test]
543 fn fill_match_arms_partial_with_deep_pattern() {
544 // Fixme: cannot handle deep patterns
545 let ra_fixture = r#"
546fn main() {
547 match $0Some(true) {
548 Some(true) => {}
549 None => {}
550 }
551}
552"#;
553 check_assist_not_applicable(
554 fill_match_arms,
555 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
484 ); 556 );
485 } 557 }
486 558
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_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 66798ea3a..720de0d1f 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -1,6 +1,7 @@
1//! A module with ide helpers for high-level ide features. 1//! A module with ide helpers for high-level ide features.
2pub mod insert_use; 2pub mod insert_use;
3pub mod import_assets; 3pub mod import_assets;
4pub mod rust_doc;
4 5
5use std::collections::VecDeque; 6use std::collections::VecDeque;
6 7
@@ -113,6 +114,10 @@ impl FamousDefs<'_, '_> {
113 self.find_module("core:iter") 114 self.find_module("core:iter")
114 } 115 }
115 116
117 pub fn core_ops_Deref(&self) -> Option<Trait> {
118 self.find_trait("core:ops:Deref")
119 }
120
116 fn find_trait(&self, path: &str) -> Option<Trait> { 121 fn find_trait(&self, path: &str) -> Option<Trait> {
117 match self.find_def(path)? { 122 match self.find_def(path)? {
118 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), 123 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/rust_doc.rs b/crates/ide_db/src/helpers/rust_doc.rs
new file mode 100644
index 000000000..e27e23867
--- /dev/null
+++ b/crates/ide_db/src/helpers/rust_doc.rs
@@ -0,0 +1,34 @@
1//! Rustdoc specific doc comment handling
2
3// stripped down version of https://github.com/rust-lang/rust/blob/392ba2ba1a7d6c542d2459fb8133bebf62a4a423/src/librustdoc/html/markdown.rs#L810-L933
4pub fn is_rust_fence(s: &str) -> bool {
5 let mut seen_rust_tags = false;
6 let mut seen_other_tags = false;
7
8 let tokens = s
9 .trim()
10 .split(|c| c == ',' || c == ' ' || c == '\t')
11 .map(str::trim)
12 .filter(|t| !t.is_empty());
13
14 for token in tokens {
15 match token {
16 "should_panic" | "no_run" | "ignore" | "allow_fail" => {
17 seen_rust_tags = !seen_other_tags
18 }
19 "rust" => seen_rust_tags = true,
20 "test_harness" | "compile_fail" => seen_rust_tags = !seen_other_tags || seen_rust_tags,
21 x if x.starts_with("edition") => {}
22 x if x.starts_with('E') && x.len() == 5 => {
23 if x[1..].parse::<u32>().is_ok() {
24 seen_rust_tags = !seen_other_tags || seen_rust_tags;
25 } else {
26 seen_other_tags = true;
27 }
28 }
29 _ => seen_other_tags = true,
30 }
31 }
32
33 !seen_other_tags || seen_rust_tags
34}
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 146b236e2..3a1d840ea 100644
--- a/crates/mbe/src/tests/expand.rs
+++ b/crates/mbe/src/tests/expand.rs
@@ -936,7 +936,7 @@ 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 ); 940 );
941} 941}
942 942
@@ -977,7 +977,27 @@ fn test_meta_doc_comments_non_latin() {
977 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 977 莊生曉夢迷蝴蝶,望帝春心託杜鵑。
978 */ 978 */
979 }"#, 979 }"#,
980 "# [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 () {}"#,
981 ); 1001 );
982} 1002}
983 1003
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..35eaffba8 100644
--- a/crates/rust-analyzer/src/markdown.rs
+++ b/crates/rust-analyzer/src/markdown.rs
@@ -1,17 +1,7 @@
1//! Transforms markdown 1//! Transforms markdown
2use ide_db::helpers::rust_doc::is_rust_fence;
2 3
3const RUSTDOC_FENCE: &str = "```"; 4const RUSTDOC_FENCE: &str = "```";
4const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC: &[&str] = &[
5 "",
6 "rust",
7 "should_panic",
8 "ignore",
9 "no_run",
10 "compile_fail",
11 "edition2015",
12 "edition2018",
13 "edition2021",
14];
15 5
16pub(crate) fn format_docs(src: &str) -> String { 6pub(crate) fn format_docs(src: &str) -> String {
17 let mut processed_lines = Vec::new(); 7 let mut processed_lines = Vec::new();
@@ -27,9 +17,7 @@ pub(crate) fn format_docs(src: &str) -> String {
27 in_code_block ^= true; 17 in_code_block ^= true;
28 18
29 if in_code_block { 19 if in_code_block {
30 is_rust = header 20 is_rust = is_rust_fence(header);
31 .split(',')
32 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUST_SPECIFIC.contains(&sub.trim()));
33 21
34 if is_rust { 22 if is_rust {
35 line = "```rust"; 23 line = "```rust";
@@ -82,6 +70,12 @@ mod tests {
82 } 70 }
83 71
84 #[test] 72 #[test]
73 fn test_format_docs_handles_error_codes() {
74 let comment = "```compile_fail,E0641\nlet b = 0 as *const _;\n```";
75 assert_eq!(format_docs(comment), "```rust\nlet b = 0 as *const _;\n```");
76 }
77
78 #[test]
85 fn test_format_docs_skips_comments_in_rust_block() { 79 fn test_format_docs_skips_comments_in_rust_block() {
86 let comment = 80 let comment =
87 "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```"; 81 "```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}