]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/moniker.rs
Rollup merge of #99593 - TaKO8Ki:suggest-removing-tuple-struct-field, r=compiler...
[rust.git] / src / tools / rust-analyzer / crates / ide / src / moniker.rs
1 //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
2 //! for LSIF and LSP.
3
4 use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
5 use ide_db::{
6     base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin},
7     defs::{Definition, IdentClass},
8     helpers::pick_best_token,
9     RootDatabase,
10 };
11 use itertools::Itertools;
12 use syntax::{AstNode, SyntaxKind::*, T};
13
14 use crate::{doc_links::token_as_doc_comment, RangeInfo};
15
16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17 pub struct MonikerIdentifier {
18     crate_name: String,
19     path: Vec<Name>,
20 }
21
22 impl ToString for MonikerIdentifier {
23     fn to_string(&self) -> String {
24         match self {
25             MonikerIdentifier { path, crate_name } => {
26                 format!("{}::{}", crate_name, path.iter().map(|x| x.to_string()).join("::"))
27             }
28         }
29     }
30 }
31
32 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33 pub enum MonikerKind {
34     Import,
35     Export,
36 }
37
38 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
39 pub struct MonikerResult {
40     pub identifier: MonikerIdentifier,
41     pub kind: MonikerKind,
42     pub package_information: PackageInformation,
43 }
44
45 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
46 pub struct PackageInformation {
47     pub name: String,
48     pub repo: String,
49     pub version: String,
50 }
51
52 pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
53     for &krate in db.relevant_crates(file_id).iter() {
54         let crate_def_map = db.crate_def_map(krate);
55         for (_, data) in crate_def_map.modules() {
56             if data.origin.file_id() == Some(file_id) {
57                 return Some(krate.into());
58             }
59         }
60     }
61     None
62 }
63
64 pub(crate) fn moniker(
65     db: &RootDatabase,
66     FilePosition { file_id, offset }: FilePosition,
67 ) -> Option<RangeInfo<Vec<MonikerResult>>> {
68     let sema = &Semantics::new(db);
69     let file = sema.parse(file_id).syntax().clone();
70     let current_crate = crate_for_file(db, file_id)?;
71     let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
72         IDENT
73         | INT_NUMBER
74         | LIFETIME_IDENT
75         | T![self]
76         | T![super]
77         | T![crate]
78         | T![Self]
79         | COMMENT => 2,
80         kind if kind.is_trivia() => 0,
81         _ => 1,
82     })?;
83     if let Some(doc_comment) = token_as_doc_comment(&original_token) {
84         return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
85             let m = def_to_moniker(db, def, current_crate)?;
86             Some(RangeInfo::new(original_token.text_range(), vec![m]))
87         });
88     }
89     let navs = sema
90         .descend_into_macros(original_token.clone())
91         .into_iter()
92         .filter_map(|token| {
93             IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
94                 it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
95             })
96         })
97         .flatten()
98         .unique()
99         .collect::<Vec<_>>();
100     Some(RangeInfo::new(original_token.text_range(), navs))
101 }
102
103 pub(crate) fn def_to_moniker(
104     db: &RootDatabase,
105     def: Definition,
106     from_crate: Crate,
107 ) -> Option<MonikerResult> {
108     if matches!(def, Definition::GenericParam(_) | Definition::SelfType(_) | Definition::Local(_)) {
109         return None;
110     }
111     let module = def.module(db)?;
112     let krate = module.krate();
113     let mut path = vec![];
114     path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db)));
115
116     // Handle associated items within a trait
117     if let Some(assoc) = def.as_assoc_item(db) {
118         let container = assoc.container(db);
119         match container {
120             AssocItemContainer::Trait(trait_) => {
121                 // Because different traits can have functions with the same name,
122                 // we have to include the trait name as part of the moniker for uniqueness.
123                 path.push(trait_.name(db));
124             }
125             AssocItemContainer::Impl(impl_) => {
126                 // Because a struct can implement multiple traits, for implementations
127                 // we add both the struct name and the trait name to the path
128                 if let Some(adt) = impl_.self_ty(db).as_adt() {
129                     path.push(adt.name(db));
130                 }
131
132                 if let Some(trait_) = impl_.trait_(db) {
133                     path.push(trait_.name(db));
134                 }
135             }
136         }
137     }
138
139     if let Definition::Field(it) = def {
140         path.push(it.parent_def(db).name(db));
141     }
142
143     path.push(def.name(db)?);
144     Some(MonikerResult {
145         identifier: MonikerIdentifier {
146             crate_name: krate.display_name(db)?.crate_name().to_string(),
147             path,
148         },
149         kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
150         package_information: {
151             let name = krate.display_name(db)?.to_string();
152             let (repo, version) = match krate.origin(db) {
153                 CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?),
154                 CrateOrigin::Lang(lang) => (
155                     "https://github.com/rust-lang/rust/".to_string(),
156                     match lang {
157                         LangCrateOrigin::Other => {
158                             "https://github.com/rust-lang/rust/library/".into()
159                         }
160                         lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
161                     },
162                 ),
163             };
164             PackageInformation { name, repo, version }
165         },
166     })
167 }
168
169 #[cfg(test)]
170 mod tests {
171     use crate::fixture;
172
173     use super::MonikerKind;
174
175     #[track_caller]
176     fn no_moniker(ra_fixture: &str) {
177         let (analysis, position) = fixture::position(ra_fixture);
178         if let Some(x) = analysis.moniker(position).unwrap() {
179             assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
180         }
181     }
182
183     #[track_caller]
184     fn check_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) {
185         let (analysis, position) = fixture::position(ra_fixture);
186         let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
187         assert_eq!(x.len(), 1);
188         let x = x.into_iter().next().unwrap();
189         assert_eq!(identifier, x.identifier.to_string());
190         assert_eq!(package, format!("{:?}", x.package_information));
191         assert_eq!(kind, x.kind);
192     }
193
194     #[test]
195     fn basic() {
196         check_moniker(
197             r#"
198 //- /lib.rs crate:main deps:foo
199 use foo::module::func;
200 fn main() {
201     func$0();
202 }
203 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
204 pub mod module {
205     pub fn func() {}
206 }
207 "#,
208             "foo::module::func",
209             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
210             MonikerKind::Import,
211         );
212         check_moniker(
213             r#"
214 //- /lib.rs crate:main deps:foo
215 use foo::module::func;
216 fn main() {
217     func();
218 }
219 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
220 pub mod module {
221     pub fn func$0() {}
222 }
223 "#,
224             "foo::module::func",
225             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
226             MonikerKind::Export,
227         );
228     }
229
230     #[test]
231     fn moniker_for_trait() {
232         check_moniker(
233             r#"
234 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
235 pub mod module {
236     pub trait MyTrait {
237         pub fn func$0() {}
238     }
239 }
240 "#,
241             "foo::module::MyTrait::func",
242             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
243             MonikerKind::Export,
244         );
245     }
246
247     #[test]
248     fn moniker_for_trait_constant() {
249         check_moniker(
250             r#"
251 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
252 pub mod module {
253     pub trait MyTrait {
254         const MY_CONST$0: u8;
255     }
256 }
257 "#,
258             "foo::module::MyTrait::MY_CONST",
259             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
260             MonikerKind::Export,
261         );
262     }
263
264     #[test]
265     fn moniker_for_trait_type() {
266         check_moniker(
267             r#"
268 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
269 pub mod module {
270     pub trait MyTrait {
271         type MyType$0;
272     }
273 }
274 "#,
275             "foo::module::MyTrait::MyType",
276             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
277             MonikerKind::Export,
278         );
279     }
280
281     #[test]
282     fn moniker_for_trait_impl_function() {
283         check_moniker(
284             r#"
285 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
286 pub mod module {
287     pub trait MyTrait {
288         pub fn func() {}
289     }
290
291     struct MyStruct {}
292
293     impl MyTrait for MyStruct {
294         pub fn func$0() {}
295     }
296 }
297 "#,
298             "foo::module::MyStruct::MyTrait::func",
299             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
300             MonikerKind::Export,
301         );
302     }
303
304     #[test]
305     fn moniker_for_field() {
306         check_moniker(
307             r#"
308 //- /lib.rs crate:main deps:foo
309 use foo::St;
310 fn main() {
311     let x = St { a$0: 2 };
312 }
313 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
314 pub struct St {
315     pub a: i32,
316 }
317 "#,
318             "foo::St::a",
319             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
320             MonikerKind::Import,
321         );
322     }
323
324     #[test]
325     fn no_moniker_for_local() {
326         no_moniker(
327             r#"
328 //- /lib.rs crate:main deps:foo
329 use foo::module::func;
330 fn main() {
331     func();
332 }
333 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
334 pub mod module {
335     pub fn func() {
336         let x$0 = 2;
337     }
338 }
339 "#,
340         );
341     }
342 }