aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs10
-rw-r--r--crates/hir/src/code_model.rs17
-rw-r--r--crates/hir_def/src/adt.rs28
-rw-r--r--crates/hir_def/src/data.rs2
-rw-r--r--crates/hir_def/src/item_tree.rs1
-rw-r--r--crates/hir_def/src/item_tree/lower.rs3
-rw-r--r--crates/hir_def/src/item_tree/tests.rs24
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/doc_links.rs (renamed from crates/ide/src/link_rewrite.rs)285
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/ide/src/lib.rs16
-rw-r--r--crates/ide/src/references.rs25
-rw-r--r--crates/ide/src/references/rename.rs175
-rw-r--r--crates/rust-analyzer/src/handlers.rs29
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/stdx/src/macros.rs8
18 files changed, 540 insertions, 102 deletions
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
index 7cd76ea06..d505e9444 100644
--- a/crates/assists/src/handlers/fix_visibility.rs
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -324,14 +324,14 @@ pub struct Foo { pub bar: () }
324 324
325 #[test] 325 #[test]
326 fn fix_visibility_of_enum_variant_field() { 326 fn fix_visibility_of_enum_variant_field() {
327 check_assist( 327 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
328 // rejects any visibility specifiers on them, so this assist should never fire on them.
329 check_assist_not_applicable(
328 fix_visibility, 330 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } } 331 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ", 332 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 ); 333 );
334 check_assist( 334 check_assist_not_applicable(
335 fix_visibility, 335 fix_visibility,
336 r" 336 r"
337//- /lib.rs 337//- /lib.rs
@@ -340,8 +340,6 @@ fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs 340//- /foo.rs
341pub enum Foo { Bar { bar: () } } 341pub enum Foo { Bar { bar: () } }
342", 342",
343 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
344",
345 ); 343 );
346 check_assist_not_applicable( 344 check_assist_not_applicable(
347 fix_visibility, 345 fix_visibility,
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 031c91ccf..650b4fa40 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -186,6 +186,16 @@ impl_from!(
186 for ModuleDef 186 for ModuleDef
187); 187);
188 188
189impl From<VariantDef> for ModuleDef {
190 fn from(var: VariantDef) -> Self {
191 match var {
192 VariantDef::Struct(t) => Adt::from(t).into(),
193 VariantDef::Union(t) => Adt::from(t).into(),
194 VariantDef::EnumVariant(t) => t.into(),
195 }
196 }
197}
198
189impl ModuleDef { 199impl ModuleDef {
190 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> { 200 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> {
191 match self { 201 match self {
@@ -752,6 +762,13 @@ impl Function {
752 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 762 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
753 hir_ty::diagnostics::validate_body(db, self.id.into(), sink) 763 hir_ty::diagnostics::validate_body(db, self.id.into(), sink)
754 } 764 }
765
766 /// Whether this function declaration has a definition.
767 ///
768 /// This is false in the case of required (not provided) trait methods.
769 pub fn has_body(self, db: &dyn HirDatabase) -> bool {
770 db.function_data(self.id).has_body
771 }
755} 772}
756 773
757// Note: logically, this belongs to `hir_ty`, but we are not using it there yet. 774// Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
diff --git a/crates/hir_def/src/adt.rs b/crates/hir_def/src/adt.rs
index d69ff2fc7..6539959c3 100644
--- a/crates/hir_def/src/adt.rs
+++ b/crates/hir_def/src/adt.rs
@@ -14,7 +14,7 @@ use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
14use crate::{ 14use crate::{
15 body::{CfgExpander, LowerCtx}, 15 body::{CfgExpander, LowerCtx},
16 db::DefDatabase, 16 db::DefDatabase,
17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem}, 17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId},
18 src::HasChildSource, 18 src::HasChildSource,
19 src::HasSource, 19 src::HasSource,
20 trace::Trace, 20 trace::Trace,
@@ -91,7 +91,7 @@ impl StructData {
91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
92 92
93 let strukt = &item_tree[loc.id.value]; 93 let strukt = &item_tree[loc.id.value];
94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields); 94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields, None);
95 Arc::new(StructData { 95 Arc::new(StructData {
96 name: strukt.name.clone(), 96 name: strukt.name.clone(),
97 variant_data: Arc::new(variant_data), 97 variant_data: Arc::new(variant_data),
@@ -105,7 +105,7 @@ impl StructData {
105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
106 106
107 let union = &item_tree[loc.id.value]; 107 let union = &item_tree[loc.id.value];
108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields); 108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields, None);
109 109
110 Arc::new(StructData { 110 Arc::new(StructData {
111 name: union.name.clone(), 111 name: union.name.clone(),
@@ -126,7 +126,8 @@ impl EnumData {
126 for var_id in enum_.variants.clone() { 126 for var_id in enum_.variants.clone() {
127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) { 127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) {
128 let var = &item_tree[var_id]; 128 let var = &item_tree[var_id];
129 let var_data = lower_fields(&item_tree, &cfg_options, &var.fields); 129 let var_data =
130 lower_fields(&item_tree, &cfg_options, &var.fields, Some(enum_.visibility));
130 131
131 variants.alloc(EnumVariantData { 132 variants.alloc(EnumVariantData {
132 name: var.name.clone(), 133 name: var.name.clone(),
@@ -296,13 +297,18 @@ fn lower_struct(
296 } 297 }
297} 298}
298 299
299fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields) -> VariantData { 300fn lower_fields(
301 item_tree: &ItemTree,
302 cfg_options: &CfgOptions,
303 fields: &Fields,
304 override_visibility: Option<RawVisibilityId>,
305) -> VariantData {
300 match fields { 306 match fields {
301 Fields::Record(flds) => { 307 Fields::Record(flds) => {
302 let mut arena = Arena::new(); 308 let mut arena = Arena::new();
303 for field_id in flds.clone() { 309 for field_id in flds.clone() {
304 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 310 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
305 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 311 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
306 } 312 }
307 } 313 }
308 VariantData::Record(arena) 314 VariantData::Record(arena)
@@ -311,7 +317,7 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
311 let mut arena = Arena::new(); 317 let mut arena = Arena::new();
312 for field_id in flds.clone() { 318 for field_id in flds.clone() {
313 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 319 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
314 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 320 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
315 } 321 }
316 } 322 }
317 VariantData::Tuple(arena) 323 VariantData::Tuple(arena)
@@ -320,10 +326,14 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
320 } 326 }
321} 327}
322 328
323fn lower_field(item_tree: &ItemTree, field: &Field) -> FieldData { 329fn lower_field(
330 item_tree: &ItemTree,
331 field: &Field,
332 override_visibility: Option<RawVisibilityId>,
333) -> FieldData {
324 FieldData { 334 FieldData {
325 name: field.name.clone(), 335 name: field.name.clone(),
326 type_ref: field.type_ref.clone(), 336 type_ref: field.type_ref.clone(),
327 visibility: item_tree[field.visibility].clone(), 337 visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
328 } 338 }
329} 339}
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 6190906da..ff1ef0df6 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -25,6 +25,7 @@ pub struct FunctionData {
25 /// True if the first param is `self`. This is relevant to decide whether this 25 /// True if the first param is `self`. This is relevant to decide whether this
26 /// can be called as a method. 26 /// can be called as a method.
27 pub has_self_param: bool, 27 pub has_self_param: bool,
28 pub has_body: bool,
28 pub is_unsafe: bool, 29 pub is_unsafe: bool,
29 pub is_varargs: bool, 30 pub is_varargs: bool,
30 pub visibility: RawVisibility, 31 pub visibility: RawVisibility,
@@ -42,6 +43,7 @@ impl FunctionData {
42 ret_type: func.ret_type.clone(), 43 ret_type: func.ret_type.clone(),
43 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(), 44 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(),
44 has_self_param: func.has_self_param, 45 has_self_param: func.has_self_param,
46 has_body: func.has_body,
45 is_unsafe: func.is_unsafe, 47 is_unsafe: func.is_unsafe,
46 is_varargs: func.is_varargs, 48 is_varargs: func.is_varargs,
47 visibility: item_tree[func.visibility].clone(), 49 visibility: item_tree[func.visibility].clone(),
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 0fd91b9d0..8a1121bbd 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -505,6 +505,7 @@ pub struct Function {
505 pub visibility: RawVisibilityId, 505 pub visibility: RawVisibilityId,
506 pub generic_params: GenericParamsId, 506 pub generic_params: GenericParamsId,
507 pub has_self_param: bool, 507 pub has_self_param: bool,
508 pub has_body: bool,
508 pub is_unsafe: bool, 509 pub is_unsafe: bool,
509 pub params: Box<[TypeRef]>, 510 pub params: Box<[TypeRef]>,
510 pub is_varargs: bool, 511 pub is_varargs: bool,
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 54814f141..3328639cf 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -330,12 +330,15 @@ impl Ctx {
330 ret_type 330 ret_type
331 }; 331 };
332 332
333 let has_body = func.body().is_some();
334
333 let ast_id = self.source_ast_id_map.ast_id(func); 335 let ast_id = self.source_ast_id_map.ast_id(func);
334 let mut res = Function { 336 let mut res = Function {
335 name, 337 name,
336 visibility, 338 visibility,
337 generic_params: GenericParamsId::EMPTY, 339 generic_params: GenericParamsId::EMPTY,
338 has_self_param, 340 has_self_param,
341 has_body,
339 is_unsafe: func.unsafe_token().is_some(), 342 is_unsafe: func.unsafe_token().is_some(),
340 params: params.into_boxed_slice(), 343 params: params.into_boxed_slice(),
341 is_varargs, 344 is_varargs,
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 1a806cda5..4b354c4c1 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -240,9 +240,9 @@ fn smoke() {
240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }] 240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }]
241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) } 241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) }
242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }] 242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }]
243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) } 243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: false, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) }
244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }] 244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }]
245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) } 245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) }
246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }] 246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }]
247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit } 247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit }
248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }] 248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }]
@@ -275,12 +275,12 @@ fn simple_inner_items() {
275 275
276 top-level items: 276 top-level items:
277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
279 279
280 inner items: 280 inner items:
281 281
282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2): 282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2):
283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
284 284
285 "#]], 285 "#]],
286 ); 286 );
@@ -303,9 +303,9 @@ fn extern_attrs() {
303 303
304 top-level items: 304 top-level items:
305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
309 "##]], 309 "##]],
310 ); 310 );
311} 311}
@@ -329,9 +329,9 @@ fn trait_attrs() {
329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }] 329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }]
330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) } 330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) }
331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
335 "##]], 335 "##]],
336 ); 336 );
337} 337}
@@ -355,9 +355,9 @@ fn impl_attrs() {
355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }] 355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }]
356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
361 "##]], 361 "##]],
362 ); 362 );
363} 363}
@@ -408,13 +408,13 @@ fn inner_item_attrs() {
408 inner attrs: Attrs { entries: None } 408 inner attrs: Attrs { entries: None }
409 409
410 top-level items: 410 top-level items:
411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) } 411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) }
412 412
413 inner items: 413 inner items:
414 414
415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1): 415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1):
416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }] 416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }]
417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
418 418
419 "##]], 419 "##]],
420 ); 420 );
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 0f3c85926..e9c62c6aa 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -17,9 +17,9 @@ ena = "0.14.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = "0.32" 20chalk-solve = "0.33"
21chalk-ir = "0.32" 21chalk-ir = "0.33"
22chalk-recursive = "0.32" 22chalk-recursive = "0.33"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
index 81c33bf3a..50d1e5c81 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -25,6 +25,7 @@ static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"), 25 ("fmt", "format!"),
26 ("panic", "panic!"), 26 ("panic", "panic!"),
27 ("println", "println!"), 27 ("println", "println!"),
28 ("eprintln", "eprintln!"),
28 ("logd", "log::debug!"), 29 ("logd", "log::debug!"),
29 ("logt", "log::trace!"), 30 ("logt", "log::trace!"),
30 ("logi", "log::info!"), 31 ("logi", "log::info!"),
@@ -259,6 +260,7 @@ mod tests {
259 fn test_into_suggestion() { 260 fn test_into_suggestion() {
260 let test_vector = &[ 261 let test_vector = &[
261 ("println!", "{}", r#"println!("{}", $1)"#), 262 ("println!", "{}", r#"println!("{}", $1)"#),
263 ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
262 ( 264 (
263 "log::info!", 265 "log::info!",
264 "{} {expr} {} {2 + 2}", 266 "{} {expr} {} {2 + 2}",
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/doc_links.rs
index c317a2379..06af36b73 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,13 +1,27 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2//!
3//! Most of the implementation can be found in [`hir::doc_links`].
4 2
5use hir::{Adt, Crate, HasAttrs, ModuleDef}; 3use std::iter::once;
6use ide_db::{defs::Definition, RootDatabase}; 4
5use itertools::Itertools;
7use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 7use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 8use url::Url;
10 9
10use hir::{
11 db::{DefDatabase, HirDatabase},
12 Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs,
13 ModuleDef,
14};
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 RootDatabase,
18};
19use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
20
21use crate::{FilePosition, Semantics};
22
23pub type DocumentationLink = String;
24
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 25/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 26pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback( 27 let doc = Parser::new_with_broken_link_callback(
@@ -80,6 +94,70 @@ pub fn remove_links(markdown: &str) -> String {
80 out 94 out
81} 95}
82 96
97// FIXME:
98// BUG: For Option::Some
99// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
100// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
101//
102// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
103// https://github.com/rust-lang/rfcs/pull/2988
104fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
105 // Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
106 // then we can join the method, field, etc onto it if required.
107 let target_def: ModuleDef = match definition {
108 Definition::ModuleDef(moddef) => match moddef {
109 ModuleDef::Function(f) => f
110 .as_assoc_item(db)
111 .and_then(|assoc| match assoc.container(db) {
112 AssocItemContainer::Trait(t) => Some(t.into()),
113 AssocItemContainer::ImplDef(impld) => {
114 impld.target_ty(db).as_adt().map(|adt| adt.into())
115 }
116 })
117 .unwrap_or_else(|| f.clone().into()),
118 moddef => moddef,
119 },
120 Definition::Field(f) => f.parent_def(db).into(),
121 // FIXME: Handle macros
122 _ => return None,
123 };
124
125 let ns = ItemInNs::from(target_def.clone());
126
127 let module = definition.module(db)?;
128 let krate = module.krate();
129 let import_map = db.import_map(krate.into());
130 let base = once(krate.declaration_name(db)?.to_string())
131 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string()))
132 .join("/");
133
134 let filename = get_symbol_filename(db, &target_def);
135 let fragment = match definition {
136 Definition::ModuleDef(moddef) => match moddef {
137 ModuleDef::Function(f) => {
138 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(f)))
139 }
140 ModuleDef::Const(c) => {
141 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(c)))
142 }
143 ModuleDef::TypeAlias(ty) => {
144 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(ty)))
145 }
146 _ => None,
147 },
148 Definition::Field(field) => get_symbol_fragment(db, &FieldOrAssocItem::Field(field)),
149 _ => None,
150 };
151
152 get_doc_url(db, &krate)
153 .and_then(|url| url.join(&base).ok())
154 .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok()))
155 .and_then(
156 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) },
157 )
158 .map(|url| url.into_string())
159}
160
83fn rewrite_intra_doc_link( 161fn rewrite_intra_doc_link(
84 db: &RootDatabase, 162 db: &RootDatabase,
85 def: Definition, 163 def: Definition,
@@ -138,7 +216,29 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
138 .map(|url| url.into_string()) 216 .map(|url| url.into_string())
139} 217}
140 218
141// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 219/// Retrieve a link to documentation for the given symbol.
220pub(crate) fn external_docs(
221 db: &RootDatabase,
222 position: &FilePosition,
223) -> Option<DocumentationLink> {
224 let sema = Semantics::new(db);
225 let file = sema.parse(position.file_id).syntax().clone();
226 let token = pick_best(file.token_at_offset(position.offset))?;
227 let token = sema.descend_into_macros(token);
228
229 let node = token.parent();
230 let definition = match_ast! {
231 match node {
232 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
233 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
234 _ => None,
235 }
236 };
237
238 get_doc_link(db, definition?)
239}
240
241/// Rewrites a markdown document, applying 'callback' to each link.
142fn map_links<'e>( 242fn map_links<'e>(
143 events: impl Iterator<Item = Event<'e>>, 243 events: impl Iterator<Item = Event<'e>>,
144 callback: impl Fn(&str, &str) -> (String, String), 244 callback: impl Fn(&str, &str) -> (String, String),
@@ -239,6 +339,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> {
239 .next() 339 .next()
240} 340}
241 341
342/// Get the root URL for the documentation of a crate.
343///
344/// ```
345/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
346/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
347/// ```
242fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { 348fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
243 krate 349 krate
244 .get_html_root_url(db) 350 .get_html_root_url(db)
@@ -255,8 +361,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
255 361
256/// Get the filename and extension generated for a symbol by rustdoc. 362/// Get the filename and extension generated for a symbol by rustdoc.
257/// 363///
258/// Example: `struct.Shard.html` 364/// ```
259fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { 365/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
366/// ^^^^^^^^^^^^^^^^^^^
367/// ```
368fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
260 Some(match definition { 369 Some(match definition {
261 ModuleDef::Adt(adt) => match adt { 370 ModuleDef::Adt(adt) => match adt {
262 Adt::Struct(s) => format!("struct.{}.html", s.name(db)), 371 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
@@ -266,7 +375,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
266 ModuleDef::Module(_) => "index.html".to_string(), 375 ModuleDef::Module(_) => "index.html".to_string(),
267 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), 376 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
268 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), 377 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
269 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), 378 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
270 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), 379 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
271 ModuleDef::EnumVariant(ev) => { 380 ModuleDef::EnumVariant(ev) => {
272 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) 381 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -275,3 +384,163 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
275 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), 384 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
276 }) 385 })
277} 386}
387
388enum FieldOrAssocItem {
389 Field(Field),
390 AssocItem(AssocItem),
391}
392
393/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
394///
395/// ```
396/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
397/// ^^^^^^^^^^^^^^
398/// ```
399fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
400 Some(match field_or_assoc {
401 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
402 FieldOrAssocItem::AssocItem(assoc) => match assoc {
403 AssocItem::Function(function) => {
404 let is_trait_method = matches!(
405 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
406 Some(AssocItemContainer::Trait(..))
407 );
408 // This distinction may get more complicated when specialisation is available.
409 // Rustdoc makes this decision based on whether a method 'has defaultness'.
410 // Currently this is only the case for provided trait methods.
411 if is_trait_method && !function.has_body(db) {
412 format!("#tymethod.{}", function.name(db))
413 } else {
414 format!("#method.{}", function.name(db))
415 }
416 }
417 AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
418 AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
419 },
420 })
421}
422
423fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
424 return tokens.max_by_key(priority);
425 fn priority(n: &SyntaxToken) -> usize {
426 match n.kind() {
427 IDENT | INT_NUMBER => 3,
428 T!['('] | T![')'] => 2,
429 kind if kind.is_trivia() => 0,
430 _ => 1,
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use expect_test::{expect, Expect};
438
439 use crate::fixture;
440
441 fn check(ra_fixture: &str, expect: Expect) {
442 let (analysis, position) = fixture::position(ra_fixture);
443 let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
444
445 expect.assert_eq(&url)
446 }
447
448 #[test]
449 fn test_doc_url_struct() {
450 check(
451 r#"
452pub struct Fo<|>o;
453"#,
454 expect![[r#"https://docs.rs/test/*/test/struct.Foo.html"#]],
455 );
456 }
457
458 #[test]
459 fn test_doc_url_fn() {
460 check(
461 r#"
462pub fn fo<|>o() {}
463"#,
464 expect![[r##"https://docs.rs/test/*/test/fn.foo.html#method.foo"##]],
465 );
466 }
467
468 #[test]
469 fn test_doc_url_inherent_method() {
470 check(
471 r#"
472pub struct Foo;
473
474impl Foo {
475 pub fn met<|>hod() {}
476}
477
478"#,
479 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#method.method"##]],
480 );
481 }
482
483 #[test]
484 fn test_doc_url_trait_provided_method() {
485 check(
486 r#"
487pub trait Bar {
488 fn met<|>hod() {}
489}
490
491"#,
492 expect![[r##"https://docs.rs/test/*/test/trait.Bar.html#method.method"##]],
493 );
494 }
495
496 #[test]
497 fn test_doc_url_trait_required_method() {
498 check(
499 r#"
500pub trait Foo {
501 fn met<|>hod();
502}
503
504"#,
505 expect![[r##"https://docs.rs/test/*/test/trait.Foo.html#tymethod.method"##]],
506 );
507 }
508
509 #[test]
510 fn test_doc_url_field() {
511 check(
512 r#"
513pub struct Foo {
514 pub fie<|>ld: ()
515}
516
517"#,
518 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#structfield.field"##]],
519 );
520 }
521
522 // FIXME: ImportMap will return re-export paths instead of public module
523 // paths. The correct path to documentation will never be a re-export.
524 // This problem stops us from resolving stdlib items included in the prelude
525 // such as `Option::Some` correctly.
526 #[ignore = "ImportMap may return re-exports"]
527 #[test]
528 fn test_reexport_order() {
529 check(
530 r#"
531pub mod wrapper {
532 pub use module::Item;
533
534 pub mod module {
535 pub struct Item;
536 }
537}
538
539fn foo() {
540 let bar: wrapper::It<|>em;
541}
542 "#,
543 expect![[r#"https://docs.rs/test/*/test/wrapper/module/struct.Item.html"#]],
544 )
545 }
546}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 53265488e..6290b35bd 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::{remove_links, rewrite_links}, 17 doc_links::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown, 18 markdown_remove::remove_markdown,
19 markup::Markup, 19 markup::Markup,
20 runnables::runnable, 20 runnables::runnable,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 57f3581b6..686cee3a1 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -45,8 +45,8 @@ mod status;
45mod syntax_highlighting; 45mod syntax_highlighting;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite;
49mod markdown_remove; 48mod markdown_remove;
49mod doc_links;
50 50
51use std::sync::Arc; 51use std::sync::Arc;
52 52
@@ -77,7 +77,9 @@ pub use crate::{
77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, 77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 markup::Markup, 79 markup::Markup,
80 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 80 references::{
81 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, RenameError,
82 },
81 runnables::{Runnable, RunnableKind, TestId}, 83 runnables::{Runnable, RunnableKind, TestId},
82 syntax_highlighting::{ 84 syntax_highlighting::{
83 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 85 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -382,6 +384,14 @@ impl Analysis {
382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) 384 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
383 } 385 }
384 386
387 /// Return URL(s) for the documentation of the symbol under the cursor.
388 pub fn external_docs(
389 &self,
390 position: FilePosition,
391 ) -> Cancelable<Option<doc_links::DocumentationLink>> {
392 self.with_db(|db| doc_links::external_docs(db, &position))
393 }
394
385 /// Computes parameter information for the given call expression. 395 /// Computes parameter information for the given call expression.
386 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 396 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
387 self.with_db(|db| call_info::call_info(db, position)) 397 self.with_db(|db| call_info::call_info(db, position))
@@ -490,7 +500,7 @@ impl Analysis {
490 &self, 500 &self,
491 position: FilePosition, 501 position: FilePosition,
492 new_name: &str, 502 new_name: &str,
493 ) -> Cancelable<Option<RangeInfo<SourceChange>>> { 503 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> {
494 self.with_db(|db| references::rename(db, position, new_name)) 504 self.with_db(|db| references::rename(db, position, new_name))
495 } 505 }
496 506
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 571dd5452..f65a05ea3 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -26,6 +26,7 @@ use syntax::{
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27 27
28pub(crate) use self::rename::rename; 28pub(crate) use self::rename::rename;
29pub use self::rename::RenameError;
29 30
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; 31pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31 32
@@ -732,6 +733,30 @@ fn f(e: En) {
732 ); 733 );
733 } 734 }
734 735
736 #[test]
737 fn test_find_all_refs_enum_var_privacy() {
738 check(
739 r#"
740mod m {
741 pub enum En {
742 Variant {
743 field<|>: u8,
744 }
745 }
746}
747
748fn f() -> m::En {
749 m::En::Variant { field: 0 }
750}
751"#,
752 expect![[r#"
753 field RECORD_FIELD FileId(0) 56..65 56..61 Other
754
755 FileId(0) 125..130 Other Read
756 "#]],
757 );
758 }
759
735 fn check(ra_fixture: &str, expect: Expect) { 760 fn check(ra_fixture: &str, expect: Expect) {
736 check_with_scope(ra_fixture, None, expect) 761 check_with_scope(ra_fixture, None, expect)
737 } 762 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f3b5cfc8c 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -6,11 +6,16 @@ use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
8}; 8};
9use std::convert::TryInto; 9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
10use syntax::{ 15use syntax::{
11 algo::find_node_at_offset, 16 algo::find_node_at_offset,
12 ast::{self, NameOwner}, 17 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 18 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14}; 19};
15use test_utils::mark; 20use test_utils::mark;
16use text_edit::TextEdit; 21use text_edit::TextEdit;
@@ -20,17 +25,37 @@ use crate::{
20 SourceChange, SourceFileEdit, TextRange, TextSize, 25 SourceChange, SourceFileEdit, TextRange, TextSize,
21}; 26};
22 27
28#[derive(Debug)]
29pub struct RenameError(pub(crate) String);
30
31impl fmt::Display for RenameError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 Display::fmt(&self.0, f)
34 }
35}
36
37impl Error for RenameError {}
38
23pub(crate) fn rename( 39pub(crate) fn rename(
24 db: &RootDatabase, 40 db: &RootDatabase,
25 position: FilePosition, 41 position: FilePosition,
26 new_name: &str, 42 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> { 43) -> Result<RangeInfo<SourceChange>, RenameError> {
28 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
29 45
30 match lex_single_valid_syntax_kind(new_name)? { 46 match lex_single_syntax_kind(new_name) {
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 47 Some(res) => match res {
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 48 (SyntaxKind::IDENT, _) => (),
33 _ => return None, 49 (SyntaxKind::UNDERSCORE, _) => (),
50 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
51 (_, Some(syntax_error)) => {
52 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
53 }
54 (_, None) => {
55 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
56 }
57 },
58 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 59 }
35 60
36 let source_file = sema.parse(position.file_id); 61 let source_file = sema.parse(position.file_id);
@@ -103,7 +128,7 @@ fn rename_mod(
103 position: FilePosition, 128 position: FilePosition,
104 module: Module, 129 module: Module,
105 new_name: &str, 130 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 131) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 132 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 133 let mut file_system_edits = Vec::new();
109 134
@@ -125,7 +150,7 @@ fn rename_mod(
125 150
126 if let Some(src) = module.declaration_source(sema.db) { 151 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 152 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 153 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 154 let edit = SourceFileEdit {
130 file_id, 155 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 156 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +158,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 158 source_file_edits.push(edit);
134 } 159 }
135 160
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 161 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
162 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 163 let ref_edits = refs
138 .references 164 .references
139 .into_iter() 165 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 166 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 167 source_file_edits.extend(ref_edits);
142 168
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 169 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 170}
145 171
146fn rename_to_self( 172fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 173 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 174 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 175) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 176 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 177 let syn = source_file.syntax();
152 178
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 179 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 180 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
181 let params =
182 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 183 if params.self_param().is_some() {
156 return None; // method already has self param 184 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 185 }
158 let first_param = params.params().next()?; 186 let first_param =
187 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 188 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 189 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 190 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 191 };
163 192
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 193 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
194 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 195
166 let param_range = first_param.syntax().text_range(); 196 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 197 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +199,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 199 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 200
171 if param_ref.is_empty() { 201 if param_ref.is_empty() {
172 return None; 202 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 203 }
174 204
175 let mut edits = usages 205 let mut edits = usages
@@ -185,7 +215,7 @@ fn rename_to_self(
185 ), 215 ),
186 }); 216 });
187 217
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 218 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 219}
190 220
191fn text_edit_from_self_param( 221fn text_edit_from_self_param(
@@ -216,12 +246,13 @@ fn rename_self_to_param(
216 position: FilePosition, 246 position: FilePosition,
217 self_token: SyntaxToken, 247 self_token: SyntaxToken,
218 new_name: &str, 248 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 249) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 250 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 251 let syn = source_file.syntax();
222 252
223 let text = sema.db.file_text(position.file_id); 253 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 254 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
255 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 256 let search_range = fn_def.syntax().text_range();
226 257
227 let mut edits: Vec<SourceFileEdit> = vec![]; 258 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +266,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 266 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 267 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 268 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 269 text_edit_from_self_param(syn, self_param, new_name)
270 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 271 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 272 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 273 };
@@ -246,15 +278,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 278 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 279 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 280
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 281 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 282}
251 283
252fn rename_reference( 284fn rename_reference(
253 sema: &Semantics<RootDatabase>, 285 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 286 position: FilePosition,
255 new_name: &str, 287 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 288) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 289 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
290 Some(range_info) => range_info,
291 None => return Err(RenameError("No references found at position".to_string())),
292 };
258 293
259 let edit = refs 294 let edit = refs
260 .into_iter() 295 .into_iter()
@@ -262,10 +297,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 297 .collect::<Vec<_>>();
263 298
264 if edit.is_empty() { 299 if edit.is_empty() {
265 return None; 300 return Err(RenameError("No references found at position".to_string()));
266 } 301 }
267 302
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 303 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 304}
270 305
271#[cfg(test)] 306#[cfg(test)]
@@ -280,25 +315,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 315 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 316 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 317 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 318 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 319 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 320 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 321 match rename_result {
287 for edit in change.info.source_file_edits { 322 Ok(source_change) => {
288 file_id = Some(edit.file_id); 323 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 324 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 325 for edit in source_change.info.source_file_edits {
326 file_id = Some(edit.file_id);
327 for indel in edit.edit.into_iter() {
328 text_edit_builder.replace(indel.delete, indel.insert);
329 }
291 } 330 }
331 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
332 text_edit_builder.finish().apply(&mut result);
333 assert_eq_text!(ra_fixture_after, &*result);
292 } 334 }
293 } 335 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 336 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 337 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 338 .chars()
339 .into_iter()
340 .skip("error:".len())
341 .collect::<String>();
342 assert_eq!(error_message.trim(), err.to_string());
343 return;
344 } else {
345 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
346 }
347 }
348 };
297 } 349 }
298 350
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 351 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 352 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 353 let source_change = analysis
354 .rename(position, new_name)
355 .unwrap()
356 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 357 expect.assert_debug_eq(&source_change)
303 } 358 }
304 359
@@ -313,11 +368,30 @@ mod tests {
313 } 368 }
314 369
315 #[test] 370 #[test]
316 fn test_rename_to_invalid_identifier() { 371 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 372 check(
318 let new_name = "invalid!"; 373 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 374 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 375 "error: Invalid name `invalid!`: not an identifier",
376 );
377 }
378
379 #[test]
380 fn test_rename_to_invalid_identifier2() {
381 check(
382 "multiple tokens",
383 r#"fn main() { let i<|> = 1; }"#,
384 "error: Invalid name `multiple tokens`: not an identifier",
385 );
386 }
387
388 #[test]
389 fn test_rename_to_invalid_identifier3() {
390 check(
391 "let",
392 r#"fn main() { let i<|> = 1; }"#,
393 "error: Invalid name `let`: not an identifier",
394 );
321 } 395 }
322 396
323 #[test] 397 #[test]
@@ -350,6 +424,15 @@ fn main() {
350 } 424 }
351 425
352 #[test] 426 #[test]
427 fn test_rename_unresolved_reference() {
428 check(
429 "new_name",
430 r#"fn main() { let _ = unresolved_ref<|>; }"#,
431 "error: No references found at position",
432 );
433 }
434
435 #[test]
353 fn test_rename_for_macro_args() { 436 fn test_rename_for_macro_args() {
354 check( 437 check(
355 "b", 438 "b",
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 468655f9c..215be850f 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -646,14 +646,9 @@ pub(crate) fn handle_prepare_rename(
646 let _p = profile::span("handle_prepare_rename"); 646 let _p = profile::span("handle_prepare_rename");
647 let position = from_proto::file_position(&snap, params)?; 647 let position = from_proto::file_position(&snap, params)?;
648 648
649 let optional_change = snap.analysis.rename(position, "dummy")?; 649 let change = snap.analysis.rename(position, "dummy")??;
650 let range = match optional_change {
651 None => return Ok(None),
652 Some(it) => it.range,
653 };
654
655 let line_index = snap.analysis.file_line_index(position.file_id)?; 650 let line_index = snap.analysis.file_line_index(position.file_id)?;
656 let range = to_proto::range(&line_index, range); 651 let range = to_proto::range(&line_index, change.range);
657 Ok(Some(PrepareRenameResponse::Range(range))) 652 Ok(Some(PrepareRenameResponse::Range(range)))
658} 653}
659 654
@@ -672,12 +667,8 @@ pub(crate) fn handle_rename(
672 .into()); 667 .into());
673 } 668 }
674 669
675 let optional_change = snap.analysis.rename(position, &*params.new_name)?; 670 let change = snap.analysis.rename(position, &*params.new_name)??;
676 let source_change = match optional_change { 671 let workspace_edit = to_proto::workspace_edit(&snap, change.info)?;
677 None => return Ok(None),
678 Some(it) => it.info,
679 };
680 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
681 Ok(Some(workspace_edit)) 672 Ok(Some(workspace_edit))
682} 673}
683 674
@@ -1310,6 +1301,18 @@ pub(crate) fn handle_semantic_tokens_range(
1310 Ok(Some(semantic_tokens.into())) 1301 Ok(Some(semantic_tokens.into()))
1311} 1302}
1312 1303
1304pub(crate) fn handle_open_docs(
1305 snap: GlobalStateSnapshot,
1306 params: lsp_types::TextDocumentPositionParams,
1307) -> Result<Option<lsp_types::Url>> {
1308 let _p = profile::span("handle_open_docs");
1309 let position = from_proto::file_position(&snap, params)?;
1310
1311 let remote = snap.analysis.external_docs(position)?;
1312
1313 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1314}
1315
1313fn implementation_title(count: usize) -> String { 1316fn implementation_title(count: usize) -> String {
1314 if count == 1 { 1317 if count == 1 {
1315 "1 implementation".into() 1318 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index fee0bb69c..f31f8d900 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -347,3 +347,11 @@ pub struct CommandLink {
347 #[serde(skip_serializing_if = "Option::is_none")] 347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub tooltip: Option<String>, 348 pub tooltip: Option<String>,
349} 349}
350
351pub enum ExternalDocs {}
352
353impl Request for ExternalDocs {
354 type Params = lsp_types::TextDocumentPositionParams;
355 type Result = Option<lsp_types::Url>;
356 const METHOD: &'static str = "experimental/externalDocs";
357}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4b7ac8224..06b38d99c 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -384,6 +384,7 @@ impl GlobalState {
384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? 384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)? 385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)? 386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
387 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)?
387 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 388 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
388 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 389 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
389 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 390 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
diff --git a/crates/stdx/src/macros.rs b/crates/stdx/src/macros.rs
index bf298460f..f5ee3484b 100644
--- a/crates/stdx/src/macros.rs
+++ b/crates/stdx/src/macros.rs
@@ -18,7 +18,13 @@ macro_rules! format_to {
18 }; 18 };
19} 19}
20 20
21// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums 21/// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums
22///
23/// # Example
24///
25/// ```rust
26/// impl_from!(Struct, Union, Enum for Adt);
27/// ```
22#[macro_export] 28#[macro_export]
23macro_rules! impl_from { 29macro_rules! impl_from {
24 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => { 30 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => {