aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/ide
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/Cargo.toml37
-rw-r--r--crates/ide/src/call_hierarchy.rs393
-rw-r--r--crates/ide/src/call_info.rs742
-rw-r--r--crates/ide/src/completion.rs207
-rw-r--r--crates/ide/src/completion/complete_attribute.rs653
-rw-r--r--crates/ide/src/completion/complete_dot.rs416
-rw-r--r--crates/ide/src/completion/complete_fn_param.rs135
-rw-r--r--crates/ide/src/completion/complete_keyword.rs527
-rw-r--r--crates/ide/src/completion/complete_macro_in_item_position.rs41
-rw-r--r--crates/ide/src/completion/complete_pattern.rs88
-rw-r--r--crates/ide/src/completion/complete_postfix.rs378
-rw-r--r--crates/ide/src/completion/complete_qualified_path.rs733
-rw-r--r--crates/ide/src/completion/complete_record.rs226
-rw-r--r--crates/ide/src/completion/complete_snippet.rs116
-rw-r--r--crates/ide/src/completion/complete_trait_impl.rs488
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs658
-rw-r--r--crates/ide/src/completion/completion_config.rs35
-rw-r--r--crates/ide/src/completion/completion_context.rs486
-rw-r--r--crates/ide/src/completion/completion_item.rs384
-rw-r--r--crates/ide/src/completion/generated_features.rs4
-rw-r--r--crates/ide/src/completion/patterns.rs194
-rw-r--r--crates/ide/src/completion/presentation.rs1227
-rw-r--r--crates/ide/src/completion/test_utils.rs114
-rw-r--r--crates/ide/src/diagnostics.rs746
-rw-r--r--crates/ide/src/diagnostics/fixes.rs175
-rw-r--r--crates/ide/src/display.rs83
-rw-r--r--crates/ide/src/display/navigation_target.rs491
-rw-r--r--crates/ide/src/display/short_label.rs111
-rw-r--r--crates/ide/src/expand_macro.rs283
-rw-r--r--crates/ide/src/extend_selection.rs654
-rw-r--r--crates/ide/src/file_structure.rs431
-rw-r--r--crates/ide/src/folding_ranges.rs422
-rw-r--r--crates/ide/src/goto_definition.rs989
-rw-r--r--crates/ide/src/goto_implementation.rs229
-rw-r--r--crates/ide/src/goto_type_definition.rs151
-rw-r--r--crates/ide/src/hover.rs2967
-rw-r--r--crates/ide/src/inlay_hints.rs927
-rw-r--r--crates/ide/src/join_lines.rs773
-rw-r--r--crates/ide/src/lib.rs516
-rw-r--r--crates/ide/src/link_rewrite.rs79
-rw-r--r--crates/ide/src/markup.rs38
-rw-r--r--crates/ide/src/matching_brace.rs73
-rw-r--r--crates/ide/src/mock_analysis.rs176
-rw-r--r--crates/ide/src/parent_module.rs155
-rw-r--r--crates/ide/src/prime_caches.rs12
-rw-r--r--crates/ide/src/references.rs696
-rw-r--r--crates/ide/src/references/rename.rs1010
-rw-r--r--crates/ide/src/runnables.rs883
-rw-r--r--crates/ide/src/status.rs145
-rw-r--r--crates/ide/src/syntax_highlighting.rs879
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs97
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs187
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs206
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs484
-rw-r--r--crates/ide/src/syntax_tree.rs359
-rw-r--r--crates/ide/src/typing.rs364
-rw-r--r--crates/ide/src/typing/on_enter.rs256
-rw-r--r--crates/ide/test_data/highlight_doctest.html102
-rw-r--r--crates/ide/test_data/highlight_extern_crate.html40
-rw-r--r--crates/ide/test_data/highlight_injection.html48
-rw-r--r--crates/ide/test_data/highlight_strings.html96
-rw-r--r--crates/ide/test_data/highlight_unsafe.html99
-rw-r--r--crates/ide/test_data/highlighting.html170
-rw-r--r--crates/ide/test_data/rainbow_highlighting.html49
64 files changed, 24933 insertions, 0 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
new file mode 100644
index 000000000..9ecf43e5a
--- /dev/null
+++ b/crates/ide/Cargo.toml
@@ -0,0 +1,37 @@
1[package]
2name = "ide"
3version = "0.0.0"
4license = "MIT OR Apache-2.0"
5authors = ["rust-analyzer developers"]
6edition = "2018"
7
8[lib]
9doctest = false
10
11[dependencies]
12either = "1.5.3"
13indexmap = "1.3.2"
14itertools = "0.9.0"
15log = "0.4.8"
16rustc-hash = "1.1.0"
17oorandom = "11.1.2"
18pulldown-cmark-to-cmark = "5.0.0"
19pulldown-cmark = {version = "0.7.2", default-features = false}
20
21stdx = { path = "../stdx" }
22syntax = { path = "../syntax" }
23text_edit = { path = "../text_edit" }
24base_db = { path = "../base_db" }
25ide_db = { path = "../ide_db" }
26cfg = { path = "../cfg" }
27profile = { path = "../profile" }
28test_utils = { path = "../test_utils" }
29assists = { path = "../assists" }
30ssr = { path = "../ssr" }
31
32# ide should depend only on the top-level `hir` package. if you need
33# something from some `hir_xxx` subpackage, reexport the API via `hir`.
34hir = { path = "../hir" }
35
36[dev-dependencies]
37expect-test = "0.1"
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
new file mode 100644
index 000000000..58e26b94c
--- /dev/null
+++ b/crates/ide/src/call_hierarchy.rs
@@ -0,0 +1,393 @@
1//! Entry point for call-hierarchy
2
3use indexmap::IndexMap;
4
5use hir::Semantics;
6use ide_db::RootDatabase;
7use syntax::{ast, match_ast, AstNode, TextRange};
8
9use crate::{
10 call_info::FnCallNode, display::ToNav, goto_definition, references, FilePosition,
11 NavigationTarget, RangeInfo,
12};
13
14#[derive(Debug, Clone)]
15pub struct CallItem {
16 pub target: NavigationTarget,
17 pub ranges: Vec<TextRange>,
18}
19
20impl CallItem {
21 #[cfg(test)]
22 pub(crate) fn assert_match(&self, expected: &str) {
23 let actual = self.debug_render();
24 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
25 }
26
27 #[cfg(test)]
28 pub(crate) fn debug_render(&self) -> String {
29 format!("{} : {:?}", self.target.debug_render(), self.ranges)
30 }
31}
32
33pub(crate) fn call_hierarchy(
34 db: &RootDatabase,
35 position: FilePosition,
36) -> Option<RangeInfo<Vec<NavigationTarget>>> {
37 goto_definition::goto_definition(db, position)
38}
39
40pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
41 let sema = Semantics::new(db);
42
43 // 1. Find all refs
44 // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
45 // 3. Add ranges relative to the start of the fndef.
46 let refs = references::find_all_refs(&sema, position, None)?;
47
48 let mut calls = CallLocations::default();
49
50 for reference in refs.info.references() {
51 let file_id = reference.file_range.file_id;
52 let file = sema.parse(file_id);
53 let file = file.syntax();
54 let token = file.token_at_offset(reference.file_range.range.start()).next()?;
55 let token = sema.descend_into_macros(token);
56 let syntax = token.parent();
57
58 // This target is the containing function
59 if let Some(nav) = syntax.ancestors().find_map(|node| {
60 match_ast! {
61 match node {
62 ast::Fn(it) => {
63 let def = sema.to_def(&it)?;
64 Some(def.to_nav(sema.db))
65 },
66 _ => None,
67 }
68 }
69 }) {
70 let relative_range = reference.file_range.range;
71 calls.add(&nav, relative_range);
72 }
73 }
74
75 Some(calls.into_items())
76}
77
78pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
79 let sema = Semantics::new(db);
80 let file_id = position.file_id;
81 let file = sema.parse(file_id);
82 let file = file.syntax();
83 let token = file.token_at_offset(position.offset).next()?;
84 let token = sema.descend_into_macros(token);
85 let syntax = token.parent();
86
87 let mut calls = CallLocations::default();
88
89 syntax
90 .descendants()
91 .filter_map(|node| FnCallNode::with_node_exact(&node))
92 .filter_map(|call_node| {
93 let name_ref = call_node.name_ref()?;
94
95 if let Some(func_target) = match &call_node {
96 FnCallNode::CallExpr(expr) => {
97 //FIXME: Type::as_callable is broken
98 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
99 match callable.kind() {
100 hir::CallableKind::Function(it) => {
101 let fn_def: hir::Function = it.into();
102 let nav = fn_def.to_nav(db);
103 Some(nav)
104 }
105 _ => None,
106 }
107 }
108 FnCallNode::MethodCallExpr(expr) => {
109 let function = sema.resolve_method_call(&expr)?;
110 Some(function.to_nav(db))
111 }
112 } {
113 Some((func_target, name_ref.syntax().text_range()))
114 } else {
115 None
116 }
117 })
118 .for_each(|(nav, range)| calls.add(&nav, range));
119
120 Some(calls.into_items())
121}
122
123#[derive(Default)]
124struct CallLocations {
125 funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
126}
127
128impl CallLocations {
129 fn add(&mut self, target: &NavigationTarget, range: TextRange) {
130 self.funcs.entry(target.clone()).or_default().push(range);
131 }
132
133 fn into_items(self) -> Vec<CallItem> {
134 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use base_db::FilePosition;
141
142 use crate::mock_analysis::analysis_and_position;
143
144 fn check_hierarchy(
145 ra_fixture: &str,
146 expected: &str,
147 expected_incoming: &[&str],
148 expected_outgoing: &[&str],
149 ) {
150 let (analysis, pos) = analysis_and_position(ra_fixture);
151
152 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
153 assert_eq!(navs.len(), 1);
154 let nav = navs.pop().unwrap();
155 nav.assert_match(expected);
156
157 let item_pos =
158 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
159 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
160 assert_eq!(incoming_calls.len(), expected_incoming.len());
161
162 for call in 0..incoming_calls.len() {
163 incoming_calls[call].assert_match(expected_incoming[call]);
164 }
165
166 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
167 assert_eq!(outgoing_calls.len(), expected_outgoing.len());
168
169 for call in 0..outgoing_calls.len() {
170 outgoing_calls[call].assert_match(expected_outgoing[call]);
171 }
172 }
173
174 #[test]
175 fn test_call_hierarchy_on_ref() {
176 check_hierarchy(
177 r#"
178//- /lib.rs
179fn callee() {}
180fn caller() {
181 call<|>ee();
182}
183"#,
184 "callee FN FileId(1) 0..14 3..9",
185 &["caller FN FileId(1) 15..44 18..24 : [33..39]"],
186 &[],
187 );
188 }
189
190 #[test]
191 fn test_call_hierarchy_on_def() {
192 check_hierarchy(
193 r#"
194//- /lib.rs
195fn call<|>ee() {}
196fn caller() {
197 callee();
198}
199"#,
200 "callee FN FileId(1) 0..14 3..9",
201 &["caller FN FileId(1) 15..44 18..24 : [33..39]"],
202 &[],
203 );
204 }
205
206 #[test]
207 fn test_call_hierarchy_in_same_fn() {
208 check_hierarchy(
209 r#"
210//- /lib.rs
211fn callee() {}
212fn caller() {
213 call<|>ee();
214 callee();
215}
216"#,
217 "callee FN FileId(1) 0..14 3..9",
218 &["caller FN FileId(1) 15..58 18..24 : [33..39, 47..53]"],
219 &[],
220 );
221 }
222
223 #[test]
224 fn test_call_hierarchy_in_different_fn() {
225 check_hierarchy(
226 r#"
227//- /lib.rs
228fn callee() {}
229fn caller1() {
230 call<|>ee();
231}
232
233fn caller2() {
234 callee();
235}
236"#,
237 "callee FN FileId(1) 0..14 3..9",
238 &[
239 "caller1 FN FileId(1) 15..45 18..25 : [34..40]",
240 "caller2 FN FileId(1) 47..77 50..57 : [66..72]",
241 ],
242 &[],
243 );
244 }
245
246 #[test]
247 fn test_call_hierarchy_in_tests_mod() {
248 check_hierarchy(
249 r#"
250//- /lib.rs cfg:test
251fn callee() {}
252fn caller1() {
253 call<|>ee();
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_caller() {
262 callee();
263 }
264}
265"#,
266 "callee FN FileId(1) 0..14 3..9",
267 &[
268 "caller1 FN FileId(1) 15..45 18..25 : [34..40]",
269 "test_caller FN FileId(1) 95..149 110..121 : [134..140]",
270 ],
271 &[],
272 );
273 }
274
275 #[test]
276 fn test_call_hierarchy_in_different_files() {
277 check_hierarchy(
278 r#"
279//- /lib.rs
280mod foo;
281use foo::callee;
282
283fn caller() {
284 call<|>ee();
285}
286
287//- /foo/mod.rs
288pub fn callee() {}
289"#,
290 "callee FN FileId(2) 0..18 7..13",
291 &["caller FN FileId(1) 27..56 30..36 : [45..51]"],
292 &[],
293 );
294 }
295
296 #[test]
297 fn test_call_hierarchy_outgoing() {
298 check_hierarchy(
299 r#"
300//- /lib.rs
301fn callee() {}
302fn call<|>er() {
303 callee();
304 callee();
305}
306"#,
307 "caller FN FileId(1) 15..58 18..24",
308 &[],
309 &["callee FN FileId(1) 0..14 3..9 : [33..39, 47..53]"],
310 );
311 }
312
313 #[test]
314 fn test_call_hierarchy_outgoing_in_different_files() {
315 check_hierarchy(
316 r#"
317//- /lib.rs
318mod foo;
319use foo::callee;
320
321fn call<|>er() {
322 callee();
323}
324
325//- /foo/mod.rs
326pub fn callee() {}
327"#,
328 "caller FN FileId(1) 27..56 30..36",
329 &[],
330 &["callee FN FileId(2) 0..18 7..13 : [45..51]"],
331 );
332 }
333
334 #[test]
335 fn test_call_hierarchy_incoming_outgoing() {
336 check_hierarchy(
337 r#"
338//- /lib.rs
339fn caller1() {
340 call<|>er2();
341}
342
343fn caller2() {
344 caller3();
345}
346
347fn caller3() {
348
349}
350"#,
351 "caller2 FN FileId(1) 33..64 36..43",
352 &["caller1 FN FileId(1) 0..31 3..10 : [19..26]"],
353 &["caller3 FN FileId(1) 66..83 69..76 : [52..59]"],
354 );
355 }
356
357 #[test]
358 fn test_call_hierarchy_issue_5103() {
359 check_hierarchy(
360 r#"
361fn a() {
362 b()
363}
364
365fn b() {}
366
367fn main() {
368 a<|>()
369}
370"#,
371 "a FN FileId(1) 0..18 3..4",
372 &["main FN FileId(1) 31..52 34..38 : [47..48]"],
373 &["b FN FileId(1) 20..29 23..24 : [13..14]"],
374 );
375
376 check_hierarchy(
377 r#"
378fn a() {
379 b<|>()
380}
381
382fn b() {}
383
384fn main() {
385 a()
386}
387"#,
388 "b FN FileId(1) 20..29 23..24",
389 &["a FN FileId(1) 0..18 3..4 : [13..14]"],
390 &[],
391 );
392 }
393}
diff --git a/crates/ide/src/call_info.rs b/crates/ide/src/call_info.rs
new file mode 100644
index 000000000..7e83a2381
--- /dev/null
+++ b/crates/ide/src/call_info.rs
@@ -0,0 +1,742 @@
1//! FIXME: write short doc here
2use either::Either;
3use hir::{Docs, HirDisplay, Semantics, Type};
4use ide_db::RootDatabase;
5use stdx::format_to;
6use syntax::{
7 ast::{self, ArgListOwner},
8 match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize,
9};
10use test_utils::mark;
11
12use crate::FilePosition;
13
14/// Contains information about a call site. Specifically the
15/// `FunctionSignature`and current parameter.
16#[derive(Debug)]
17pub struct CallInfo {
18 pub doc: Option<String>,
19 pub signature: String,
20 pub active_parameter: Option<usize>,
21 parameters: Vec<TextRange>,
22}
23
24impl CallInfo {
25 pub fn parameter_labels(&self) -> impl Iterator<Item = &str> + '_ {
26 self.parameters.iter().map(move |&it| &self.signature[it])
27 }
28 pub fn parameter_ranges(&self) -> &[TextRange] {
29 &self.parameters
30 }
31 fn push_param(&mut self, param: &str) {
32 if !self.signature.ends_with('(') {
33 self.signature.push_str(", ");
34 }
35 let start = TextSize::of(&self.signature);
36 self.signature.push_str(param);
37 let end = TextSize::of(&self.signature);
38 self.parameters.push(TextRange::new(start, end))
39 }
40}
41
42/// Computes parameter information for the given call expression.
43pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
44 let sema = Semantics::new(db);
45 let file = sema.parse(position.file_id);
46 let file = file.syntax();
47 let token = file.token_at_offset(position.offset).next()?;
48 let token = sema.descend_into_macros(token);
49
50 let (callable, active_parameter) = call_info_impl(&sema, token)?;
51
52 let mut res =
53 CallInfo { doc: None, signature: String::new(), parameters: vec![], active_parameter };
54
55 match callable.kind() {
56 hir::CallableKind::Function(func) => {
57 res.doc = func.docs(db).map(|it| it.as_str().to_string());
58 format_to!(res.signature, "fn {}", func.name(db));
59 }
60 hir::CallableKind::TupleStruct(strukt) => {
61 res.doc = strukt.docs(db).map(|it| it.as_str().to_string());
62 format_to!(res.signature, "struct {}", strukt.name(db));
63 }
64 hir::CallableKind::TupleEnumVariant(variant) => {
65 res.doc = variant.docs(db).map(|it| it.as_str().to_string());
66 format_to!(
67 res.signature,
68 "enum {}::{}",
69 variant.parent_enum(db).name(db),
70 variant.name(db)
71 );
72 }
73 hir::CallableKind::Closure => (),
74 }
75
76 res.signature.push('(');
77 {
78 if let Some(self_param) = callable.receiver_param(db) {
79 format_to!(res.signature, "{}", self_param)
80 }
81 let mut buf = String::new();
82 for (pat, ty) in callable.params(db) {
83 buf.clear();
84 if let Some(pat) = pat {
85 match pat {
86 Either::Left(_self) => format_to!(buf, "self: "),
87 Either::Right(pat) => format_to!(buf, "{}: ", pat),
88 }
89 }
90 format_to!(buf, "{}", ty.display(db));
91 res.push_param(&buf);
92 }
93 }
94 res.signature.push(')');
95
96 match callable.kind() {
97 hir::CallableKind::Function(_) | hir::CallableKind::Closure => {
98 let ret_type = callable.return_type();
99 if !ret_type.is_unit() {
100 format_to!(res.signature, " -> {}", ret_type.display(db));
101 }
102 }
103 hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
104 }
105 Some(res)
106}
107
108fn call_info_impl(
109 sema: &Semantics<RootDatabase>,
110 token: SyntaxToken,
111) -> Option<(hir::Callable, Option<usize>)> {
112 // Find the calling expression and it's NameRef
113 let calling_node = FnCallNode::with_node(&token.parent())?;
114
115 let callable = match &calling_node {
116 FnCallNode::CallExpr(call) => sema.type_of_expr(&call.expr()?)?.as_callable(sema.db)?,
117 FnCallNode::MethodCallExpr(call) => sema.resolve_method_call_as_callable(call)?,
118 };
119 let active_param = if let Some(arg_list) = calling_node.arg_list() {
120 // Number of arguments specified at the call site
121 let num_args_at_callsite = arg_list.args().count();
122
123 let arg_list_range = arg_list.syntax().text_range();
124 if !arg_list_range.contains_inclusive(token.text_range().start()) {
125 mark::hit!(call_info_bad_offset);
126 return None;
127 }
128 let param = std::cmp::min(
129 num_args_at_callsite,
130 arg_list
131 .args()
132 .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
133 .count(),
134 );
135
136 Some(param)
137 } else {
138 None
139 };
140 Some((callable, active_param))
141}
142
143#[derive(Debug)]
144pub(crate) struct ActiveParameter {
145 pub(crate) ty: Type,
146 pub(crate) name: String,
147}
148
149impl ActiveParameter {
150 pub(crate) fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
151 let sema = Semantics::new(db);
152 let file = sema.parse(position.file_id);
153 let file = file.syntax();
154 let token = file.token_at_offset(position.offset).next()?;
155 let token = sema.descend_into_macros(token);
156 Self::at_token(&sema, token)
157 }
158
159 pub(crate) fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
160 let (signature, active_parameter) = call_info_impl(&sema, token)?;
161
162 let idx = active_parameter?;
163 let mut params = signature.params(sema.db);
164 if !(idx < params.len()) {
165 mark::hit!(too_many_arguments);
166 return None;
167 }
168 let (pat, ty) = params.swap_remove(idx);
169 let name = pat?.to_string();
170 Some(ActiveParameter { ty, name })
171 }
172}
173
174#[derive(Debug)]
175pub(crate) enum FnCallNode {
176 CallExpr(ast::CallExpr),
177 MethodCallExpr(ast::MethodCallExpr),
178}
179
180impl FnCallNode {
181 fn with_node(syntax: &SyntaxNode) -> Option<FnCallNode> {
182 syntax.ancestors().find_map(|node| {
183 match_ast! {
184 match node {
185 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
186 ast::MethodCallExpr(it) => {
187 let arg_list = it.arg_list()?;
188 if !arg_list.syntax().text_range().contains_range(syntax.text_range()) {
189 return None;
190 }
191 Some(FnCallNode::MethodCallExpr(it))
192 },
193 _ => None,
194 }
195 }
196 })
197 }
198
199 pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
200 match_ast! {
201 match node {
202 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
203 ast::MethodCallExpr(it) => Some(FnCallNode::MethodCallExpr(it)),
204 _ => None,
205 }
206 }
207 }
208
209 pub(crate) fn name_ref(&self) -> Option<ast::NameRef> {
210 match self {
211 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
212 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
213 _ => return None,
214 }),
215
216 FnCallNode::MethodCallExpr(call_expr) => {
217 call_expr.syntax().children().filter_map(ast::NameRef::cast).next()
218 }
219 }
220 }
221
222 fn arg_list(&self) -> Option<ast::ArgList> {
223 match self {
224 FnCallNode::CallExpr(expr) => expr.arg_list(),
225 FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use expect_test::{expect, Expect};
233 use test_utils::mark;
234
235 use crate::mock_analysis::analysis_and_position;
236
237 fn check(ra_fixture: &str, expect: Expect) {
238 let (analysis, position) = analysis_and_position(ra_fixture);
239 let call_info = analysis.call_info(position).unwrap();
240 let actual = match call_info {
241 Some(call_info) => {
242 let docs = match &call_info.doc {
243 None => "".to_string(),
244 Some(docs) => format!("{}\n------\n", docs.as_str()),
245 };
246 let params = call_info
247 .parameter_labels()
248 .enumerate()
249 .map(|(i, param)| {
250 if Some(i) == call_info.active_parameter {
251 format!("<{}>", param)
252 } else {
253 param.to_string()
254 }
255 })
256 .collect::<Vec<_>>()
257 .join(", ");
258 format!("{}{}\n({})\n", docs, call_info.signature, params)
259 }
260 None => String::new(),
261 };
262 expect.assert_eq(&actual);
263 }
264
265 #[test]
266 fn test_fn_signature_two_args() {
267 check(
268 r#"
269fn foo(x: u32, y: u32) -> u32 {x + y}
270fn bar() { foo(<|>3, ); }
271"#,
272 expect![[r#"
273 fn foo(x: u32, y: u32) -> u32
274 (<x: u32>, y: u32)
275 "#]],
276 );
277 check(
278 r#"
279fn foo(x: u32, y: u32) -> u32 {x + y}
280fn bar() { foo(3<|>, ); }
281"#,
282 expect![[r#"
283 fn foo(x: u32, y: u32) -> u32
284 (<x: u32>, y: u32)
285 "#]],
286 );
287 check(
288 r#"
289fn foo(x: u32, y: u32) -> u32 {x + y}
290fn bar() { foo(3,<|> ); }
291"#,
292 expect![[r#"
293 fn foo(x: u32, y: u32) -> u32
294 (x: u32, <y: u32>)
295 "#]],
296 );
297 check(
298 r#"
299fn foo(x: u32, y: u32) -> u32 {x + y}
300fn bar() { foo(3, <|>); }
301"#,
302 expect![[r#"
303 fn foo(x: u32, y: u32) -> u32
304 (x: u32, <y: u32>)
305 "#]],
306 );
307 }
308
309 #[test]
310 fn test_fn_signature_two_args_empty() {
311 check(
312 r#"
313fn foo(x: u32, y: u32) -> u32 {x + y}
314fn bar() { foo(<|>); }
315"#,
316 expect![[r#"
317 fn foo(x: u32, y: u32) -> u32
318 (<x: u32>, y: u32)
319 "#]],
320 );
321 }
322
323 #[test]
324 fn test_fn_signature_two_args_first_generics() {
325 check(
326 r#"
327fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
328 where T: Copy + Display, U: Debug
329{ x + y }
330
331fn bar() { foo(<|>3, ); }
332"#,
333 expect![[r#"
334 fn foo(x: i32, y: {unknown}) -> u32
335 (<x: i32>, y: {unknown})
336 "#]],
337 );
338 }
339
340 #[test]
341 fn test_fn_signature_no_params() {
342 check(
343 r#"
344fn foo<T>() -> T where T: Copy + Display {}
345fn bar() { foo(<|>); }
346"#,
347 expect![[r#"
348 fn foo() -> {unknown}
349 ()
350 "#]],
351 );
352 }
353
354 #[test]
355 fn test_fn_signature_for_impl() {
356 check(
357 r#"
358struct F;
359impl F { pub fn new() { } }
360fn bar() {
361 let _ : F = F::new(<|>);
362}
363"#,
364 expect![[r#"
365 fn new()
366 ()
367 "#]],
368 );
369 }
370
371 #[test]
372 fn test_fn_signature_for_method_self() {
373 check(
374 r#"
375struct S;
376impl S { pub fn do_it(&self) {} }
377
378fn bar() {
379 let s: S = S;
380 s.do_it(<|>);
381}
382"#,
383 expect![[r#"
384 fn do_it(&self)
385 ()
386 "#]],
387 );
388 }
389
390 #[test]
391 fn test_fn_signature_for_method_with_arg() {
392 check(
393 r#"
394struct S;
395impl S {
396 fn foo(&self, x: i32) {}
397}
398
399fn main() { S.foo(<|>); }
400"#,
401 expect![[r#"
402 fn foo(&self, x: i32)
403 (<x: i32>)
404 "#]],
405 );
406 }
407
408 #[test]
409 fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
410 check(
411 r#"
412struct S;
413impl S {
414 fn foo(&self, x: i32) {}
415}
416
417fn main() { S::foo(<|>); }
418"#,
419 expect![[r#"
420 fn foo(self: &S, x: i32)
421 (<self: &S>, x: i32)
422 "#]],
423 );
424 }
425
426 #[test]
427 fn test_fn_signature_with_docs_simple() {
428 check(
429 r#"
430/// test
431// non-doc-comment
432fn foo(j: u32) -> u32 {
433 j
434}
435
436fn bar() {
437 let _ = foo(<|>);
438}
439"#,
440 expect![[r#"
441 test
442 ------
443 fn foo(j: u32) -> u32
444 (<j: u32>)
445 "#]],
446 );
447 }
448
449 #[test]
450 fn test_fn_signature_with_docs() {
451 check(
452 r#"
453/// Adds one to the number given.
454///
455/// # Examples
456///
457/// ```
458/// let five = 5;
459///
460/// assert_eq!(6, my_crate::add_one(5));
461/// ```
462pub fn add_one(x: i32) -> i32 {
463 x + 1
464}
465
466pub fn do() {
467 add_one(<|>
468}"#,
469 expect![[r##"
470 Adds one to the number given.
471
472 # Examples
473
474 ```
475 let five = 5;
476
477 assert_eq!(6, my_crate::add_one(5));
478 ```
479 ------
480 fn add_one(x: i32) -> i32
481 (<x: i32>)
482 "##]],
483 );
484 }
485
486 #[test]
487 fn test_fn_signature_with_docs_impl() {
488 check(
489 r#"
490struct addr;
491impl addr {
492 /// Adds one to the number given.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// let five = 5;
498 ///
499 /// assert_eq!(6, my_crate::add_one(5));
500 /// ```
501 pub fn add_one(x: i32) -> i32 {
502 x + 1
503 }
504}
505
506pub fn do_it() {
507 addr {};
508 addr::add_one(<|>);
509}
510"#,
511 expect![[r##"
512 Adds one to the number given.
513
514 # Examples
515
516 ```
517 let five = 5;
518
519 assert_eq!(6, my_crate::add_one(5));
520 ```
521 ------
522 fn add_one(x: i32) -> i32
523 (<x: i32>)
524 "##]],
525 );
526 }
527
528 #[test]
529 fn test_fn_signature_with_docs_from_actix() {
530 check(
531 r#"
532struct WriteHandler<E>;
533
534impl<E> WriteHandler<E> {
535 /// Method is called when writer emits error.
536 ///
537 /// If this method returns `ErrorAction::Continue` writer processing
538 /// continues otherwise stream processing stops.
539 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
540 Running::Stop
541 }
542
543 /// Method is called when writer finishes.
544 ///
545 /// By default this method stops actor's `Context`.
546 fn finished(&mut self, ctx: &mut Self::Context) {
547 ctx.stop()
548 }
549}
550
551pub fn foo(mut r: WriteHandler<()>) {
552 r.finished(<|>);
553}
554"#,
555 expect![[r#"
556 Method is called when writer finishes.
557
558 By default this method stops actor's `Context`.
559 ------
560 fn finished(&mut self, ctx: &mut {unknown})
561 (<ctx: &mut {unknown}>)
562 "#]],
563 );
564 }
565
566 #[test]
567 fn call_info_bad_offset() {
568 mark::check!(call_info_bad_offset);
569 check(
570 r#"
571fn foo(x: u32, y: u32) -> u32 {x + y}
572fn bar() { foo <|> (3, ); }
573"#,
574 expect![[""]],
575 );
576 }
577
578 #[test]
579 fn test_nested_method_in_lambda() {
580 check(
581 r#"
582struct Foo;
583impl Foo { fn bar(&self, _: u32) { } }
584
585fn bar(_: u32) { }
586
587fn main() {
588 let foo = Foo;
589 std::thread::spawn(move || foo.bar(<|>));
590}
591"#,
592 expect![[r#"
593 fn bar(&self, _: u32)
594 (<_: u32>)
595 "#]],
596 );
597 }
598
599 #[test]
600 fn works_for_tuple_structs() {
601 check(
602 r#"
603/// A cool tuple struct
604struct S(u32, i32);
605fn main() {
606 let s = S(0, <|>);
607}
608"#,
609 expect![[r#"
610 A cool tuple struct
611 ------
612 struct S(u32, i32)
613 (u32, <i32>)
614 "#]],
615 );
616 }
617
618 #[test]
619 fn generic_struct() {
620 check(
621 r#"
622struct S<T>(T);
623fn main() {
624 let s = S(<|>);
625}
626"#,
627 expect![[r#"
628 struct S({unknown})
629 (<{unknown}>)
630 "#]],
631 );
632 }
633
634 #[test]
635 fn works_for_enum_variants() {
636 check(
637 r#"
638enum E {
639 /// A Variant
640 A(i32),
641 /// Another
642 B,
643 /// And C
644 C { a: i32, b: i32 }
645}
646
647fn main() {
648 let a = E::A(<|>);
649}
650"#,
651 expect![[r#"
652 A Variant
653 ------
654 enum E::A(i32)
655 (<i32>)
656 "#]],
657 );
658 }
659
660 #[test]
661 fn cant_call_struct_record() {
662 check(
663 r#"
664struct S { x: u32, y: i32 }
665fn main() {
666 let s = S(<|>);
667}
668"#,
669 expect![[""]],
670 );
671 }
672
673 #[test]
674 fn cant_call_enum_record() {
675 check(
676 r#"
677enum E {
678 /// A Variant
679 A(i32),
680 /// Another
681 B,
682 /// And C
683 C { a: i32, b: i32 }
684}
685
686fn main() {
687 let a = E::C(<|>);
688}
689"#,
690 expect![[""]],
691 );
692 }
693
694 #[test]
695 fn fn_signature_for_call_in_macro() {
696 check(
697 r#"
698macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
699fn foo() { }
700id! {
701 fn bar() { foo(<|>); }
702}
703"#,
704 expect![[r#"
705 fn foo()
706 ()
707 "#]],
708 );
709 }
710
711 #[test]
712 fn call_info_for_lambdas() {
713 check(
714 r#"
715struct S;
716fn foo(s: S) -> i32 { 92 }
717fn main() {
718 (|s| foo(s))(<|>)
719}
720 "#,
721 expect![[r#"
722 (S) -> i32
723 (<S>)
724 "#]],
725 )
726 }
727
728 #[test]
729 fn call_info_for_fn_ptr() {
730 check(
731 r#"
732fn main(f: fn(i32, f64) -> char) {
733 f(0, <|>)
734}
735 "#,
736 expect![[r#"
737 (i32, f64) -> char
738 (i32, <f64>)
739 "#]],
740 )
741 }
742}
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
new file mode 100644
index 000000000..25e580d80
--- /dev/null
+++ b/crates/ide/src/completion.rs
@@ -0,0 +1,207 @@
1mod completion_config;
2mod completion_item;
3mod completion_context;
4mod presentation;
5mod patterns;
6mod generated_features;
7#[cfg(test)]
8mod test_utils;
9
10mod complete_attribute;
11mod complete_dot;
12mod complete_record;
13mod complete_pattern;
14mod complete_fn_param;
15mod complete_keyword;
16mod complete_snippet;
17mod complete_qualified_path;
18mod complete_unqualified_path;
19mod complete_postfix;
20mod complete_macro_in_item_position;
21mod complete_trait_impl;
22
23use ide_db::RootDatabase;
24
25use crate::{
26 completion::{
27 completion_context::CompletionContext,
28 completion_item::{CompletionKind, Completions},
29 },
30 FilePosition,
31};
32
33pub use crate::completion::{
34 completion_config::CompletionConfig,
35 completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
36};
37
38//FIXME: split the following feature into fine-grained features.
39
40// Feature: Magic Completions
41//
42// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
43// completions as well:
44//
45// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
46// is placed at the appropriate position. Even though `if` is easy to type, you
47// still want to complete it, to get ` { }` for free! `return` is inserted with a
48// space or `;` depending on the return type of the function.
49//
50// When completing a function call, `()` are automatically inserted. If a function
51// takes arguments, the cursor is positioned inside the parenthesis.
52//
53// There are postfix completions, which can be triggered by typing something like
54// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
55//
56// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
57// - `expr.match` -> `match expr {}`
58// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
59// - `expr.ref` -> `&expr`
60// - `expr.refm` -> `&mut expr`
61// - `expr.not` -> `!expr`
62// - `expr.dbg` -> `dbg!(expr)`
63//
64// There also snippet completions:
65//
66// .Expressions
67// - `pd` -> `eprintln!(" = {:?}", );`
68// - `ppd` -> `eprintln!(" = {:#?}", );`
69//
70// .Items
71// - `tfn` -> `#[test] fn feature(){}`
72// - `tmod` ->
73// ```rust
74// #[cfg(test)]
75// mod tests {
76// use super::*;
77//
78// #[test]
79// fn test_name() {}
80// }
81// ```
82
83/// Main entry point for completion. We run completion as a two-phase process.
84///
85/// First, we look at the position and collect a so-called `CompletionContext.
86/// This is a somewhat messy process, because, during completion, syntax tree is
87/// incomplete and can look really weird.
88///
89/// Once the context is collected, we run a series of completion routines which
90/// look at the context and produce completion items. One subtlety about this
91/// phase is that completion engine should not filter by the substring which is
92/// already present, it should give all possible variants for the identifier at
93/// the caret. In other words, for
94///
95/// ```no-run
96/// fn f() {
97/// let foo = 92;
98/// let _ = bar<|>
99/// }
100/// ```
101///
102/// `foo` *should* be present among the completion variants. Filtering by
103/// identifier prefix/fuzzy match should be done higher in the stack, together
104/// with ordering of completions (currently this is done by the client).
105pub(crate) fn completions(
106 db: &RootDatabase,
107 config: &CompletionConfig,
108 position: FilePosition,
109) -> Option<Completions> {
110 let ctx = CompletionContext::new(db, position, config)?;
111
112 let mut acc = Completions::default();
113 complete_attribute::complete_attribute(&mut acc, &ctx);
114 complete_fn_param::complete_fn_param(&mut acc, &ctx);
115 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
116 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
117 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
118 complete_snippet::complete_item_snippet(&mut acc, &ctx);
119 complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
120 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
121 complete_dot::complete_dot(&mut acc, &ctx);
122 complete_record::complete_record(&mut acc, &ctx);
123 complete_pattern::complete_pattern(&mut acc, &ctx);
124 complete_postfix::complete_postfix(&mut acc, &ctx);
125 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
126 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
127
128 Some(acc)
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::completion::completion_config::CompletionConfig;
134 use crate::mock_analysis::analysis_and_position;
135
136 struct DetailAndDocumentation<'a> {
137 detail: &'a str,
138 documentation: &'a str,
139 }
140
141 fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
142 let (analysis, position) = analysis_and_position(ra_fixture);
143 let config = CompletionConfig::default();
144 let completions = analysis.completions(&config, position).unwrap().unwrap();
145 for item in completions {
146 if item.detail() == Some(expected.detail) {
147 let opt = item.documentation();
148 let doc = opt.as_ref().map(|it| it.as_str());
149 assert_eq!(doc, Some(expected.documentation));
150 return;
151 }
152 }
153 panic!("completion detail not found: {}", expected.detail)
154 }
155
156 #[test]
157 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
158 check_detail_and_documentation(
159 r#"
160 //- /lib.rs
161 macro_rules! bar {
162 () => {
163 struct Bar;
164 impl Bar {
165 #[doc = "Do the foo"]
166 fn foo(&self) {}
167 }
168 }
169 }
170
171 bar!();
172
173 fn foo() {
174 let bar = Bar;
175 bar.fo<|>;
176 }
177 "#,
178 DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
179 );
180 }
181
182 #[test]
183 fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
184 check_detail_and_documentation(
185 r#"
186 //- /lib.rs
187 macro_rules! bar {
188 () => {
189 struct Bar;
190 impl Bar {
191 /// Do the foo
192 fn foo(&self) {}
193 }
194 }
195 }
196
197 bar!();
198
199 fn foo() {
200 let bar = Bar;
201 bar.fo<|>;
202 }
203 "#,
204 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
205 );
206 }
207}
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs
new file mode 100644
index 000000000..0abfaebcb
--- /dev/null
+++ b/crates/ide/src/completion/complete_attribute.rs
@@ -0,0 +1,653 @@
1//! Completion for attributes
2//!
3//! This module uses a bit of static metadata to provide completions
4//! for built-in attributes.
5
6use rustc_hash::FxHashSet;
7use syntax::{ast, AstNode, SyntaxKind};
8
9use crate::completion::{
10 completion_context::CompletionContext,
11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
12 generated_features::FEATURES,
13};
14
15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16 let attribute = ctx.attribute_under_caret.as_ref()?;
17 match (attribute.path(), attribute.token_tree()) {
18 (Some(path), Some(token_tree)) if path.to_string() == "derive" => {
19 complete_derive(acc, ctx, token_tree)
20 }
21 (Some(path), Some(token_tree)) if path.to_string() == "feature" => {
22 complete_lint(acc, ctx, token_tree, FEATURES)
23 }
24 (Some(path), Some(token_tree))
25 if ["allow", "warn", "deny", "forbid"]
26 .iter()
27 .any(|lint_level| lint_level == &path.to_string()) =>
28 {
29 complete_lint(acc, ctx, token_tree, DEFAULT_LINT_COMPLETIONS)
30 }
31 (_, Some(_token_tree)) => {}
32 _ => complete_attribute_start(acc, ctx, attribute),
33 }
34 Some(())
35}
36
37fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
38 for attr_completion in ATTRIBUTES {
39 let mut item = CompletionItem::new(
40 CompletionKind::Attribute,
41 ctx.source_range(),
42 attr_completion.label,
43 )
44 .kind(CompletionItemKind::Attribute);
45
46 if let Some(lookup) = attr_completion.lookup {
47 item = item.lookup_by(lookup);
48 }
49
50 match (attr_completion.snippet, ctx.config.snippet_cap) {
51 (Some(snippet), Some(cap)) => {
52 item = item.insert_snippet(cap, snippet);
53 }
54 _ => {}
55 }
56
57 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
58 acc.add(item);
59 }
60 }
61}
62
63struct AttrCompletion {
64 label: &'static str,
65 lookup: Option<&'static str>,
66 snippet: Option<&'static str>,
67 prefer_inner: bool,
68}
69
70impl AttrCompletion {
71 const fn prefer_inner(self) -> AttrCompletion {
72 AttrCompletion { prefer_inner: true, ..self }
73 }
74}
75
76const fn attr(
77 label: &'static str,
78 lookup: Option<&'static str>,
79 snippet: Option<&'static str>,
80) -> AttrCompletion {
81 AttrCompletion { label, lookup, snippet, prefer_inner: false }
82}
83
84const ATTRIBUTES: &[AttrCompletion] = &[
85 attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
86 attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
87 attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
88 attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
89 attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)),
90 attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
91 attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
92 attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
93 attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
94 // FIXME: resolve through macro resolution?
95 attr("global_allocator", None, None).prefer_inner(),
96 attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
97 attr("inline(…)", Some("inline"), Some("inline(${0:lint})")),
98 attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
99 attr("link", None, None),
100 attr("macro_export", None, None),
101 attr("macro_use", None, None),
102 attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)),
103 attr("no_mangle", None, None),
104 attr("no_std", None, None).prefer_inner(),
105 attr("non_exhaustive", None, None),
106 attr("panic_handler", None, None).prefer_inner(),
107 attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")),
108 attr("proc_macro", None, None),
109 attr("proc_macro_attribute", None, None),
110 attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
111 attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
112 .prefer_inner(),
113 attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
114 attr(
115 "should_panic(…)",
116 Some("should_panic"),
117 Some(r#"should_panic(expected = "${0:reason}")"#),
118 ),
119 attr(
120 r#"target_feature = "…""#,
121 Some("target_feature"),
122 Some("target_feature = \"${0:feature}\""),
123 ),
124 attr("test", None, None),
125 attr("used", None, None),
126 attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
127 attr(
128 r#"windows_subsystem = "…""#,
129 Some("windows_subsystem"),
130 Some(r#"windows_subsystem = "${0:subsystem}""#),
131 )
132 .prefer_inner(),
133];
134
135fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
136 if let Ok(existing_derives) = parse_comma_sep_input(derive_input) {
137 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
138 .into_iter()
139 .filter(|completion| !existing_derives.contains(completion.label))
140 {
141 let mut label = derive_completion.label.to_owned();
142 for dependency in derive_completion
143 .dependencies
144 .into_iter()
145 .filter(|&&dependency| !existing_derives.contains(dependency))
146 {
147 label.push_str(", ");
148 label.push_str(dependency);
149 }
150 acc.add(
151 CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
152 .kind(CompletionItemKind::Attribute),
153 );
154 }
155
156 for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
157 acc.add(
158 CompletionItem::new(
159 CompletionKind::Attribute,
160 ctx.source_range(),
161 custom_derive_name,
162 )
163 .kind(CompletionItemKind::Attribute),
164 );
165 }
166 }
167}
168
169fn complete_lint(
170 acc: &mut Completions,
171 ctx: &CompletionContext,
172 derive_input: ast::TokenTree,
173 lints_completions: &[LintCompletion],
174) {
175 if let Ok(existing_lints) = parse_comma_sep_input(derive_input) {
176 for lint_completion in lints_completions
177 .into_iter()
178 .filter(|completion| !existing_lints.contains(completion.label))
179 {
180 acc.add(
181 CompletionItem::new(
182 CompletionKind::Attribute,
183 ctx.source_range(),
184 lint_completion.label,
185 )
186 .kind(CompletionItemKind::Attribute)
187 .detail(lint_completion.description),
188 );
189 }
190 }
191}
192
193fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
194 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
195 (Some(left_paren), Some(right_paren))
196 if left_paren.kind() == SyntaxKind::L_PAREN
197 && right_paren.kind() == SyntaxKind::R_PAREN =>
198 {
199 let mut input_derives = FxHashSet::default();
200 let mut current_derive = String::new();
201 for token in derive_input
202 .syntax()
203 .children_with_tokens()
204 .filter_map(|token| token.into_token())
205 .skip_while(|token| token != &left_paren)
206 .skip(1)
207 .take_while(|token| token != &right_paren)
208 {
209 if SyntaxKind::COMMA == token.kind() {
210 if !current_derive.is_empty() {
211 input_derives.insert(current_derive);
212 current_derive = String::new();
213 }
214 } else {
215 current_derive.push_str(token.to_string().trim());
216 }
217 }
218
219 if !current_derive.is_empty() {
220 input_derives.insert(current_derive);
221 }
222 Ok(input_derives)
223 }
224 _ => Err(()),
225 }
226}
227
228fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
229 let mut result = FxHashSet::default();
230 ctx.scope.process_all_names(&mut |name, scope_def| {
231 if let hir::ScopeDef::MacroDef(mac) = scope_def {
232 if mac.is_derive_macro() {
233 result.insert(name.to_string());
234 }
235 }
236 });
237 result
238}
239
240struct DeriveCompletion {
241 label: &'static str,
242 dependencies: &'static [&'static str],
243}
244
245/// Standard Rust derives and the information about their dependencies
246/// (the dependencies are needed so that the main derive don't break the compilation when added)
247#[rustfmt::skip]
248const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
249 DeriveCompletion { label: "Clone", dependencies: &[] },
250 DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
251 DeriveCompletion { label: "Debug", dependencies: &[] },
252 DeriveCompletion { label: "Default", dependencies: &[] },
253 DeriveCompletion { label: "Hash", dependencies: &[] },
254 DeriveCompletion { label: "PartialEq", dependencies: &[] },
255 DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
256 DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
257 DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
258];
259
260pub(super) struct LintCompletion {
261 pub(super) label: &'static str,
262 pub(super) description: &'static str,
263}
264
265#[rustfmt::skip]
266const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[
267 LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# },
268 LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# },
269 LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# },
270 LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# },
271 LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# },
272 LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# },
273 LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# },
274 LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# },
275 LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# },
276 LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# },
277 LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# },
278 LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# },
279 LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# },
280 LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# },
281 LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# },
282 LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# },
283 LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# },
284 LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# },
285 LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# },
286 LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# },
287 LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# },
288 LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# },
289 LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# },
290 LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# },
291 LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# },
292 LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# },
293 LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# },
294 LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# },
295 LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# },
296 LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# },
297 LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# },
298 LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# },
299 LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# },
300 LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# },
301 LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# },
302 LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# },
303 LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# },
304 LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# },
305 LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# },
306 LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# },
307 LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# },
308 LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# },
309 LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# },
310 LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# },
311 LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# },
312 LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# },
313 LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# },
314 LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# },
315 LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# },
316 LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# },
317 LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# },
318 LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# },
319 LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# },
320 LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# },
321 LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# },
322 LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# },
323 LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# },
324 LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# },
325 LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# },
326 LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# },
327 LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# },
328 LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# },
329 LintCompletion { label: "path_statements", description: r#"path statements with no effect"# },
330 LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# },
331 LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# },
332 LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# },
333 LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# },
334 LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# },
335 LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# },
336 LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# },
337 LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# },
338 LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# },
339 LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# },
340 LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# },
341 LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# },
342 LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# },
343 LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# },
344 LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# },
345 LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# },
346 LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# },
347 LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# },
348 LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# },
349 LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# },
350 LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# },
351 LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# },
352 LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# },
353 LintCompletion { label: "unused_imports", description: r#"imports that are never used"# },
354 LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# },
355 LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# },
356 LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# },
357 LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# },
358 LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# },
359 LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# },
360 LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# },
361 LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# },
362 LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# },
363 LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# },
364 LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# },
365 LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# },
366 LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# },
367 LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# },
368 LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# },
369 LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# },
370 LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# },
371 LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# },
372 LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# },
373 LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# },
374 LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# },
375 LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# },
376 LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# },
377 LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# },
378 LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# },
379 LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# },
380 LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# },
381 LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# },
382];
383
384#[cfg(test)]
385mod tests {
386 use expect_test::{expect, Expect};
387
388 use crate::completion::{test_utils::completion_list, CompletionKind};
389
390 fn check(ra_fixture: &str, expect: Expect) {
391 let actual = completion_list(ra_fixture, CompletionKind::Attribute);
392 expect.assert_eq(&actual);
393 }
394
395 #[test]
396 fn empty_derive_completion() {
397 check(
398 r#"
399#[derive(<|>)]
400struct Test {}
401 "#,
402 expect![[r#"
403 at Clone
404 at Copy, Clone
405 at Debug
406 at Default
407 at Eq, PartialEq
408 at Hash
409 at Ord, PartialOrd, Eq, PartialEq
410 at PartialEq
411 at PartialOrd, PartialEq
412 "#]],
413 );
414 }
415
416 #[test]
417 fn empty_lint_completion() {
418 check(
419 r#"#[allow(<|>)]"#,
420 expect![[r#"
421 at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name
422 at ambiguous_associated_items ambiguous associated items
423 at anonymous_parameters detects anonymous parameters
424 at arithmetic_overflow arithmetic operation overflows
425 at array_into_iter detects calling `into_iter` on arrays
426 at asm_sub_register using only a subset of a register for inline asm inputs
427 at bare_trait_objects suggest using `dyn Trait` for trait objects
428 at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants
429 at box_pointers use of owned (Box type) heap memory
430 at cenum_impl_drop_cast a C-like enum implementing Drop is cast
431 at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types
432 at coherence_leak_check distinct impls distinguished only by the leak-check code
433 at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice
434 at confusable_idents detects visually confusable pairs between identifiers
435 at const_err constant evaluation detected erroneous expression
436 at dead_code detect unused, unexported items
437 at deprecated detects use of deprecated items
438 at deprecated_in_future detects use of items that will be deprecated in a future version
439 at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated
440 at ellipsis_inclusive_range_patterns `...` range patterns are deprecated
441 at explicit_outlives_requirements outlives requirements can be inferred
442 at exported_private_dependencies public interface leaks type from a private dependency
443 at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice
444 at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns
445 at improper_ctypes proper use of libc types in foreign modules
446 at improper_ctypes_definitions proper use of libc types in foreign item definitions
447 at incomplete_features incomplete features that may function improperly in some or all cases
448 at incomplete_include trailing content in included file
449 at indirect_structural_match pattern with const indirectly referencing non-structural-match type
450 at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`
451 at intra_doc_link_resolution_failure failures in resolving intra-doc link targets
452 at invalid_codeblock_attributes codeblock attribute looks a lot like a known one
453 at invalid_type_param_default type parameter default erroneously allowed in invalid location
454 at invalid_value an invalid value is being created (such as a NULL reference)
455 at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements
456 at keyword_idents detects edition keywords being used as an identifier
457 at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters
458 at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
459 at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system
460 at meta_variable_misuse possible meta-variable misuse at macro definition
461 at missing_copy_implementations detects potentially-forgotten implementations of `Copy`
462 at missing_crate_level_docs detects crates with no crate-level documentation
463 at missing_debug_implementations detects missing implementations of Debug
464 at missing_doc_code_examples detects publicly-exported items without code samples in their documentation
465 at missing_docs detects missing documentation for public members
466 at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns
467 at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used
468 at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows
469 at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior
470 at no_mangle_const_items const items will not have their symbols exported
471 at no_mangle_generic_items generic items must be mangled
472 at non_ascii_idents detects non-ASCII identifiers
473 at non_camel_case_types types, variants, traits and type parameters should have camel case names
474 at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern
475 at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names
476 at non_upper_case_globals static constants should have uppercase identifiers
477 at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order
478 at overflowing_literals literal out of range for its type
479 at overlapping_patterns detects overlapping patterns
480 at path_statements path statements with no effect
481 at patterns_in_fns_without_body patterns in functions without body were erroneously allowed
482 at private_doc_tests detects code samples in docs of private items not documented by rustdoc
483 at private_in_public detect private items in public interfaces not caught by the old implementation
484 at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules
485 at pub_use_of_private_extern_crate detect public re-exports of private extern crates
486 at redundant_semicolons detects unnecessary trailing semicolons
487 at renamed_and_removed_lints lints that have been renamed or removed
488 at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed
489 at single_use_lifetimes detects lifetime parameters that are only used once
490 at soft_unstable a feature gate that doesn't break dependent crates
491 at stable_features stable features found in `#[feature]` directive
492 at trivial_bounds these bounds don't depend on an type parameters
493 at trivial_casts detects trivial casts which could be removed
494 at trivial_numeric_casts detects trivial casts of numeric types which could be removed
495 at type_alias_bounds bounds in type aliases are not enforced
496 at tyvar_behind_raw_pointer raw pointer to an inference variable
497 at unaligned_references detects unaligned references to fields of packed structs
498 at uncommon_codepoints detects uncommon Unicode codepoints in identifiers
499 at unconditional_panic operation will cause a panic at runtime
500 at unconditional_recursion functions that cannot return without calling themselves
501 at unknown_crate_types unknown crate type found in `#[crate_type]` directive
502 at unknown_lints unrecognized lint attribute
503 at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]`
504 at unreachable_code detects unreachable code paths
505 at unreachable_patterns detects unreachable patterns
506 at unreachable_pub `pub` items not reachable from crate root
507 at unsafe_code usage of `unsafe` code
508 at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated
509 at unstable_features enabling unstable features (deprecated. do not use)
510 at unstable_name_collisions detects name collision with an existing but unstable method
511 at unused_allocation detects unnecessary allocations that can be eliminated
512 at unused_assignments detect assignments that will never be read
513 at unused_attributes detects attributes that were not used by the compiler
514 at unused_braces unnecessary braces around an expression
515 at unused_comparisons comparisons made useless by limits of the types involved
516 at unused_crate_dependencies crate dependencies that are never used
517 at unused_doc_comments detects doc comments that aren't used by rustdoc
518 at unused_extern_crates extern crates that are never used
519 at unused_features unused features found in crate-level `#[feature]` directives
520 at unused_import_braces unnecessary braces around an imported item
521 at unused_imports imports that are never used
522 at unused_labels detects labels that are never used
523 at unused_lifetimes detects lifetime parameters that are never used
524 at unused_macros detects macros that were not used
525 at unused_must_use unused result of a type flagged as `#[must_use]`
526 at unused_mut detect mut variables which don't need to be mutable
527 at unused_parens `if`, `match`, `while` and `return` do not need parentheses
528 at unused_qualifications detects unnecessarily qualified names
529 at unused_results unused result of an expression in a statement
530 at unused_unsafe unnecessary use of an `unsafe` block
531 at unused_variables detect variables which are not used in any way
532 at variant_size_differences detects enums with widely varying variant sizes
533 at warnings mass-change the level for lints which produce warnings
534 at where_clauses_object_safety checks the object safety of where clauses
535 at while_true suggest using `loop { }` instead of `while true { }`
536 "#]],
537 )
538 }
539
540 #[test]
541 fn no_completion_for_incorrect_derive() {
542 check(
543 r#"
544#[derive{<|>)]
545struct Test {}
546"#,
547 expect![[r#""#]],
548 )
549 }
550
551 #[test]
552 fn derive_with_input_completion() {
553 check(
554 r#"
555#[derive(serde::Serialize, PartialEq, <|>)]
556struct Test {}
557"#,
558 expect![[r#"
559 at Clone
560 at Copy, Clone
561 at Debug
562 at Default
563 at Eq
564 at Hash
565 at Ord, PartialOrd, Eq
566 at PartialOrd
567 "#]],
568 )
569 }
570
571 #[test]
572 fn test_attribute_completion() {
573 check(
574 r#"#[<|>]"#,
575 expect![[r#"
576 at allow(…)
577 at cfg(…)
578 at cfg_attr(…)
579 at deny(…)
580 at deprecated = "…"
581 at derive(…)
582 at doc = "…"
583 at forbid(…)
584 at ignore = "…"
585 at inline(…)
586 at link
587 at link_name = "…"
588 at macro_export
589 at macro_use
590 at must_use = "…"
591 at no_mangle
592 at non_exhaustive
593 at path = "…"
594 at proc_macro
595 at proc_macro_attribute
596 at proc_macro_derive(…)
597 at repr(…)
598 at should_panic(…)
599 at target_feature = "…"
600 at test
601 at used
602 at warn(…)
603 "#]],
604 )
605 }
606
607 #[test]
608 fn test_attribute_completion_inside_nested_attr() {
609 check(r#"#[cfg(<|>)]"#, expect![[]])
610 }
611
612 #[test]
613 fn test_inner_attribute_completion() {
614 check(
615 r"#![<|>]",
616 expect![[r#"
617 at allow(…)
618 at cfg(…)
619 at cfg_attr(…)
620 at deny(…)
621 at deprecated = "…"
622 at derive(…)
623 at doc = "…"
624 at feature(…)
625 at forbid(…)
626 at global_allocator
627 at ignore = "…"
628 at inline(…)
629 at link
630 at link_name = "…"
631 at macro_export
632 at macro_use
633 at must_use = "…"
634 at no_mangle
635 at no_std
636 at non_exhaustive
637 at panic_handler
638 at path = "…"
639 at proc_macro
640 at proc_macro_attribute
641 at proc_macro_derive(…)
642 at recursion_limit = …
643 at repr(…)
644 at should_panic(…)
645 at target_feature = "…"
646 at test
647 at used
648 at warn(…)
649 at windows_subsystem = "…"
650 "#]],
651 );
652 }
653}
diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/ide/src/completion/complete_dot.rs
new file mode 100644
index 000000000..0b9f1798a
--- /dev/null
+++ b/crates/ide/src/completion/complete_dot.rs
@@ -0,0 +1,416 @@
1//! Completes references after dot (fields and method calls).
2
3use hir::{HasVisibility, Type};
4use rustc_hash::FxHashSet;
5use test_utils::mark;
6
7use crate::completion::{completion_context::CompletionContext, completion_item::Completions};
8
9/// Complete dot accesses, i.e. fields or methods.
10pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
11 let dot_receiver = match &ctx.dot_receiver {
12 Some(expr) => expr,
13 _ => return,
14 };
15
16 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
17 Some(ty) => ty,
18 _ => return,
19 };
20
21 if ctx.is_call {
22 mark::hit!(test_no_struct_field_completion_for_method_call);
23 } else {
24 complete_fields(acc, ctx, &receiver_ty);
25 }
26 complete_methods(acc, ctx, &receiver_ty);
27}
28
29fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
30 for receiver in receiver.autoderef(ctx.db) {
31 for (field, ty) in receiver.fields(ctx.db) {
32 if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
33 // Skip private field. FIXME: If the definition location of the
34 // field is editable, we should show the completion
35 continue;
36 }
37 acc.add_field(ctx, field, &ty);
38 }
39 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
40 // FIXME: Handle visibility
41 acc.add_tuple_field(ctx, i, &ty);
42 }
43 }
44}
45
46fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
47 if let Some(krate) = ctx.krate {
48 let mut seen_methods = FxHashSet::default();
49 let traits_in_scope = ctx.scope.traits_in_scope();
50 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
51 if func.self_param(ctx.db).is_some()
52 && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m))
53 && seen_methods.insert(func.name(ctx.db))
54 {
55 acc.add_function(ctx, func, None);
56 }
57 None::<()>
58 });
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use expect_test::{expect, Expect};
65 use test_utils::mark;
66
67 use crate::completion::{test_utils::completion_list, CompletionKind};
68
69 fn check(ra_fixture: &str, expect: Expect) {
70 let actual = completion_list(ra_fixture, CompletionKind::Reference);
71 expect.assert_eq(&actual);
72 }
73
74 #[test]
75 fn test_struct_field_and_method_completion() {
76 check(
77 r#"
78struct S { foo: u32 }
79impl S {
80 fn bar(&self) {}
81}
82fn foo(s: S) { s.<|> }
83"#,
84 expect![[r#"
85 me bar() fn bar(&self)
86 fd foo u32
87 "#]],
88 );
89 }
90
91 #[test]
92 fn test_struct_field_completion_self() {
93 check(
94 r#"
95struct S { the_field: (u32,) }
96impl S {
97 fn foo(self) { self.<|> }
98}
99"#,
100 expect![[r#"
101 me foo() fn foo(self)
102 fd the_field (u32,)
103 "#]],
104 )
105 }
106
107 #[test]
108 fn test_struct_field_completion_autoderef() {
109 check(
110 r#"
111struct A { the_field: (u32, i32) }
112impl A {
113 fn foo(&self) { self.<|> }
114}
115"#,
116 expect![[r#"
117 me foo() fn foo(&self)
118 fd the_field (u32, i32)
119 "#]],
120 )
121 }
122
123 #[test]
124 fn test_no_struct_field_completion_for_method_call() {
125 mark::check!(test_no_struct_field_completion_for_method_call);
126 check(
127 r#"
128struct A { the_field: u32 }
129fn foo(a: A) { a.<|>() }
130"#,
131 expect![[""]],
132 );
133 }
134
135 #[test]
136 fn test_visibility_filtering() {
137 check(
138 r#"
139mod inner {
140 pub struct A {
141 private_field: u32,
142 pub pub_field: u32,
143 pub(crate) crate_field: u32,
144 pub(super) super_field: u32,
145 }
146}
147fn foo(a: inner::A) { a.<|> }
148"#,
149 expect![[r#"
150 fd crate_field u32
151 fd pub_field u32
152 fd super_field u32
153 "#]],
154 );
155
156 check(
157 r#"
158struct A {}
159mod m {
160 impl super::A {
161 fn private_method(&self) {}
162 pub(super) fn the_method(&self) {}
163 }
164}
165fn foo(a: A) { a.<|> }
166"#,
167 expect![[r#"
168 me the_method() pub(super) fn the_method(&self)
169 "#]],
170 );
171 }
172
173 #[test]
174 fn test_union_field_completion() {
175 check(
176 r#"
177union U { field: u8, other: u16 }
178fn foo(u: U) { u.<|> }
179"#,
180 expect![[r#"
181 fd field u8
182 fd other u16
183 "#]],
184 );
185 }
186
187 #[test]
188 fn test_method_completion_only_fitting_impls() {
189 check(
190 r#"
191struct A<T> {}
192impl A<u32> {
193 fn the_method(&self) {}
194}
195impl A<i32> {
196 fn the_other_method(&self) {}
197}
198fn foo(a: A<u32>) { a.<|> }
199"#,
200 expect![[r#"
201 me the_method() fn the_method(&self)
202 "#]],
203 )
204 }
205
206 #[test]
207 fn test_trait_method_completion() {
208 check(
209 r#"
210struct A {}
211trait Trait { fn the_method(&self); }
212impl Trait for A {}
213fn foo(a: A) { a.<|> }
214"#,
215 expect![[r#"
216 me the_method() fn the_method(&self)
217 "#]],
218 );
219 }
220
221 #[test]
222 fn test_trait_method_completion_deduplicated() {
223 check(
224 r"
225struct A {}
226trait Trait { fn the_method(&self); }
227impl<T> Trait for T {}
228fn foo(a: &A) { a.<|> }
229",
230 expect![[r#"
231 me the_method() fn the_method(&self)
232 "#]],
233 );
234 }
235
236 #[test]
237 fn completes_trait_method_from_other_module() {
238 check(
239 r"
240struct A {}
241mod m {
242 pub trait Trait { fn the_method(&self); }
243}
244use m::Trait;
245impl Trait for A {}
246fn foo(a: A) { a.<|> }
247",
248 expect![[r#"
249 me the_method() fn the_method(&self)
250 "#]],
251 );
252 }
253
254 #[test]
255 fn test_no_non_self_method() {
256 check(
257 r#"
258struct A {}
259impl A {
260 fn the_method() {}
261}
262fn foo(a: A) {
263 a.<|>
264}
265"#,
266 expect![[""]],
267 );
268 }
269
270 #[test]
271 fn test_tuple_field_completion() {
272 check(
273 r#"
274fn foo() {
275 let b = (0, 3.14);
276 b.<|>
277}
278"#,
279 expect![[r#"
280 fd 0 i32
281 fd 1 f64
282 "#]],
283 )
284 }
285
286 #[test]
287 fn test_tuple_field_inference() {
288 check(
289 r#"
290pub struct S;
291impl S { pub fn blah(&self) {} }
292
293struct T(S);
294
295impl T {
296 fn foo(&self) {
297 // FIXME: This doesn't work without the trailing `a` as `0.` is a float
298 self.0.a<|>
299 }
300}
301"#,
302 expect![[r#"
303 me blah() pub fn blah(&self)
304 "#]],
305 );
306 }
307
308 #[test]
309 fn test_completion_works_in_consts() {
310 check(
311 r#"
312struct A { the_field: u32 }
313const X: u32 = {
314 A { the_field: 92 }.<|>
315};
316"#,
317 expect![[r#"
318 fd the_field u32
319 "#]],
320 );
321 }
322
323 #[test]
324 fn works_in_simple_macro_1() {
325 check(
326 r#"
327macro_rules! m { ($e:expr) => { $e } }
328struct A { the_field: u32 }
329fn foo(a: A) {
330 m!(a.x<|>)
331}
332"#,
333 expect![[r#"
334 fd the_field u32
335 "#]],
336 );
337 }
338
339 #[test]
340 fn works_in_simple_macro_2() {
341 // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
342 check(
343 r#"
344macro_rules! m { ($e:expr) => { $e } }
345struct A { the_field: u32 }
346fn foo(a: A) {
347 m!(a.<|>)
348}
349"#,
350 expect![[r#"
351 fd the_field u32
352 "#]],
353 );
354 }
355
356 #[test]
357 fn works_in_simple_macro_recursive_1() {
358 check(
359 r#"
360macro_rules! m { ($e:expr) => { $e } }
361struct A { the_field: u32 }
362fn foo(a: A) {
363 m!(m!(m!(a.x<|>)))
364}
365"#,
366 expect![[r#"
367 fd the_field u32
368 "#]],
369 );
370 }
371
372 #[test]
373 fn macro_expansion_resilient() {
374 check(
375 r#"
376macro_rules! dbg {
377 () => {};
378 ($val:expr) => {
379 match $val { tmp => { tmp } }
380 };
381 // Trailing comma with single argument is ignored
382 ($val:expr,) => { $crate::dbg!($val) };
383 ($($val:expr),+ $(,)?) => {
384 ($($crate::dbg!($val)),+,)
385 };
386}
387struct A { the_field: u32 }
388fn foo(a: A) {
389 dbg!(a.<|>)
390}
391"#,
392 expect![[r#"
393 fd the_field u32
394 "#]],
395 );
396 }
397
398 #[test]
399 fn test_method_completion_issue_3547() {
400 check(
401 r#"
402struct HashSet<T> {}
403impl<T> HashSet<T> {
404 pub fn the_method(&self) {}
405}
406fn foo() {
407 let s: HashSet<_>;
408 s.<|>
409}
410"#,
411 expect![[r#"
412 me the_method() pub fn the_method(&self)
413 "#]],
414 );
415 }
416}
diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/ide/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..9efe25461
--- /dev/null
+++ b/crates/ide/src/completion/complete_fn_param.rs
@@ -0,0 +1,135 @@
1//! See `complete_fn_param`.
2
3use rustc_hash::FxHashMap;
4use syntax::{
5 ast::{self, ModuleItemOwner},
6 match_ast, AstNode,
7};
8
9use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
10
11/// Complete repeated parameters, both name and type. For example, if all
12/// functions in a file have a `spam: &mut Spam` parameter, a completion with
13/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
14/// suggested.
15pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
16 if !ctx.is_param {
17 return;
18 }
19
20 let mut params = FxHashMap::default();
21
22 let me = ctx.token.ancestors().find_map(ast::Fn::cast);
23 let mut process_fn = |func: ast::Fn| {
24 if Some(&func) == me.as_ref() {
25 return;
26 }
27 func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
28 let text = param.syntax().text().to_string();
29 params.entry(text).or_insert(param);
30 })
31 };
32
33 for node in ctx.token.parent().ancestors() {
34 match_ast! {
35 match node {
36 ast::SourceFile(it) => it.items().filter_map(|item| match item {
37 ast::Item::Fn(it) => Some(it),
38 _ => None,
39 }).for_each(&mut process_fn),
40 ast::ItemList(it) => it.items().filter_map(|item| match item {
41 ast::Item::Fn(it) => Some(it),
42 _ => None,
43 }).for_each(&mut process_fn),
44 ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
45 ast::AssocItem::Fn(it) => Some(it),
46 _ => None,
47 }).for_each(&mut process_fn),
48 _ => continue,
49 }
50 };
51 }
52
53 params
54 .into_iter()
55 .filter_map(|(label, param)| {
56 let lookup = param.pat()?.syntax().text().to_string();
57 Some((label, lookup))
58 })
59 .for_each(|(label, lookup)| {
60 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
61 .kind(crate::CompletionItemKind::Binding)
62 .lookup_by(lookup)
63 .add_to(acc)
64 });
65}
66
67#[cfg(test)]
68mod tests {
69 use expect_test::{expect, Expect};
70
71 use crate::completion::{test_utils::completion_list, CompletionKind};
72
73 fn check(ra_fixture: &str, expect: Expect) {
74 let actual = completion_list(ra_fixture, CompletionKind::Magic);
75 expect.assert_eq(&actual);
76 }
77
78 #[test]
79 fn test_param_completion_last_param() {
80 check(
81 r#"
82fn foo(file_id: FileId) {}
83fn bar(file_id: FileId) {}
84fn baz(file<|>) {}
85"#,
86 expect![[r#"
87 bn file_id: FileId
88 "#]],
89 );
90 }
91
92 #[test]
93 fn test_param_completion_nth_param() {
94 check(
95 r#"
96fn foo(file_id: FileId) {}
97fn baz(file<|>, x: i32) {}
98"#,
99 expect![[r#"
100 bn file_id: FileId
101 "#]],
102 );
103 }
104
105 #[test]
106 fn test_param_completion_trait_param() {
107 check(
108 r#"
109pub(crate) trait SourceRoot {
110 pub fn contains(&self, file_id: FileId) -> bool;
111 pub fn module_map(&self) -> &ModuleMap;
112 pub fn lines(&self, file_id: FileId) -> &LineIndex;
113 pub fn syntax(&self, file<|>)
114}
115"#,
116 expect![[r#"
117 bn file_id: FileId
118 "#]],
119 );
120 }
121
122 #[test]
123 fn completes_param_in_inner_function() {
124 check(
125 r#"
126fn outer(text: String) {
127 fn inner(<|>)
128}
129"#,
130 expect![[r#"
131 bn text: String
132 "#]],
133 )
134 }
135}
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..95e4ff1ac
--- /dev/null
+++ b/crates/ide/src/completion/complete_keyword.rs
@@ -0,0 +1,527 @@
1//! FIXME: write short doc here
2
3use syntax::{ast, SyntaxKind};
4use test_utils::mark;
5
6use crate::completion::{
7 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
8};
9
10pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
11 // complete keyword "crate" in use stmt
12 let source_range = ctx.source_range();
13
14 if ctx.use_item_syntax.is_some() {
15 if ctx.path_qual.is_none() {
16 CompletionItem::new(CompletionKind::Keyword, source_range, "crate::")
17 .kind(CompletionItemKind::Keyword)
18 .insert_text("crate::")
19 .add_to(acc);
20 }
21 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
22 .kind(CompletionItemKind::Keyword)
23 .add_to(acc);
24 CompletionItem::new(CompletionKind::Keyword, source_range, "super::")
25 .kind(CompletionItemKind::Keyword)
26 .insert_text("super::")
27 .add_to(acc);
28 }
29
30 // Suggest .await syntax for types that implement Future trait
31 if let Some(receiver) = &ctx.dot_receiver {
32 if let Some(ty) = ctx.sema.type_of_expr(receiver) {
33 if ty.impls_future(ctx.db) {
34 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await")
35 .kind(CompletionItemKind::Keyword)
36 .detail("expr.await")
37 .insert_text("await")
38 .add_to(acc);
39 }
40 };
41 }
42}
43
44pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
45 if ctx.token.kind() == SyntaxKind::COMMENT {
46 mark::hit!(no_keyword_completion_in_comments);
47 return;
48 }
49
50 let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
51 if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
52 add_keyword(ctx, acc, "where", "where ");
53 return;
54 }
55 if ctx.unsafe_is_prev {
56 if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
57 add_keyword(ctx, acc, "fn", "fn $0() {}")
58 }
59
60 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
61 add_keyword(ctx, acc, "trait", "trait $0 {}");
62 add_keyword(ctx, acc, "impl", "impl $0 {}");
63 }
64
65 return;
66 }
67 if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
68 {
69 add_keyword(ctx, acc, "fn", "fn $0() {}");
70 }
71 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
72 add_keyword(ctx, acc, "use", "use ");
73 add_keyword(ctx, acc, "impl", "impl $0 {}");
74 add_keyword(ctx, acc, "trait", "trait $0 {}");
75 }
76
77 if ctx.has_item_list_or_source_file_parent {
78 add_keyword(ctx, acc, "enum", "enum $0 {}");
79 add_keyword(ctx, acc, "struct", "struct $0");
80 add_keyword(ctx, acc, "union", "union $0 {}");
81 }
82
83 if ctx.is_expr {
84 add_keyword(ctx, acc, "match", "match $0 {}");
85 add_keyword(ctx, acc, "while", "while $0 {}");
86 add_keyword(ctx, acc, "loop", "loop {$0}");
87 add_keyword(ctx, acc, "if", "if ");
88 add_keyword(ctx, acc, "if let", "if let ");
89 }
90
91 if ctx.if_is_prev || ctx.block_expr_parent {
92 add_keyword(ctx, acc, "let", "let ");
93 }
94
95 if ctx.after_if {
96 add_keyword(ctx, acc, "else", "else {$0}");
97 add_keyword(ctx, acc, "else if", "else if $0 {}");
98 }
99 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
100 add_keyword(ctx, acc, "mod", "mod $0 {}");
101 }
102 if ctx.bind_pat_parent || ctx.ref_pat_parent {
103 add_keyword(ctx, acc, "mut", "mut ");
104 }
105 if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
106 {
107 add_keyword(ctx, acc, "const", "const ");
108 add_keyword(ctx, acc, "type", "type ");
109 }
110 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
111 add_keyword(ctx, acc, "static", "static ");
112 };
113 if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
114 add_keyword(ctx, acc, "extern", "extern ");
115 }
116 if ctx.has_item_list_or_source_file_parent
117 || has_trait_or_impl_parent
118 || ctx.block_expr_parent
119 || ctx.is_match_arm
120 {
121 add_keyword(ctx, acc, "unsafe", "unsafe ");
122 }
123 if ctx.in_loop_body {
124 if ctx.can_be_stmt {
125 add_keyword(ctx, acc, "continue", "continue;");
126 add_keyword(ctx, acc, "break", "break;");
127 } else {
128 add_keyword(ctx, acc, "continue", "continue");
129 add_keyword(ctx, acc, "break", "break");
130 }
131 }
132 if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent {
133 add_keyword(ctx, acc, "pub", "pub ")
134 }
135
136 if !ctx.is_trivial_path {
137 return;
138 }
139 let fn_def = match &ctx.function_syntax {
140 Some(it) => it,
141 None => return,
142 };
143 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
144}
145
146fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
147 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
148 .kind(CompletionItemKind::Keyword);
149
150 match ctx.config.snippet_cap {
151 Some(cap) => res.insert_snippet(cap, snippet),
152 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
153 }
154 .build()
155}
156
157fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
158 acc.add(keyword(ctx, kw, snippet));
159}
160
161fn complete_return(
162 ctx: &CompletionContext,
163 fn_def: &ast::Fn,
164 can_be_stmt: bool,
165) -> Option<CompletionItem> {
166 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
167 (true, true) => "return $0;",
168 (true, false) => "return;",
169 (false, true) => "return $0",
170 (false, false) => "return",
171 };
172 Some(keyword(ctx, "return", snip))
173}
174
175#[cfg(test)]
176mod tests {
177 use expect_test::{expect, Expect};
178
179 use crate::completion::{
180 test_utils::{check_edit, completion_list},
181 CompletionKind,
182 };
183 use test_utils::mark;
184
185 fn check(ra_fixture: &str, expect: Expect) {
186 let actual = completion_list(ra_fixture, CompletionKind::Keyword);
187 expect.assert_eq(&actual)
188 }
189
190 #[test]
191 fn test_keywords_in_use_stmt() {
192 check(
193 r"use <|>",
194 expect![[r#"
195 kw crate::
196 kw self
197 kw super::
198 "#]],
199 );
200
201 check(
202 r"use a::<|>",
203 expect![[r#"
204 kw self
205 kw super::
206 "#]],
207 );
208
209 check(
210 r"use a::{b, <|>}",
211 expect![[r#"
212 kw self
213 kw super::
214 "#]],
215 );
216 }
217
218 #[test]
219 fn test_keywords_at_source_file_level() {
220 check(
221 r"m<|>",
222 expect![[r#"
223 kw const
224 kw enum
225 kw extern
226 kw fn
227 kw impl
228 kw mod
229 kw pub
230 kw static
231 kw struct
232 kw trait
233 kw type
234 kw union
235 kw unsafe
236 kw use
237 "#]],
238 );
239 }
240
241 #[test]
242 fn test_keywords_in_function() {
243 check(
244 r"fn quux() { <|> }",
245 expect![[r#"
246 kw const
247 kw extern
248 kw fn
249 kw if
250 kw if let
251 kw impl
252 kw let
253 kw loop
254 kw match
255 kw mod
256 kw return
257 kw static
258 kw trait
259 kw type
260 kw unsafe
261 kw use
262 kw while
263 "#]],
264 );
265 }
266
267 #[test]
268 fn test_keywords_inside_block() {
269 check(
270 r"fn quux() { if true { <|> } }",
271 expect![[r#"
272 kw const
273 kw extern
274 kw fn
275 kw if
276 kw if let
277 kw impl
278 kw let
279 kw loop
280 kw match
281 kw mod
282 kw return
283 kw static
284 kw trait
285 kw type
286 kw unsafe
287 kw use
288 kw while
289 "#]],
290 );
291 }
292
293 #[test]
294 fn test_keywords_after_if() {
295 check(
296 r#"fn quux() { if true { () } <|> }"#,
297 expect![[r#"
298 kw const
299 kw else
300 kw else if
301 kw extern
302 kw fn
303 kw if
304 kw if let
305 kw impl
306 kw let
307 kw loop
308 kw match
309 kw mod
310 kw return
311 kw static
312 kw trait
313 kw type
314 kw unsafe
315 kw use
316 kw while
317 "#]],
318 );
319 check_edit(
320 "else",
321 r#"fn quux() { if true { () } <|> }"#,
322 r#"fn quux() { if true { () } else {$0} }"#,
323 );
324 }
325
326 #[test]
327 fn test_keywords_in_match_arm() {
328 check(
329 r#"
330fn quux() -> i32 {
331 match () { () => <|> }
332}
333"#,
334 expect![[r#"
335 kw if
336 kw if let
337 kw loop
338 kw match
339 kw return
340 kw unsafe
341 kw while
342 "#]],
343 );
344 }
345
346 #[test]
347 fn test_keywords_in_trait_def() {
348 check(
349 r"trait My { <|> }",
350 expect![[r#"
351 kw const
352 kw fn
353 kw type
354 kw unsafe
355 "#]],
356 );
357 }
358
359 #[test]
360 fn test_keywords_in_impl_def() {
361 check(
362 r"impl My { <|> }",
363 expect![[r#"
364 kw const
365 kw fn
366 kw pub
367 kw type
368 kw unsafe
369 "#]],
370 );
371 }
372
373 #[test]
374 fn test_keywords_in_loop() {
375 check(
376 r"fn my() { loop { <|> } }",
377 expect![[r#"
378 kw break
379 kw const
380 kw continue
381 kw extern
382 kw fn
383 kw if
384 kw if let
385 kw impl
386 kw let
387 kw loop
388 kw match
389 kw mod
390 kw return
391 kw static
392 kw trait
393 kw type
394 kw unsafe
395 kw use
396 kw while
397 "#]],
398 );
399 }
400
401 #[test]
402 fn test_keywords_after_unsafe_in_item_list() {
403 check(
404 r"unsafe <|>",
405 expect![[r#"
406 kw fn
407 kw impl
408 kw trait
409 "#]],
410 );
411 }
412
413 #[test]
414 fn test_keywords_after_unsafe_in_block_expr() {
415 check(
416 r"fn my_fn() { unsafe <|> }",
417 expect![[r#"
418 kw fn
419 kw impl
420 kw trait
421 "#]],
422 );
423 }
424
425 #[test]
426 fn test_mut_in_ref_and_in_fn_parameters_list() {
427 check(
428 r"fn my_fn(&<|>) {}",
429 expect![[r#"
430 kw mut
431 "#]],
432 );
433 check(
434 r"fn my_fn(<|>) {}",
435 expect![[r#"
436 kw mut
437 "#]],
438 );
439 check(
440 r"fn my_fn() { let &<|> }",
441 expect![[r#"
442 kw mut
443 "#]],
444 );
445 }
446
447 #[test]
448 fn test_where_keyword() {
449 check(
450 r"trait A <|>",
451 expect![[r#"
452 kw where
453 "#]],
454 );
455 check(
456 r"impl A <|>",
457 expect![[r#"
458 kw where
459 "#]],
460 );
461 }
462
463 #[test]
464 fn no_keyword_completion_in_comments() {
465 mark::check!(no_keyword_completion_in_comments);
466 check(
467 r#"
468fn test() {
469 let x = 2; // A comment<|>
470}
471"#,
472 expect![[""]],
473 );
474 check(
475 r#"
476/*
477Some multi-line comment<|>
478*/
479"#,
480 expect![[""]],
481 );
482 check(
483 r#"
484/// Some doc comment
485/// let test<|> = 1
486"#,
487 expect![[""]],
488 );
489 }
490
491 #[test]
492 fn test_completion_await_impls_future() {
493 check(
494 r#"
495//- /main.rs
496use std::future::*;
497struct A {}
498impl Future for A {}
499fn foo(a: A) { a.<|> }
500
501//- /std/lib.rs
502pub mod future {
503 #[lang = "future_trait"]
504 pub trait Future {}
505}
506"#,
507 expect![[r#"
508 kw await expr.await
509 "#]],
510 )
511 }
512
513 #[test]
514 fn after_let() {
515 check(
516 r#"fn main() { let _ = <|> }"#,
517 expect![[r#"
518 kw if
519 kw if let
520 kw loop
521 kw match
522 kw return
523 kw while
524 "#]],
525 )
526 }
527}
diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/ide/src/completion/complete_macro_in_item_position.rs
new file mode 100644
index 000000000..fc8625d8e
--- /dev/null
+++ b/crates/ide/src/completion/complete_macro_in_item_position.rs
@@ -0,0 +1,41 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
6 // Show only macros in top level.
7 if ctx.is_new_item {
8 ctx.scope.process_all_names(&mut |name, res| {
9 if let hir::ScopeDef::MacroDef(mac) = res {
10 acc.add_macro(ctx, Some(name.to_string()), mac);
11 }
12 })
13 }
14}
15
16#[cfg(test)]
17mod tests {
18 use expect_test::{expect, Expect};
19
20 use crate::completion::{test_utils::completion_list, CompletionKind};
21
22 fn check(ra_fixture: &str, expect: Expect) {
23 let actual = completion_list(ra_fixture, CompletionKind::Reference);
24 expect.assert_eq(&actual)
25 }
26
27 #[test]
28 fn completes_macros_as_item() {
29 check(
30 r#"
31macro_rules! foo { () => {} }
32fn foo() {}
33
34<|>
35"#,
36 expect![[r#"
37 ma foo!(…) macro_rules! foo
38 "#]],
39 )
40 }
41}
diff --git a/crates/ide/src/completion/complete_pattern.rs b/crates/ide/src/completion/complete_pattern.rs
new file mode 100644
index 000000000..5a13574d4
--- /dev/null
+++ b/crates/ide/src/completion/complete_pattern.rs
@@ -0,0 +1,88 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Completes constats and paths in patterns.
6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
7 if !ctx.is_pat_binding_or_const {
8 return;
9 }
10 if ctx.record_pat_syntax.is_some() {
11 return;
12 }
13
14 // FIXME: ideally, we should look at the type we are matching against and
15 // suggest variants + auto-imports
16 ctx.scope.process_all_names(&mut |name, res| {
17 match &res {
18 hir::ScopeDef::ModuleDef(def) => match def {
19 hir::ModuleDef::Adt(hir::Adt::Enum(..))
20 | hir::ModuleDef::Adt(hir::Adt::Struct(..))
21 | hir::ModuleDef::EnumVariant(..)
22 | hir::ModuleDef::Const(..)
23 | hir::ModuleDef::Module(..) => (),
24 _ => return,
25 },
26 hir::ScopeDef::MacroDef(_) => (),
27 _ => return,
28 };
29
30 acc.add_resolution(ctx, name.to_string(), &res)
31 });
32}
33
34#[cfg(test)]
35mod tests {
36 use expect_test::{expect, Expect};
37
38 use crate::completion::{test_utils::completion_list, CompletionKind};
39
40 fn check(ra_fixture: &str, expect: Expect) {
41 let actual = completion_list(ra_fixture, CompletionKind::Reference);
42 expect.assert_eq(&actual)
43 }
44
45 #[test]
46 fn completes_enum_variants_and_modules() {
47 check(
48 r#"
49enum E { X }
50use self::E::X;
51const Z: E = E::X;
52mod m {}
53
54static FOO: E = E::X;
55struct Bar { f: u32 }
56
57fn foo() {
58 match E::X { <|> }
59}
60"#,
61 expect![[r#"
62 st Bar
63 en E
64 ev X ()
65 ct Z
66 md m
67 "#]],
68 );
69 }
70
71 #[test]
72 fn completes_in_simple_macro_call() {
73 check(
74 r#"
75macro_rules! m { ($e:expr) => { $e } }
76enum E { X }
77
78fn foo() {
79 m!(match E::X { <|> })
80}
81"#,
82 expect![[r#"
83 en E
84 ma m!(…) macro_rules! m
85 "#]],
86 );
87 }
88}
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs
new file mode 100644
index 000000000..84c4e129d
--- /dev/null
+++ b/crates/ide/src/completion/complete_postfix.rs
@@ -0,0 +1,378 @@
1//! FIXME: write short doc here
2use assists::utils::TryEnum;
3use syntax::{
4 ast::{self, AstNode},
5 TextRange, TextSize,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 completion::{
11 completion_config::SnippetCap,
12 completion_context::CompletionContext,
13 completion_item::{Builder, CompletionKind, Completions},
14 },
15 CompletionItem, CompletionItemKind,
16};
17
18pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
19 if !ctx.config.enable_postfix_completions {
20 return;
21 }
22
23 let dot_receiver = match &ctx.dot_receiver {
24 Some(it) => it,
25 None => return,
26 };
27
28 let receiver_text =
29 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
30
31 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
32 Some(it) => it,
33 None => return,
34 };
35
36 let cap = match ctx.config.snippet_cap {
37 Some(it) => it,
38 None => return,
39 };
40 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty);
41 if let Some(try_enum) = &try_enum {
42 match try_enum {
43 TryEnum::Result => {
44 postfix_snippet(
45 ctx,
46 cap,
47 &dot_receiver,
48 "ifl",
49 "if let Ok {}",
50 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
51 )
52 .add_to(acc);
53
54 postfix_snippet(
55 ctx,
56 cap,
57 &dot_receiver,
58 "while",
59 "while let Ok {}",
60 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
61 )
62 .add_to(acc);
63 }
64 TryEnum::Option => {
65 postfix_snippet(
66 ctx,
67 cap,
68 &dot_receiver,
69 "ifl",
70 "if let Some {}",
71 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
72 )
73 .add_to(acc);
74
75 postfix_snippet(
76 ctx,
77 cap,
78 &dot_receiver,
79 "while",
80 "while let Some {}",
81 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
82 )
83 .add_to(acc);
84 }
85 }
86 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
87 postfix_snippet(
88 ctx,
89 cap,
90 &dot_receiver,
91 "if",
92 "if expr {}",
93 &format!("if {} {{\n $0\n}}", receiver_text),
94 )
95 .add_to(acc);
96 postfix_snippet(
97 ctx,
98 cap,
99 &dot_receiver,
100 "while",
101 "while expr {}",
102 &format!("while {} {{\n $0\n}}", receiver_text),
103 )
104 .add_to(acc);
105 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
106 .add_to(acc);
107 }
108
109 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
110 .add_to(acc);
111 postfix_snippet(
112 ctx,
113 cap,
114 &dot_receiver,
115 "refm",
116 "&mut expr",
117 &format!("&mut {}", receiver_text),
118 )
119 .add_to(acc);
120
121 // The rest of the postfix completions create an expression that moves an argument,
122 // so it's better to consider references now to avoid breaking the compilation
123 let dot_receiver = include_references(dot_receiver);
124 let receiver_text =
125 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
126
127 match try_enum {
128 Some(try_enum) => match try_enum {
129 TryEnum::Result => {
130 postfix_snippet(
131 ctx,
132 cap,
133 &dot_receiver,
134 "match",
135 "match expr {}",
136 &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
137 )
138 .add_to(acc);
139 }
140 TryEnum::Option => {
141 postfix_snippet(
142 ctx,
143 cap,
144 &dot_receiver,
145 "match",
146 "match expr {}",
147 &format!(
148 "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
149 receiver_text
150 ),
151 )
152 .add_to(acc);
153 }
154 },
155 None => {
156 postfix_snippet(
157 ctx,
158 cap,
159 &dot_receiver,
160 "match",
161 "match expr {}",
162 &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
163 )
164 .add_to(acc);
165 }
166 }
167
168 postfix_snippet(
169 ctx,
170 cap,
171 &dot_receiver,
172 "box",
173 "Box::new(expr)",
174 &format!("Box::new({})", receiver_text),
175 )
176 .add_to(acc);
177
178 postfix_snippet(
179 ctx,
180 cap,
181 &dot_receiver,
182 "dbg",
183 "dbg!(expr)",
184 &format!("dbg!({})", receiver_text),
185 )
186 .add_to(acc);
187
188 postfix_snippet(
189 ctx,
190 cap,
191 &dot_receiver,
192 "call",
193 "function(expr)",
194 &format!("${{1}}({})", receiver_text),
195 )
196 .add_to(acc);
197}
198
199fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
200 if receiver_is_ambiguous_float_literal {
201 let text = receiver.syntax().text();
202 let without_dot = ..text.len() - TextSize::of('.');
203 text.slice(without_dot).to_string()
204 } else {
205 receiver.to_string()
206 }
207}
208
209fn include_references(initial_element: &ast::Expr) -> ast::Expr {
210 let mut resulting_element = initial_element.clone();
211 while let Some(parent_ref_element) =
212 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
213 {
214 resulting_element = ast::Expr::from(parent_ref_element);
215 }
216 resulting_element
217}
218
219fn postfix_snippet(
220 ctx: &CompletionContext,
221 cap: SnippetCap,
222 receiver: &ast::Expr,
223 label: &str,
224 detail: &str,
225 snippet: &str,
226) -> Builder {
227 let edit = {
228 let receiver_syntax = receiver.syntax();
229 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
230 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
231 TextEdit::replace(delete_range, snippet.to_string())
232 };
233 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
234 .detail(detail)
235 .kind(CompletionItemKind::Snippet)
236 .snippet_edit(cap, edit)
237}
238
239#[cfg(test)]
240mod tests {
241 use expect_test::{expect, Expect};
242
243 use crate::completion::{
244 test_utils::{check_edit, completion_list},
245 CompletionKind,
246 };
247
248 fn check(ra_fixture: &str, expect: Expect) {
249 let actual = completion_list(ra_fixture, CompletionKind::Postfix);
250 expect.assert_eq(&actual)
251 }
252
253 #[test]
254 fn postfix_completion_works_for_trivial_path_expression() {
255 check(
256 r#"
257fn main() {
258 let bar = true;
259 bar.<|>
260}
261"#,
262 expect![[r#"
263 sn box Box::new(expr)
264 sn call function(expr)
265 sn dbg dbg!(expr)
266 sn if if expr {}
267 sn match match expr {}
268 sn not !expr
269 sn ref &expr
270 sn refm &mut expr
271 sn while while expr {}
272 "#]],
273 );
274 }
275
276 #[test]
277 fn postfix_type_filtering() {
278 check(
279 r#"
280fn main() {
281 let bar: u8 = 12;
282 bar.<|>
283}
284"#,
285 expect![[r#"
286 sn box Box::new(expr)
287 sn call function(expr)
288 sn dbg dbg!(expr)
289 sn match match expr {}
290 sn ref &expr
291 sn refm &mut expr
292 "#]],
293 )
294 }
295
296 #[test]
297 fn option_iflet() {
298 check_edit(
299 "ifl",
300 r#"
301enum Option<T> { Some(T), None }
302
303fn main() {
304 let bar = Option::Some(true);
305 bar.<|>
306}
307"#,
308 r#"
309enum Option<T> { Some(T), None }
310
311fn main() {
312 let bar = Option::Some(true);
313 if let Some($1) = bar {
314 $0
315}
316}
317"#,
318 );
319 }
320
321 #[test]
322 fn result_match() {
323 check_edit(
324 "match",
325 r#"
326enum Result<T, E> { Ok(T), Err(E) }
327
328fn main() {
329 let bar = Result::Ok(true);
330 bar.<|>
331}
332"#,
333 r#"
334enum Result<T, E> { Ok(T), Err(E) }
335
336fn main() {
337 let bar = Result::Ok(true);
338 match bar {
339 Ok(${1:_}) => {$2},
340 Err(${3:_}) => {$0},
341}
342}
343"#,
344 );
345 }
346
347 #[test]
348 fn postfix_completion_works_for_ambiguous_float_literal() {
349 check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#)
350 }
351
352 #[test]
353 fn works_in_simple_macro() {
354 check_edit(
355 "dbg",
356 r#"
357macro_rules! m { ($e:expr) => { $e } }
358fn main() {
359 let bar: u8 = 12;
360 m!(bar.d<|>)
361}
362"#,
363 r#"
364macro_rules! m { ($e:expr) => { $e } }
365fn main() {
366 let bar: u8 = 12;
367 m!(dbg!(bar))
368}
369"#,
370 );
371 }
372
373 #[test]
374 fn postfix_completion_for_references() {
375 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#);
376 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#);
377 }
378}
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs
new file mode 100644
index 000000000..accb09f7e
--- /dev/null
+++ b/crates/ide/src/completion/complete_qualified_path.rs
@@ -0,0 +1,733 @@
1//! Completion of paths, i.e. `some::prefix::<|>`.
2
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use rustc_hash::FxHashSet;
5use syntax::AstNode;
6use test_utils::mark;
7
8use crate::completion::{CompletionContext, Completions};
9
10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
11 let path = match &ctx.path_qual {
12 Some(path) => path.clone(),
13 None => return,
14 };
15
16 if ctx.attribute_under_caret.is_some() {
17 return;
18 }
19
20 let context_module = ctx.scope.module();
21
22 let resolution = match ctx.sema.resolve_path(&path) {
23 Some(res) => res,
24 None => return,
25 };
26
27 // Add associated types on type parameters and `Self`.
28 resolution.assoc_type_shorthand_candidates(ctx.db, |alias| {
29 acc.add_type_alias(ctx, alias);
30 None::<()>
31 });
32
33 match resolution {
34 PathResolution::Def(hir::ModuleDef::Module(module)) => {
35 let module_scope = module.scope(ctx.db, context_module);
36 for (name, def) in module_scope {
37 if ctx.use_item_syntax.is_some() {
38 if let ScopeDef::Unknown = def {
39 if let Some(name_ref) = ctx.name_ref_syntax.as_ref() {
40 if name_ref.syntax().text() == name.to_string().as_str() {
41 // for `use self::foo<|>`, don't suggest `foo` as a completion
42 mark::hit!(dont_complete_current_use);
43 continue;
44 }
45 }
46 }
47 }
48
49 acc.add_resolution(ctx, name.to_string(), &def);
50 }
51 }
52 PathResolution::Def(def @ hir::ModuleDef::Adt(_))
53 | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => {
54 if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
55 for variant in e.variants(ctx.db) {
56 acc.add_enum_variant(ctx, variant, None);
57 }
58 }
59 let ty = match def {
60 hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
61 hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
62 _ => unreachable!(),
63 };
64
65 // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
66 // (where AssocType is defined on a trait, not an inherent impl)
67
68 let krate = ctx.krate;
69 if let Some(krate) = krate {
70 let traits_in_scope = ctx.scope.traits_in_scope();
71 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
72 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
73 return None;
74 }
75 match item {
76 hir::AssocItem::Function(func) => {
77 acc.add_function(ctx, func, None);
78 }
79 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
80 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
81 }
82 None::<()>
83 });
84
85 // Iterate assoc types separately
86 ty.iterate_assoc_items(ctx.db, krate, |item| {
87 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
88 return None;
89 }
90 match item {
91 hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
92 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
93 }
94 None::<()>
95 });
96 }
97 }
98 PathResolution::Def(hir::ModuleDef::Trait(t)) => {
99 // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
100 for item in t.items(ctx.db) {
101 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
102 continue;
103 }
104 match item {
105 hir::AssocItem::Function(func) => {
106 acc.add_function(ctx, func, None);
107 }
108 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
109 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
110 }
111 }
112 }
113 PathResolution::TypeParam(_) | PathResolution::SelfType(_) => {
114 if let Some(krate) = ctx.krate {
115 let ty = match resolution {
116 PathResolution::TypeParam(param) => param.ty(ctx.db),
117 PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db),
118 _ => return,
119 };
120
121 let traits_in_scope = ctx.scope.traits_in_scope();
122 let mut seen = FxHashSet::default();
123 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
124 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
125 return None;
126 }
127
128 // We might iterate candidates of a trait multiple times here, so deduplicate
129 // them.
130 if seen.insert(item) {
131 match item {
132 hir::AssocItem::Function(func) => {
133 acc.add_function(ctx, func, None);
134 }
135 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
136 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
137 }
138 }
139 None::<()>
140 });
141 }
142 }
143 _ => {}
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use expect_test::{expect, Expect};
150 use test_utils::mark;
151
152 use crate::completion::{
153 test_utils::{check_edit, completion_list},
154 CompletionKind,
155 };
156
157 fn check(ra_fixture: &str, expect: Expect) {
158 let actual = completion_list(ra_fixture, CompletionKind::Reference);
159 expect.assert_eq(&actual);
160 }
161
162 fn check_builtin(ra_fixture: &str, expect: Expect) {
163 let actual = completion_list(ra_fixture, CompletionKind::BuiltinType);
164 expect.assert_eq(&actual);
165 }
166
167 #[test]
168 fn dont_complete_current_use() {
169 mark::check!(dont_complete_current_use);
170 check(r#"use self::foo<|>;"#, expect![[""]]);
171 }
172
173 #[test]
174 fn dont_complete_current_use_in_braces_with_glob() {
175 check(
176 r#"
177mod foo { pub struct S; }
178use self::{foo::*, bar<|>};
179"#,
180 expect![[r#"
181 st S
182 md foo
183 "#]],
184 );
185 }
186
187 #[test]
188 fn dont_complete_primitive_in_use() {
189 check_builtin(r#"use self::<|>;"#, expect![[""]]);
190 }
191
192 #[test]
193 fn dont_complete_primitive_in_module_scope() {
194 check_builtin(r#"fn foo() { self::<|> }"#, expect![[""]]);
195 }
196
197 #[test]
198 fn completes_primitives() {
199 check_builtin(
200 r#"fn main() { let _: <|> = 92; }"#,
201 expect![[r#"
202 bt bool
203 bt char
204 bt f32
205 bt f64
206 bt i128
207 bt i16
208 bt i32
209 bt i64
210 bt i8
211 bt isize
212 bt str
213 bt u128
214 bt u16
215 bt u32
216 bt u64
217 bt u8
218 bt usize
219 "#]],
220 );
221 }
222
223 #[test]
224 fn completes_mod_with_same_name_as_function() {
225 check(
226 r#"
227use self::my::<|>;
228
229mod my { pub struct Bar; }
230fn my() {}
231"#,
232 expect![[r#"
233 st Bar
234 "#]],
235 );
236 }
237
238 #[test]
239 fn filters_visibility() {
240 check(
241 r#"
242use self::my::<|>;
243
244mod my {
245 struct Bar;
246 pub struct Foo;
247 pub use Bar as PublicBar;
248}
249"#,
250 expect![[r#"
251 st Foo
252 st PublicBar
253 "#]],
254 );
255 }
256
257 #[test]
258 fn completes_use_item_starting_with_self() {
259 check(
260 r#"
261use self::m::<|>;
262
263mod m { pub struct Bar; }
264"#,
265 expect![[r#"
266 st Bar
267 "#]],
268 );
269 }
270
271 #[test]
272 fn completes_use_item_starting_with_crate() {
273 check(
274 r#"
275//- /lib.rs
276mod foo;
277struct Spam;
278//- /foo.rs
279use crate::Sp<|>
280"#,
281 expect![[r#"
282 st Spam
283 md foo
284 "#]],
285 );
286 }
287
288 #[test]
289 fn completes_nested_use_tree() {
290 check(
291 r#"
292//- /lib.rs
293mod foo;
294struct Spam;
295//- /foo.rs
296use crate::{Sp<|>};
297"#,
298 expect![[r#"
299 st Spam
300 md foo
301 "#]],
302 );
303 }
304
305 #[test]
306 fn completes_deeply_nested_use_tree() {
307 check(
308 r#"
309//- /lib.rs
310mod foo;
311pub mod bar {
312 pub mod baz {
313 pub struct Spam;
314 }
315}
316//- /foo.rs
317use crate::{bar::{baz::Sp<|>}};
318"#,
319 expect![[r#"
320 st Spam
321 "#]],
322 );
323 }
324
325 #[test]
326 fn completes_enum_variant() {
327 check(
328 r#"
329enum E { Foo, Bar(i32) }
330fn foo() { let _ = E::<|> }
331"#,
332 expect![[r#"
333 ev Bar(…) (i32)
334 ev Foo ()
335 "#]],
336 );
337 }
338
339 #[test]
340 fn completes_struct_associated_items() {
341 check(
342 r#"
343//- /lib.rs
344struct S;
345
346impl S {
347 fn a() {}
348 fn b(&self) {}
349 const C: i32 = 42;
350 type T = i32;
351}
352
353fn foo() { let _ = S::<|> }
354"#,
355 expect![[r#"
356 ct C const C: i32 = 42;
357 ta T type T = i32;
358 fn a() fn a()
359 me b() fn b(&self)
360 "#]],
361 );
362 }
363
364 #[test]
365 fn associated_item_visibility() {
366 check(
367 r#"
368struct S;
369
370mod m {
371 impl super::S {
372 pub(super) fn public_method() { }
373 fn private_method() { }
374 pub(super) type PublicType = u32;
375 type PrivateType = u32;
376 pub(super) const PUBLIC_CONST: u32 = 1;
377 const PRIVATE_CONST: u32 = 1;
378 }
379}
380
381fn foo() { let _ = S::<|> }
382"#,
383 expect![[r#"
384 ct PUBLIC_CONST pub(super) const PUBLIC_CONST: u32 = 1;
385 ta PublicType pub(super) type PublicType = u32;
386 fn public_method() pub(super) fn public_method()
387 "#]],
388 );
389 }
390
391 #[test]
392 fn completes_enum_associated_method() {
393 check(
394 r#"
395enum E {};
396impl E { fn m() { } }
397
398fn foo() { let _ = E::<|> }
399 "#,
400 expect![[r#"
401 fn m() fn m()
402 "#]],
403 );
404 }
405
406 #[test]
407 fn completes_union_associated_method() {
408 check(
409 r#"
410union U {};
411impl U { fn m() { } }
412
413fn foo() { let _ = U::<|> }
414"#,
415 expect![[r#"
416 fn m() fn m()
417 "#]],
418 );
419 }
420
421 #[test]
422 fn completes_use_paths_across_crates() {
423 check(
424 r#"
425//- /main.rs
426use foo::<|>;
427
428//- /foo/lib.rs
429pub mod bar { pub struct S; }
430"#,
431 expect![[r#"
432 md bar
433 "#]],
434 );
435 }
436
437 #[test]
438 fn completes_trait_associated_method_1() {
439 check(
440 r#"
441trait Trait { fn m(); }
442
443fn foo() { let _ = Trait::<|> }
444"#,
445 expect![[r#"
446 fn m() fn m()
447 "#]],
448 );
449 }
450
451 #[test]
452 fn completes_trait_associated_method_2() {
453 check(
454 r#"
455trait Trait { fn m(); }
456
457struct S;
458impl Trait for S {}
459
460fn foo() { let _ = S::<|> }
461"#,
462 expect![[r#"
463 fn m() fn m()
464 "#]],
465 );
466 }
467
468 #[test]
469 fn completes_trait_associated_method_3() {
470 check(
471 r#"
472trait Trait { fn m(); }
473
474struct S;
475impl Trait for S {}
476
477fn foo() { let _ = <S as Trait>::<|> }
478"#,
479 expect![[r#"
480 fn m() fn m()
481 "#]],
482 );
483 }
484
485 #[test]
486 fn completes_ty_param_assoc_ty() {
487 check(
488 r#"
489trait Super {
490 type Ty;
491 const CONST: u8;
492 fn func() {}
493 fn method(&self) {}
494}
495
496trait Sub: Super {
497 type SubTy;
498 const C2: ();
499 fn subfunc() {}
500 fn submethod(&self) {}
501}
502
503fn foo<T: Sub>() { T::<|> }
504"#,
505 expect![[r#"
506 ct C2 const C2: ();
507 ct CONST const CONST: u8;
508 ta SubTy type SubTy;
509 ta Ty type Ty;
510 fn func() fn func()
511 me method() fn method(&self)
512 fn subfunc() fn subfunc()
513 me submethod() fn submethod(&self)
514 "#]],
515 );
516 }
517
518 #[test]
519 fn completes_self_param_assoc_ty() {
520 check(
521 r#"
522trait Super {
523 type Ty;
524 const CONST: u8 = 0;
525 fn func() {}
526 fn method(&self) {}
527}
528
529trait Sub: Super {
530 type SubTy;
531 const C2: () = ();
532 fn subfunc() {}
533 fn submethod(&self) {}
534}
535
536struct Wrap<T>(T);
537impl<T> Super for Wrap<T> {}
538impl<T> Sub for Wrap<T> {
539 fn subfunc() {
540 // Should be able to assume `Self: Sub + Super`
541 Self::<|>
542 }
543}
544"#,
545 expect![[r#"
546 ct C2 const C2: () = ();
547 ct CONST const CONST: u8 = 0;
548 ta SubTy type SubTy;
549 ta Ty type Ty;
550 fn func() fn func()
551 me method() fn method(&self)
552 fn subfunc() fn subfunc()
553 me submethod() fn submethod(&self)
554 "#]],
555 );
556 }
557
558 #[test]
559 fn completes_type_alias() {
560 check(
561 r#"
562struct S;
563impl S { fn foo() {} }
564type T = S;
565impl T { fn bar() {} }
566
567fn main() { T::<|>; }
568"#,
569 expect![[r#"
570 fn bar() fn bar()
571 fn foo() fn foo()
572 "#]],
573 );
574 }
575
576 #[test]
577 fn completes_qualified_macros() {
578 check(
579 r#"
580#[macro_export]
581macro_rules! foo { () => {} }
582
583fn main() { let _ = crate::<|> }
584 "#,
585 expect![[r##"
586 ma foo!(…) #[macro_export]
587 macro_rules! foo
588 fn main() fn main()
589 "##]],
590 );
591 }
592
593 #[test]
594 fn test_super_super_completion() {
595 check(
596 r#"
597mod a {
598 const A: usize = 0;
599 mod b {
600 const B: usize = 0;
601 mod c { use super::super::<|> }
602 }
603}
604"#,
605 expect![[r#"
606 ct A
607 md b
608 "#]],
609 );
610 }
611
612 #[test]
613 fn completes_reexported_items_under_correct_name() {
614 check(
615 r#"
616fn foo() { self::m::<|> }
617
618mod m {
619 pub use super::p::wrong_fn as right_fn;
620 pub use super::p::WRONG_CONST as RIGHT_CONST;
621 pub use super::p::WrongType as RightType;
622}
623mod p {
624 fn wrong_fn() {}
625 const WRONG_CONST: u32 = 1;
626 struct WrongType {};
627}
628"#,
629 expect![[r#"
630 ct RIGHT_CONST
631 st RightType
632 fn right_fn() fn wrong_fn()
633 "#]],
634 );
635
636 check_edit(
637 "RightType",
638 r#"
639fn foo() { self::m::<|> }
640
641mod m {
642 pub use super::p::wrong_fn as right_fn;
643 pub use super::p::WRONG_CONST as RIGHT_CONST;
644 pub use super::p::WrongType as RightType;
645}
646mod p {
647 fn wrong_fn() {}
648 const WRONG_CONST: u32 = 1;
649 struct WrongType {};
650}
651"#,
652 r#"
653fn foo() { self::m::RightType }
654
655mod m {
656 pub use super::p::wrong_fn as right_fn;
657 pub use super::p::WRONG_CONST as RIGHT_CONST;
658 pub use super::p::WrongType as RightType;
659}
660mod p {
661 fn wrong_fn() {}
662 const WRONG_CONST: u32 = 1;
663 struct WrongType {};
664}
665"#,
666 );
667 }
668
669 #[test]
670 fn completes_in_simple_macro_call() {
671 check(
672 r#"
673macro_rules! m { ($e:expr) => { $e } }
674fn main() { m!(self::f<|>); }
675fn foo() {}
676"#,
677 expect![[r#"
678 fn foo() fn foo()
679 fn main() fn main()
680 "#]],
681 );
682 }
683
684 #[test]
685 fn function_mod_share_name() {
686 check(
687 r#"
688fn foo() { self::m::<|> }
689
690mod m {
691 pub mod z {}
692 pub fn z() {}
693}
694"#,
695 expect![[r#"
696 md z
697 fn z() pub fn z()
698 "#]],
699 );
700 }
701
702 #[test]
703 fn completes_hashmap_new() {
704 check(
705 r#"
706struct RandomState;
707struct HashMap<K, V, S = RandomState> {}
708
709impl<K, V> HashMap<K, V, RandomState> {
710 pub fn new() -> HashMap<K, V, RandomState> { }
711}
712fn foo() {
713 HashMap::<|>
714}
715"#,
716 expect![[r#"
717 fn new() pub fn new() -> HashMap<K, V, RandomState>
718 "#]],
719 );
720 }
721
722 #[test]
723 fn dont_complete_attr() {
724 check(
725 r#"
726mod foo { pub struct Foo; }
727#[foo::<|>]
728fn f() {}
729"#,
730 expect![[""]],
731 );
732 }
733}
diff --git a/crates/ide/src/completion/complete_record.rs b/crates/ide/src/completion/complete_record.rs
new file mode 100644
index 000000000..ceb8d16c1
--- /dev/null
+++ b/crates/ide/src/completion/complete_record.rs
@@ -0,0 +1,226 @@
1//! Complete fields in record literals and patterns.
2use crate::completion::{CompletionContext, Completions};
3
4pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
5 let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
6 (None, None) => return None,
7 (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
8 (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
9 (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit),
10 };
11
12 for (field, ty) in missing_fields {
13 acc.add_field(ctx, field, &ty)
14 }
15
16 Some(())
17}
18
19#[cfg(test)]
20mod tests {
21 use expect_test::{expect, Expect};
22
23 use crate::completion::{test_utils::completion_list, CompletionKind};
24
25 fn check(ra_fixture: &str, expect: Expect) {
26 let actual = completion_list(ra_fixture, CompletionKind::Reference);
27 expect.assert_eq(&actual);
28 }
29
30 #[test]
31 fn test_record_pattern_field() {
32 check(
33 r#"
34struct S { foo: u32 }
35
36fn process(f: S) {
37 match f {
38 S { f<|>: 92 } => (),
39 }
40}
41"#,
42 expect![[r#"
43 fd foo u32
44 "#]],
45 );
46 }
47
48 #[test]
49 fn test_record_pattern_enum_variant() {
50 check(
51 r#"
52enum E { S { foo: u32, bar: () } }
53
54fn process(e: E) {
55 match e {
56 E::S { <|> } => (),
57 }
58}
59"#,
60 expect![[r#"
61 fd bar ()
62 fd foo u32
63 "#]],
64 );
65 }
66
67 #[test]
68 fn test_record_pattern_field_in_simple_macro() {
69 check(
70 r"
71macro_rules! m { ($e:expr) => { $e } }
72struct S { foo: u32 }
73
74fn process(f: S) {
75 m!(match f {
76 S { f<|>: 92 } => (),
77 })
78}
79",
80 expect![[r#"
81 fd foo u32
82 "#]],
83 );
84 }
85
86 #[test]
87 fn only_missing_fields_are_completed_in_destruct_pats() {
88 check(
89 r#"
90struct S {
91 foo1: u32, foo2: u32,
92 bar: u32, baz: u32,
93}
94
95fn main() {
96 let s = S {
97 foo1: 1, foo2: 2,
98 bar: 3, baz: 4,
99 };
100 if let S { foo1, foo2: a, <|> } = s {}
101}
102"#,
103 expect![[r#"
104 fd bar u32
105 fd baz u32
106 "#]],
107 );
108 }
109
110 #[test]
111 fn test_record_literal_field() {
112 check(
113 r#"
114struct A { the_field: u32 }
115fn foo() {
116 A { the<|> }
117}
118"#,
119 expect![[r#"
120 fd the_field u32
121 "#]],
122 );
123 }
124
125 #[test]
126 fn test_record_literal_enum_variant() {
127 check(
128 r#"
129enum E { A { a: u32 } }
130fn foo() {
131 let _ = E::A { <|> }
132}
133"#,
134 expect![[r#"
135 fd a u32
136 "#]],
137 );
138 }
139
140 #[test]
141 fn test_record_literal_two_structs() {
142 check(
143 r#"
144struct A { a: u32 }
145struct B { b: u32 }
146
147fn foo() {
148 let _: A = B { <|> }
149}
150"#,
151 expect![[r#"
152 fd b u32
153 "#]],
154 );
155 }
156
157 #[test]
158 fn test_record_literal_generic_struct() {
159 check(
160 r#"
161struct A<T> { a: T }
162
163fn foo() {
164 let _: A<u32> = A { <|> }
165}
166"#,
167 expect![[r#"
168 fd a u32
169 "#]],
170 );
171 }
172
173 #[test]
174 fn test_record_literal_field_in_simple_macro() {
175 check(
176 r#"
177macro_rules! m { ($e:expr) => { $e } }
178struct A { the_field: u32 }
179fn foo() {
180 m!(A { the<|> })
181}
182"#,
183 expect![[r#"
184 fd the_field u32
185 "#]],
186 );
187 }
188
189 #[test]
190 fn only_missing_fields_are_completed() {
191 check(
192 r#"
193struct S {
194 foo1: u32, foo2: u32,
195 bar: u32, baz: u32,
196}
197
198fn main() {
199 let foo1 = 1;
200 let s = S { foo1, foo2: 5, <|> }
201}
202"#,
203 expect![[r#"
204 fd bar u32
205 fd baz u32
206 "#]],
207 );
208 }
209
210 #[test]
211 fn completes_functional_update() {
212 check(
213 r#"
214struct S { foo1: u32, foo2: u32 }
215
216fn main() {
217 let foo1 = 1;
218 let s = S { foo1, <|> .. loop {} }
219}
220"#,
221 expect![[r#"
222 fd foo2 u32
223 "#]],
224 );
225 }
226}
diff --git a/crates/ide/src/completion/complete_snippet.rs b/crates/ide/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..c3b03b199
--- /dev/null
+++ b/crates/ide/src/completion/complete_snippet.rs
@@ -0,0 +1,116 @@
1//! FIXME: write short doc here
2
3use crate::completion::{
4 completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem,
5 CompletionItemKind, CompletionKind, Completions,
6};
7
8fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
9 CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label)
10 .insert_snippet(cap, snippet)
11 .kind(CompletionItemKind::Snippet)
12}
13
14pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
15 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
16 return;
17 }
18 let cap = match ctx.config.snippet_cap {
19 Some(it) => it,
20 None => return,
21 };
22
23 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
24 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
25}
26
27pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
28 if !ctx.is_new_item {
29 return;
30 }
31 let cap = match ctx.config.snippet_cap {
32 Some(it) => it,
33 None => return,
34 };
35
36 snippet(
37 ctx,
38 cap,
39 "tmod (Test module)",
40 "\
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn ${1:test_name}() {
47 $0
48 }
49}",
50 )
51 .lookup_by("tmod")
52 .add_to(acc);
53
54 snippet(
55 ctx,
56 cap,
57 "tfn (Test function)",
58 "\
59#[test]
60fn ${1:feature}() {
61 $0
62}",
63 )
64 .lookup_by("tfn")
65 .add_to(acc);
66
67 snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc);
68 snippet(ctx, cap, "pub(crate)", "pub(crate) $0").add_to(acc);
69}
70
71#[cfg(test)]
72mod tests {
73 use expect_test::{expect, Expect};
74
75 use crate::completion::{test_utils::completion_list, CompletionKind};
76
77 fn check(ra_fixture: &str, expect: Expect) {
78 let actual = completion_list(ra_fixture, CompletionKind::Snippet);
79 expect.assert_eq(&actual)
80 }
81
82 #[test]
83 fn completes_snippets_in_expressions() {
84 check(
85 r#"fn foo(x: i32) { <|> }"#,
86 expect![[r#"
87 sn pd
88 sn ppd
89 "#]],
90 );
91 }
92
93 #[test]
94 fn should_not_complete_snippets_in_path() {
95 check(r#"fn foo(x: i32) { ::foo<|> }"#, expect![[""]]);
96 check(r#"fn foo(x: i32) { ::<|> }"#, expect![[""]]);
97 }
98
99 #[test]
100 fn completes_snippets_in_items() {
101 check(
102 r#"
103#[cfg(test)]
104mod tests {
105 <|>
106}
107"#,
108 expect![[r#"
109 sn macro_rules
110 sn pub(crate)
111 sn tfn (Test function)
112 sn tmod (Test module)
113 "#]],
114 )
115 }
116}
diff --git a/crates/ide/src/completion/complete_trait_impl.rs b/crates/ide/src/completion/complete_trait_impl.rs
new file mode 100644
index 000000000..1a2b1e8a5
--- /dev/null
+++ b/crates/ide/src/completion/complete_trait_impl.rs
@@ -0,0 +1,488 @@
1//! Completion for associated items in a trait implementation.
2//!
3//! This module adds the completion items related to implementing associated
4//! items within a `impl Trait for Struct` block. The current context node
5//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node
6//! and an direct child of an `IMPL`.
7//!
8//! # Examples
9//!
10//! Considering the following trait `impl`:
11//!
12//! ```ignore
13//! trait SomeTrait {
14//! fn foo();
15//! }
16//!
17//! impl SomeTrait for () {
18//! fn f<|>
19//! }
20//! ```
21//!
22//! may result in the completion of the following method:
23//!
24//! ```ignore
25//! # trait SomeTrait {
26//! # fn foo();
27//! # }
28//!
29//! impl SomeTrait for () {
30//! fn foo() {}<|>
31//! }
32//! ```
33
34use assists::utils::get_missing_assoc_items;
35use hir::{self, Docs, HasSource};
36use syntax::{
37 ast::{self, edit, Impl},
38 AstNode, SyntaxKind, SyntaxNode, TextRange, T,
39};
40use text_edit::TextEdit;
41
42use crate::{
43 completion::{
44 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
45 },
46 display::function_declaration,
47};
48
49pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
50 if let Some((trigger, impl_def)) = completion_match(ctx) {
51 match trigger.kind() {
52 SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def)
53 .into_iter()
54 .for_each(|item| match item {
55 hir::AssocItem::Function(fn_item) => {
56 add_function_impl(&trigger, acc, ctx, fn_item)
57 }
58 hir::AssocItem::TypeAlias(type_item) => {
59 add_type_alias_impl(&trigger, acc, ctx, type_item)
60 }
61 hir::AssocItem::Const(const_item) => {
62 add_const_impl(&trigger, acc, ctx, const_item)
63 }
64 }),
65
66 SyntaxKind::FN => {
67 for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def)
68 .into_iter()
69 .filter_map(|item| match item {
70 hir::AssocItem::Function(fn_item) => Some(fn_item),
71 _ => None,
72 })
73 {
74 add_function_impl(&trigger, acc, ctx, missing_fn);
75 }
76 }
77
78 SyntaxKind::TYPE_ALIAS => {
79 for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def)
80 .into_iter()
81 .filter_map(|item| match item {
82 hir::AssocItem::TypeAlias(type_item) => Some(type_item),
83 _ => None,
84 })
85 {
86 add_type_alias_impl(&trigger, acc, ctx, missing_fn);
87 }
88 }
89
90 SyntaxKind::CONST => {
91 for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def)
92 .into_iter()
93 .filter_map(|item| match item {
94 hir::AssocItem::Const(const_item) => Some(const_item),
95 _ => None,
96 })
97 {
98 add_const_impl(&trigger, acc, ctx, missing_fn);
99 }
100 }
101
102 _ => {}
103 }
104 }
105}
106
107fn completion_match(ctx: &CompletionContext) -> Option<(SyntaxNode, Impl)> {
108 let (trigger, impl_def_offset) = ctx.token.ancestors().find_map(|p| match p.kind() {
109 SyntaxKind::FN | SyntaxKind::TYPE_ALIAS | SyntaxKind::CONST | SyntaxKind::BLOCK_EXPR => {
110 Some((p, 2))
111 }
112 SyntaxKind::NAME_REF => Some((p, 5)),
113 _ => None,
114 })?;
115 let impl_def = (0..impl_def_offset - 1)
116 .try_fold(trigger.parent()?, |t, _| t.parent())
117 .and_then(ast::Impl::cast)?;
118 Some((trigger, impl_def))
119}
120
121fn add_function_impl(
122 fn_def_node: &SyntaxNode,
123 acc: &mut Completions,
124 ctx: &CompletionContext,
125 func: hir::Function,
126) {
127 let fn_name = func.name(ctx.db).to_string();
128
129 let label = if func.params(ctx.db).is_empty() {
130 format!("fn {}()", fn_name)
131 } else {
132 format!("fn {}(..)", fn_name)
133 };
134
135 let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
136 .lookup_by(fn_name)
137 .set_documentation(func.docs(ctx.db));
138
139 let completion_kind = if func.self_param(ctx.db).is_some() {
140 CompletionItemKind::Method
141 } else {
142 CompletionItemKind::Function
143 };
144 let range = TextRange::new(fn_def_node.text_range().start(), ctx.source_range().end());
145
146 let function_decl = function_declaration(&func.source(ctx.db).value);
147 match ctx.config.snippet_cap {
148 Some(cap) => {
149 let snippet = format!("{} {{\n $0\n}}", function_decl);
150 builder.snippet_edit(cap, TextEdit::replace(range, snippet))
151 }
152 None => {
153 let header = format!("{} {{", function_decl);
154 builder.text_edit(TextEdit::replace(range, header))
155 }
156 }
157 .kind(completion_kind)
158 .add_to(acc);
159}
160
161fn add_type_alias_impl(
162 type_def_node: &SyntaxNode,
163 acc: &mut Completions,
164 ctx: &CompletionContext,
165 type_alias: hir::TypeAlias,
166) {
167 let alias_name = type_alias.name(ctx.db).to_string();
168
169 let snippet = format!("type {} = ", alias_name);
170
171 let range = TextRange::new(type_def_node.text_range().start(), ctx.source_range().end());
172
173 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone())
174 .text_edit(TextEdit::replace(range, snippet))
175 .lookup_by(alias_name)
176 .kind(CompletionItemKind::TypeAlias)
177 .set_documentation(type_alias.docs(ctx.db))
178 .add_to(acc);
179}
180
181fn add_const_impl(
182 const_def_node: &SyntaxNode,
183 acc: &mut Completions,
184 ctx: &CompletionContext,
185 const_: hir::Const,
186) {
187 let const_name = const_.name(ctx.db).map(|n| n.to_string());
188
189 if let Some(const_name) = const_name {
190 let snippet = make_const_compl_syntax(&const_.source(ctx.db).value);
191
192 let range = TextRange::new(const_def_node.text_range().start(), ctx.source_range().end());
193
194 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone())
195 .text_edit(TextEdit::replace(range, snippet))
196 .lookup_by(const_name)
197 .kind(CompletionItemKind::Const)
198 .set_documentation(const_.docs(ctx.db))
199 .add_to(acc);
200 }
201}
202
203fn make_const_compl_syntax(const_: &ast::Const) -> String {
204 let const_ = edit::remove_attrs_and_docs(const_);
205
206 let const_start = const_.syntax().text_range().start();
207 let const_end = const_.syntax().text_range().end();
208
209 let start =
210 const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start());
211
212 let end = const_
213 .syntax()
214 .children_with_tokens()
215 .find(|s| s.kind() == T![;] || s.kind() == T![=])
216 .map_or(const_end, |f| f.text_range().start());
217
218 let len = end - start;
219 let range = TextRange::new(0.into(), len);
220
221 let syntax = const_.syntax().text().slice(range).to_string();
222
223 format!("{} = ", syntax.trim_end())
224}
225
226#[cfg(test)]
227mod tests {
228 use expect_test::{expect, Expect};
229
230 use crate::completion::{
231 test_utils::{check_edit, completion_list},
232 CompletionKind,
233 };
234
235 fn check(ra_fixture: &str, expect: Expect) {
236 let actual = completion_list(ra_fixture, CompletionKind::Magic);
237 expect.assert_eq(&actual)
238 }
239
240 #[test]
241 fn name_ref_function_type_const() {
242 check(
243 r#"
244trait Test {
245 type TestType;
246 const TEST_CONST: u16;
247 fn test();
248}
249struct T;
250
251impl Test for T {
252 t<|>
253}
254"#,
255 expect![["
256ct const TEST_CONST: u16 = \n\
257fn fn test()
258ta type TestType = \n\
259 "]],
260 );
261 }
262
263 #[test]
264 fn no_nested_fn_completions() {
265 check(
266 r"
267trait Test {
268 fn test();
269 fn test2();
270}
271struct T;
272
273impl Test for T {
274 fn test() {
275 t<|>
276 }
277}
278",
279 expect![[""]],
280 );
281 }
282
283 #[test]
284 fn name_ref_single_function() {
285 check_edit(
286 "test",
287 r#"
288trait Test {
289 fn test();
290}
291struct T;
292
293impl Test for T {
294 t<|>
295}
296"#,
297 r#"
298trait Test {
299 fn test();
300}
301struct T;
302
303impl Test for T {
304 fn test() {
305 $0
306}
307}
308"#,
309 );
310 }
311
312 #[test]
313 fn single_function() {
314 check_edit(
315 "test",
316 r#"
317trait Test {
318 fn test();
319}
320struct T;
321
322impl Test for T {
323 fn t<|>
324}
325"#,
326 r#"
327trait Test {
328 fn test();
329}
330struct T;
331
332impl Test for T {
333 fn test() {
334 $0
335}
336}
337"#,
338 );
339 }
340
341 #[test]
342 fn hide_implemented_fn() {
343 check(
344 r#"
345trait Test {
346 fn foo();
347 fn foo_bar();
348}
349struct T;
350
351impl Test for T {
352 fn foo() {}
353 fn f<|>
354}
355"#,
356 expect![[r#"
357 fn fn foo_bar()
358 "#]],
359 );
360 }
361
362 #[test]
363 fn generic_fn() {
364 check_edit(
365 "foo",
366 r#"
367trait Test {
368 fn foo<T>();
369}
370struct T;
371
372impl Test for T {
373 fn f<|>
374}
375"#,
376 r#"
377trait Test {
378 fn foo<T>();
379}
380struct T;
381
382impl Test for T {
383 fn foo<T>() {
384 $0
385}
386}
387"#,
388 );
389 check_edit(
390 "foo",
391 r#"
392trait Test {
393 fn foo<T>() where T: Into<String>;
394}
395struct T;
396
397impl Test for T {
398 fn f<|>
399}
400"#,
401 r#"
402trait Test {
403 fn foo<T>() where T: Into<String>;
404}
405struct T;
406
407impl Test for T {
408 fn foo<T>()
409where T: Into<String> {
410 $0
411}
412}
413"#,
414 );
415 }
416
417 #[test]
418 fn associated_type() {
419 check_edit(
420 "SomeType",
421 r#"
422trait Test {
423 type SomeType;
424}
425
426impl Test for () {
427 type S<|>
428}
429"#,
430 "
431trait Test {
432 type SomeType;
433}
434
435impl Test for () {
436 type SomeType = \n\
437}
438",
439 );
440 }
441
442 #[test]
443 fn associated_const() {
444 check_edit(
445 "SOME_CONST",
446 r#"
447trait Test {
448 const SOME_CONST: u16;
449}
450
451impl Test for () {
452 const S<|>
453}
454"#,
455 "
456trait Test {
457 const SOME_CONST: u16;
458}
459
460impl Test for () {
461 const SOME_CONST: u16 = \n\
462}
463",
464 );
465
466 check_edit(
467 "SOME_CONST",
468 r#"
469trait Test {
470 const SOME_CONST: u16 = 92;
471}
472
473impl Test for () {
474 const S<|>
475}
476"#,
477 "
478trait Test {
479 const SOME_CONST: u16 = 92;
480}
481
482impl Test for () {
483 const SOME_CONST: u16 = \n\
484}
485",
486 );
487 }
488}
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs
new file mode 100644
index 000000000..1f1b682a7
--- /dev/null
+++ b/crates/ide/src/completion/complete_unqualified_path.rs
@@ -0,0 +1,658 @@
1//! Completion of names from the current scope, e.g. locals and imported items.
2
3use hir::{Adt, ModuleDef, ScopeDef, Type};
4use syntax::AstNode;
5use test_utils::mark;
6
7use crate::completion::{CompletionContext, Completions};
8
9pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
11 return;
12 }
13 if ctx.record_lit_syntax.is_some()
14 || ctx.record_pat_syntax.is_some()
15 || ctx.attribute_under_caret.is_some()
16 {
17 return;
18 }
19
20 if let Some(ty) = &ctx.expected_type {
21 complete_enum_variants(acc, ctx, ty);
22 }
23
24 if ctx.is_pat_binding_or_const {
25 return;
26 }
27
28 ctx.scope.process_all_names(&mut |name, res| {
29 if ctx.use_item_syntax.is_some() {
30 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
31 if name_ref.syntax().text() == name.to_string().as_str() {
32 mark::hit!(self_fulfilling_completion);
33 return;
34 }
35 }
36 }
37 acc.add_resolution(ctx, name.to_string(), &res)
38 });
39}
40
41fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
42 if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
43 let variants = enum_data.variants(ctx.db);
44
45 let module = if let Some(module) = ctx.scope.module() {
46 // Compute path from the completion site if available.
47 module
48 } else {
49 // Otherwise fall back to the enum's definition site.
50 enum_data.module(ctx.db)
51 };
52
53 for variant in variants {
54 if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
55 // Variants with trivial paths are already added by the existing completion logic,
56 // so we should avoid adding these twice
57 if path.segments.len() > 1 {
58 acc.add_qualified_enum_variant(ctx, variant, path);
59 }
60 }
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use expect_test::{expect, Expect};
68 use test_utils::mark;
69
70 use crate::completion::{
71 test_utils::{check_edit, completion_list},
72 CompletionKind,
73 };
74
75 fn check(ra_fixture: &str, expect: Expect) {
76 let actual = completion_list(ra_fixture, CompletionKind::Reference);
77 expect.assert_eq(&actual)
78 }
79
80 #[test]
81 fn self_fulfilling_completion() {
82 mark::check!(self_fulfilling_completion);
83 check(
84 r#"
85use foo<|>
86use std::collections;
87"#,
88 expect![[r#"
89 ?? collections
90 "#]],
91 );
92 }
93
94 #[test]
95 fn bind_pat_and_path_ignore_at() {
96 check(
97 r#"
98enum Enum { A, B }
99fn quux(x: Option<Enum>) {
100 match x {
101 None => (),
102 Some(en<|> @ Enum::A) => (),
103 }
104}
105"#,
106 expect![[""]],
107 );
108 }
109
110 #[test]
111 fn bind_pat_and_path_ignore_ref() {
112 check(
113 r#"
114enum Enum { A, B }
115fn quux(x: Option<Enum>) {
116 match x {
117 None => (),
118 Some(ref en<|>) => (),
119 }
120}
121"#,
122 expect![[""]],
123 );
124 }
125
126 #[test]
127 fn bind_pat_and_path() {
128 check(
129 r#"
130enum Enum { A, B }
131fn quux(x: Option<Enum>) {
132 match x {
133 None => (),
134 Some(En<|>) => (),
135 }
136}
137"#,
138 expect![[r#"
139 en Enum
140 "#]],
141 );
142 }
143
144 #[test]
145 fn completes_bindings_from_let() {
146 check(
147 r#"
148fn quux(x: i32) {
149 let y = 92;
150 1 + <|>;
151 let z = ();
152}
153"#,
154 expect![[r#"
155 fn quux(…) fn quux(x: i32)
156 bn x i32
157 bn y i32
158 "#]],
159 );
160 }
161
162 #[test]
163 fn completes_bindings_from_if_let() {
164 check(
165 r#"
166fn quux() {
167 if let Some(x) = foo() {
168 let y = 92;
169 };
170 if let Some(a) = bar() {
171 let b = 62;
172 1 + <|>
173 }
174}
175"#,
176 expect![[r#"
177 bn a
178 bn b i32
179 fn quux() fn quux()
180 "#]],
181 );
182 }
183
184 #[test]
185 fn completes_bindings_from_for() {
186 check(
187 r#"
188fn quux() {
189 for x in &[1, 2, 3] { <|> }
190}
191"#,
192 expect![[r#"
193 fn quux() fn quux()
194 bn x
195 "#]],
196 );
197 }
198
199 #[test]
200 fn completes_if_prefix_is_keyword() {
201 mark::check!(completes_if_prefix_is_keyword);
202 check_edit(
203 "wherewolf",
204 r#"
205fn main() {
206 let wherewolf = 92;
207 drop(where<|>)
208}
209"#,
210 r#"
211fn main() {
212 let wherewolf = 92;
213 drop(wherewolf)
214}
215"#,
216 )
217 }
218
219 #[test]
220 fn completes_generic_params() {
221 check(
222 r#"fn quux<T>() { <|> }"#,
223 expect![[r#"
224 tp T
225 fn quux() fn quux<T>()
226 "#]],
227 );
228 }
229
230 #[test]
231 fn completes_generic_params_in_struct() {
232 check(
233 r#"struct S<T> { x: <|>}"#,
234 expect![[r#"
235 st S<…>
236 tp Self
237 tp T
238 "#]],
239 );
240 }
241
242 #[test]
243 fn completes_self_in_enum() {
244 check(
245 r#"enum X { Y(<|>) }"#,
246 expect![[r#"
247 tp Self
248 en X
249 "#]],
250 );
251 }
252
253 #[test]
254 fn completes_module_items() {
255 check(
256 r#"
257struct S;
258enum E {}
259fn quux() { <|> }
260"#,
261 expect![[r#"
262 en E
263 st S
264 fn quux() fn quux()
265 "#]],
266 );
267 }
268
269 #[test]
270 fn completes_extern_prelude() {
271 check(
272 r#"
273//- /lib.rs
274use <|>;
275
276//- /other_crate/lib.rs
277// nothing here
278"#,
279 expect![[r#"
280 md other_crate
281 "#]],
282 );
283 }
284
285 #[test]
286 fn completes_module_items_in_nested_modules() {
287 check(
288 r#"
289struct Foo;
290mod m {
291 struct Bar;
292 fn quux() { <|> }
293}
294"#,
295 expect![[r#"
296 st Bar
297 fn quux() fn quux()
298 "#]],
299 );
300 }
301
302 #[test]
303 fn completes_return_type() {
304 check(
305 r#"
306struct Foo;
307fn x() -> <|>
308"#,
309 expect![[r#"
310 st Foo
311 fn x() fn x()
312 "#]],
313 );
314 }
315
316 #[test]
317 fn dont_show_both_completions_for_shadowing() {
318 check(
319 r#"
320fn foo() {
321 let bar = 92;
322 {
323 let bar = 62;
324 drop(<|>)
325 }
326}
327"#,
328 // FIXME: should be only one bar here
329 expect![[r#"
330 bn bar i32
331 bn bar i32
332 fn foo() fn foo()
333 "#]],
334 );
335 }
336
337 #[test]
338 fn completes_self_in_methods() {
339 check(
340 r#"impl S { fn foo(&self) { <|> } }"#,
341 expect![[r#"
342 tp Self
343 bn self &{unknown}
344 "#]],
345 );
346 }
347
348 #[test]
349 fn completes_prelude() {
350 check(
351 r#"
352//- /main.rs
353fn foo() { let x: <|> }
354
355//- /std/lib.rs
356#[prelude_import]
357use prelude::*;
358
359mod prelude { struct Option; }
360"#,
361 expect![[r#"
362 st Option
363 fn foo() fn foo()
364 md std
365 "#]],
366 );
367 }
368
369 #[test]
370 fn completes_std_prelude_if_core_is_defined() {
371 check(
372 r#"
373//- /main.rs
374fn foo() { let x: <|> }
375
376//- /core/lib.rs
377#[prelude_import]
378use prelude::*;
379
380mod prelude { struct Option; }
381
382//- /std/lib.rs
383#[prelude_import]
384use prelude::*;
385
386mod prelude { struct String; }
387"#,
388 expect![[r#"
389 st String
390 md core
391 fn foo() fn foo()
392 md std
393 "#]],
394 );
395 }
396
397 #[test]
398 fn completes_macros_as_value() {
399 check(
400 r#"
401macro_rules! foo { () => {} }
402
403#[macro_use]
404mod m1 {
405 macro_rules! bar { () => {} }
406}
407
408mod m2 {
409 macro_rules! nope { () => {} }
410
411 #[macro_export]
412 macro_rules! baz { () => {} }
413}
414
415fn main() { let v = <|> }
416"#,
417 expect![[r##"
418 ma bar!(…) macro_rules! bar
419 ma baz!(…) #[macro_export]
420 macro_rules! baz
421 ma foo!(…) macro_rules! foo
422 md m1
423 md m2
424 fn main() fn main()
425 "##]],
426 );
427 }
428
429 #[test]
430 fn completes_both_macro_and_value() {
431 check(
432 r#"
433macro_rules! foo { () => {} }
434fn foo() { <|> }
435"#,
436 expect![[r#"
437 ma foo!(…) macro_rules! foo
438 fn foo() fn foo()
439 "#]],
440 );
441 }
442
443 #[test]
444 fn completes_macros_as_type() {
445 check(
446 r#"
447macro_rules! foo { () => {} }
448fn main() { let x: <|> }
449"#,
450 expect![[r#"
451 ma foo!(…) macro_rules! foo
452 fn main() fn main()
453 "#]],
454 );
455 }
456
457 #[test]
458 fn completes_macros_as_stmt() {
459 check(
460 r#"
461macro_rules! foo { () => {} }
462fn main() { <|> }
463"#,
464 expect![[r#"
465 ma foo!(…) macro_rules! foo
466 fn main() fn main()
467 "#]],
468 );
469 }
470
471 #[test]
472 fn completes_local_item() {
473 check(
474 r#"
475fn main() {
476 return f<|>;
477 fn frobnicate() {}
478}
479"#,
480 expect![[r#"
481 fn frobnicate() fn frobnicate()
482 fn main() fn main()
483 "#]],
484 );
485 }
486
487 #[test]
488 fn completes_in_simple_macro_1() {
489 check(
490 r#"
491macro_rules! m { ($e:expr) => { $e } }
492fn quux(x: i32) {
493 let y = 92;
494 m!(<|>);
495}
496"#,
497 expect![[r#"
498 ma m!(…) macro_rules! m
499 fn quux(…) fn quux(x: i32)
500 bn x i32
501 bn y i32
502 "#]],
503 );
504 }
505
506 #[test]
507 fn completes_in_simple_macro_2() {
508 check(
509 r"
510macro_rules! m { ($e:expr) => { $e } }
511fn quux(x: i32) {
512 let y = 92;
513 m!(x<|>);
514}
515",
516 expect![[r#"
517 ma m!(…) macro_rules! m
518 fn quux(…) fn quux(x: i32)
519 bn x i32
520 bn y i32
521 "#]],
522 );
523 }
524
525 #[test]
526 fn completes_in_simple_macro_without_closing_parens() {
527 check(
528 r#"
529macro_rules! m { ($e:expr) => { $e } }
530fn quux(x: i32) {
531 let y = 92;
532 m!(x<|>
533}
534"#,
535 expect![[r#"
536 ma m!(…) macro_rules! m
537 fn quux(…) fn quux(x: i32)
538 bn x i32
539 bn y i32
540 "#]],
541 );
542 }
543
544 #[test]
545 fn completes_unresolved_uses() {
546 check(
547 r#"
548use spam::Quux;
549
550fn main() { <|> }
551"#,
552 expect![[r#"
553 ?? Quux
554 fn main() fn main()
555 "#]],
556 );
557 }
558 #[test]
559 fn completes_enum_variant_matcharm() {
560 check(
561 r#"
562enum Foo { Bar, Baz, Quux }
563
564fn main() {
565 let foo = Foo::Quux;
566 match foo { Qu<|> }
567}
568"#,
569 expect![[r#"
570 en Foo
571 ev Foo::Bar ()
572 ev Foo::Baz ()
573 ev Foo::Quux ()
574 "#]],
575 )
576 }
577
578 #[test]
579 fn completes_enum_variant_iflet() {
580 check(
581 r#"
582enum Foo { Bar, Baz, Quux }
583
584fn main() {
585 let foo = Foo::Quux;
586 if let Qu<|> = foo { }
587}
588"#,
589 expect![[r#"
590 en Foo
591 ev Foo::Bar ()
592 ev Foo::Baz ()
593 ev Foo::Quux ()
594 "#]],
595 )
596 }
597
598 #[test]
599 fn completes_enum_variant_basic_expr() {
600 check(
601 r#"
602enum Foo { Bar, Baz, Quux }
603fn main() { let foo: Foo = Q<|> }
604"#,
605 expect![[r#"
606 en Foo
607 ev Foo::Bar ()
608 ev Foo::Baz ()
609 ev Foo::Quux ()
610 fn main() fn main()
611 "#]],
612 )
613 }
614
615 #[test]
616 fn completes_enum_variant_from_module() {
617 check(
618 r#"
619mod m { pub enum E { V } }
620fn f() -> m::E { V<|> }
621"#,
622 expect![[r#"
623 fn f() fn f() -> m::E
624 md m
625 ev m::E::V ()
626 "#]],
627 )
628 }
629
630 #[test]
631 fn dont_complete_attr() {
632 check(
633 r#"
634struct Foo;
635#[<|>]
636fn f() {}
637"#,
638 expect![[""]],
639 )
640 }
641
642 #[test]
643 fn completes_type_or_trait_in_impl_block() {
644 check(
645 r#"
646trait MyTrait {}
647struct MyStruct {}
648
649impl My<|>
650"#,
651 expect![[r#"
652 st MyStruct
653 tt MyTrait
654 tp Self
655 "#]],
656 )
657 }
658}
diff --git a/crates/ide/src/completion/completion_config.rs b/crates/ide/src/completion/completion_config.rs
new file mode 100644
index 000000000..71b49ace8
--- /dev/null
+++ b/crates/ide/src/completion/completion_config.rs
@@ -0,0 +1,35 @@
1//! Settings for tweaking completion.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! completions if we are allowed to.
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct CompletionConfig {
9 pub enable_postfix_completions: bool,
10 pub add_call_parenthesis: bool,
11 pub add_call_argument_snippets: bool,
12 pub snippet_cap: Option<SnippetCap>,
13}
14
15impl CompletionConfig {
16 pub fn allow_snippets(&mut self, yes: bool) {
17 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct SnippetCap {
23 _private: (),
24}
25
26impl Default for CompletionConfig {
27 fn default() -> Self {
28 CompletionConfig {
29 enable_postfix_completions: true,
30 add_call_parenthesis: true,
31 add_call_argument_snippets: true,
32 snippet_cap: Some(SnippetCap { _private: () }),
33 }
34 }
35}
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs
new file mode 100644
index 000000000..5adac7ebc
--- /dev/null
+++ b/crates/ide/src/completion/completion_context.rs
@@ -0,0 +1,486 @@
1//! FIXME: write short doc here
2
3use base_db::SourceDatabase;
4use hir::{Semantics, SemanticsScope, Type};
5use ide_db::RootDatabase;
6use syntax::{
7 algo::{find_covering_element, find_node_at_offset},
8 ast, match_ast, AstNode, NodeOrToken,
9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextSize,
11};
12use test_utils::mark;
13use text_edit::Indel;
14
15use crate::{
16 call_info::ActiveParameter,
17 completion::{
18 patterns::{
19 has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent,
20 has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
21 has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev,
22 },
23 CompletionConfig,
24 },
25 FilePosition,
26};
27
28/// `CompletionContext` is created early during completion to figure out, where
29/// exactly is the cursor, syntax-wise.
30#[derive(Debug)]
31pub(crate) struct CompletionContext<'a> {
32 pub(super) sema: Semantics<'a, RootDatabase>,
33 pub(super) scope: SemanticsScope<'a>,
34 pub(super) db: &'a RootDatabase,
35 pub(super) config: &'a CompletionConfig,
36 pub(super) position: FilePosition,
37 /// The token before the cursor, in the original file.
38 pub(super) original_token: SyntaxToken,
39 /// The token before the cursor, in the macro-expanded file.
40 pub(super) token: SyntaxToken,
41 pub(super) krate: Option<hir::Crate>,
42 pub(super) expected_type: Option<Type>,
43 pub(super) name_ref_syntax: Option<ast::NameRef>,
44 pub(super) function_syntax: Option<ast::Fn>,
45 pub(super) use_item_syntax: Option<ast::Use>,
46 pub(super) record_lit_syntax: Option<ast::RecordExpr>,
47 pub(super) record_pat_syntax: Option<ast::RecordPat>,
48 pub(super) record_field_syntax: Option<ast::RecordExprField>,
49 pub(super) impl_def: Option<ast::Impl>,
50 /// FIXME: `ActiveParameter` is string-based, which is very very wrong
51 pub(super) active_parameter: Option<ActiveParameter>,
52 pub(super) is_param: bool,
53 /// If a name-binding or reference to a const in a pattern.
54 /// Irrefutable patterns (like let) are excluded.
55 pub(super) is_pat_binding_or_const: bool,
56 /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
57 pub(super) is_trivial_path: bool,
58 /// If not a trivial path, the prefix (qualifier).
59 pub(super) path_qual: Option<ast::Path>,
60 pub(super) after_if: bool,
61 /// `true` if we are a statement or a last expr in the block.
62 pub(super) can_be_stmt: bool,
63 /// `true` if we expect an expression at the cursor position.
64 pub(super) is_expr: bool,
65 /// Something is typed at the "top" level, in module or impl/trait.
66 pub(super) is_new_item: bool,
67 /// The receiver if this is a field or method access, i.e. writing something.<|>
68 pub(super) dot_receiver: Option<ast::Expr>,
69 pub(super) dot_receiver_is_ambiguous_float_literal: bool,
70 /// If this is a call (method or function) in particular, i.e. the () are already there.
71 pub(super) is_call: bool,
72 /// Like `is_call`, but for tuple patterns.
73 pub(super) is_pattern_call: bool,
74 /// If this is a macro call, i.e. the () are already there.
75 pub(super) is_macro_call: bool,
76 pub(super) is_path_type: bool,
77 pub(super) has_type_args: bool,
78 pub(super) attribute_under_caret: Option<ast::Attr>,
79 pub(super) unsafe_is_prev: bool,
80 pub(super) if_is_prev: bool,
81 pub(super) block_expr_parent: bool,
82 pub(super) bind_pat_parent: bool,
83 pub(super) ref_pat_parent: bool,
84 pub(super) in_loop_body: bool,
85 pub(super) has_trait_parent: bool,
86 pub(super) has_impl_parent: bool,
87 pub(super) trait_as_prev_sibling: bool,
88 pub(super) impl_as_prev_sibling: bool,
89 pub(super) is_match_arm: bool,
90 pub(super) has_item_list_or_source_file_parent: bool,
91}
92
93impl<'a> CompletionContext<'a> {
94 pub(super) fn new(
95 db: &'a RootDatabase,
96 position: FilePosition,
97 config: &'a CompletionConfig,
98 ) -> Option<CompletionContext<'a>> {
99 let sema = Semantics::new(db);
100
101 let original_file = sema.parse(position.file_id);
102
103 // Insert a fake ident to get a valid parse tree. We will use this file
104 // to determine context, though the original_file will be used for
105 // actual completion.
106 let file_with_fake_ident = {
107 let parse = db.parse(position.file_id);
108 let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
109 parse.reparse(&edit).tree()
110 };
111 let fake_ident_token =
112 file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
113
114 let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
115 let original_token =
116 original_file.syntax().token_at_offset(position.offset).left_biased()?;
117 let token = sema.descend_into_macros(original_token.clone());
118 let scope = sema.scope_at_offset(&token.parent(), position.offset);
119 let mut ctx = CompletionContext {
120 sema,
121 scope,
122 db,
123 config,
124 original_token,
125 token,
126 position,
127 krate,
128 expected_type: None,
129 name_ref_syntax: None,
130 function_syntax: None,
131 use_item_syntax: None,
132 record_lit_syntax: None,
133 record_pat_syntax: None,
134 record_field_syntax: None,
135 impl_def: None,
136 active_parameter: ActiveParameter::at(db, position),
137 is_param: false,
138 is_pat_binding_or_const: false,
139 is_trivial_path: false,
140 path_qual: None,
141 after_if: false,
142 can_be_stmt: false,
143 is_expr: false,
144 is_new_item: false,
145 dot_receiver: None,
146 is_call: false,
147 is_pattern_call: false,
148 is_macro_call: false,
149 is_path_type: false,
150 has_type_args: false,
151 dot_receiver_is_ambiguous_float_literal: false,
152 attribute_under_caret: None,
153 unsafe_is_prev: false,
154 in_loop_body: false,
155 ref_pat_parent: false,
156 bind_pat_parent: false,
157 block_expr_parent: false,
158 has_trait_parent: false,
159 has_impl_parent: false,
160 trait_as_prev_sibling: false,
161 impl_as_prev_sibling: false,
162 if_is_prev: false,
163 is_match_arm: false,
164 has_item_list_or_source_file_parent: false,
165 };
166
167 let mut original_file = original_file.syntax().clone();
168 let mut hypothetical_file = file_with_fake_ident.syntax().clone();
169 let mut offset = position.offset;
170 let mut fake_ident_token = fake_ident_token;
171
172 // Are we inside a macro call?
173 while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
174 find_node_at_offset::<ast::MacroCall>(&original_file, offset),
175 find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
176 ) {
177 if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
178 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
179 {
180 break;
181 }
182 let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
183 Some(tt) => tt,
184 None => break,
185 };
186 if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
187 ctx.sema.expand(&actual_macro_call),
188 ctx.sema.speculative_expand(
189 &actual_macro_call,
190 &hypothetical_args,
191 fake_ident_token,
192 ),
193 ) {
194 let new_offset = hypothetical_expansion.1.text_range().start();
195 if new_offset > actual_expansion.text_range().end() {
196 break;
197 }
198 original_file = actual_expansion;
199 hypothetical_file = hypothetical_expansion.0;
200 fake_ident_token = hypothetical_expansion.1;
201 offset = new_offset;
202 } else {
203 break;
204 }
205 }
206 ctx.fill_keyword_patterns(&hypothetical_file, offset);
207 ctx.fill(&original_file, hypothetical_file, offset);
208 Some(ctx)
209 }
210
211 // The range of the identifier that is being completed.
212 pub(crate) fn source_range(&self) -> TextRange {
213 // check kind of macro-expanded token, but use range of original token
214 if self.token.kind() == IDENT || self.token.kind().is_keyword() {
215 mark::hit!(completes_if_prefix_is_keyword);
216 self.original_token.text_range()
217 } else {
218 TextRange::empty(self.position.offset)
219 }
220 }
221
222 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
223 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
224 let syntax_element = NodeOrToken::Token(fake_ident_token);
225 self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
226 self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
227 self.if_is_prev = if_is_prev(syntax_element.clone());
228 self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
229 self.ref_pat_parent = has_ref_parent(syntax_element.clone());
230 self.in_loop_body = is_in_loop_body(syntax_element.clone());
231 self.has_trait_parent = has_trait_parent(syntax_element.clone());
232 self.has_impl_parent = has_impl_parent(syntax_element.clone());
233 self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
234 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
235 self.is_match_arm = is_match_arm(syntax_element.clone());
236 self.has_item_list_or_source_file_parent =
237 has_item_list_or_source_file_parent(syntax_element);
238 }
239
240 fn fill(
241 &mut self,
242 original_file: &SyntaxNode,
243 file_with_fake_ident: SyntaxNode,
244 offset: TextSize,
245 ) {
246 // FIXME: this is wrong in at least two cases:
247 // * when there's no token `foo(<|>)`
248 // * when there is a token, but it happens to have type of it's own
249 self.expected_type = self
250 .token
251 .ancestors()
252 .find_map(|node| {
253 let ty = match_ast! {
254 match node {
255 ast::Pat(it) => self.sema.type_of_pat(&it),
256 ast::Expr(it) => self.sema.type_of_expr(&it),
257 _ => return None,
258 }
259 };
260 Some(ty)
261 })
262 .flatten();
263 self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
264
265 // First, let's try to complete a reference to some declaration.
266 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
267 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
268 // See RFC#1685.
269 if is_node::<ast::Param>(name_ref.syntax()) {
270 self.is_param = true;
271 return;
272 }
273 // FIXME: remove this (V) duplication and make the check more precise
274 if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
275 self.record_pat_syntax =
276 self.sema.find_node_at_offset_with_macros(&original_file, offset);
277 }
278 self.classify_name_ref(original_file, name_ref, offset);
279 }
280
281 // Otherwise, see if this is a declaration. We can use heuristics to
282 // suggest declaration names, see `CompletionKind::Magic`.
283 if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
284 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) {
285 self.is_pat_binding_or_const = true;
286 if bind_pat.at_token().is_some()
287 || bind_pat.ref_token().is_some()
288 || bind_pat.mut_token().is_some()
289 {
290 self.is_pat_binding_or_const = false;
291 }
292 if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
293 self.is_pat_binding_or_const = false;
294 }
295 if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) {
296 if let Some(pat) = let_stmt.pat() {
297 if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range())
298 {
299 self.is_pat_binding_or_const = false;
300 }
301 }
302 }
303 }
304 if is_node::<ast::Param>(name.syntax()) {
305 self.is_param = true;
306 return;
307 }
308 // FIXME: remove this (^) duplication and make the check more precise
309 if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
310 self.record_pat_syntax =
311 self.sema.find_node_at_offset_with_macros(&original_file, offset);
312 }
313 }
314 }
315
316 fn classify_name_ref(
317 &mut self,
318 original_file: &SyntaxNode,
319 name_ref: ast::NameRef,
320 offset: TextSize,
321 ) {
322 self.name_ref_syntax =
323 find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
324 let name_range = name_ref.syntax().text_range();
325 if ast::RecordExprField::for_field_name(&name_ref).is_some() {
326 self.record_lit_syntax =
327 self.sema.find_node_at_offset_with_macros(&original_file, offset);
328 }
329
330 self.impl_def = self
331 .sema
332 .ancestors_with_macros(self.token.parent())
333 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
334 .find_map(ast::Impl::cast);
335
336 let top_node = name_ref
337 .syntax()
338 .ancestors()
339 .take_while(|it| it.text_range() == name_range)
340 .last()
341 .unwrap();
342
343 match top_node.parent().map(|it| it.kind()) {
344 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
345 self.is_new_item = true;
346 return;
347 }
348 _ => (),
349 }
350
351 self.use_item_syntax =
352 self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast);
353
354 self.function_syntax = self
355 .sema
356 .ancestors_with_macros(self.token.parent())
357 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
358 .find_map(ast::Fn::cast);
359
360 self.record_field_syntax = self
361 .sema
362 .ancestors_with_macros(self.token.parent())
363 .take_while(|it| {
364 it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR
365 })
366 .find_map(ast::RecordExprField::cast);
367
368 let parent = match name_ref.syntax().parent() {
369 Some(it) => it,
370 None => return,
371 };
372
373 if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
374 let path = segment.parent_path();
375 self.is_call = path
376 .syntax()
377 .parent()
378 .and_then(ast::PathExpr::cast)
379 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
380 .is_some();
381 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
382 self.is_pattern_call =
383 path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some();
384
385 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
386 self.has_type_args = segment.generic_arg_list().is_some();
387
388 if let Some(path) = path_or_use_tree_qualifier(&path) {
389 self.path_qual = path
390 .segment()
391 .and_then(|it| {
392 find_node_with_range::<ast::PathSegment>(
393 original_file,
394 it.syntax().text_range(),
395 )
396 })
397 .map(|it| it.parent_path());
398 return;
399 }
400
401 if let Some(segment) = path.segment() {
402 if segment.coloncolon_token().is_some() {
403 return;
404 }
405 }
406
407 self.is_trivial_path = true;
408
409 // Find either enclosing expr statement (thing with `;`) or a
410 // block. If block, check that we are the last expr.
411 self.can_be_stmt = name_ref
412 .syntax()
413 .ancestors()
414 .find_map(|node| {
415 if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
416 return Some(stmt.syntax().text_range() == name_ref.syntax().text_range());
417 }
418 if let Some(block) = ast::BlockExpr::cast(node) {
419 return Some(
420 block.expr().map(|e| e.syntax().text_range())
421 == Some(name_ref.syntax().text_range()),
422 );
423 }
424 None
425 })
426 .unwrap_or(false);
427 self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some();
428
429 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
430 if let Some(if_expr) =
431 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
432 {
433 if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start()
434 {
435 self.after_if = true;
436 }
437 }
438 }
439 }
440 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
441 // The receiver comes before the point of insertion of the fake
442 // ident, so it should have the same range in the non-modified file
443 self.dot_receiver = field_expr
444 .expr()
445 .map(|e| e.syntax().text_range())
446 .and_then(|r| find_node_with_range(original_file, r));
447 self.dot_receiver_is_ambiguous_float_literal =
448 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
449 match l.kind() {
450 ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
451 _ => false,
452 }
453 } else {
454 false
455 }
456 }
457 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
458 // As above
459 self.dot_receiver = method_call_expr
460 .receiver()
461 .map(|e| e.syntax().text_range())
462 .and_then(|r| find_node_with_range(original_file, r));
463 self.is_call = true;
464 }
465 }
466}
467
468fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
469 find_covering_element(syntax, range).ancestors().find_map(N::cast)
470}
471
472fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
473 match node.ancestors().find_map(N::cast) {
474 None => false,
475 Some(n) => n.syntax().text_range() == node.text_range(),
476 }
477}
478
479fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> {
480 if let Some(qual) = path.qualifier() {
481 return Some(qual);
482 }
483 let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
484 let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
485 use_tree.path()
486}
diff --git a/crates/ide/src/completion/completion_item.rs b/crates/ide/src/completion/completion_item.rs
new file mode 100644
index 000000000..9377cdc57
--- /dev/null
+++ b/crates/ide/src/completion/completion_item.rs
@@ -0,0 +1,384 @@
1//! FIXME: write short doc here
2
3use std::fmt;
4
5use hir::Documentation;
6use syntax::TextRange;
7use text_edit::TextEdit;
8
9use crate::completion::completion_config::SnippetCap;
10
11/// `CompletionItem` describes a single completion variant in the editor pop-up.
12/// It is basically a POD with various properties. To construct a
13/// `CompletionItem`, use `new` method and the `Builder` struct.
14pub struct CompletionItem {
15 /// Used only internally in tests, to check only specific kind of
16 /// completion (postfix, keyword, reference, etc).
17 #[allow(unused)]
18 pub(crate) completion_kind: CompletionKind,
19 /// Label in the completion pop up which identifies completion.
20 label: String,
21 /// Range of identifier that is being completed.
22 ///
23 /// It should be used primarily for UI, but we also use this to convert
24 /// genetic TextEdit into LSP's completion edit (see conv.rs).
25 ///
26 /// `source_range` must contain the completion offset. `insert_text` should
27 /// start with what `source_range` points to, or VSCode will filter out the
28 /// completion silently.
29 source_range: TextRange,
30 /// What happens when user selects this item.
31 ///
32 /// Typically, replaces `source_range` with new identifier.
33 text_edit: TextEdit,
34 insert_text_format: InsertTextFormat,
35
36 /// What item (struct, function, etc) are we completing.
37 kind: Option<CompletionItemKind>,
38
39 /// Lookup is used to check if completion item indeed can complete current
40 /// ident.
41 ///
42 /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it
43 /// contains `bar` sub sequence), and `quux` will rejected.
44 lookup: Option<String>,
45
46 /// Additional info to show in the UI pop up.
47 detail: Option<String>,
48 documentation: Option<Documentation>,
49
50 /// Whether this item is marked as deprecated
51 deprecated: bool,
52
53 /// If completing a function call, ask the editor to show parameter popup
54 /// after completion.
55 trigger_call_info: bool,
56
57 /// Score is useful to pre select or display in better order completion items
58 score: Option<CompletionScore>,
59}
60
61// We use custom debug for CompletionItem to make snapshot tests more readable.
62impl fmt::Debug for CompletionItem {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let mut s = f.debug_struct("CompletionItem");
65 s.field("label", &self.label()).field("source_range", &self.source_range());
66 if self.text_edit().len() == 1 {
67 let atom = &self.text_edit().iter().next().unwrap();
68 s.field("delete", &atom.delete);
69 s.field("insert", &atom.insert);
70 } else {
71 s.field("text_edit", &self.text_edit);
72 }
73 if let Some(kind) = self.kind().as_ref() {
74 s.field("kind", kind);
75 }
76 if self.lookup() != self.label() {
77 s.field("lookup", &self.lookup());
78 }
79 if let Some(detail) = self.detail() {
80 s.field("detail", &detail);
81 }
82 if let Some(documentation) = self.documentation() {
83 s.field("documentation", &documentation);
84 }
85 if self.deprecated {
86 s.field("deprecated", &true);
87 }
88 if let Some(score) = &self.score {
89 s.field("score", score);
90 }
91 if self.trigger_call_info {
92 s.field("trigger_call_info", &true);
93 }
94 s.finish()
95 }
96}
97
98#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
99pub enum CompletionScore {
100 /// If only type match
101 TypeMatch,
102 /// If type and name match
103 TypeAndNameMatch,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum CompletionItemKind {
108 Snippet,
109 Keyword,
110 Module,
111 Function,
112 BuiltinType,
113 Struct,
114 Enum,
115 EnumVariant,
116 Binding,
117 Field,
118 Static,
119 Const,
120 Trait,
121 TypeAlias,
122 Method,
123 TypeParam,
124 Macro,
125 Attribute,
126 UnresolvedReference,
127}
128
129impl CompletionItemKind {
130 #[cfg(test)]
131 pub(crate) fn tag(&self) -> &'static str {
132 match self {
133 CompletionItemKind::Attribute => "at",
134 CompletionItemKind::Binding => "bn",
135 CompletionItemKind::BuiltinType => "bt",
136 CompletionItemKind::Const => "ct",
137 CompletionItemKind::Enum => "en",
138 CompletionItemKind::EnumVariant => "ev",
139 CompletionItemKind::Field => "fd",
140 CompletionItemKind::Function => "fn",
141 CompletionItemKind::Keyword => "kw",
142 CompletionItemKind::Macro => "ma",
143 CompletionItemKind::Method => "me",
144 CompletionItemKind::Module => "md",
145 CompletionItemKind::Snippet => "sn",
146 CompletionItemKind::Static => "sc",
147 CompletionItemKind::Struct => "st",
148 CompletionItemKind::Trait => "tt",
149 CompletionItemKind::TypeAlias => "ta",
150 CompletionItemKind::TypeParam => "tp",
151 CompletionItemKind::UnresolvedReference => "??",
152 }
153 }
154}
155
156#[derive(Debug, PartialEq, Eq, Copy, Clone)]
157pub(crate) enum CompletionKind {
158 /// Parser-based keyword completion.
159 Keyword,
160 /// Your usual "complete all valid identifiers".
161 Reference,
162 /// "Secret sauce" completions.
163 Magic,
164 Snippet,
165 Postfix,
166 BuiltinType,
167 Attribute,
168}
169
170#[derive(Debug, PartialEq, Eq, Copy, Clone)]
171pub enum InsertTextFormat {
172 PlainText,
173 Snippet,
174}
175
176impl CompletionItem {
177 pub(crate) fn new(
178 completion_kind: CompletionKind,
179 source_range: TextRange,
180 label: impl Into<String>,
181 ) -> Builder {
182 let label = label.into();
183 Builder {
184 source_range,
185 completion_kind,
186 label,
187 insert_text: None,
188 insert_text_format: InsertTextFormat::PlainText,
189 detail: None,
190 documentation: None,
191 lookup: None,
192 kind: None,
193 text_edit: None,
194 deprecated: None,
195 trigger_call_info: None,
196 score: None,
197 }
198 }
199 /// What user sees in pop-up in the UI.
200 pub fn label(&self) -> &str {
201 &self.label
202 }
203 pub fn source_range(&self) -> TextRange {
204 self.source_range
205 }
206
207 pub fn insert_text_format(&self) -> InsertTextFormat {
208 self.insert_text_format
209 }
210
211 pub fn text_edit(&self) -> &TextEdit {
212 &self.text_edit
213 }
214
215 /// Short one-line additional information, like a type
216 pub fn detail(&self) -> Option<&str> {
217 self.detail.as_deref()
218 }
219 /// A doc-comment
220 pub fn documentation(&self) -> Option<Documentation> {
221 self.documentation.clone()
222 }
223 /// What string is used for filtering.
224 pub fn lookup(&self) -> &str {
225 self.lookup.as_deref().unwrap_or(&self.label)
226 }
227
228 pub fn kind(&self) -> Option<CompletionItemKind> {
229 self.kind
230 }
231
232 pub fn deprecated(&self) -> bool {
233 self.deprecated
234 }
235
236 pub fn score(&self) -> Option<CompletionScore> {
237 self.score
238 }
239
240 pub fn trigger_call_info(&self) -> bool {
241 self.trigger_call_info
242 }
243}
244
245/// A helper to make `CompletionItem`s.
246#[must_use]
247pub(crate) struct Builder {
248 source_range: TextRange,
249 completion_kind: CompletionKind,
250 label: String,
251 insert_text: Option<String>,
252 insert_text_format: InsertTextFormat,
253 detail: Option<String>,
254 documentation: Option<Documentation>,
255 lookup: Option<String>,
256 kind: Option<CompletionItemKind>,
257 text_edit: Option<TextEdit>,
258 deprecated: Option<bool>,
259 trigger_call_info: Option<bool>,
260 score: Option<CompletionScore>,
261}
262
263impl Builder {
264 pub(crate) fn add_to(self, acc: &mut Completions) {
265 acc.add(self.build())
266 }
267
268 pub(crate) fn build(self) -> CompletionItem {
269 let label = self.label;
270 let text_edit = match self.text_edit {
271 Some(it) => it,
272 None => TextEdit::replace(
273 self.source_range,
274 self.insert_text.unwrap_or_else(|| label.clone()),
275 ),
276 };
277
278 CompletionItem {
279 source_range: self.source_range,
280 label,
281 insert_text_format: self.insert_text_format,
282 text_edit,
283 detail: self.detail,
284 documentation: self.documentation,
285 lookup: self.lookup,
286 kind: self.kind,
287 completion_kind: self.completion_kind,
288 deprecated: self.deprecated.unwrap_or(false),
289 trigger_call_info: self.trigger_call_info.unwrap_or(false),
290 score: self.score,
291 }
292 }
293 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
294 self.lookup = Some(lookup.into());
295 self
296 }
297 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
298 self.label = label.into();
299 self
300 }
301 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
302 self.insert_text = Some(insert_text.into());
303 self
304 }
305 pub(crate) fn insert_snippet(
306 mut self,
307 _cap: SnippetCap,
308 snippet: impl Into<String>,
309 ) -> Builder {
310 self.insert_text_format = InsertTextFormat::Snippet;
311 self.insert_text(snippet)
312 }
313 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
314 self.kind = Some(kind);
315 self
316 }
317 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
318 self.text_edit = Some(edit);
319 self
320 }
321 pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
322 self.insert_text_format = InsertTextFormat::Snippet;
323 self.text_edit(edit)
324 }
325 #[allow(unused)]
326 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
327 self.set_detail(Some(detail))
328 }
329 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
330 self.detail = detail.map(Into::into);
331 self
332 }
333 #[allow(unused)]
334 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
335 self.set_documentation(Some(docs))
336 }
337 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
338 self.documentation = docs.map(Into::into);
339 self
340 }
341 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
342 self.deprecated = Some(deprecated);
343 self
344 }
345 pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
346 self.score = Some(score);
347 self
348 }
349 pub(crate) fn trigger_call_info(mut self) -> Builder {
350 self.trigger_call_info = Some(true);
351 self
352 }
353}
354
355impl<'a> Into<CompletionItem> for Builder {
356 fn into(self) -> CompletionItem {
357 self.build()
358 }
359}
360
361/// Represents an in-progress set of completions being built.
362#[derive(Debug, Default)]
363pub(crate) struct Completions {
364 buf: Vec<CompletionItem>,
365}
366
367impl Completions {
368 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
369 self.buf.push(item.into())
370 }
371 pub(crate) fn add_all<I>(&mut self, items: I)
372 where
373 I: IntoIterator,
374 I::Item: Into<CompletionItem>,
375 {
376 items.into_iter().for_each(|item| self.add(item.into()))
377 }
378}
379
380impl Into<Vec<CompletionItem>> for Completions {
381 fn into(self) -> Vec<CompletionItem> {
382 self.buf
383 }
384}
diff --git a/crates/ide/src/completion/generated_features.rs b/crates/ide/src/completion/generated_features.rs
new file mode 100644
index 000000000..24754a8cf
--- /dev/null
+++ b/crates/ide/src/completion/generated_features.rs
@@ -0,0 +1,4 @@
1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2
3use crate::completion::complete_attribute::LintCompletion;
4pub ( super ) const FEATURES : & [ LintCompletion ] = & [ LintCompletion { label : "doc_cfg" , description : "# `doc_cfg`\n\nThe tracking issue for this feature is: [#43781]\n\n------\n\nThe `doc_cfg` feature allows an API be documented as only available in some specific platforms.\nThis attribute has two effects:\n\n1. In the annotated item's documentation, there will be a message saying \"This is supported on\n (platform) only\".\n\n2. The item's doc-tests will only run on the specific platform.\n\nIn addition to allowing the use of the `#[doc(cfg)]` attribute, this feature enables the use of a\nspecial conditional compilation flag, `#[cfg(doc)]`, set whenever building documentation on your\ncrate.\n\nThis feature was introduced as part of PR [#43348] to allow the platform-specific parts of the\nstandard library be documented.\n\n```rust\n#![feature(doc_cfg)]\n\n#[cfg(any(windows, doc))]\n#[doc(cfg(windows))]\n/// The application's icon in the notification area (a.k.a. system tray).\n///\n/// # Examples\n///\n/// ```no_run\n/// extern crate my_awesome_ui_library;\n/// use my_awesome_ui_library::current_app;\n/// use my_awesome_ui_library::windows::notification;\n///\n/// let icon = current_app().get::<notification::Icon>();\n/// icon.show();\n/// icon.show_message(\"Hello\");\n/// ```\npub struct Icon {\n // ...\n}\n```\n\n[#43781]: https://github.com/rust-lang/rust/issues/43781\n[#43348]: https://github.com/rust-lang/rust/issues/43348\n" } , LintCompletion { label : "impl_trait_in_bindings" , description : "# `impl_trait_in_bindings`\n\nThe tracking issue for this feature is: [#63065]\n\n[#63065]: https://github.com/rust-lang/rust/issues/63065\n\n------------------------\n\nThe `impl_trait_in_bindings` feature gate lets you use `impl Trait` syntax in\n`let`, `static`, and `const` bindings.\n\nA simple example is:\n\n```rust\n#![feature(impl_trait_in_bindings)]\n\nuse std::fmt::Debug;\n\nfn main() {\n let a: impl Debug + Clone = 42;\n let b = a.clone();\n println!(\"{:?}\", b); // prints `42`\n}\n```\n\nNote however that because the types of `a` and `b` are opaque in the above\nexample, calling inherent methods or methods outside of the specified traits\n(e.g., `a.abs()` or `b.abs()`) is not allowed, and yields an error.\n" } , LintCompletion { label : "plugin" , description : "# `plugin`\n\nThe tracking issue for this feature is: [#29597]\n\n[#29597]: https://github.com/rust-lang/rust/issues/29597\n\n\nThis feature is part of \"compiler plugins.\" It will often be used with the\n[`plugin_registrar`] and `rustc_private` features.\n\n[`plugin_registrar`]: plugin-registrar.md\n\n------------------------\n\n`rustc` can load compiler plugins, which are user-provided libraries that\nextend the compiler's behavior with new lint checks, etc.\n\nA plugin is a dynamic library crate with a designated *registrar* function that\nregisters extensions with `rustc`. Other crates can load these extensions using\nthe crate attribute `#![plugin(...)]`. See the\n`rustc_driver::plugin` documentation for more about the\nmechanics of defining and loading a plugin.\n\nIn the vast majority of cases, a plugin should *only* be used through\n`#![plugin]` and not through an `extern crate` item. Linking a plugin would\npull in all of librustc_ast and librustc as dependencies of your crate. This is\ngenerally unwanted unless you are building another plugin.\n\nThe usual practice is to put compiler plugins in their own crate, separate from\nany `macro_rules!` macros or ordinary Rust code meant to be used by consumers\nof a library.\n\n# Lint plugins\n\nPlugins can extend [Rust's lint\ninfrastructure](../../reference/attributes/diagnostics.md#lint-check-attributes) with\nadditional checks for code style, safety, etc. Now let's write a plugin\n[`lint-plugin-test.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs)\nthat warns about any item named `lintme`.\n\n```rust,ignore\n#![feature(plugin_registrar)]\n#![feature(box_syntax, rustc_private)]\n\nextern crate rustc_ast;\n\n// Load rustc as a plugin to get macros\nextern crate rustc_driver;\n#[macro_use]\nextern crate rustc_lint;\n#[macro_use]\nextern crate rustc_session;\n\nuse rustc_driver::plugin::Registry;\nuse rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};\nuse rustc_ast::ast;\ndeclare_lint!(TEST_LINT, Warn, \"Warn about items named 'lintme'\");\n\ndeclare_lint_pass!(Pass => [TEST_LINT]);\n\nimpl EarlyLintPass for Pass {\n fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {\n if it.ident.name.as_str() == \"lintme\" {\n cx.lint(TEST_LINT, |lint| {\n lint.build(\"item is named 'lintme'\").set_span(it.span).emit()\n });\n }\n }\n}\n\n#[plugin_registrar]\npub fn plugin_registrar(reg: &mut Registry) {\n reg.lint_store.register_lints(&[&TEST_LINT]);\n reg.lint_store.register_early_pass(|| box Pass);\n}\n```\n\nThen code like\n\n```rust,ignore\n#![feature(plugin)]\n#![plugin(lint_plugin_test)]\n\nfn lintme() { }\n```\n\nwill produce a compiler warning:\n\n```txt\nfoo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default\nfoo.rs:4 fn lintme() { }\n ^~~~~~~~~~~~~~~\n```\n\nThe components of a lint plugin are:\n\n* one or more `declare_lint!` invocations, which define static `Lint` structs;\n\n* a struct holding any state needed by the lint pass (here, none);\n\n* a `LintPass`\n implementation defining how to check each syntax element. A single\n `LintPass` may call `span_lint` for several different `Lint`s, but should\n register them all through the `get_lints` method.\n\nLint passes are syntax traversals, but they run at a late stage of compilation\nwhere type information is available. `rustc`'s [built-in\nlints](https://github.com/rust-lang/rust/blob/master/src/librustc_session/lint/builtin.rs)\nmostly use the same infrastructure as lint plugins, and provide examples of how\nto access type information.\n\nLints defined by plugins are controlled by the usual [attributes and compiler\nflags](../../reference/attributes/diagnostics.md#lint-check-attributes), e.g.\n`#[allow(test_lint)]` or `-A test-lint`. These identifiers are derived from the\nfirst argument to `declare_lint!`, with appropriate case and punctuation\nconversion.\n\nYou can run `rustc -W help foo.rs` to see a list of lints known to `rustc`,\nincluding those provided by plugins loaded by `foo.rs`.\n" } , LintCompletion { label : "infer_static_outlives_requirements" , description : "# `infer_static_outlives_requirements`\n\nThe tracking issue for this feature is: [#54185]\n\n[#54185]: https://github.com/rust-lang/rust/issues/54185\n\n------------------------\nThe `infer_static_outlives_requirements` feature indicates that certain\n`'static` outlives requirements can be inferred by the compiler rather than\nstating them explicitly.\n\nNote: It is an accompanying feature to `infer_outlives_requirements`,\nwhich must be enabled to infer outlives requirements.\n\nFor example, currently generic struct definitions that contain\nreferences, require where-clauses of the form T: 'static. By using\nthis feature the outlives predicates will be inferred, although\nthey may still be written explicitly.\n\n```rust,ignore (pseudo-Rust)\nstruct Foo<U> where U: 'static { // <-- currently required\n bar: Bar<U>\n}\nstruct Bar<T: 'static> {\n x: T,\n}\n```\n\n\n## Examples:\n\n```rust,ignore (pseudo-Rust)\n#![feature(infer_outlives_requirements)]\n#![feature(infer_static_outlives_requirements)]\n\n#[rustc_outlives]\n// Implicitly infer U: 'static\nstruct Foo<U> {\n bar: Bar<U>\n}\nstruct Bar<T: 'static> {\n x: T,\n}\n```\n\n" } , LintCompletion { label : "doc_alias" , description : "# `doc_alias`\n\nThe tracking issue for this feature is: [#50146]\n\n[#50146]: https://github.com/rust-lang/rust/issues/50146\n\n------------------------\n\nYou can add alias(es) to an item when using the `rustdoc` search through the\n`doc(alias)` attribute. Example:\n\n```rust,no_run\n#![feature(doc_alias)]\n\n#[doc(alias = \"x\")]\n#[doc(alias = \"big\")]\npub struct BigX;\n```\n\nThen, when looking for it through the `rustdoc` search, if you enter \"x\" or\n\"big\", search will show the `BigX` struct first.\n\nNote that this feature is currently hidden behind the `feature(doc_alias)` gate.\n" } , LintCompletion { label : "optin_builtin_traits" , description : "# `optin_builtin_traits`\n\nThe tracking issue for this feature is [#13231] \n\n[#13231]: https://github.com/rust-lang/rust/issues/13231\n\n----\n\nThe `optin_builtin_traits` feature gate allows you to define auto traits.\n\nAuto traits, like [`Send`] or [`Sync`] in the standard library, are marker traits\nthat are automatically implemented for every type, unless the type, or a type it contains, \nhas explicitly opted out via a negative impl. (Negative impls are separately controlled\nby the `negative_impls` feature.)\n\n[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html\n[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html\n\n```rust,ignore\nimpl !Trait for Type\n```\n\nExample:\n\n```rust\n#![feature(negative_impls)]\n#![feature(optin_builtin_traits)]\n\nauto trait Valid {}\n\nstruct True;\nstruct False;\n\nimpl !Valid for False {}\n\nstruct MaybeValid<T>(T);\n\nfn must_be_valid<T: Valid>(_t: T) { }\n\nfn main() {\n // works\n must_be_valid( MaybeValid(True) );\n \n // compiler error - trait bound not satisfied\n // must_be_valid( MaybeValid(False) );\n}\n```\n\n## Automatic trait implementations\n\nWhen a type is declared as an `auto trait`, we will automatically\ncreate impls for every struct/enum/union, unless an explicit impl is\nprovided. These automatic impls contain a where clause for each field\nof the form `T: AutoTrait`, where `T` is the type of the field and\n`AutoTrait` is the auto trait in question. As an example, consider the\nstruct `List` and the auto trait `Send`:\n\n```rust\nstruct List<T> {\n data: T,\n next: Option<Box<List<T>>>,\n}\n```\n\nPresuming that there is no explicit impl of `Send` for `List`, the\ncompiler will supply an automatic impl of the form:\n\n```rust\nstruct List<T> {\n data: T,\n next: Option<Box<List<T>>>,\n}\n\nunsafe impl<T> Send for List<T>\nwhere\n T: Send, // from the field `data`\n Option<Box<List<T>>>: Send, // from the field `next`\n{ }\n```\n\nExplicit impls may be either positive or negative. They take the form:\n\n```rust,ignore\nimpl<...> AutoTrait for StructName<..> { }\nimpl<...> !AutoTrait for StructName<..> { }\n```\n\n## Coinduction: Auto traits permit cyclic matching\n\nUnlike ordinary trait matching, auto traits are **coinductive**. This\nmeans, in short, that cycles which occur in trait matching are\nconsidered ok. As an example, consider the recursive struct `List`\nintroduced in the previous section. In attempting to determine whether\n`List: Send`, we would wind up in a cycle: to apply the impl, we must\nshow that `Option<Box<List>>: Send`, which will in turn require\n`Box<List>: Send` and then finally `List: Send` again. Under ordinary\ntrait matching, this cycle would be an error, but for an auto trait it\nis considered a successful match.\n\n## Items\n\nAuto traits cannot have any trait items, such as methods or associated types. This ensures that we can generate default implementations.\n\n## Supertraits\n\nAuto traits cannot have supertraits. This is for soundness reasons, as the interaction of coinduction with implied bounds is difficult to reconcile.\n\n" } , LintCompletion { label : "const_in_array_repeat_expressions" , description : "# `const_in_array_repeat_expressions`\n\nThe tracking issue for this feature is: [#49147]\n\n[#49147]: https://github.com/rust-lang/rust/issues/49147\n\n------------------------\n\nRelaxes the rules for repeat expressions, `[x; N]` such that `x` may also be `const` (strictly\nspeaking rvalue promotable), in addition to `typeof(x): Copy`. The result of `[x; N]` where `x` is\n`const` is itself also `const`.\n" } , LintCompletion { label : "generators" , description : "# `generators`\n\nThe tracking issue for this feature is: [#43122]\n\n[#43122]: https://github.com/rust-lang/rust/issues/43122\n\n------------------------\n\nThe `generators` feature gate in Rust allows you to define generator or\ncoroutine literals. A generator is a \"resumable function\" that syntactically\nresembles a closure but compiles to much different semantics in the compiler\nitself. The primary feature of a generator is that it can be suspended during\nexecution to be resumed at a later date. Generators use the `yield` keyword to\n\"return\", and then the caller can `resume` a generator to resume execution just\nafter the `yield` keyword.\n\nGenerators are an extra-unstable feature in the compiler right now. Added in\n[RFC 2033] they're mostly intended right now as a information/constraint\ngathering phase. The intent is that experimentation can happen on the nightly\ncompiler before actual stabilization. A further RFC will be required to\nstabilize generators/coroutines and will likely contain at least a few small\ntweaks to the overall design.\n\n[RFC 2033]: https://github.com/rust-lang/rfcs/pull/2033\n\nA syntactical example of a generator is:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::{Generator, GeneratorState};\nuse std::pin::Pin;\n\nfn main() {\n let mut generator = || {\n yield 1;\n return \"foo\"\n };\n\n match Pin::new(&mut generator).resume(()) {\n GeneratorState::Yielded(1) => {}\n _ => panic!(\"unexpected value from resume\"),\n }\n match Pin::new(&mut generator).resume(()) {\n GeneratorState::Complete(\"foo\") => {}\n _ => panic!(\"unexpected value from resume\"),\n }\n}\n```\n\nGenerators are closure-like literals which can contain a `yield` statement. The\n`yield` statement takes an optional expression of a value to yield out of the\ngenerator. All generator literals implement the `Generator` trait in the\n`std::ops` module. The `Generator` trait has one main method, `resume`, which\nresumes execution of the generator at the previous suspension point.\n\nAn example of the control flow of generators is that the following example\nprints all numbers in order:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::Generator;\nuse std::pin::Pin;\n\nfn main() {\n let mut generator = || {\n println!(\"2\");\n yield;\n println!(\"4\");\n };\n\n println!(\"1\");\n Pin::new(&mut generator).resume(());\n println!(\"3\");\n Pin::new(&mut generator).resume(());\n println!(\"5\");\n}\n```\n\nAt this time the main intended use case of generators is an implementation\nprimitive for async/await syntax, but generators will likely be extended to\nergonomic implementations of iterators and other primitives in the future.\nFeedback on the design and usage is always appreciated!\n\n### The `Generator` trait\n\nThe `Generator` trait in `std::ops` currently looks like:\n\n```rust\n# #![feature(arbitrary_self_types, generator_trait)]\n# use std::ops::GeneratorState;\n# use std::pin::Pin;\n\npub trait Generator<R = ()> {\n type Yield;\n type Return;\n fn resume(self: Pin<&mut Self>, resume: R) -> GeneratorState<Self::Yield, Self::Return>;\n}\n```\n\nThe `Generator::Yield` type is the type of values that can be yielded with the\n`yield` statement. The `Generator::Return` type is the returned type of the\ngenerator. This is typically the last expression in a generator's definition or\nany value passed to `return` in a generator. The `resume` function is the entry\npoint for executing the `Generator` itself.\n\nThe return value of `resume`, `GeneratorState`, looks like:\n\n```rust\npub enum GeneratorState<Y, R> {\n Yielded(Y),\n Complete(R),\n}\n```\n\nThe `Yielded` variant indicates that the generator can later be resumed. This\ncorresponds to a `yield` point in a generator. The `Complete` variant indicates\nthat the generator is complete and cannot be resumed again. Calling `resume`\nafter a generator has returned `Complete` will likely result in a panic of the\nprogram.\n\n### Closure-like semantics\n\nThe closure-like syntax for generators alludes to the fact that they also have\nclosure-like semantics. Namely:\n\n* When created, a generator executes no code. A closure literal does not\n actually execute any of the closure's code on construction, and similarly a\n generator literal does not execute any code inside the generator when\n constructed.\n\n* Generators can capture outer variables by reference or by move, and this can\n be tweaked with the `move` keyword at the beginning of the closure. Like\n closures all generators will have an implicit environment which is inferred by\n the compiler. Outer variables can be moved into a generator for use as the\n generator progresses.\n\n* Generator literals produce a value with a unique type which implements the\n `std::ops::Generator` trait. This allows actual execution of the generator\n through the `Generator::resume` method as well as also naming it in return\n types and such.\n\n* Traits like `Send` and `Sync` are automatically implemented for a `Generator`\n depending on the captured variables of the environment. Unlike closures,\n generators also depend on variables live across suspension points. This means\n that although the ambient environment may be `Send` or `Sync`, the generator\n itself may not be due to internal variables live across `yield` points being\n not-`Send` or not-`Sync`. Note that generators do\n not implement traits like `Copy` or `Clone` automatically.\n\n* Whenever a generator is dropped it will drop all captured environment\n variables.\n\n### Generators as state machines\n\nIn the compiler, generators are currently compiled as state machines. Each\n`yield` expression will correspond to a different state that stores all live\nvariables over that suspension point. Resumption of a generator will dispatch on\nthe current state and then execute internally until a `yield` is reached, at\nwhich point all state is saved off in the generator and a value is returned.\n\nLet's take a look at an example to see what's going on here:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::Generator;\nuse std::pin::Pin;\n\nfn main() {\n let ret = \"foo\";\n let mut generator = move || {\n yield 1;\n return ret\n };\n\n Pin::new(&mut generator).resume(());\n Pin::new(&mut generator).resume(());\n}\n```\n\nThis generator literal will compile down to something similar to:\n\n```rust\n#![feature(arbitrary_self_types, generators, generator_trait)]\n\nuse std::ops::{Generator, GeneratorState};\nuse std::pin::Pin;\n\nfn main() {\n let ret = \"foo\";\n let mut generator = {\n enum __Generator {\n Start(&'static str),\n Yield1(&'static str),\n Done,\n }\n\n impl Generator for __Generator {\n type Yield = i32;\n type Return = &'static str;\n\n fn resume(mut self: Pin<&mut Self>, resume: ()) -> GeneratorState<i32, &'static str> {\n use std::mem;\n match mem::replace(&mut *self, __Generator::Done) {\n __Generator::Start(s) => {\n *self = __Generator::Yield1(s);\n GeneratorState::Yielded(1)\n }\n\n __Generator::Yield1(s) => {\n *self = __Generator::Done;\n GeneratorState::Complete(s)\n }\n\n __Generator::Done => {\n panic!(\"generator resumed after completion\")\n }\n }\n }\n }\n\n __Generator::Start(ret)\n };\n\n Pin::new(&mut generator).resume(());\n Pin::new(&mut generator).resume(());\n}\n```\n\nNotably here we can see that the compiler is generating a fresh type,\n`__Generator` in this case. This type has a number of states (represented here\nas an `enum`) corresponding to each of the conceptual states of the generator.\nAt the beginning we're closing over our outer variable `foo` and then that\nvariable is also live over the `yield` point, so it's stored in both states.\n\nWhen the generator starts it'll immediately yield 1, but it saves off its state\njust before it does so indicating that it has reached the yield point. Upon\nresuming again we'll execute the `return ret` which returns the `Complete`\nstate.\n\nHere we can also note that the `Done` state, if resumed, panics immediately as\nit's invalid to resume a completed generator. It's also worth noting that this\nis just a rough desugaring, not a normative specification for what the compiler\ndoes.\n" } , LintCompletion { label : "unsized_tuple_coercion" , description : "# `unsized_tuple_coercion`\n\nThe tracking issue for this feature is: [#42877]\n\n[#42877]: https://github.com/rust-lang/rust/issues/42877\n\n------------------------\n\nThis is a part of [RFC0401]. According to the RFC, there should be an implementation like this:\n\n```rust,ignore\nimpl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}\n```\n\nThis implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:\n\n```rust\n#![feature(unsized_tuple_coercion)]\n\nfn main() {\n let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);\n let y : &([i32; 3], [i32]) = &x;\n assert_eq!(y.1[0], 4);\n}\n```\n\n[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md\n" } , LintCompletion { label : "cfg_version" , description : "# `cfg_version`\n\nThe tracking issue for this feature is: [#64796]\n\n[#64796]: https://github.com/rust-lang/rust/issues/64796\n\n------------------------\n\nThe `cfg_version` feature makes it possible to execute different code\ndepending on the compiler version.\n\n## Examples\n\n```rust\n#![feature(cfg_version)]\n\n#[cfg(version(\"1.42\"))]\nfn a() {\n // ...\n}\n\n#[cfg(not(version(\"1.42\")))]\nfn a() {\n // ...\n}\n\nfn b() {\n if cfg!(version(\"1.42\")) {\n // ...\n } else {\n // ...\n }\n}\n```\n" } , LintCompletion { label : "ffi_const" , description : "# `ffi_const`\n\nThe `#[ffi_const]` attribute applies clang's `const` attribute to foreign\nfunctions declarations.\n\nThat is, `#[ffi_const]` functions shall have no effects except for its return\nvalue, which can only depend on the values of the function parameters, and is\nnot affected by changes to the observable state of the program.\n\nApplying the `#[ffi_const]` attribute to a function that violates these\nrequirements is undefined behaviour.\n\nThis attribute enables Rust to perform common optimizations, like sub-expression\nelimination, and it can avoid emitting some calls in repeated invocations of the\nfunction with the same argument values regardless of other operations being\nperformed in between these functions calls (as opposed to `#[ffi_pure]`\nfunctions).\n\n## Pitfalls\n\nA `#[ffi_const]` function can only read global memory that would not affect\nits return value for the whole execution of the program (e.g. immutable global\nmemory). `#[ffi_const]` functions are referentially-transparent and therefore\nmore strict than `#[ffi_pure]` functions.\n\nA common pitfall involves applying the `#[ffi_const]` attribute to a\nfunction that reads memory through pointer arguments which do not necessarily\npoint to immutable global memory.\n\nA `#[ffi_const]` function that returns unit has no effect on the abstract\nmachine's state, and a `#[ffi_const]` function cannot be `#[ffi_pure]`.\n\nA `#[ffi_const]` function must not diverge, neither via a side effect (e.g. a\ncall to `abort`) nor by infinite loops.\n\nWhen translating C headers to Rust FFI, it is worth verifying for which targets\nthe `const` attribute is enabled in those headers, and using the appropriate\n`cfg` macros in the Rust side to match those definitions. While the semantics of\n`const` are implemented identically by many C and C++ compilers, e.g., clang,\n[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily\nimplemented in this way on all of them. It is therefore also worth verifying\nthat the semantics of the C toolchain used to compile the binary being linked\nagainst are compatible with those of the `#[ffi_const]`.\n\n[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacgigch.html\n[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute\n[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_const.htm\n" } , LintCompletion { label : "const_fn" , description : "# `const_fn`\n\nThe tracking issue for this feature is: [#57563]\n\n[#57563]: https://github.com/rust-lang/rust/issues/57563\n\n------------------------\n\nThe `const_fn` feature allows marking free functions and inherent methods as\n`const`, enabling them to be called in constants contexts, with constant\narguments.\n\n## Examples\n\n```rust\n#![feature(const_fn)]\n\nconst fn double(x: i32) -> i32 {\n x * 2\n}\n\nconst FIVE: i32 = 5;\nconst TEN: i32 = double(FIVE);\n\nfn main() {\n assert_eq!(5, FIVE);\n assert_eq!(10, TEN);\n}\n```\n" } , LintCompletion { label : "unsized_locals" , description : "# `unsized_locals`\n\nThe tracking issue for this feature is: [#48055]\n\n[#48055]: https://github.com/rust-lang/rust/issues/48055\n\n------------------------\n\nThis implements [RFC1909]. When turned on, you can have unsized arguments and locals:\n\n[RFC1909]: https://github.com/rust-lang/rfcs/blob/master/text/1909-unsized-rvalues.md\n\n```rust\n#![feature(unsized_locals)]\n\nuse std::any::Any;\n\nfn main() {\n let x: Box<dyn Any> = Box::new(42);\n let x: dyn Any = *x;\n // ^ unsized local variable\n // ^^ unsized temporary\n foo(x);\n}\n\nfn foo(_: dyn Any) {}\n// ^^^^^^ unsized argument\n```\n\nThe RFC still forbids the following unsized expressions:\n\n```rust,ignore\n#![feature(unsized_locals)]\n\nuse std::any::Any;\n\nstruct MyStruct<T: ?Sized> {\n content: T,\n}\n\nstruct MyTupleStruct<T: ?Sized>(T);\n\nfn answer() -> Box<dyn Any> {\n Box::new(42)\n}\n\nfn main() {\n // You CANNOT have unsized statics.\n static X: dyn Any = *answer(); // ERROR\n const Y: dyn Any = *answer(); // ERROR\n\n // You CANNOT have struct initialized unsized.\n MyStruct { content: *answer() }; // ERROR\n MyTupleStruct(*answer()); // ERROR\n (42, *answer()); // ERROR\n\n // You CANNOT have unsized return types.\n fn my_function() -> dyn Any { *answer() } // ERROR\n\n // You CAN have unsized local variables...\n let mut x: dyn Any = *answer(); // OK\n // ...but you CANNOT reassign to them.\n x = *answer(); // ERROR\n\n // You CANNOT even initialize them separately.\n let y: dyn Any; // OK\n y = *answer(); // ERROR\n\n // Not mentioned in the RFC, but by-move captured variables are also Sized.\n let x: dyn Any = *answer();\n (move || { // ERROR\n let y = x;\n })();\n\n // You CAN create a closure with unsized arguments,\n // but you CANNOT call it.\n // This is an implementation detail and may be changed in the future.\n let f = |x: dyn Any| {};\n f(*answer()); // ERROR\n}\n```\n\n## By-value trait objects\n\nWith this feature, you can have by-value `self` arguments without `Self: Sized` bounds.\n\n```rust\n#![feature(unsized_locals)]\n\ntrait Foo {\n fn foo(self) {}\n}\n\nimpl<T: ?Sized> Foo for T {}\n\nfn main() {\n let slice: Box<[i32]> = Box::new([1, 2, 3]);\n <[i32] as Foo>::foo(*slice);\n}\n```\n\nAnd `Foo` will also be object-safe.\n\n```rust\n#![feature(unsized_locals)]\n\ntrait Foo {\n fn foo(self) {}\n}\n\nimpl<T: ?Sized> Foo for T {}\n\nfn main () {\n let slice: Box<dyn Foo> = Box::new([1, 2, 3]);\n // doesn't compile yet\n <dyn Foo as Foo>::foo(*slice);\n}\n```\n\nOne of the objectives of this feature is to allow `Box<dyn FnOnce>`.\n\n## Variable length arrays\n\nThe RFC also describes an extension to the array literal syntax: `[e; dyn n]`. In the syntax, `n` isn't necessarily a constant expression. The array is dynamically allocated on the stack and has the type of `[T]`, instead of `[T; n]`.\n\n```rust,ignore\n#![feature(unsized_locals)]\n\nfn mergesort<T: Ord>(a: &mut [T]) {\n let mut tmp = [T; dyn a.len()];\n // ...\n}\n\nfn main() {\n let mut a = [3, 1, 5, 6];\n mergesort(&mut a);\n assert_eq!(a, [1, 3, 5, 6]);\n}\n```\n\nVLAs are not implemented yet. The syntax isn't final, either. We may need an alternative syntax for Rust 2015 because, in Rust 2015, expressions like `[e; dyn(1)]` would be ambiguous. One possible alternative proposed in the RFC is `[e; n]`: if `n` captures one or more local variables, then it is considered as `[e; dyn n]`.\n\n## Advisory on stack usage\n\nIt's advised not to casually use the `#![feature(unsized_locals)]` feature. Typical use-cases are:\n\n- When you need a by-value trait objects.\n- When you really need a fast allocation of small temporary arrays.\n\nAnother pitfall is repetitive allocation and temporaries. Currently the compiler simply extends the stack frame every time it encounters an unsized assignment. So for example, the code\n\n```rust\n#![feature(unsized_locals)]\n\nfn main() {\n let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);\n let _x = {{{{{{{{{{*x}}}}}}}}}};\n}\n```\n\nand the code\n\n```rust\n#![feature(unsized_locals)]\n\nfn main() {\n for _ in 0..10 {\n let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);\n let _x = *x;\n }\n}\n```\n\nwill unnecessarily extend the stack frame.\n" } , LintCompletion { label : "or_patterns" , description : "# `or_patterns`\n\nThe tracking issue for this feature is: [#54883]\n\n[#54883]: https://github.com/rust-lang/rust/issues/54883\n\n------------------------\n\nThe `or_pattern` language feature allows `|` to be arbitrarily nested within\na pattern, for example, `Some(A(0) | B(1 | 2))` becomes a valid pattern.\n\n## Examples\n\n```rust,ignore\n#![feature(or_patterns)]\n\npub enum Foo {\n Bar,\n Baz,\n Quux,\n}\n\npub fn example(maybe_foo: Option<Foo>) {\n match maybe_foo {\n Some(Foo::Bar | Foo::Baz) => {\n println!(\"The value contained `Bar` or `Baz`\");\n }\n Some(_) => {\n println!(\"The value did not contain `Bar` or `Baz`\");\n }\n None => {\n println!(\"The value was `None`\");\n }\n }\n}\n```\n" } , LintCompletion { label : "no_sanitize" , description : "# `no_sanitize`\n\nThe tracking issue for this feature is: [#39699]\n\n[#39699]: https://github.com/rust-lang/rust/issues/39699\n\n------------------------\n\nThe `no_sanitize` attribute can be used to selectively disable sanitizer\ninstrumentation in an annotated function. This might be useful to: avoid\ninstrumentation overhead in a performance critical function, or avoid\ninstrumenting code that contains constructs unsupported by given sanitizer.\n\nThe precise effect of this annotation depends on particular sanitizer in use.\nFor example, with `no_sanitize(thread)`, the thread sanitizer will no longer\ninstrument non-atomic store / load operations, but it will instrument atomic\noperations to avoid reporting false positives and provide meaning full stack\ntraces.\n\n## Examples\n\n``` rust\n#![feature(no_sanitize)]\n\n#[no_sanitize(address)]\nfn foo() {\n // ...\n}\n```\n" } , LintCompletion { label : "doc_spotlight" , description : "# `doc_spotlight`\n\nThe tracking issue for this feature is: [#45040]\n\nThe `doc_spotlight` feature allows the use of the `spotlight` parameter to the `#[doc]` attribute,\nto \"spotlight\" a specific trait on the return values of functions. Adding a `#[doc(spotlight)]`\nattribute to a trait definition will make rustdoc print extra information for functions which return\na type that implements that trait. This attribute is applied to the `Iterator`, `io::Read`, and\n`io::Write` traits in the standard library.\n\nYou can do this on your own traits, like this:\n\n```\n#![feature(doc_spotlight)]\n\n#[doc(spotlight)]\npub trait MyTrait {}\n\npub struct MyStruct;\nimpl MyTrait for MyStruct {}\n\n/// The docs for this function will have an extra line about `MyStruct` implementing `MyTrait`,\n/// without having to write that yourself!\npub fn my_fn() -> MyStruct { MyStruct }\n```\n\nThis feature was originally implemented in PR [#45039].\n\n[#45040]: https://github.com/rust-lang/rust/issues/45040\n[#45039]: https://github.com/rust-lang/rust/pull/45039\n" } , LintCompletion { label : "cfg_sanitize" , description : "# `cfg_sanitize`\n\nThe tracking issue for this feature is: [#39699]\n\n[#39699]: https://github.com/rust-lang/rust/issues/39699\n\n------------------------\n\nThe `cfg_sanitize` feature makes it possible to execute different code\ndepending on whether a particular sanitizer is enabled or not.\n\n## Examples\n\n```rust\n#![feature(cfg_sanitize)]\n\n#[cfg(sanitize = \"thread\")]\nfn a() {\n // ...\n}\n\n#[cfg(not(sanitize = \"thread\"))]\nfn a() {\n // ...\n}\n\nfn b() {\n if cfg!(sanitize = \"leak\") {\n // ...\n } else {\n // ...\n }\n}\n```\n" } , LintCompletion { label : "doc_masked" , description : "# `doc_masked`\n\nThe tracking issue for this feature is: [#44027]\n\n-----\n\nThe `doc_masked` feature allows a crate to exclude types from a given crate from appearing in lists\nof trait implementations. The specifics of the feature are as follows:\n\n1. When rustdoc encounters an `extern crate` statement annotated with a `#[doc(masked)]` attribute,\n it marks the crate as being masked.\n\n2. When listing traits a given type implements, rustdoc ensures that traits from masked crates are\n not emitted into the documentation.\n\n3. When listing types that implement a given trait, rustdoc ensures that types from masked crates\n are not emitted into the documentation.\n\nThis feature was introduced in PR [#44026] to ensure that compiler-internal and\nimplementation-specific types and traits were not included in the standard library's documentation.\nSuch types would introduce broken links into the documentation.\n\n[#44026]: https://github.com/rust-lang/rust/pull/44026\n[#44027]: https://github.com/rust-lang/rust/pull/44027\n" } , LintCompletion { label : "abi_thiscall" , description : "# `abi_thiscall`\n\nThe tracking issue for this feature is: [#42202]\n\n[#42202]: https://github.com/rust-lang/rust/issues/42202\n\n------------------------\n\nThe MSVC ABI on x86 Windows uses the `thiscall` calling convention for C++\ninstance methods by default; it is identical to the usual (C) calling\nconvention on x86 Windows except that the first parameter of the method,\nthe `this` pointer, is passed in the ECX register.\n" } , LintCompletion { label : "lang_items" , description : "# `lang_items`\n\nThe tracking issue for this feature is: None.\n\n------------------------\n\nThe `rustc` compiler has certain pluggable operations, that is,\nfunctionality that isn't hard-coded into the language, but is\nimplemented in libraries, with a special marker to tell the compiler\nit exists. The marker is the attribute `#[lang = \"...\"]` and there are\nvarious different values of `...`, i.e. various different 'lang\nitems'.\n\nFor example, `Box` pointers require two lang items, one for allocation\nand one for deallocation. A freestanding program that uses the `Box`\nsugar for dynamic allocations via `malloc` and `free`:\n\n```rust,ignore\n#![feature(lang_items, box_syntax, start, libc, core_intrinsics)]\n#![no_std]\nuse core::intrinsics;\nuse core::panic::PanicInfo;\n\nextern crate libc;\n\n#[lang = \"owned_box\"]\npub struct Box<T>(*mut T);\n\n#[lang = \"exchange_malloc\"]\nunsafe fn allocate(size: usize, _align: usize) -> *mut u8 {\n let p = libc::malloc(size as libc::size_t) as *mut u8;\n\n // Check if `malloc` failed:\n if p as usize == 0 {\n intrinsics::abort();\n }\n\n p\n}\n\n#[lang = \"box_free\"]\nunsafe fn box_free<T: ?Sized>(ptr: *mut T) {\n libc::free(ptr as *mut libc::c_void)\n}\n\n#[start]\nfn main(_argc: isize, _argv: *const *const u8) -> isize {\n let _x = box 1;\n\n 0\n}\n\n#[lang = \"eh_personality\"] extern fn rust_eh_personality() {}\n#[lang = \"panic_impl\"] extern fn rust_begin_panic(info: &PanicInfo) -> ! { unsafe { intrinsics::abort() } }\n#[no_mangle] pub extern fn rust_eh_register_frames () {}\n#[no_mangle] pub extern fn rust_eh_unregister_frames () {}\n```\n\nNote the use of `abort`: the `exchange_malloc` lang item is assumed to\nreturn a valid pointer, and so needs to do the check internally.\n\nOther features provided by lang items include:\n\n- overloadable operators via traits: the traits corresponding to the\n `==`, `<`, dereferencing (`*`) and `+` (etc.) operators are all\n marked with lang items; those specific four are `eq`, `ord`,\n `deref`, and `add` respectively.\n- stack unwinding and general failure; the `eh_personality`,\n `panic` and `panic_bounds_checks` lang items.\n- the traits in `std::marker` used to indicate types of\n various kinds; lang items `send`, `sync` and `copy`.\n- the marker types and variance indicators found in\n `std::marker`; lang items `covariant_type`,\n `contravariant_lifetime`, etc.\n\nLang items are loaded lazily by the compiler; e.g. if one never uses\n`Box` then there is no need to define functions for `exchange_malloc`\nand `box_free`. `rustc` will emit an error when an item is needed\nbut not found in the current crate or any that it depends on.\n\nMost lang items are defined by `libcore`, but if you're trying to build\nan executable without the standard library, you'll run into the need\nfor lang items. The rest of this page focuses on this use-case, even though\nlang items are a bit broader than that.\n\n### Using libc\n\nIn order to build a `#[no_std]` executable we will need libc as a dependency.\nWe can specify this using our `Cargo.toml` file:\n\n```toml\n[dependencies]\nlibc = { version = \"0.2.14\", default-features = false }\n```\n\nNote that the default features have been disabled. This is a critical step -\n**the default features of libc include the standard library and so must be\ndisabled.**\n\n### Writing an executable without stdlib\n\nControlling the entry point is possible in two ways: the `#[start]` attribute,\nor overriding the default shim for the C `main` function with your own.\n\nThe function marked `#[start]` is passed the command line parameters\nin the same format as C:\n\n```rust,ignore\n#![feature(lang_items, core_intrinsics)]\n#![feature(start)]\n#![no_std]\nuse core::intrinsics;\nuse core::panic::PanicInfo;\n\n// Pull in the system libc library for what crt0.o likely requires.\nextern crate libc;\n\n// Entry point for this program.\n#[start]\nfn start(_argc: isize, _argv: *const *const u8) -> isize {\n 0\n}\n\n// These functions are used by the compiler, but not\n// for a bare-bones hello world. These are normally\n// provided by libstd.\n#[lang = \"eh_personality\"]\n#[no_mangle]\npub extern fn rust_eh_personality() {\n}\n\n#[lang = \"panic_impl\"]\n#[no_mangle]\npub extern fn rust_begin_panic(info: &PanicInfo) -> ! {\n unsafe { intrinsics::abort() }\n}\n```\n\nTo override the compiler-inserted `main` shim, one has to disable it\nwith `#![no_main]` and then create the appropriate symbol with the\ncorrect ABI and the correct name, which requires overriding the\ncompiler's name mangling too:\n\n```rust,ignore\n#![feature(lang_items, core_intrinsics)]\n#![feature(start)]\n#![no_std]\n#![no_main]\nuse core::intrinsics;\nuse core::panic::PanicInfo;\n\n// Pull in the system libc library for what crt0.o likely requires.\nextern crate libc;\n\n// Entry point for this program.\n#[no_mangle] // ensure that this symbol is called `main` in the output\npub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 {\n 0\n}\n\n// These functions are used by the compiler, but not\n// for a bare-bones hello world. These are normally\n// provided by libstd.\n#[lang = \"eh_personality\"]\n#[no_mangle]\npub extern fn rust_eh_personality() {\n}\n\n#[lang = \"panic_impl\"]\n#[no_mangle]\npub extern fn rust_begin_panic(info: &PanicInfo) -> ! {\n unsafe { intrinsics::abort() }\n}\n```\n\nIn many cases, you may need to manually link to the `compiler_builtins` crate\nwhen building a `no_std` binary. You may observe this via linker error messages\nsuch as \"```undefined reference to `__rust_probestack'```\".\n\n## More about the language items\n\nThe compiler currently makes a few assumptions about symbols which are\navailable in the executable to call. Normally these functions are provided by\nthe standard library, but without it you must define your own. These symbols\nare called \"language items\", and they each have an internal name, and then a\nsignature that an implementation must conform to.\n\nThe first of these functions, `rust_eh_personality`, is used by the failure\nmechanisms of the compiler. This is often mapped to GCC's personality function\n(see the [libstd implementation][unwind] for more information), but crates\nwhich do not trigger a panic can be assured that this function is never\ncalled. The language item's name is `eh_personality`.\n\n[unwind]: https://github.com/rust-lang/rust/blob/master/src/libpanic_unwind/gcc.rs\n\nThe second function, `rust_begin_panic`, is also used by the failure mechanisms of the\ncompiler. When a panic happens, this controls the message that's displayed on\nthe screen. While the language item's name is `panic_impl`, the symbol name is\n`rust_begin_panic`.\n\nFinally, a `eh_catch_typeinfo` static is needed for certain targets which\nimplement Rust panics on top of C++ exceptions.\n\n## List of all language items\n\nThis is a list of all language items in Rust along with where they are located in\nthe source code.\n\n- Primitives\n - `i8`: `libcore/num/mod.rs`\n - `i16`: `libcore/num/mod.rs`\n - `i32`: `libcore/num/mod.rs`\n - `i64`: `libcore/num/mod.rs`\n - `i128`: `libcore/num/mod.rs`\n - `isize`: `libcore/num/mod.rs`\n - `u8`: `libcore/num/mod.rs`\n - `u16`: `libcore/num/mod.rs`\n - `u32`: `libcore/num/mod.rs`\n - `u64`: `libcore/num/mod.rs`\n - `u128`: `libcore/num/mod.rs`\n - `usize`: `libcore/num/mod.rs`\n - `f32`: `libstd/f32.rs`\n - `f64`: `libstd/f64.rs`\n - `char`: `libcore/char.rs`\n - `slice`: `liballoc/slice.rs`\n - `str`: `liballoc/str.rs`\n - `const_ptr`: `libcore/ptr.rs`\n - `mut_ptr`: `libcore/ptr.rs`\n - `unsafe_cell`: `libcore/cell.rs`\n- Runtime\n - `start`: `libstd/rt.rs`\n - `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC)\n - `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)\n - `eh_personality`: `libpanic_unwind/seh.rs` (SEH)\n - `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)\n - `panic`: `libcore/panicking.rs`\n - `panic_bounds_check`: `libcore/panicking.rs`\n - `panic_impl`: `libcore/panicking.rs`\n - `panic_impl`: `libstd/panicking.rs`\n- Allocations\n - `owned_box`: `liballoc/boxed.rs`\n - `exchange_malloc`: `liballoc/heap.rs`\n - `box_free`: `liballoc/heap.rs`\n- Operands\n - `not`: `libcore/ops/bit.rs`\n - `bitand`: `libcore/ops/bit.rs`\n - `bitor`: `libcore/ops/bit.rs`\n - `bitxor`: `libcore/ops/bit.rs`\n - `shl`: `libcore/ops/bit.rs`\n - `shr`: `libcore/ops/bit.rs`\n - `bitand_assign`: `libcore/ops/bit.rs`\n - `bitor_assign`: `libcore/ops/bit.rs`\n - `bitxor_assign`: `libcore/ops/bit.rs`\n - `shl_assign`: `libcore/ops/bit.rs`\n - `shr_assign`: `libcore/ops/bit.rs`\n - `deref`: `libcore/ops/deref.rs`\n - `deref_mut`: `libcore/ops/deref.rs`\n - `index`: `libcore/ops/index.rs`\n - `index_mut`: `libcore/ops/index.rs`\n - `add`: `libcore/ops/arith.rs`\n - `sub`: `libcore/ops/arith.rs`\n - `mul`: `libcore/ops/arith.rs`\n - `div`: `libcore/ops/arith.rs`\n - `rem`: `libcore/ops/arith.rs`\n - `neg`: `libcore/ops/arith.rs`\n - `add_assign`: `libcore/ops/arith.rs`\n - `sub_assign`: `libcore/ops/arith.rs`\n - `mul_assign`: `libcore/ops/arith.rs`\n - `div_assign`: `libcore/ops/arith.rs`\n - `rem_assign`: `libcore/ops/arith.rs`\n - `eq`: `libcore/cmp.rs`\n - `ord`: `libcore/cmp.rs`\n- Functions\n - `fn`: `libcore/ops/function.rs`\n - `fn_mut`: `libcore/ops/function.rs`\n - `fn_once`: `libcore/ops/function.rs`\n - `generator_state`: `libcore/ops/generator.rs`\n - `generator`: `libcore/ops/generator.rs`\n- Other\n - `coerce_unsized`: `libcore/ops/unsize.rs`\n - `drop`: `libcore/ops/drop.rs`\n - `drop_in_place`: `libcore/ptr.rs`\n - `clone`: `libcore/clone.rs`\n - `copy`: `libcore/marker.rs`\n - `send`: `libcore/marker.rs`\n - `sized`: `libcore/marker.rs`\n - `unsize`: `libcore/marker.rs`\n - `sync`: `libcore/marker.rs`\n - `phantom_data`: `libcore/marker.rs`\n - `discriminant_kind`: `libcore/marker.rs`\n - `freeze`: `libcore/marker.rs`\n - `debug_trait`: `libcore/fmt/mod.rs`\n - `non_zero`: `libcore/nonzero.rs`\n - `arc`: `liballoc/sync.rs`\n - `rc`: `liballoc/rc.rs`\n" } , LintCompletion { label : "abi_msp430_interrupt" , description : "# `abi_msp430_interrupt`\n\nThe tracking issue for this feature is: [#38487]\n\n[#38487]: https://github.com/rust-lang/rust/issues/38487\n\n------------------------\n\nIn the MSP430 architecture, interrupt handlers have a special calling\nconvention. You can use the `\"msp430-interrupt\"` ABI to make the compiler apply\nthe right calling convention to the interrupt handlers you define.\n\n<!-- NOTE(ignore) this example is specific to the msp430 target -->\n\n``` rust,ignore\n#![feature(abi_msp430_interrupt)]\n#![no_std]\n\n// Place the interrupt handler at the appropriate memory address\n// (Alternatively, you can use `#[used]` and remove `pub` and `#[no_mangle]`)\n#[link_section = \"__interrupt_vector_10\"]\n#[no_mangle]\npub static TIM0_VECTOR: extern \"msp430-interrupt\" fn() = tim0;\n\n// The interrupt handler\nextern \"msp430-interrupt\" fn tim0() {\n // ..\n}\n```\n\n``` text\n$ msp430-elf-objdump -CD ./target/msp430/release/app\nDisassembly of section __interrupt_vector_10:\n\n0000fff2 <TIM0_VECTOR>:\n fff2: 00 c0 interrupt service routine at 0xc000\n\nDisassembly of section .text:\n\n0000c000 <int::tim0>:\n c000: 00 13 reti\n```\n" } , LintCompletion { label : "link_args" , description : "# `link_args`\n\nThe tracking issue for this feature is: [#29596]\n\n[#29596]: https://github.com/rust-lang/rust/issues/29596\n\n------------------------\n\nYou can tell `rustc` how to customize linking, and that is via the `link_args`\nattribute. This attribute is applied to `extern` blocks and specifies raw flags\nwhich need to get passed to the linker when producing an artifact. An example\nusage would be:\n\n```rust,no_run\n#![feature(link_args)]\n\n#[link_args = \"-foo -bar -baz\"]\nextern {}\n# fn main() {}\n```\n\nNote that this feature is currently hidden behind the `feature(link_args)` gate\nbecause this is not a sanctioned way of performing linking. Right now `rustc`\nshells out to the system linker (`gcc` on most systems, `link.exe` on MSVC), so\nit makes sense to provide extra command line arguments, but this will not\nalways be the case. In the future `rustc` may use LLVM directly to link native\nlibraries, in which case `link_args` will have no meaning. You can achieve the\nsame effect as the `link_args` attribute with the `-C link-args` argument to\n`rustc`.\n\nIt is highly recommended to *not* use this attribute, and rather use the more\nformal `#[link(...)]` attribute on `extern` blocks instead.\n" } , LintCompletion { label : "const_eval_limit" , description : "# `const_eval_limit`\n\nThe tracking issue for this feature is: [#67217]\n\n[#67217]: https://github.com/rust-lang/rust/issues/67217\n\nThe `const_eval_limit` allows someone to limit the evaluation steps the CTFE undertakes to evaluate a `const fn`.\n" } , LintCompletion { label : "negative_impls" , description : "# `negative_impls`\n\nThe tracking issue for this feature is [#68318].\n\n[#68318]: https://github.com/rust-lang/rust/issues/68318\n\n----\n\nWith the feature gate `negative_impls`, you can write negative impls as well as positive ones:\n\n```rust\n#![feature(negative_impls)]\ntrait DerefMut { }\nimpl<T: ?Sized> !DerefMut for &T { }\n```\n\nNegative impls indicate a semver guarantee that the given trait will not be implemented for the given types. Negative impls play an additional purpose for auto traits, described below.\n\nNegative impls have the following characteristics:\n\n* They do not have any items.\n* They must obey the orphan rules as if they were a positive impl.\n* They cannot \"overlap\" with any positive impls.\n\n## Semver interaction\n\nIt is a breaking change to remove a negative impl. Negative impls are a commitment not to implement the given trait for the named types.\n\n## Orphan and overlap rules\n\nNegative impls must obey the same orphan rules as a positive impl. This implies you cannot add a negative impl for types defined in upstream crates and so forth.\n\nSimilarly, negative impls cannot overlap with positive impls, again using the same \"overlap\" check that we ordinarily use to determine if two impls overlap. (Note that positive impls typically cannot overlap with one another either, except as permitted by specialization.)\n\n## Interaction with auto traits\n\nDeclaring a negative impl `impl !SomeAutoTrait for SomeType` for an\nauto-trait serves two purposes:\n\n* as with any trait, it declares that `SomeType` will never implement `SomeAutoTrait`;\n* it disables the automatic `SomeType: SomeAutoTrait` impl that would otherwise have been generated.\n\nNote that, at present, there is no way to indicate that a given type\ndoes not implement an auto trait *but that it may do so in the\nfuture*. For ordinary types, this is done by simply not declaring any\nimpl at all, but that is not an option for auto traits. A workaround\nis that one could embed a marker type as one of the fields, where the\nmarker type is `!AutoTrait`.\n\n## Immediate uses\n\nNegative impls are used to declare that `&T: !DerefMut` and `&mut T: !Clone`, as required to fix the soundness of `Pin` described in [#66544](https://github.com/rust-lang/rust/issues/66544).\n\nThis serves two purposes:\n\n* For proving the correctness of unsafe code, we can use that impl as evidence that no `DerefMut` or `Clone` impl exists.\n* It prevents downstream crates from creating such impls.\n" } , LintCompletion { label : "non_ascii_idents" , description : "# `non_ascii_idents`\n\nThe tracking issue for this feature is: [#55467]\n\n[#55467]: https://github.com/rust-lang/rust/issues/55467\n\n------------------------\n\nThe `non_ascii_idents` feature adds support for non-ASCII identifiers.\n\n## Examples\n\n```rust\n#![feature(non_ascii_idents)]\n\nconst ε: f64 = 0.00001f64;\nconst Π: f64 = 3.14f64;\n```\n\n## Changes to the language reference\n\n> **<sup>Lexer:<sup>** \n> IDENTIFIER : \n> &nbsp;&nbsp; &nbsp;&nbsp; XID_start XID_continue<sup>\\*</sup> \n> &nbsp;&nbsp; | `_` XID_continue<sup>+</sup> \n\nAn identifier is any nonempty Unicode string of the following form:\n\nEither\n\n * The first character has property [`XID_start`]\n * The remaining characters have property [`XID_continue`]\n\nOr\n\n * The first character is `_`\n * The identifier is more than one character, `_` alone is not an identifier\n * The remaining characters have property [`XID_continue`]\n\nthat does _not_ occur in the set of [strict keywords].\n\n> **Note**: [`XID_start`] and [`XID_continue`] as character properties cover the\n> character ranges used to form the more familiar C and Java language-family\n> identifiers.\n\n[`XID_start`]: http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Start%3A%5D&abb=on&g=&i=\n[`XID_continue`]: http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Continue%3A%5D&abb=on&g=&i=\n[strict keywords]: ../../reference/keywords.md#strict-keywords\n" } , LintCompletion { label : "transparent_unions" , description : "# `transparent_unions`\n\nThe tracking issue for this feature is [#60405]\n\n[#60405]: https://github.com/rust-lang/rust/issues/60405\n\n----\n\nThe `transparent_unions` feature allows you mark `union`s as\n`#[repr(transparent)]`. A `union` may be `#[repr(transparent)]` in exactly the\nsame conditions in which a `struct` may be `#[repr(transparent)]` (generally,\nthis means the `union` must have exactly one non-zero-sized field). Some\nconcrete illustrations follow.\n\n```rust\n#![feature(transparent_unions)]\n\n// This union has the same representation as `f32`.\n#[repr(transparent)]\nunion SingleFieldUnion {\n field: f32,\n}\n\n// This union has the same representation as `usize`.\n#[repr(transparent)]\nunion MultiFieldUnion {\n field: usize,\n nothing: (),\n}\n```\n\nFor consistency with transparent `struct`s, `union`s must have exactly one\nnon-zero-sized field. If all fields are zero-sized, the `union` must not be\n`#[repr(transparent)]`:\n\n```rust\n#![feature(transparent_unions)]\n\n// This (non-transparent) union is already valid in stable Rust:\npub union GoodUnion {\n pub nothing: (),\n}\n\n// Error: transparent union needs exactly one non-zero-sized field, but has 0\n// #[repr(transparent)]\n// pub union BadUnion {\n// pub nothing: (),\n// }\n```\n\nThe one exception is if the `union` is generic over `T` and has a field of type\n`T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:\n\n```rust\n#![feature(transparent_unions)]\n\n// This union has the same representation as `T`.\n#[repr(transparent)]\npub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.\n pub field: T,\n pub nothing: (),\n}\n\n// This is okay even though `()` is a zero-sized type.\npub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () };\n```\n\nLike transarent `struct`s, a transparent `union` of type `U` has the same\nlayout, size, and ABI as its single non-ZST field. If it is generic over a type\n`T`, and all its fields are ZSTs except for exactly one field of type `T`, then\nit has the same layout and ABI as `T` (even if `T` is a ZST when monomorphized).\n\nLike transparent `struct`s, transparent `union`s are FFI-safe if and only if\ntheir underlying representation type is also FFI-safe.\n\nA `union` may not be eligible for the same nonnull-style optimizations that a\n`struct` or `enum` (with the same fields) are eligible for. Adding\n`#[repr(transparent)]` to `union` does not change this. To give a more concrete\nexample, it is unspecified whether `size_of::<T>()` is equal to\n`size_of::<Option<T>>()`, where `T` is a `union` (regardless of whether or not\nit is transparent). The Rust compiler is free to perform this optimization if\npossible, but is not required to, and different compiler versions may differ in\ntheir application of these optimizations.\n" } , LintCompletion { label : "box_syntax" , description : "# `box_syntax`\n\nThe tracking issue for this feature is: [#49733]\n\n[#49733]: https://github.com/rust-lang/rust/issues/49733\n\nSee also [`box_patterns`](box-patterns.md)\n\n------------------------\n\nCurrently the only stable way to create a `Box` is via the `Box::new` method.\nAlso it is not possible in stable Rust to destructure a `Box` in a match\npattern. The unstable `box` keyword can be used to create a `Box`. An example\nusage would be:\n\n```rust\n#![feature(box_syntax)]\n\nfn main() {\n let b = box 5;\n}\n```\n" } , LintCompletion { label : "repr128" , description : "# `repr128`\n\nThe tracking issue for this feature is: [#56071]\n\n[#56071]: https://github.com/rust-lang/rust/issues/56071\n\n------------------------\n\nThe `repr128` feature adds support for `#[repr(u128)]` on `enum`s.\n\n```rust\n#![feature(repr128)]\n\n#[repr(u128)]\nenum Foo {\n Bar(u64),\n}\n```\n" } , LintCompletion { label : "member_constraints" , description : "# `member_constraints`\n\nThe tracking issue for this feature is: [#61997]\n\n[#61997]: https://github.com/rust-lang/rust/issues/61997\n\n------------------------\n\nThe `member_constraints` feature gate lets you use `impl Trait` syntax with\nmultiple unrelated lifetime parameters.\n\nA simple example is:\n\n```rust\n#![feature(member_constraints)]\n\ntrait Trait<'a, 'b> { }\nimpl<T> Trait<'_, '_> for T {}\n\nfn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> impl Trait<'a, 'b> {\n (x, y)\n}\n\nfn main() { }\n```\n\nWithout the `member_constraints` feature gate, the above example is an\nerror because both `'a` and `'b` appear in the impl Trait bounds, but\nneither outlives the other.\n" } , LintCompletion { label : "link_cfg" , description : "# `link_cfg`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "c_variadic" , description : "# `c_variadic`\n\nThe tracking issue for this feature is: [#44930]\n\n[#44930]: https://github.com/rust-lang/rust/issues/44930\n\n------------------------\n\nThe `c_variadic` language feature enables C-variadic functions to be\ndefined in Rust. The may be called both from within Rust and via FFI.\n\n## Examples\n\n```rust\n#![feature(c_variadic)]\n\npub unsafe extern \"C\" fn add(n: usize, mut args: ...) -> usize {\n let mut sum = 0;\n for _ in 0..n {\n sum += args.arg::<usize>();\n }\n sum\n}\n```\n" } , LintCompletion { label : "abi_ptx" , description : "# `abi_ptx`\n\nThe tracking issue for this feature is: [#38788]\n\n[#38788]: https://github.com/rust-lang/rust/issues/38788\n\n------------------------\n\nWhen emitting PTX code, all vanilla Rust functions (`fn`) get translated to\n\"device\" functions. These functions are *not* callable from the host via the\nCUDA API so a crate with only device functions is not too useful!\n\nOTOH, \"global\" functions *can* be called by the host; you can think of them\nas the real public API of your crate. To produce a global function use the\n`\"ptx-kernel\"` ABI.\n\n<!-- NOTE(ignore) this example is specific to the nvptx targets -->\n\n``` rust,ignore\n#![feature(abi_ptx)]\n#![no_std]\n\npub unsafe extern \"ptx-kernel\" fn global_function() {\n device_function();\n}\n\npub fn device_function() {\n // ..\n}\n```\n\n``` text\n$ xargo rustc --target nvptx64-nvidia-cuda --release -- --emit=asm\n\n$ cat $(find -name '*.s')\n//\n// Generated by LLVM NVPTX Back-End\n//\n\n.version 3.2\n.target sm_20\n.address_size 64\n\n // .globl _ZN6kernel15global_function17h46111ebe6516b382E\n\n.visible .entry _ZN6kernel15global_function17h46111ebe6516b382E()\n{\n\n\n ret;\n}\n\n // .globl _ZN6kernel15device_function17hd6a0e4993bbf3f78E\n.visible .func _ZN6kernel15device_function17hd6a0e4993bbf3f78E()\n{\n\n\n ret;\n}\n```\n" } , LintCompletion { label : "ffi_pure" , description : "# `ffi_pure`\n\nThe `#[ffi_pure]` attribute applies clang's `pure` attribute to foreign\nfunctions declarations.\n\nThat is, `#[ffi_pure]` functions shall have no effects except for its return\nvalue, which shall not change across two consecutive function calls with\nthe same parameters.\n\nApplying the `#[ffi_pure]` attribute to a function that violates these\nrequirements is undefined behavior.\n\nThis attribute enables Rust to perform common optimizations, like sub-expression\nelimination and loop optimizations. Some common examples of pure functions are\n`strlen` or `memcmp`.\n\nThese optimizations are only applicable when the compiler can prove that no\nprogram state observable by the `#[ffi_pure]` function has changed between calls\nof the function, which could alter the result. See also the `#[ffi_const]`\nattribute, which provides stronger guarantees regarding the allowable behavior\nof a function, enabling further optimization.\n\n## Pitfalls\n\nA `#[ffi_pure]` function can read global memory through the function\nparameters (e.g. pointers), globals, etc. `#[ffi_pure]` functions are not\nreferentially-transparent, and are therefore more relaxed than `#[ffi_const]`\nfunctions.\n\nHowever, accesing global memory through volatile or atomic reads can violate the\nrequirement that two consecutive function calls shall return the same value.\n\nA `pure` function that returns unit has no effect on the abstract machine's\nstate.\n\nA `#[ffi_pure]` function must not diverge, neither via a side effect (e.g. a\ncall to `abort`) nor by infinite loops.\n\nWhen translating C headers to Rust FFI, it is worth verifying for which targets\nthe `pure` attribute is enabled in those headers, and using the appropriate\n`cfg` macros in the Rust side to match those definitions. While the semantics of\n`pure` are implemented identically by many C and C++ compilers, e.g., clang,\n[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily\nimplemented in this way on all of them. It is therefore also worth verifying\nthat the semantics of the C toolchain used to compile the binary being linked\nagainst are compatible with those of the `#[ffi_pure]`.\n\n\n[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacigdac.html\n[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute\n[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_pure.htm\n" } , LintCompletion { label : "compiler_builtins" , description : "# `compiler_builtins`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "unboxed_closures" , description : "# `unboxed_closures`\n\nThe tracking issue for this feature is [#29625]\n\nSee Also: [`fn_traits`](../library-features/fn-traits.md)\n\n[#29625]: https://github.com/rust-lang/rust/issues/29625\n\n----\n\nThe `unboxed_closures` feature allows you to write functions using the `\"rust-call\"` ABI,\nrequired for implementing the [`Fn*`] family of traits. `\"rust-call\"` functions must have \nexactly one (non self) argument, a tuple representing the argument list.\n\n[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html\n\n```rust\n#![feature(unboxed_closures)]\n\nextern \"rust-call\" fn add_args(args: (u32, u32)) -> u32 {\n args.0 + args.1\n}\n\nfn main() {}\n```\n" } , LintCompletion { label : "arbitrary_enum_discriminant" , description : "# `arbitrary_enum_discriminant`\n\nThe tracking issue for this feature is: [#60553]\n\n[#60553]: https://github.com/rust-lang/rust/issues/60553\n\n------------------------\n\nThe `arbitrary_enum_discriminant` feature permits tuple-like and\nstruct-like enum variants with `#[repr(<int-type>)]` to have explicit discriminants.\n\n## Examples\n\n```rust\n#![feature(arbitrary_enum_discriminant)]\n\n#[allow(dead_code)]\n#[repr(u8)]\nenum Enum {\n Unit = 3,\n Tuple(u16) = 2,\n Struct {\n a: u8,\n b: u16,\n } = 1,\n}\n\nimpl Enum {\n fn tag(&self) -> u8 {\n unsafe { *(self as *const Self as *const u8) }\n }\n}\n\nassert_eq!(3, Enum::Unit.tag());\nassert_eq!(2, Enum::Tuple(5).tag());\nassert_eq!(1, Enum::Struct{a: 7, b: 11}.tag());\n```\n" } , LintCompletion { label : "marker_trait_attr" , description : "# `marker_trait_attr`\n\nThe tracking issue for this feature is: [#29864]\n\n[#29864]: https://github.com/rust-lang/rust/issues/29864\n\n------------------------\n\nNormally, Rust keeps you from adding trait implementations that could\noverlap with each other, as it would be ambiguous which to use. This\nfeature, however, carves out an exception to that rule: a trait can\nopt-in to having overlapping implementations, at the cost that those\nimplementations are not allowed to override anything (and thus the\ntrait itself cannot have any associated items, as they're pointless\nwhen they'd need to do the same thing for every type anyway).\n\n```rust\n#![feature(marker_trait_attr)]\n\n#[marker] trait CheapToClone: Clone {}\n\nimpl<T: Copy> CheapToClone for T {}\n\n// These could potentially overlap with the blanket implementation above,\n// so are only allowed because CheapToClone is a marker trait.\nimpl<T: CheapToClone, U: CheapToClone> CheapToClone for (T, U) {}\nimpl<T: CheapToClone> CheapToClone for std::ops::Range<T> {}\n\nfn cheap_clone<T: CheapToClone>(t: T) -> T {\n t.clone()\n}\n```\n\nThis is expected to replace the unstable `overlapping_marker_traits`\nfeature, which applied to all empty traits (without needing an opt-in).\n" } , LintCompletion { label : "plugin_registrar" , description : "# `plugin_registrar`\n\nThe tracking issue for this feature is: [#29597]\n\n[#29597]: https://github.com/rust-lang/rust/issues/29597\n\nThis feature is part of \"compiler plugins.\" It will often be used with the\n[`plugin`] and `rustc_private` features as well. For more details, see\ntheir docs.\n\n[`plugin`]: plugin.md\n\n------------------------\n" } , LintCompletion { label : "profiler_runtime" , description : "# `profiler_runtime`\n\nThe tracking issue for this feature is: [#42524](https://github.com/rust-lang/rust/issues/42524).\n\n------------------------\n" } , LintCompletion { label : "trait_alias" , description : "# `trait_alias`\n\nThe tracking issue for this feature is: [#41517]\n\n[#41517]: https://github.com/rust-lang/rust/issues/41517\n\n------------------------\n\nThe `trait_alias` feature adds support for trait aliases. These allow aliases\nto be created for one or more traits (currently just a single regular trait plus\nany number of auto-traits), and used wherever traits would normally be used as\neither bounds or trait objects.\n\n```rust\n#![feature(trait_alias)]\n\ntrait Foo = std::fmt::Debug + Send;\ntrait Bar = Foo + Sync;\n\n// Use trait alias as bound on type parameter.\nfn foo<T: Foo>(v: &T) {\n println!(\"{:?}\", v);\n}\n\npub fn main() {\n foo(&1);\n\n // Use trait alias for trait objects.\n let a: &Bar = &123;\n println!(\"{:?}\", a);\n let b = Box::new(456) as Box<dyn Foo>;\n println!(\"{:?}\", b);\n}\n```\n" } , LintCompletion { label : "try_blocks" , description : "# `try_blocks`\n\nThe tracking issue for this feature is: [#31436]\n\n[#31436]: https://github.com/rust-lang/rust/issues/31436\n\n------------------------\n\nThe `try_blocks` feature adds support for `try` blocks. A `try`\nblock creates a new scope one can use the `?` operator in.\n\n```rust,edition2018\n#![feature(try_blocks)]\n\nuse std::num::ParseIntError;\n\nlet result: Result<i32, ParseIntError> = try {\n \"1\".parse::<i32>()?\n + \"2\".parse::<i32>()?\n + \"3\".parse::<i32>()?\n};\nassert_eq!(result, Ok(6));\n\nlet result: Result<i32, ParseIntError> = try {\n \"1\".parse::<i32>()?\n + \"foo\".parse::<i32>()?\n + \"3\".parse::<i32>()?\n};\nassert!(result.is_err());\n```\n" } , LintCompletion { label : "box_patterns" , description : "# `box_patterns`\n\nThe tracking issue for this feature is: [#29641]\n\n[#29641]: https://github.com/rust-lang/rust/issues/29641\n\nSee also [`box_syntax`](box-syntax.md)\n\n------------------------\n\nBox patterns let you match on `Box<T>`s:\n\n\n```rust\n#![feature(box_patterns)]\n\nfn main() {\n let b = Some(Box::new(5));\n match b {\n Some(box n) if n < 0 => {\n println!(\"Box contains negative number {}\", n);\n },\n Some(box n) if n >= 0 => {\n println!(\"Box contains non-negative number {}\", n);\n },\n None => {\n println!(\"No box\");\n },\n _ => unreachable!()\n }\n}\n```\n" } , LintCompletion { label : "crate_visibility_modifier" , description : "# `crate_visibility_modifier`\n\nThe tracking issue for this feature is: [#53120]\n\n[#53120]: https://github.com/rust-lang/rust/issues/53120\n\n-----\n\nThe `crate_visibility_modifier` feature allows the `crate` keyword to be used\nas a visibility modifier synonymous to `pub(crate)`, indicating that a type\n(function, _&c._) is to be visible to the entire enclosing crate, but not to\nother crates.\n\n```rust\n#![feature(crate_visibility_modifier)]\n\ncrate struct Foo {\n bar: usize,\n}\n```\n" } , LintCompletion { label : "allocator_internals" , description : "# `allocator_internals`\n\nThis feature does not have a tracking issue, it is an unstable implementation\ndetail of the `global_allocator` feature not intended for use outside the\ncompiler.\n\n------------------------\n" } , LintCompletion { label : "intrinsics" , description : "# `intrinsics`\n\nThe tracking issue for this feature is: None.\n\nIntrinsics are never intended to be stable directly, but intrinsics are often\nexported in some sort of stable manner. Prefer using the stable interfaces to\nthe intrinsic directly when you can.\n\n------------------------\n\n\nThese are imported as if they were FFI functions, with the special\n`rust-intrinsic` ABI. For example, if one was in a freestanding\ncontext, but wished to be able to `transmute` between types, and\nperform efficient pointer arithmetic, one would import those functions\nvia a declaration like\n\n```rust\n#![feature(intrinsics)]\n# fn main() {}\n\nextern \"rust-intrinsic\" {\n fn transmute<T, U>(x: T) -> U;\n\n fn offset<T>(dst: *const T, offset: isize) -> *const T;\n}\n```\n\nAs with any other FFI functions, these are always `unsafe` to call.\n\n" } , LintCompletion { label : "custom_test_frameworks" , description : "# `custom_test_frameworks`\n\nThe tracking issue for this feature is: [#50297]\n\n[#50297]: https://github.com/rust-lang/rust/issues/50297\n\n------------------------\n\nThe `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`.\nAny function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`)\nand be passed to the test runner determined by the `#![test_runner]` crate attribute.\n\n```rust\n#![feature(custom_test_frameworks)]\n#![test_runner(my_runner)]\n\nfn my_runner(tests: &[&i32]) {\n for t in tests {\n if **t == 0 {\n println!(\"PASSED\");\n } else {\n println!(\"FAILED\");\n }\n }\n}\n\n#[test_case]\nconst WILL_PASS: i32 = 0;\n\n#[test_case]\nconst WILL_FAIL: i32 = 4;\n```\n\n" } , LintCompletion { label : "external_doc" , description : "# `external_doc`\n\nThe tracking issue for this feature is: [#44732]\n\nThe `external_doc` feature allows the use of the `include` parameter to the `#[doc]` attribute, to\ninclude external files in documentation. Use the attribute in place of, or in addition to, regular\ndoc comments and `#[doc]` attributes, and `rustdoc` will load the given file when it renders\ndocumentation for your crate.\n\nWith the following files in the same directory:\n\n`external-doc.md`:\n\n```markdown\n# My Awesome Type\n\nThis is the documentation for this spectacular type.\n```\n\n`lib.rs`:\n\n```no_run (needs-external-files)\n#![feature(external_doc)]\n\n#[doc(include = \"external-doc.md\")]\npub struct MyAwesomeType;\n```\n\n`rustdoc` will load the file `external-doc.md` and use it as the documentation for the `MyAwesomeType`\nstruct.\n\nWhen locating files, `rustdoc` will base paths in the `src/` directory, as if they were alongside the\n`lib.rs` for your crate. So if you want a `docs/` folder to live alongside the `src/` directory,\nstart your paths with `../docs/` for `rustdoc` to properly find the file.\n\nThis feature was proposed in [RFC #1990] and initially implemented in PR [#44781].\n\n[#44732]: https://github.com/rust-lang/rust/issues/44732\n[RFC #1990]: https://github.com/rust-lang/rfcs/pull/1990\n[#44781]: https://github.com/rust-lang/rust/pull/44781\n" } , LintCompletion { label : "rustc_attrs" , description : "# `rustc_attrs`\n\nThis feature has no tracking issue, and is therefore internal to\nthe compiler, not being intended for general use.\n\nNote: `rustc_attrs` enables many rustc-internal attributes and this page\nonly discuss a few of them.\n\n------------------------\n\nThe `rustc_attrs` feature allows debugging rustc type layouts by using\n`#[rustc_layout(...)]` to debug layout at compile time (it even works\nwith `cargo check`) as an alternative to `rustc -Z print-type-sizes`\nthat is way more verbose.\n\nOptions provided by `#[rustc_layout(...)]` are `debug`, `size`, `abi`.\nNote that it only work best with sized type without generics.\n\n## Examples\n\n```rust,ignore\n#![feature(rustc_attrs)]\n\n#[rustc_layout(abi, size)]\npub enum X {\n Y(u8, u8, u8),\n Z(isize),\n}\n```\n\nWhen that is compiled, the compiler will error with something like\n\n```text\nerror: abi: Aggregate { sized: true }\n --> src/lib.rs:4:1\n |\n4 | / pub enum T {\n5 | | Y(u8, u8, u8),\n6 | | Z(isize),\n7 | | }\n | |_^\n\nerror: size: Size { raw: 16 }\n --> src/lib.rs:4:1\n |\n4 | / pub enum T {\n5 | | Y(u8, u8, u8),\n6 | | Z(isize),\n7 | | }\n | |_^\n\nerror: aborting due to 2 previous errors\n```\n" } , LintCompletion { label : "profiler_runtime_lib" , description : "# `profiler_runtime_lib`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "fmt_internals" , description : "# `fmt_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "libstd_io_internals" , description : "# `libstd_io_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "dec2flt" , description : "# `dec2flt`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "try_trait" , description : "# `try_trait`\n\nThe tracking issue for this feature is: [#42327]\n\n[#42327]: https://github.com/rust-lang/rust/issues/42327\n\n------------------------\n\nThis introduces a new trait `Try` for extending the `?` operator to types\nother than `Result` (a part of [RFC 1859]). The trait provides the canonical\nway to _view_ a type in terms of a success/failure dichotomy. This will\nallow `?` to supplant the `try_opt!` macro on `Option` and the `try_ready!`\nmacro on `Poll`, among other things.\n\n[RFC 1859]: https://github.com/rust-lang/rfcs/pull/1859\n\nHere's an example implementation of the trait:\n\n```rust,ignore\n/// A distinct type to represent the `None` value of an `Option`.\n///\n/// This enables using the `?` operator on `Option`; it's rarely useful alone.\n#[derive(Debug)]\n#[unstable(feature = \"try_trait\", issue = \"42327\")]\npub struct None { _priv: () }\n\n#[unstable(feature = \"try_trait\", issue = \"42327\")]\nimpl<T> ops::Try for Option<T> {\n type Ok = T;\n type Error = None;\n\n fn into_result(self) -> Result<T, None> {\n self.ok_or(None { _priv: () })\n }\n\n fn from_ok(v: T) -> Self {\n Some(v)\n }\n\n fn from_error(_: None) -> Self {\n None\n }\n}\n```\n\nNote the `Error` associated type here is a new marker. The `?` operator\nallows interconversion between different `Try` implementers only when\nthe error type can be converted `Into` the error type of the enclosing\nfunction (or catch block). Having a distinct error type (as opposed to\njust `()`, or similar) restricts this to where it's semantically meaningful.\n" } , LintCompletion { label : "windows_handle" , description : "# `windows_handle`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "windows_stdio" , description : "# `windows_stdio`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "int_error_internals" , description : "# `int_error_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "core_panic" , description : "# `core_panic`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "core_private_bignum" , description : "# `core_private_bignum`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "derive_eq" , description : "# `derive_eq`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "thread_local_internals" , description : "# `thread_local_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "print_internals" , description : "# `print_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "c_void_variant" , description : "# `c_void_variant`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "fn_traits" , description : "# `fn_traits`\n\nThe tracking issue for this feature is [#29625]\n\nSee Also: [`unboxed_closures`](../language-features/unboxed-closures.md)\n\n[#29625]: https://github.com/rust-lang/rust/issues/29625\n\n----\n\nThe `fn_traits` feature allows for implementation of the [`Fn*`] traits\nfor creating custom closure-like types.\n\n[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html\n\n```rust\n#![feature(unboxed_closures)]\n#![feature(fn_traits)]\n\nstruct Adder {\n a: u32\n}\n\nimpl FnOnce<(u32, )> for Adder {\n type Output = u32;\n extern \"rust-call\" fn call_once(self, b: (u32, )) -> Self::Output {\n self.a + b.0\n }\n}\n\nfn main() {\n let adder = Adder { a: 3 };\n assert_eq!(adder(2), 5);\n}\n```\n" } , LintCompletion { label : "rt" , description : "# `rt`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "default_free_fn" , description : "# `default_free_fn`\n\nThe tracking issue for this feature is: [#73014]\n\n[#73014]: https://github.com/rust-lang/rust/issues/73014\n\n------------------------\n\nAdds a free `default()` function to the `std::default` module. This function\njust forwards to [`Default::default()`], but may remove repetition of the word\n\"default\" from the call site.\n\nHere is an example:\n\n```rust\n#![feature(default_free_fn)]\nuse std::default::default;\n\n#[derive(Default)]\nstruct AppConfig {\n foo: FooConfig,\n bar: BarConfig,\n}\n\n#[derive(Default)]\nstruct FooConfig {\n foo: i32,\n}\n\n#[derive(Default)]\nstruct BarConfig {\n bar: f32,\n baz: u8,\n}\n\nfn main() {\n let options = AppConfig {\n foo: default(),\n bar: BarConfig {\n bar: 10.1,\n ..default()\n },\n };\n}\n```\n" } , LintCompletion { label : "update_panic_count" , description : "# `update_panic_count`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "str_internals" , description : "# `str_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "fd" , description : "# `fd`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "char_error_internals" , description : "# `char_error_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "core_intrinsics" , description : "# `core_intrinsics`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "windows_c" , description : "# `windows_c`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "libstd_sys_internals" , description : "# `libstd_sys_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "fd_read" , description : "# `fd_read`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "c_variadic" , description : "# `c_variadic`\n\nThe tracking issue for this feature is: [#44930]\n\n[#44930]: https://github.com/rust-lang/rust/issues/44930\n\n------------------------\n\nThe `c_variadic` library feature exposes the `VaList` structure,\nRust's analogue of C's `va_list` type.\n\n## Examples\n\n```rust\n#![feature(c_variadic)]\n\nuse std::ffi::VaList;\n\npub unsafe extern \"C\" fn vadd(n: usize, mut args: VaList) -> usize {\n let mut sum = 0;\n for _ in 0..n {\n sum += args.arg::<usize>();\n }\n sum\n}\n```\n" } , LintCompletion { label : "allocator_api" , description : "# `allocator_api`\n\nThe tracking issue for this feature is [#32838]\n\n[#32838]: https://github.com/rust-lang/rust/issues/32838\n\n------------------------\n\nSometimes you want the memory for one collection to use a different\nallocator than the memory for another collection. In this case,\nreplacing the global allocator is not a workable option. Instead,\nyou need to pass in an instance of an `AllocRef` to each collection\nfor which you want a custom allocator.\n\nTBD\n" } , LintCompletion { label : "flt2dec" , description : "# `flt2dec`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "global_asm" , description : "# `global_asm`\n\nThe tracking issue for this feature is: [#35119]\n\n[#35119]: https://github.com/rust-lang/rust/issues/35119\n\n------------------------\n\nThe `global_asm!` macro allows the programmer to write arbitrary\nassembly outside the scope of a function body, passing it through\n`rustc` and `llvm` to the assembler. The macro is a no-frills\ninterface to LLVM's concept of [module-level inline assembly]. That is,\nall caveats applicable to LLVM's module-level inline assembly apply\nto `global_asm!`.\n\n[module-level inline assembly]: http://llvm.org/docs/LangRef.html#module-level-inline-assembly\n\n`global_asm!` fills a role not currently satisfied by either `asm!`\nor `#[naked]` functions. The programmer has _all_ features of the\nassembler at their disposal. The linker will expect to resolve any\nsymbols defined in the inline assembly, modulo any symbols marked as\nexternal. It also means syntax for directives and assembly follow the\nconventions of the assembler in your toolchain.\n\nA simple usage looks like this:\n\n```rust,ignore\n# #![feature(global_asm)]\n# you also need relevant target_arch cfgs\nglobal_asm!(include_str!(\"something_neato.s\"));\n```\n\nAnd a more complicated usage looks like this:\n\n```rust,ignore\n# #![feature(global_asm)]\n# #![cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n\npub mod sally {\n global_asm!(r#\"\n .global foo\n foo:\n jmp baz\n \"#);\n\n #[no_mangle]\n pub unsafe extern \"C\" fn baz() {}\n}\n\n// the symbols `foo` and `bar` are global, no matter where\n// `global_asm!` was used.\nextern \"C\" {\n fn foo();\n fn bar();\n}\n\npub mod harry {\n global_asm!(r#\"\n .global bar\n bar:\n jmp quux\n \"#);\n\n #[no_mangle]\n pub unsafe extern \"C\" fn quux() {}\n}\n```\n\nYou may use `global_asm!` multiple times, anywhere in your crate, in\nwhatever way suits you. The effect is as if you concatenated all\nusages and placed the larger, single usage in the crate root.\n\n------------------------\n\nIf you don't need quite as much power and flexibility as\n`global_asm!` provides, and you don't mind restricting your inline\nassembly to `fn` bodies only, you might try the\n[asm](asm.md) feature instead.\n" } , LintCompletion { label : "asm" , description : "# `asm`\n\nThe tracking issue for this feature is: [#72016]\n\n[#72016]: https://github.com/rust-lang/rust/issues/72016\n\n------------------------\n\nFor extremely low-level manipulations and performance reasons, one\nmight wish to control the CPU directly. Rust supports using inline\nassembly to do this via the `asm!` macro.\n\n# Guide-level explanation\n[guide-level-explanation]: #guide-level-explanation\n\nRust provides support for inline assembly via the `asm!` macro.\nIt can be used to embed handwritten assembly in the assembly output generated by the compiler.\nGenerally this should not be necessary, but might be where the required performance or timing\ncannot be otherwise achieved. Accessing low level hardware primitives, e.g. in kernel code, may also demand this functionality.\n\n> **Note**: the examples here are given in x86/x86-64 assembly, but other architectures are also supported.\n\nInline assembly is currently supported on the following architectures:\n- x86 and x86-64\n- ARM\n- AArch64\n- RISC-V\n- NVPTX\n- Hexagon\n\n## Basic usage\n\nLet us start with the simplest possible example:\n\n```rust,allow_fail\n# #![feature(asm)]\nunsafe {\n asm!(\"nop\");\n}\n```\n\nThis will insert a NOP (no operation) instruction into the assembly generated by the compiler.\nNote that all `asm!` invocations have to be inside an `unsafe` block, as they could insert\narbitrary instructions and break various invariants. The instructions to be inserted are listed\nin the first argument of the `asm!` macro as a string literal.\n\n## Inputs and outputs\n\nNow inserting an instruction that does nothing is rather boring. Let us do something that\nactually acts on data:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet x: u64;\nunsafe {\n asm!(\"mov {}, 5\", out(reg) x);\n}\nassert_eq!(x, 5);\n```\n\nThis will write the value `5` into the `u64` variable `x`.\nYou can see that the string literal we use to specify instructions is actually a template string.\nIt is governed by the same rules as Rust [format strings][format-syntax].\nThe arguments that are inserted into the template however look a bit different then you may\nbe familiar with. First we need to specify if the variable is an input or an output of the\ninline assembly. In this case it is an output. We declared this by writing `out`.\nWe also need to specify in what kind of register the assembly expects the variable.\nIn this case we put it in an arbitrary general purpose register by specifying `reg`.\nThe compiler will choose an appropriate register to insert into\nthe template and will read the variable from there after the inline assembly finishes executing.\n\nLet us see another example that also uses an input:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet i: u64 = 3;\nlet o: u64;\nunsafe {\n asm!(\n \"mov {0}, {1}\",\n \"add {0}, {number}\",\n out(reg) o,\n in(reg) i,\n number = const 5,\n );\n}\nassert_eq!(o, 8);\n```\n\nThis will add `5` to the input in variable `i` and write the result to variable `o`.\nThe particular way this assembly does this is first copying the value from `i` to the output,\nand then adding `5` to it.\n\nThe example shows a few things:\n\nFirst, we can see that `asm!` allows multiple template string arguments; each\none is treated as a separate line of assembly code, as if they were all joined\ntogether with newlines between them. This makes it easy to format assembly\ncode.\n\nSecond, we can see that inputs are declared by writing `in` instead of `out`.\n\nThird, one of our operands has a type we haven't seen yet, `const`.\nThis tells the compiler to expand this argument to value directly inside the assembly template.\nThis is only possible for constants and literals.\n\nFourth, we can see that we can specify an argument number, or name as in any format string.\nFor inline assembly templates this is particularly useful as arguments are often used more than once.\nFor more complex inline assembly using this facility is generally recommended, as it improves\nreadability, and allows reordering instructions without changing the argument order.\n\nWe can further refine the above example to avoid the `mov` instruction:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet mut x: u64 = 3;\nunsafe {\n asm!(\"add {0}, {number}\", inout(reg) x, number = const 5);\n}\nassert_eq!(x, 8);\n```\n\nWe can see that `inout` is used to specify an argument that is both input and output.\nThis is different from specifying an input and output separately in that it is guaranteed to assign both to the same register.\n\nIt is also possible to specify different variables for the input and output parts of an `inout` operand:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet x: u64 = 3;\nlet y: u64;\nunsafe {\n asm!(\"add {0}, {number}\", inout(reg) x => y, number = const 5);\n}\nassert_eq!(y, 8);\n```\n\n## Late output operands\n\nThe Rust compiler is conservative with its allocation of operands. It is assumed that an `out`\ncan be written at any time, and can therefore not share its location with any other argument.\nHowever, to guarantee optimal performance it is important to use as few registers as possible,\nso they won't have to be saved and reloaded around the inline assembly block.\nTo achieve this Rust provides a `lateout` specifier. This can be used on any output that is\nwritten only after all inputs have been consumed.\nThere is also a `inlateout` variant of this specifier.\n\nHere is an example where `inlateout` *cannot* be used:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet mut a: u64 = 4;\nlet b: u64 = 4;\nlet c: u64 = 4;\nunsafe {\n asm!(\n \"add {0}, {1}\",\n \"add {0}, {2}\",\n inout(reg) a,\n in(reg) b,\n in(reg) c,\n );\n}\nassert_eq!(a, 12);\n```\n\nHere the compiler is free to allocate the same register for inputs `b` and `c` since it knows they have the same value. However it must allocate a separate register for `a` since it uses `inout` and not `inlateout`. If `inlateout` was used, then `a` and `c` could be allocated to the same register, in which case the first instruction to overwrite the value of `c` and cause the assembly code to produce the wrong result.\n\nHowever the following example can use `inlateout` since the output is only modified after all input registers have been read:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet mut a: u64 = 4;\nlet b: u64 = 4;\nunsafe {\n asm!(\"add {0}, {1}\", inlateout(reg) a, in(reg) b);\n}\nassert_eq!(a, 8);\n```\n\nAs you can see, this assembly fragment will still work correctly if `a` and `b` are assigned to the same register.\n\n## Explicit register operands\n\nSome instructions require that the operands be in a specific register.\nTherefore, Rust inline assembly provides some more specific constraint specifiers.\nWhile `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi`\namong others can be addressed by their name.\n\n```rust,allow_fail,no_run\n# #![feature(asm)]\nlet cmd = 0xd1;\nunsafe {\n asm!(\"out 0x64, eax\", in(\"eax\") cmd);\n}\n```\n\nIn this example we call the `out` instruction to output the content of the `cmd` variable\nto port `0x64`. Since the `out` instruction only accepts `eax` (and its sub registers) as operand\nwe had to use the `eax` constraint specifier.\n\nNote that unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types.\n\nConsider this example which uses the x86 `mul` instruction:\n\n```rust,allow_fail\n# #![feature(asm)]\nfn mul(a: u64, b: u64) -> u128 {\n let lo: u64;\n let hi: u64;\n\n unsafe {\n asm!(\n // The x86 mul instruction takes rax as an implicit input and writes\n // the 128-bit result of the multiplication to rax:rdx.\n \"mul {}\",\n in(reg) a,\n inlateout(\"rax\") b => lo,\n lateout(\"rdx\") hi\n );\n }\n\n ((hi as u128) << 64) + lo as u128\n}\n```\n\nThis uses the `mul` instruction to multiply two 64-bit inputs with a 128-bit result.\nThe only explicit operand is a register, that we fill from the variable `a`.\nThe second operand is implicit, and must be the `rax` register, which we fill from the variable `b`.\nThe lower 64 bits of the result are stored in `rax` from which we fill the variable `lo`.\nThe higher 64 bits are stored in `rdx` from which we fill the variable `hi`.\n\n## Clobbered registers\n\nIn many cases inline assembly will modify state that is not needed as an output.\nUsually this is either because we have to use a scratch register in the assembly,\nor instructions modify state that we don't need to further examine.\nThis state is generally referred to as being \"clobbered\".\nWe need to tell the compiler about this since it may need to save and restore this state\naround the inline assembly block.\n\n```rust,allow_fail\n# #![feature(asm)]\nlet ebx: u32;\nlet ecx: u32;\n\nunsafe {\n asm!(\n \"cpuid\",\n // EAX 4 selects the \"Deterministic Cache Parameters\" CPUID leaf\n inout(\"eax\") 4 => _,\n // ECX 0 selects the L0 cache information.\n inout(\"ecx\") 0 => ecx,\n lateout(\"ebx\") ebx,\n lateout(\"edx\") _,\n );\n}\n\nprintln!(\n \"L1 Cache: {}\",\n ((ebx >> 22) + 1) * (((ebx >> 12) & 0x3ff) + 1) * ((ebx & 0xfff) + 1) * (ecx + 1)\n);\n```\n\nIn the example above we use the `cpuid` instruction to get the L1 cache size.\nThis instruction writes to `eax`, `ebx`, `ecx`, and `edx`, but for the cache size we only care about the contents of `ebx` and `ecx`.\n\nHowever we still need to tell the compiler that `eax` and `edx` have been modified so that it can save any values that were in these registers before the asm. This is done by declaring these as outputs but with `_` instead of a variable name, which indicates that the output value is to be discarded.\n\nThis can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code:\n\n```rust,allow_fail\n# #![feature(asm)]\n// Multiply x by 6 using shifts and adds\nlet mut x: u64 = 4;\nunsafe {\n asm!(\n \"mov {tmp}, {x}\",\n \"shl {tmp}, 1\",\n \"shl {x}, 2\",\n \"add {x}, {tmp}\",\n x = inout(reg) x,\n tmp = out(reg) _,\n );\n}\nassert_eq!(x, 4 * 6);\n```\n\n## Symbol operands\n\nA special operand type, `sym`, allows you to use the symbol name of a `fn` or `static` in inline assembly code.\nThis allows you to call a function or access a global variable without needing to keep its address in a register.\n\n```rust,allow_fail\n# #![feature(asm)]\nextern \"C\" fn foo(arg: i32) {\n println!(\"arg = {}\", arg);\n}\n\nfn call_foo(arg: i32) {\n unsafe {\n asm!(\n \"call {}\",\n sym foo,\n // 1st argument in rdi, which is caller-saved\n inout(\"rdi\") arg => _,\n // All caller-saved registers must be marked as clobberred\n out(\"rax\") _, out(\"rcx\") _, out(\"rdx\") _, out(\"rsi\") _,\n out(\"r8\") _, out(\"r9\") _, out(\"r10\") _, out(\"r11\") _,\n out(\"xmm0\") _, out(\"xmm1\") _, out(\"xmm2\") _, out(\"xmm3\") _,\n out(\"xmm4\") _, out(\"xmm5\") _, out(\"xmm6\") _, out(\"xmm7\") _,\n out(\"xmm8\") _, out(\"xmm9\") _, out(\"xmm10\") _, out(\"xmm11\") _,\n out(\"xmm12\") _, out(\"xmm13\") _, out(\"xmm14\") _, out(\"xmm15\") _,\n )\n }\n}\n```\n\nNote that the `fn` or `static` item does not need to be public or `#[no_mangle]`:\nthe compiler will automatically insert the appropriate mangled symbol name into the assembly code.\n\n## Register template modifiers\n\nIn some cases, fine control is needed over the way a register name is formatted when inserted into the template string. This is needed when an architecture's assembly language has several names for the same register, each typically being a \"view\" over a subset of the register (e.g. the low 32 bits of a 64-bit register).\n\nBy default the compiler will always choose the name that refers to the full register size (e.g. `rax` on x86-64, `eax` on x86, etc).\n\nThis default can be overriden by using modifiers on the template string operands, just like you would with format strings:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet mut x: u16 = 0xab;\n\nunsafe {\n asm!(\"mov {0:h}, {0:l}\", inout(reg_abcd) x);\n}\n\nassert_eq!(x, 0xabab);\n```\n\nIn this example, we use the `reg_abcd` register class to restrict the register allocator to the 4 legacy x86 register (`ax`, `bx`, `cx`, `dx`) of which the first two bytes can be addressed independently.\n\nLet us assume that the register allocator has chosen to allocate `x` in the `ax` register.\nThe `h` modifier will emit the register name for the high byte of that register and the `l` modifier will emit the register name for the low byte. The asm code will therefore be expanded as `mov ah, al` which copies the low byte of the value into the high byte.\n\nIf you use a smaller data type (e.g. `u16`) with an operand and forget the use template modifiers, the compiler will emit a warning and suggest the correct modifier to use.\n\n## Options\n\nBy default, an inline assembly block is treated the same way as an external FFI function call with a custom calling convention: it may read/write memory, have observable side effects, etc. However in many cases, it is desirable to give the compiler more information about what the assembly code is actually doing so that it can optimize better.\n\nLet's take our previous example of an `add` instruction:\n\n```rust,allow_fail\n# #![feature(asm)]\nlet mut a: u64 = 4;\nlet b: u64 = 4;\nunsafe {\n asm!(\n \"add {0}, {1}\",\n inlateout(reg) a, in(reg) b,\n options(pure, nomem, nostack),\n );\n}\nassert_eq!(a, 8);\n```\n\nOptions can be provided as an optional final argument to the `asm!` macro. We specified three options here:\n- `pure` means that the asm code has no observable side effects and that its output depends only on its inputs. This allows the compiler optimizer to call the inline asm fewer times or even eliminate it entirely.\n- `nomem` means that the asm code does not read or write to memory. By default the compiler will assume that inline assembly can read or write any memory address that is accessible to it (e.g. through a pointer passed as an operand, or a global).\n- `nostack` means that the asm code does not push any data onto the stack. This allows the compiler to use optimizations such as the stack red zone on x86-64 to avoid stack pointer adjustments.\n\nThese allow the compiler to better optimize code using `asm!`, for example by eliminating pure `asm!` blocks whose outputs are not needed.\n\nSee the reference for the full list of available options and their effects.\n\n# Reference-level explanation\n[reference-level-explanation]: #reference-level-explanation\n\nInline assembler is implemented as an unsafe macro `asm!()`.\nThe first argument to this macro is a template string literal used to build the final assembly.\nThe following arguments specify input and output operands.\nWhen required, options are specified as the final argument.\n\nThe following ABNF specifies the general syntax:\n\n```ignore\ndir_spec := \"in\" / \"out\" / \"lateout\" / \"inout\" / \"inlateout\"\nreg_spec := <register class> / \"<explicit register>\"\noperand_expr := expr / \"_\" / expr \"=>\" expr / expr \"=>\" \"_\"\nreg_operand := dir_spec \"(\" reg_spec \")\" operand_expr\noperand := reg_operand / \"const\" const_expr / \"sym\" path\noption := \"pure\" / \"nomem\" / \"readonly\" / \"preserves_flags\" / \"noreturn\" / \"att_syntax\"\noptions := \"options(\" option *[\",\" option] [\",\"] \")\"\nasm := \"asm!(\" format_string *(\",\" format_string) *(\",\" [ident \"=\"] operand) [\",\" options] [\",\"] \")\"\n```\n\nThe macro will initially be supported only on ARM, AArch64, Hexagon, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target.\n\n[format-syntax]: https://doc.rust-lang.org/std/fmt/#syntax\n\n## Template string arguments\n\nThe assembler template uses the same syntax as [format strings][format-syntax] (i.e. placeholders are specified by curly braces). The corresponding arguments are accessed in order, by index, or by name. However, implicit named arguments (introduced by [RFC #2795][rfc-2795]) are not supported.\n\nAn `asm!` invocation may have one or more template string arguments; an `asm!` with multiple template string arguments is treated as if all the strings were concatenated with a `\\n` between them. The expected usage is for each template string argument to correspond to a line of assembly code. All template string arguments must appear before any other arguments.\n\nAs with format strings, named arguments must appear after positional arguments. Explicit register operands must appear at the end of the operand list, after named arguments if any.\n\nExplicit register operands cannot be used by placeholders in the template string. All other named and positional operands must appear at least once in the template string, otherwise a compiler error is generated.\n\nThe exact assembly code syntax is target-specific and opaque to the compiler except for the way operands are substituted into the template string to form the code passed to the assembler.\n\nThe 5 targets specified in this RFC (x86, ARM, AArch64, RISC-V, Hexagon) all use the assembly code syntax of the GNU assembler (GAS). On x86, the `.intel_syntax noprefix` mode of GAS is used by default. On ARM, the `.syntax unified` mode is used. These targets impose an additional restriction on the assembly code: any assembler state (e.g. the current section which can be changed with `.section`) must be restored to its original value at the end of the asm string. Assembly code that does not conform to the GAS syntax will result in assembler-specific behavior.\n\n[rfc-2795]: https://github.com/rust-lang/rfcs/pull/2795\n\n## Operand type\n\nSeveral types of operands are supported:\n\n* `in(<reg>) <expr>`\n - `<reg>` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.\n - The allocated register will contain the value of `<expr>` at the start of the asm code.\n - The allocated register must contain the same value at the end of the asm code (except if a `lateout` is allocated to the same register).\n* `out(<reg>) <expr>`\n - `<reg>` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.\n - The allocated register will contain an undefined value at the start of the asm code.\n - `<expr>` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code.\n - An underscore (`_`) may be specified instead of an expression, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber).\n* `lateout(<reg>) <expr>`\n - Identical to `out` except that the register allocator can reuse a register allocated to an `in`.\n - You should only write to the register after all inputs are read, otherwise you may clobber an input.\n* `inout(<reg>) <expr>`\n - `<reg>` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string.\n - The allocated register will contain the value of `<expr>` at the start of the asm code.\n - `<expr>` must be a mutable initialized place expression, to which the contents of the allocated register is written to at the end of the asm code.\n* `inout(<reg>) <in expr> => <out expr>`\n - Same as `inout` except that the initial value of the register is taken from the value of `<in expr>`.\n - `<out expr>` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code.\n - An underscore (`_`) may be specified instead of an expression for `<out expr>`, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber).\n - `<in expr>` and `<out expr>` may have different types.\n* `inlateout(<reg>) <expr>` / `inlateout(<reg>) <in expr> => <out expr>`\n - Identical to `inout` except that the register allocator can reuse a register allocated to an `in` (this can happen if the compiler knows the `in` has the same initial value as the `inlateout`).\n - You should only write to the register after all inputs are read, otherwise you may clobber an input.\n* `const <expr>`\n - `<expr>` must be an integer or floating-point constant expression.\n - The value of the expression is formatted as a string and substituted directly into the asm template string.\n* `sym <path>`\n - `<path>` must refer to a `fn` or `static`.\n - A mangled symbol name referring to the item is substituted into the asm template string.\n - The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc).\n - `<path>` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data.\n\nOperand expressions are evaluated from left to right, just like function call arguments. After the `asm!` has executed, outputs are written to in left to right order. This is significant if two outputs point to the same place: that place will contain the value of the rightmost output.\n\n## Register operands\n\nInput and output operands can be specified either as an explicit register or as a register class from which the register allocator can select a register. Explicit registers are specified as string literals (e.g. `\"eax\"`) while register classes are specified as identifiers (e.g. `reg`). Using string literals for register names enables support for architectures that use special characters in register names, such as MIPS (`$0`, `$1`, etc).\n\nNote that explicit registers treat register aliases (e.g. `r14` vs `lr` on ARM) and smaller views of a register (e.g. `eax` vs `rax`) as equivalent to the base register. It is a compile-time error to use the same explicit register for two input operands or two output operands. Additionally, it is also a compile-time error to use overlapping registers (e.g. ARM VFP) in input operands or in output operands.\n\nOnly the following types are allowed as operands for inline assembly:\n- Integers (signed and unsigned)\n- Floating-point numbers\n- Pointers (thin only)\n- Function pointers\n- SIMD vectors (structs defined with `#[repr(simd)]` and which implement `Copy`). This includes architecture-specific vector types defined in `std::arch` such as `__m128` (x86) or `int8x16_t` (ARM).\n\nHere is the list of currently supported register classes:\n\n| Architecture | Register class | Registers | LLVM constraint code |\n| ------------ | -------------- | --------- | -------------------- |\n| x86 | `reg` | `ax`, `bx`, `cx`, `dx`, `si`, `di`, `r[8-15]` (x86-64 only) | `r` |\n| x86 | `reg_abcd` | `ax`, `bx`, `cx`, `dx` | `Q` |\n| x86-32 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `ah`, `bh`, `ch`, `dh` | `q` |\n| x86-64 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `sil`, `dil`, `r[8-15]b`, `ah`\\*, `bh`\\*, `ch`\\*, `dh`\\* | `q` |\n| x86 | `xmm_reg` | `xmm[0-7]` (x86) `xmm[0-15]` (x86-64) | `x` |\n| x86 | `ymm_reg` | `ymm[0-7]` (x86) `ymm[0-15]` (x86-64) | `x` |\n| x86 | `zmm_reg` | `zmm[0-7]` (x86) `zmm[0-31]` (x86-64) | `v` |\n| x86 | `kreg` | `k[1-7]` | `Yk` |\n| AArch64 | `reg` | `x[0-28]`, `x30` | `r` |\n| AArch64 | `vreg` | `v[0-31]` | `w` |\n| AArch64 | `vreg_low16` | `v[0-15]` | `x` |\n| ARM | `reg` | `r[0-5]` `r7`\\*, `r[8-10]`, `r11`\\*, `r12`, `r14` | `r` |\n| ARM (Thumb) | `reg_thumb` | `r[0-r7]` | `l` |\n| ARM (ARM) | `reg_thumb` | `r[0-r10]`, `r12`, `r14` | `l` |\n| ARM | `sreg` | `s[0-31]` | `t` |\n| ARM | `sreg_low16` | `s[0-15]` | `x` |\n| ARM | `dreg` | `d[0-31]` | `w` |\n| ARM | `dreg_low16` | `d[0-15]` | `t` |\n| ARM | `dreg_low8` | `d[0-8]` | `x` |\n| ARM | `qreg` | `q[0-15]` | `w` |\n| ARM | `qreg_low8` | `q[0-7]` | `t` |\n| ARM | `qreg_low4` | `q[0-3]` | `x` |\n| NVPTX | `reg16` | None\\* | `h` |\n| NVPTX | `reg32` | None\\* | `r` |\n| NVPTX | `reg64` | None\\* | `l` |\n| RISC-V | `reg` | `x1`, `x[5-7]`, `x[9-15]`, `x[16-31]` (non-RV32E) | `r` |\n| RISC-V | `freg` | `f[0-31]` | `f` |\n| Hexagon | `reg` | `r[0-28]` | `r` |\n\n> **Note**: On x86 we treat `reg_byte` differently from `reg` because the compiler can allocate `al` and `ah` separately whereas `reg` reserves the whole register.\n>\n> Note #2: On x86-64 the high byte registers (e.g. `ah`) are only available when used as an explicit register. Specifying the `reg_byte` register class for an operand will always allocate a low byte register.\n>\n> Note #3: NVPTX doesn't have a fixed register set, so named registers are not supported.\n>\n> Note #4: On ARM the frame pointer is either `r7` or `r11` depending on the platform.\n\nAdditional register classes may be added in the future based on demand (e.g. MMX, x87, etc).\n\nEach register class has constraints on which value types they can be used with. This is necessary because the way a value is loaded into a register depends on its type. For example, on big-endian systems, loading a `i32x4` and a `i8x16` into a SIMD register may result in different register contents even if the byte-wise memory representation of both values is identical. The availability of supported types for a particular register class may depend on what target features are currently enabled.\n\n| Architecture | Register class | Target feature | Allowed types |\n| ------------ | -------------- | -------------- | ------------- |\n| x86-32 | `reg` | None | `i16`, `i32`, `f32` |\n| x86-64 | `reg` | None | `i16`, `i32`, `f32`, `i64`, `f64` |\n| x86 | `reg_byte` | None | `i8` |\n| x86 | `xmm_reg` | `sse` | `i32`, `f32`, `i64`, `f64`, <br> `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` |\n| x86 | `ymm_reg` | `avx` | `i32`, `f32`, `i64`, `f64`, <br> `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` <br> `i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4` |\n| x86 | `zmm_reg` | `avx512f` | `i32`, `f32`, `i64`, `f64`, <br> `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` <br> `i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4` <br> `i8x64`, `i16x32`, `i32x16`, `i64x8`, `f32x16`, `f64x8` |\n| x86 | `kreg` | `axv512f` | `i8`, `i16` |\n| x86 | `kreg` | `axv512bw` | `i32`, `i64` |\n| AArch64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` |\n| AArch64 | `vreg` | `fp` | `i8`, `i16`, `i32`, `f32`, `i64`, `f64`, <br> `i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2`, `f64x1`, <br> `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` |\n| ARM | `reg` | None | `i8`, `i16`, `i32`, `f32` |\n| ARM | `sreg` | `vfp2` | `i32`, `f32` |\n| ARM | `dreg` | `vfp2` | `i64`, `f64`, `i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2` |\n| ARM | `qreg` | `neon` | `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4` |\n| NVPTX | `reg16` | None | `i8`, `i16` |\n| NVPTX | `reg32` | None | `i8`, `i16`, `i32`, `f32` |\n| NVPTX | `reg64` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` |\n| RISC-V32 | `reg` | None | `i8`, `i16`, `i32`, `f32` |\n| RISC-V64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` |\n| RISC-V | `freg` | `f` | `f32` |\n| RISC-V | `freg` | `d` | `f64` |\n| Hexagon | `reg` | None | `i8`, `i16`, `i32`, `f32` |\n\n> **Note**: For the purposes of the above table pointers, function pointers and `isize`/`usize` are treated as the equivalent integer type (`i16`/`i32`/`i64` depending on the target).\n\nIf a value is of a smaller size than the register it is allocated in then the upper bits of that register will have an undefined value for inputs and will be ignored for outputs. The only exception is the `freg` register class on RISC-V where `f32` values are NaN-boxed in a `f64` as required by the RISC-V architecture.\n\nWhen separate input and output expressions are specified for an `inout` operand, both expressions must have the same type. The only exception is if both operands are pointers or integers, in which case they are only required to have the same size. This restriction exists because the register allocators in LLVM and GCC sometimes cannot handle tied operands with different types.\n\n## Register names\n\nSome registers have multiple names. These are all treated by the compiler as identical to the base register name. Here is the list of all supported register aliases:\n\n| Architecture | Base register | Aliases |\n| ------------ | ------------- | ------- |\n| x86 | `ax` | `eax`, `rax` |\n| x86 | `bx` | `ebx`, `rbx` |\n| x86 | `cx` | `ecx`, `rcx` |\n| x86 | `dx` | `edx`, `rdx` |\n| x86 | `si` | `esi`, `rsi` |\n| x86 | `di` | `edi`, `rdi` |\n| x86 | `bp` | `bpl`, `ebp`, `rbp` |\n| x86 | `sp` | `spl`, `esp`, `rsp` |\n| x86 | `ip` | `eip`, `rip` |\n| x86 | `st(0)` | `st` |\n| x86 | `r[8-15]` | `r[8-15]b`, `r[8-15]w`, `r[8-15]d` |\n| x86 | `xmm[0-31]` | `ymm[0-31]`, `zmm[0-31]` |\n| AArch64 | `x[0-30]` | `w[0-30]` |\n| AArch64 | `x29` | `fp` |\n| AArch64 | `x30` | `lr` |\n| AArch64 | `sp` | `wsp` |\n| AArch64 | `xzr` | `wzr` |\n| AArch64 | `v[0-31]` | `b[0-31]`, `h[0-31]`, `s[0-31]`, `d[0-31]`, `q[0-31]` |\n| ARM | `r[0-3]` | `a[1-4]` |\n| ARM | `r[4-9]` | `v[1-6]` |\n| ARM | `r9` | `rfp` |\n| ARM | `r10` | `sl` |\n| ARM | `r11` | `fp` |\n| ARM | `r12` | `ip` |\n| ARM | `r13` | `sp` |\n| ARM | `r14` | `lr` |\n| ARM | `r15` | `pc` |\n| RISC-V | `x0` | `zero` |\n| RISC-V | `x1` | `ra` |\n| RISC-V | `x2` | `sp` |\n| RISC-V | `x3` | `gp` |\n| RISC-V | `x4` | `tp` |\n| RISC-V | `x[5-7]` | `t[0-2]` |\n| RISC-V | `x8` | `fp`, `s0` |\n| RISC-V | `x9` | `s1` |\n| RISC-V | `x[10-17]` | `a[0-7]` |\n| RISC-V | `x[18-27]` | `s[2-11]` |\n| RISC-V | `x[28-31]` | `t[3-6]` |\n| RISC-V | `f[0-7]` | `ft[0-7]` |\n| RISC-V | `f[8-9]` | `fs[0-1]` |\n| RISC-V | `f[10-17]` | `fa[0-7]` |\n| RISC-V | `f[18-27]` | `fs[2-11]` |\n| RISC-V | `f[28-31]` | `ft[8-11]` |\n| Hexagon | `r29` | `sp` |\n| Hexagon | `r30` | `fr` |\n| Hexagon | `r31` | `lr` |\n\nSome registers cannot be used for input or output operands:\n\n| Architecture | Unsupported register | Reason |\n| ------------ | -------------------- | ------ |\n| All | `sp` | The stack pointer must be restored to its original value at the end of an asm code block. |\n| All | `bp` (x86), `x29` (AArch64), `x8` (RISC-V), `fr` (Hexagon) | The frame pointer cannot be used as an input or output. |\n| ARM | `r7` or `r11` | On ARM the frame pointer can be either `r7` or `r11` depending on the target. The frame pointer cannot be used as an input or output. |\n| ARM | `r6` | `r6` is used internally by LLVM as a base pointer and therefore cannot be used as an input or output. |\n| x86 | `k0` | This is a constant zero register which can't be modified. |\n| x86 | `ip` | This is the program counter, not a real register. |\n| x86 | `mm[0-7]` | MMX registers are not currently supported (but may be in the future). |\n| x86 | `st([0-7])` | x87 registers are not currently supported (but may be in the future). |\n| AArch64 | `xzr` | This is a constant zero register which can't be modified. |\n| ARM | `pc` | This is the program counter, not a real register. |\n| RISC-V | `x0` | This is a constant zero register which can't be modified. |\n| RISC-V | `gp`, `tp` | These registers are reserved and cannot be used as inputs or outputs. |\n| Hexagon | `lr` | This is the link register which cannot be used as an input or output. |\n\nIn some cases LLVM will allocate a \"reserved register\" for `reg` operands even though this register cannot be explicitly specified. Assembly code making use of reserved registers should be careful since `reg` operands may alias with those registers. Reserved registers are:\n- The frame pointer on all architectures.\n- `r6` on ARM.\n\n## Template modifiers\n\nThe placeholders can be augmented by modifiers which are specified after the `:` in the curly braces. These modifiers do not affect register allocation, but change the way operands are formatted when inserted into the template string. Only one modifier is allowed per template placeholder.\n\nThe supported modifiers are a subset of LLVM's (and GCC's) [asm template argument modifiers][llvm-argmod], but do not use the same letter codes.\n\n| Architecture | Register class | Modifier | Example output | LLVM modifier |\n| ------------ | -------------- | -------- | -------------- | ------------- |\n| x86-32 | `reg` | None | `eax` | `k` |\n| x86-64 | `reg` | None | `rax` | `q` |\n| x86-32 | `reg_abcd` | `l` | `al` | `b` |\n| x86-64 | `reg` | `l` | `al` | `b` |\n| x86 | `reg_abcd` | `h` | `ah` | `h` |\n| x86 | `reg` | `x` | `ax` | `w` |\n| x86 | `reg` | `e` | `eax` | `k` |\n| x86-64 | `reg` | `r` | `rax` | `q` |\n| x86 | `reg_byte` | None | `al` / `ah` | None |\n| x86 | `xmm_reg` | None | `xmm0` | `x` |\n| x86 | `ymm_reg` | None | `ymm0` | `t` |\n| x86 | `zmm_reg` | None | `zmm0` | `g` |\n| x86 | `*mm_reg` | `x` | `xmm0` | `x` |\n| x86 | `*mm_reg` | `y` | `ymm0` | `t` |\n| x86 | `*mm_reg` | `z` | `zmm0` | `g` |\n| x86 | `kreg` | None | `k1` | None |\n| AArch64 | `reg` | None | `x0` | `x` |\n| AArch64 | `reg` | `w` | `w0` | `w` |\n| AArch64 | `reg` | `x` | `x0` | `x` |\n| AArch64 | `vreg` | None | `v0` | None |\n| AArch64 | `vreg` | `v` | `v0` | None |\n| AArch64 | `vreg` | `b` | `b0` | `b` |\n| AArch64 | `vreg` | `h` | `h0` | `h` |\n| AArch64 | `vreg` | `s` | `s0` | `s` |\n| AArch64 | `vreg` | `d` | `d0` | `d` |\n| AArch64 | `vreg` | `q` | `q0` | `q` |\n| ARM | `reg` | None | `r0` | None |\n| ARM | `sreg` | None | `s0` | None |\n| ARM | `dreg` | None | `d0` | `P` |\n| ARM | `qreg` | None | `q0` | `q` |\n| ARM | `qreg` | `e` / `f` | `d0` / `d1` | `e` / `f` |\n| NVPTX | `reg16` | None | `rs0` | None |\n| NVPTX | `reg32` | None | `r0` | None |\n| NVPTX | `reg64` | None | `rd0` | None |\n| RISC-V | `reg` | None | `x1` | None |\n| RISC-V | `freg` | None | `f0` | None |\n| Hexagon | `reg` | None | `r0` | None |\n\n> Notes:\n> - on ARM `e` / `f`: this prints the low or high doubleword register name of a NEON quad (128-bit) register.\n> - on x86: our behavior for `reg` with no modifiers differs from what GCC does. GCC will infer the modifier based on the operand value type, while we default to the full register size.\n> - on x86 `xmm_reg`: the `x`, `t` and `g` LLVM modifiers are not yet implemented in LLVM (they are supported by GCC only), but this should be a simple change.\n\nAs stated in the previous section, passing an input value smaller than the register width will result in the upper bits of the register containing undefined values. This is not a problem if the inline asm only accesses the lower bits of the register, which can be done by using a template modifier to use a subregister name in the asm code (e.g. `ax` instead of `rax`). Since this an easy pitfall, the compiler will suggest a template modifier to use where appropriate given the input type. If all references to an operand already have modifiers then the warning is suppressed for that operand.\n\n[llvm-argmod]: http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers\n\n## Options\n\nFlags are used to further influence the behavior of the inline assembly block.\nCurrently the following options are defined:\n- `pure`: The `asm` block has no side effects, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless the `nomem` options is also set). This allows the compiler to execute the `asm` block fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used.\n- `nomem`: The `asm` blocks does not read or write to any memory. This allows the compiler to cache the values of modified global variables in registers across the `asm` block since it knows that they are not read or written to by the `asm`.\n- `readonly`: The `asm` block does not write to any memory. This allows the compiler to cache the values of unmodified global variables in registers across the `asm` block since it knows that they are not written to by the `asm`.\n- `preserves_flags`: The `asm` block does not modify the flags register (defined in the rules below). This allows the compiler to avoid recomputing the condition flags after the `asm` block.\n- `noreturn`: The `asm` block never returns, and its return type is defined as `!` (never). Behavior is undefined if execution falls through past the end of the asm code. A `noreturn` asm block behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked.\n- `nostack`: The `asm` block does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is *not* used then the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call.\n- `att_syntax`: This option is only valid on x86, and causes the assembler to use the `.att_syntax prefix` mode of the GNU assembler. Register operands are substituted in with a leading `%`.\n\nThe compiler performs some additional checks on options:\n- The `nomem` and `readonly` options are mutually exclusive: it is a compile-time error to specify both.\n- The `pure` option must be combined with either the `nomem` or `readonly` options, otherwise a compile-time error is emitted.\n- It is a compile-time error to specify `pure` on an asm block with no outputs or only discarded outputs (`_`).\n- It is a compile-time error to specify `noreturn` on an asm block with outputs.\n\n## Rules for inline assembly\n\n- Any registers not specified as inputs will contain an undefined value on entry to the asm block.\n - An \"undefined value\" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code).\n- Any registers not specified as outputs must have the same value upon exiting the asm block as they had on entry, otherwise behavior is undefined.\n - This only applies to registers which can be specified as an input or output. Other registers follow target-specific rules.\n - Note that a `lateout` may be allocated to the same register as an `in`, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation.\n- Behavior is undefined if execution unwinds out of an asm block.\n - This also applies if the assembly code calls a function which then unwinds.\n- The set of memory locations that assembly code is allowed the read and write are the same as those allowed for an FFI function.\n - Refer to the unsafe code guidelines for the exact rules.\n - If the `readonly` option is set, then only memory reads are allowed.\n - If the `nomem` option is set then no reads or writes to memory are allowed.\n - These rules do not apply to memory which is private to the asm code, such as stack space allocated within the asm block.\n- The compiler cannot assume that the instructions in the asm are the ones that will actually end up executed.\n - This effectively means that the compiler must treat the `asm!` as a black box and only take the interface specification into account, not the instructions themselves.\n - Runtime code patching is allowed, via target-specific mechanisms (outside the scope of this RFC).\n- Unless the `nostack` option is set, asm code is allowed to use stack space below the stack pointer.\n - On entry to the asm block the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call.\n - You are responsible for making sure you don't overflow the stack (e.g. use stack probing to ensure you hit a guard page).\n - You should adjust the stack pointer when allocating stack memory as required by the target ABI.\n - The stack pointer must be restored to its original value before leaving the asm block.\n- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the asm block.\n- If the `pure` option is set then behavior is undefined if the `asm` has side-effects other than its direct outputs. Behavior is also undefined if two executions of the `asm` code with the same inputs result in different outputs.\n - When used with the `nomem` option, \"inputs\" are just the direct inputs of the `asm!`.\n - When used with the `readonly` option, \"inputs\" comprise the direct inputs of the `asm!` and any memory that the `asm!` block is allowed to read.\n- These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set:\n - x86\n - Status flags in `EFLAGS` (CF, PF, AF, ZF, SF, OF).\n - Floating-point status word (all).\n - Floating-point exception flags in `MXCSR` (PE, UE, OE, ZE, DE, IE).\n - ARM\n - Condition flags in `CPSR` (N, Z, C, V)\n - Saturation flag in `CPSR` (Q)\n - Greater than or equal flags in `CPSR` (GE).\n - Condition flags in `FPSCR` (N, Z, C, V)\n - Saturation flag in `FPSCR` (QC)\n - Floating-point exception flags in `FPSCR` (IDC, IXC, UFC, OFC, DZC, IOC).\n - AArch64\n - Condition flags (`NZCV` register).\n - Floating-point status (`FPSR` register).\n - RISC-V\n - Floating-point exception flags in `fcsr` (`fflags`).\n- On x86, the direction flag (DF in `EFLAGS`) is clear on entry to an asm block and must be clear on exit.\n - Behavior is undefined if the direction flag is set on exiting an asm block.\n- The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting an `asm!` block.\n - This means that `asm!` blocks that never return (even if not marked `noreturn`) don't need to preserve these registers.\n - When returning to a different `asm!` block than you entered (e.g. for context switching), these registers must contain the value they had upon entering the `asm!` block that you are *exiting*.\n - You cannot exit an `asm!` block that has not been entered. Neither can you exit an `asm!` block that has already been exited.\n - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds).\n - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited.\n- You cannot assume that an `asm!` block will appear exactly once in the output binary. The compiler is allowed to instantiate multiple copies of the `asm!` block, for example when the function containing it is inlined in multiple places.\n - As a consequence, you should only use [local labels] inside inline assembly code. Defining symbols in assembly code may lead to assembler and/or linker errors due to duplicate symbol definitions.\n\n> **Note**: As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call.\n\n[local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels\n" } , LintCompletion { label : "core_private_diy_float" , description : "# `core_private_diy_float`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "trace_macros" , description : "# `trace_macros`\n\nThe tracking issue for this feature is [#29598].\n\n[#29598]: https://github.com/rust-lang/rust/issues/29598\n\n------------------------\n\nWith `trace_macros` you can trace the expansion of macros in your code.\n\n## Examples\n\n```rust\n#![feature(trace_macros)]\n\nfn main() {\n trace_macros!(true);\n println!(\"Hello, Rust!\");\n trace_macros!(false);\n}\n```\n\nThe `cargo build` output:\n\n```txt\nnote: trace_macro\n --> src/main.rs:5:5\n |\n5 | println!(\"Hello, Rust!\");\n | ^^^^^^^^^^^^^^^^^^^^^^^^^\n |\n = note: expanding `println! { \"Hello, Rust!\" }`\n = note: to `print ! ( concat ! ( \"Hello, Rust!\" , \"\\n\" ) )`\n = note: expanding `print! { concat ! ( \"Hello, Rust!\" , \"\\n\" ) }`\n = note: to `$crate :: io :: _print ( format_args ! ( concat ! ( \"Hello, Rust!\" , \"\\n\" ) )\n )`\n\n Finished dev [unoptimized + debuginfo] target(s) in 0.60 secs\n```\n" } , LintCompletion { label : "concat_idents" , description : "# `concat_idents`\n\nThe tracking issue for this feature is: [#29599]\n\n[#29599]: https://github.com/rust-lang/rust/issues/29599\n\n------------------------\n\nThe `concat_idents` feature adds a macro for concatenating multiple identifiers\ninto one identifier.\n\n## Examples\n\n```rust\n#![feature(concat_idents)]\n\nfn main() {\n fn foobar() -> u32 { 23 }\n let f = concat_idents!(foo, bar);\n assert_eq!(f(), 23);\n}\n```" } , LintCompletion { label : "windows_net" , description : "# `windows_net`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "derive_clone_copy" , description : "# `derive_clone_copy`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "libstd_thread_internals" , description : "# `libstd_thread_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "test" , description : "# `test`\n\nThe tracking issue for this feature is: None.\n\n------------------------\n\nThe internals of the `test` crate are unstable, behind the `test` flag. The\nmost widely used part of the `test` crate are benchmark tests, which can test\nthe performance of your code. Let's make our `src/lib.rs` look like this\n(comments elided):\n\n```rust,ignore\n#![feature(test)]\n\nextern crate test;\n\npub fn add_two(a: i32) -> i32 {\n a + 2\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use test::Bencher;\n\n #[test]\n fn it_works() {\n assert_eq!(4, add_two(2));\n }\n\n #[bench]\n fn bench_add_two(b: &mut Bencher) {\n b.iter(|| add_two(2));\n }\n}\n```\n\nNote the `test` feature gate, which enables this unstable feature.\n\nWe've imported the `test` crate, which contains our benchmarking support.\nWe have a new function as well, with the `bench` attribute. Unlike regular\ntests, which take no arguments, benchmark tests take a `&mut Bencher`. This\n`Bencher` provides an `iter` method, which takes a closure. This closure\ncontains the code we'd like to benchmark.\n\nWe can run benchmark tests with `cargo bench`:\n\n```bash\n$ cargo bench\n Compiling adder v0.0.1 (file:///home/steve/tmp/adder)\n Running target/release/adder-91b3e234d4ed382a\n\nrunning 2 tests\ntest tests::it_works ... ignored\ntest tests::bench_add_two ... bench: 1 ns/iter (+/- 0)\n\ntest result: ok. 0 passed; 0 failed; 1 ignored; 1 measured\n```\n\nOur non-benchmark test was ignored. You may have noticed that `cargo bench`\ntakes a bit longer than `cargo test`. This is because Rust runs our benchmark\na number of times, and then takes the average. Because we're doing so little\nwork in this example, we have a `1 ns/iter (+/- 0)`, but this would show\nthe variance if there was one.\n\nAdvice on writing benchmarks:\n\n\n* Move setup code outside the `iter` loop; only put the part you want to measure inside\n* Make the code do \"the same thing\" on each iteration; do not accumulate or change state\n* Make the outer function idempotent too; the benchmark runner is likely to run\n it many times\n* Make the inner `iter` loop short and fast so benchmark runs are fast and the\n calibrator can adjust the run-length at fine resolution\n* Make the code in the `iter` loop do something simple, to assist in pinpointing\n performance improvements (or regressions)\n\n## Gotcha: optimizations\n\nThere's another tricky part to writing benchmarks: benchmarks compiled with\noptimizations activated can be dramatically changed by the optimizer so that\nthe benchmark is no longer benchmarking what one expects. For example, the\ncompiler might recognize that some calculation has no external effects and\nremove it entirely.\n\n```rust,ignore\n#![feature(test)]\n\nextern crate test;\nuse test::Bencher;\n\n#[bench]\nfn bench_xor_1000_ints(b: &mut Bencher) {\n b.iter(|| {\n (0..1000).fold(0, |old, new| old ^ new);\n });\n}\n```\n\ngives the following results\n\n```text\nrunning 1 test\ntest bench_xor_1000_ints ... bench: 0 ns/iter (+/- 0)\n\ntest result: ok. 0 passed; 0 failed; 0 ignored; 1 measured\n```\n\nThe benchmarking runner offers two ways to avoid this. Either, the closure that\nthe `iter` method receives can return an arbitrary value which forces the\noptimizer to consider the result used and ensures it cannot remove the\ncomputation entirely. This could be done for the example above by adjusting the\n`b.iter` call to\n\n```rust\n# struct X;\n# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;\nb.iter(|| {\n // Note lack of `;` (could also use an explicit `return`).\n (0..1000).fold(0, |old, new| old ^ new)\n});\n```\n\nOr, the other option is to call the generic `test::black_box` function, which\nis an opaque \"black box\" to the optimizer and so forces it to consider any\nargument as used.\n\n```rust\n#![feature(test)]\n\nextern crate test;\n\n# fn main() {\n# struct X;\n# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;\nb.iter(|| {\n let n = test::black_box(1000);\n\n (0..n).fold(0, |a, b| a ^ b)\n})\n# }\n```\n\nNeither of these read or modify the value, and are very cheap for small values.\nLarger values can be passed indirectly to reduce overhead (e.g.\n`black_box(&huge_struct)`).\n\nPerforming either of the above changes gives the following benchmarking results\n\n```text\nrunning 1 test\ntest bench_xor_1000_ints ... bench: 131 ns/iter (+/- 3)\n\ntest result: ok. 0 passed; 0 failed; 0 ignored; 1 measured\n```\n\nHowever, the optimizer can still modify a testcase in an undesirable manner\neven when using either of the above.\n" } , LintCompletion { label : "sort_internals" , description : "# `sort_internals`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } , LintCompletion { label : "is_sorted" , description : "# `is_sorted`\n\nThe tracking issue for this feature is: [#53485]\n\n[#53485]: https://github.com/rust-lang/rust/issues/53485\n\n------------------------\n\nAdd the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to `[T]`;\nadd the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to\n`Iterator`.\n" } , LintCompletion { label : "llvm_asm" , description : "# `llvm_asm`\n\nThe tracking issue for this feature is: [#70173]\n\n[#70173]: https://github.com/rust-lang/rust/issues/70173\n\n------------------------\n\nFor extremely low-level manipulations and performance reasons, one\nmight wish to control the CPU directly. Rust supports using inline\nassembly to do this via the `llvm_asm!` macro.\n\n```rust,ignore\nllvm_asm!(assembly template\n : output operands\n : input operands\n : clobbers\n : options\n );\n```\n\nAny use of `llvm_asm` is feature gated (requires `#![feature(llvm_asm)]` on the\ncrate to allow) and of course requires an `unsafe` block.\n\n> **Note**: the examples here are given in x86/x86-64 assembly, but\n> all platforms are supported.\n\n## Assembly template\n\nThe `assembly template` is the only required parameter and must be a\nliteral string (i.e. `\"\"`)\n\n```rust\n#![feature(llvm_asm)]\n\n#[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\nfn foo() {\n unsafe {\n llvm_asm!(\"NOP\");\n }\n}\n\n// Other platforms:\n#[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\nfn foo() { /* ... */ }\n\nfn main() {\n // ...\n foo();\n // ...\n}\n```\n\n(The `feature(llvm_asm)` and `#[cfg]`s are omitted from now on.)\n\nOutput operands, input operands, clobbers and options are all optional\nbut you must add the right number of `:` if you skip them:\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n# fn main() { unsafe {\nllvm_asm!(\"xor %eax, %eax\"\n :\n :\n : \"eax\"\n );\n# } }\n# #[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\n# fn main() {}\n```\n\nWhitespace also doesn't matter:\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n# fn main() { unsafe {\nllvm_asm!(\"xor %eax, %eax\" ::: \"eax\");\n# } }\n# #[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\n# fn main() {}\n```\n\n## Operands\n\nInput and output operands follow the same format: `:\n\"constraints1\"(expr1), \"constraints2\"(expr2), ...\"`. Output operand\nexpressions must be mutable place, or not yet assigned:\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\nfn add(a: i32, b: i32) -> i32 {\n let c: i32;\n unsafe {\n llvm_asm!(\"add $2, $0\"\n : \"=r\"(c)\n : \"0\"(a), \"r\"(b)\n );\n }\n c\n}\n# #[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\n# fn add(a: i32, b: i32) -> i32 { a + b }\n\nfn main() {\n assert_eq!(add(3, 14159), 14162)\n}\n```\n\nIf you would like to use real operands in this position, however,\nyou are required to put curly braces `{}` around the register that\nyou want, and you are required to put the specific size of the\noperand. This is useful for very low level programming, where\nwhich register you use is important:\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n# unsafe fn read_byte_in(port: u16) -> u8 {\nlet result: u8;\nllvm_asm!(\"in %dx, %al\" : \"={al}\"(result) : \"{dx}\"(port));\nresult\n# }\n```\n\n## Clobbers\n\nSome instructions modify registers which might otherwise have held\ndifferent values so we use the clobbers list to indicate to the\ncompiler not to assume any values loaded into those registers will\nstay valid.\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n# fn main() { unsafe {\n// Put the value 0x200 in eax:\nllvm_asm!(\"mov $$0x200, %eax\" : /* no outputs */ : /* no inputs */ : \"eax\");\n# } }\n# #[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\n# fn main() {}\n```\n\nInput and output registers need not be listed since that information\nis already communicated by the given constraints. Otherwise, any other\nregisters used either implicitly or explicitly should be listed.\n\nIf the assembly changes the condition code register `cc` should be\nspecified as one of the clobbers. Similarly, if the assembly modifies\nmemory, `memory` should also be specified.\n\n## Options\n\nThe last section, `options` is specific to Rust. The format is comma\nseparated literal strings (i.e. `:\"foo\", \"bar\", \"baz\"`). It's used to\nspecify some extra info about the inline assembly:\n\nCurrent valid options are:\n\n1. *volatile* - specifying this is analogous to\n `__asm__ __volatile__ (...)` in gcc/clang.\n2. *alignstack* - certain instructions expect the stack to be\n aligned a certain way (i.e. SSE) and specifying this indicates to\n the compiler to insert its usual stack alignment code\n3. *intel* - use intel syntax instead of the default AT&T.\n\n```rust\n# #![feature(llvm_asm)]\n# #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n# fn main() {\nlet result: i32;\nunsafe {\n llvm_asm!(\"mov eax, 2\" : \"={eax}\"(result) : : : \"intel\")\n}\nprintln!(\"eax is currently {}\", result);\n# }\n# #[cfg(not(any(target_arch = \"x86\", target_arch = \"x86_64\")))]\n# fn main() {}\n```\n\n## More Information\n\nThe current implementation of the `llvm_asm!` macro is a direct binding to [LLVM's\ninline assembler expressions][llvm-docs], so be sure to check out [their\ndocumentation as well][llvm-docs] for more information about clobbers,\nconstraints, etc.\n\n[llvm-docs]: http://llvm.org/docs/LangRef.html#inline-assembler-expressions\n\nIf you need more power and don't mind losing some of the niceties of\n`llvm_asm!`, check out [global_asm](global-asm.md).\n" } , LintCompletion { label : "format_args_capture" , description : "# `format_args_capture`\n\nThe tracking issue for this feature is: [#67984]\n\n[#67984]: https://github.com/rust-lang/rust/issues/67984\n\n------------------------\n\nEnables `format_args!` (and macros which use `format_args!` in their implementation, such\nas `format!`, `print!` and `panic!`) to capture variables from the surrounding scope.\nThis avoids the need to pass named parameters when the binding in question\nalready exists in scope.\n\n```rust\n#![feature(format_args_capture)]\n\nlet (person, species, name) = (\"Charlie Brown\", \"dog\", \"Snoopy\");\n\n// captures named argument `person`\nprint!(\"Hello {person}\");\n\n// captures named arguments `species` and `name`\nformat!(\"The {species}'s name is {name}.\");\n```\n\nThis also works for formatting parameters such as width and precision:\n\n```rust\n#![feature(format_args_capture)]\n\nlet precision = 2;\nlet s = format!(\"{:.precision$}\", 1.324223);\n\nassert_eq!(&s, \"1.32\");\n```\n\nA non-exhaustive list of macros which benefit from this functionality include:\n- `format!`\n- `print!` and `println!`\n- `eprint!` and `eprintln!`\n- `write!` and `writeln!`\n- `panic!`\n- `unreachable!`\n- `unimplemented!`\n- `todo!`\n- `assert!` and similar\n- macros in many thirdparty crates, such as `log`\n" } , LintCompletion { label : "set_stdio" , description : "# `set_stdio`\n\nThis feature is internal to the Rust compiler and is not intended for general use.\n\n------------------------\n" } ] ;
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs
new file mode 100644
index 000000000..ffc97c076
--- /dev/null
+++ b/crates/ide/src/completion/patterns.rs
@@ -0,0 +1,194 @@
1//! Patterns telling us certain facts about current syntax element, they are used in completion context
2
3use syntax::{
4 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken,
9};
10
11#[cfg(test)]
12use crate::completion::test_utils::check_pattern_is_applicable;
13
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
15 not_same_range_ancestor(element)
16 .filter(|it| it.kind() == ASSOC_ITEM_LIST)
17 .and_then(|it| it.parent())
18 .filter(|it| it.kind() == TRAIT)
19 .is_some()
20}
21#[test]
22fn test_has_trait_parent() {
23 check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent);
24}
25
26pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
27 not_same_range_ancestor(element)
28 .filter(|it| it.kind() == ASSOC_ITEM_LIST)
29 .and_then(|it| it.parent())
30 .filter(|it| it.kind() == IMPL)
31 .is_some()
32}
33#[test]
34fn test_has_impl_parent() {
35 check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
36}
37
38pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
39 not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
40}
41#[test]
42fn test_has_block_expr_parent() {
43 check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent);
44}
45
46pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
47 element.ancestors().find(|it| it.kind() == IDENT_PAT).is_some()
48}
49#[test]
50fn test_has_bind_pat_parent() {
51 check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent);
52 check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent);
53}
54
55pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
56 not_same_range_ancestor(element)
57 .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
58 .is_some()
59}
60#[test]
61fn test_has_ref_parent() {
62 check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent);
63 check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent);
64}
65
66pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
67 let ancestor = not_same_range_ancestor(element);
68 if !ancestor.is_some() {
69 return true;
70 }
71 ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some()
72}
73#[test]
74fn test_has_item_list_or_source_file_parent() {
75 check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent);
76 check_pattern_is_applicable(r"mod foo { f<|> }", has_item_list_or_source_file_parent);
77}
78
79pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
80 not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
81 && previous_sibling_or_ancestor_sibling(element)
82 .and_then(|it| it.into_token())
83 .filter(|it| it.kind() == FAT_ARROW)
84 .is_some()
85}
86#[test]
87fn test_is_match_arm() {
88 check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm);
89}
90
91pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
92 element
93 .into_token()
94 .and_then(|it| previous_non_trivia_token(it))
95 .filter(|it| it.kind() == UNSAFE_KW)
96 .is_some()
97}
98#[test]
99fn test_unsafe_is_prev() {
100 check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev);
101}
102
103pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
104 element
105 .into_token()
106 .and_then(|it| previous_non_trivia_token(it))
107 .filter(|it| it.kind() == IF_KW)
108 .is_some()
109}
110#[test]
111fn test_if_is_prev() {
112 check_pattern_is_applicable(r"if l<|>", if_is_prev);
113}
114
115pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
116 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some()
117}
118#[test]
119fn test_has_trait_as_prev_sibling() {
120 check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling);
121}
122
123pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool {
124 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some()
125}
126#[test]
127fn test_has_impl_as_prev_sibling() {
128 check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling);
129}
130
131pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
132 let leaf = match element {
133 NodeOrToken::Node(node) => node,
134 NodeOrToken::Token(token) => token.parent(),
135 };
136 for node in leaf.ancestors() {
137 if node.kind() == FN || node.kind() == CLOSURE_EXPR {
138 break;
139 }
140 let loop_body = match_ast! {
141 match node {
142 ast::ForExpr(it) => it.loop_body(),
143 ast::WhileExpr(it) => it.loop_body(),
144 ast::LoopExpr(it) => it.loop_body(),
145 _ => None,
146 }
147 };
148 if let Some(body) = loop_body {
149 if body.syntax().text_range().contains_range(leaf.text_range()) {
150 return true;
151 }
152 }
153 }
154 false
155}
156
157fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
158 element
159 .ancestors()
160 .take_while(|it| it.text_range() == element.text_range())
161 .last()
162 .and_then(|it| it.parent())
163}
164
165fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
166 let mut token = token.prev_token();
167 while let Some(inner) = token.clone() {
168 if !inner.kind().is_trivia() {
169 return Some(inner);
170 } else {
171 token = inner.prev_token();
172 }
173 }
174 None
175}
176
177fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> {
178 let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev);
179 if let Some(sibling) = token_sibling {
180 Some(sibling)
181 } else {
182 // if not trying to find first ancestor which has such a sibling
183 let node = match element {
184 NodeOrToken::Node(node) => node,
185 NodeOrToken::Token(token) => token.parent(),
186 };
187 let range = node.text_range();
188 let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?;
189 let prev_sibling_node = top_node.ancestors().find(|it| {
190 non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some()
191 })?;
192 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
193 }
194}
diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs
new file mode 100644
index 000000000..3371aed2d
--- /dev/null
+++ b/crates/ide/src/completion/presentation.rs
@@ -0,0 +1,1227 @@
1//! This modules takes care of rendering various definitions as completion items.
2//! It also handles scoring (sorting) completions.
3
4use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
5use itertools::Itertools;
6use syntax::ast::NameOwner;
7use test_utils::mark;
8
9use crate::{
10 completion::{
11 completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
12 CompletionKind, Completions,
13 },
14 display::{const_label, function_declaration, macro_label, type_label},
15 CompletionScore, RootDatabase,
16};
17
18impl Completions {
19 pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) {
20 let is_deprecated = is_deprecated(field, ctx.db);
21 let name = field.name(ctx.db);
22 let mut completion_item =
23 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
24 .kind(CompletionItemKind::Field)
25 .detail(ty.display(ctx.db).to_string())
26 .set_documentation(field.docs(ctx.db))
27 .set_deprecated(is_deprecated);
28
29 if let Some(score) = compute_score(ctx, &ty, &name.to_string()) {
30 completion_item = completion_item.set_score(score);
31 }
32
33 completion_item.add_to(self);
34 }
35
36 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
37 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string())
38 .kind(CompletionItemKind::Field)
39 .detail(ty.display(ctx.db).to_string())
40 .add_to(self);
41 }
42
43 pub(crate) fn add_resolution(
44 &mut self,
45 ctx: &CompletionContext,
46 local_name: String,
47 resolution: &ScopeDef,
48 ) {
49 use hir::ModuleDef::*;
50
51 let completion_kind = match resolution {
52 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType,
53 _ => CompletionKind::Reference,
54 };
55
56 let kind = match resolution {
57 ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
58 ScopeDef::ModuleDef(Function(func)) => {
59 return self.add_function(ctx, *func, Some(local_name));
60 }
61 ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
62 // FIXME: add CompletionItemKind::Union
63 ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct,
64 ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
65
66 ScopeDef::ModuleDef(EnumVariant(var)) => {
67 return self.add_enum_variant(ctx, *var, Some(local_name));
68 }
69 ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
70 ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
71 ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait,
72 ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias,
73 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
74 ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam,
75 ScopeDef::Local(..) => CompletionItemKind::Binding,
76 // (does this need its own kind?)
77 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
78 ScopeDef::MacroDef(mac) => {
79 return self.add_macro(ctx, Some(local_name), *mac);
80 }
81 ScopeDef::Unknown => {
82 return self.add(
83 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name)
84 .kind(CompletionItemKind::UnresolvedReference),
85 );
86 }
87 };
88
89 let docs = match resolution {
90 ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db),
91 ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db),
92 ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db),
93 ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db),
94 ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db),
95 ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db),
96 ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db),
97 _ => None,
98 };
99
100 let mut completion_item =
101 CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone());
102 if let ScopeDef::Local(local) = resolution {
103 let ty = local.ty(ctx.db);
104 if !ty.is_unknown() {
105 completion_item = completion_item.detail(ty.display(ctx.db).to_string());
106 }
107 };
108
109 if let ScopeDef::Local(local) = resolution {
110 if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) {
111 completion_item = completion_item.set_score(score);
112 }
113 }
114
115 // Add `<>` for generic types
116 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
117 if let Some(cap) = ctx.config.snippet_cap {
118 let has_non_default_type_params = match resolution {
119 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
120 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
121 _ => false,
122 };
123 if has_non_default_type_params {
124 mark::hit!(inserts_angle_brackets_for_generics);
125 completion_item = completion_item
126 .lookup_by(local_name.clone())
127 .label(format!("{}<…>", local_name))
128 .insert_snippet(cap, format!("{}<$0>", local_name));
129 }
130 }
131 }
132
133 completion_item.kind(kind).set_documentation(docs).add_to(self)
134 }
135
136 pub(crate) fn add_macro(
137 &mut self,
138 ctx: &CompletionContext,
139 name: Option<String>,
140 macro_: hir::MacroDef,
141 ) {
142 // FIXME: Currently proc-macro do not have ast-node,
143 // such that it does not have source
144 if macro_.is_proc_macro() {
145 return;
146 }
147
148 let name = match name {
149 Some(it) => it,
150 None => return,
151 };
152
153 let ast_node = macro_.source(ctx.db).value;
154 let detail = macro_label(&ast_node);
155
156 let docs = macro_.docs(ctx.db);
157
158 let mut builder = CompletionItem::new(
159 CompletionKind::Reference,
160 ctx.source_range(),
161 &format!("{}!", name),
162 )
163 .kind(CompletionItemKind::Macro)
164 .set_documentation(docs.clone())
165 .set_deprecated(is_deprecated(macro_, ctx.db))
166 .detail(detail);
167
168 let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call;
169 builder = match ctx.config.snippet_cap {
170 Some(cap) if needs_bang => {
171 let docs = docs.as_ref().map_or("", |s| s.as_str());
172 let (bra, ket) = guess_macro_braces(&name, docs);
173 builder
174 .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket))
175 .label(format!("{}!{}…{}", name, bra, ket))
176 .lookup_by(format!("{}!", name))
177 }
178 None if needs_bang => builder.insert_text(format!("{}!", name)),
179 _ => {
180 mark::hit!(dont_insert_macro_call_parens_unncessary);
181 builder.insert_text(name)
182 }
183 };
184
185 self.add(builder);
186 }
187
188 pub(crate) fn add_function(
189 &mut self,
190 ctx: &CompletionContext,
191 func: hir::Function,
192 local_name: Option<String>,
193 ) {
194 let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string());
195 let ast_node = func.source(ctx.db).value;
196
197 let mut builder =
198 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
199 .kind(if func.self_param(ctx.db).is_some() {
200 CompletionItemKind::Method
201 } else {
202 CompletionItemKind::Function
203 })
204 .set_documentation(func.docs(ctx.db))
205 .set_deprecated(is_deprecated(func, ctx.db))
206 .detail(function_declaration(&ast_node));
207
208 let params = ast_node
209 .param_list()
210 .into_iter()
211 .flat_map(|it| it.params())
212 .flat_map(|it| it.pat())
213 .map(|pat| pat.to_string().trim_start_matches('_').into())
214 .collect();
215
216 builder = builder.add_call_parens(ctx, name, Params::Named(params));
217
218 self.add(builder)
219 }
220
221 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
222 let ast_node = constant.source(ctx.db).value;
223 let name = match ast_node.name() {
224 Some(name) => name,
225 _ => return,
226 };
227 let detail = const_label(&ast_node);
228
229 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
230 .kind(CompletionItemKind::Const)
231 .set_documentation(constant.docs(ctx.db))
232 .set_deprecated(is_deprecated(constant, ctx.db))
233 .detail(detail)
234 .add_to(self);
235 }
236
237 pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) {
238 let type_def = type_alias.source(ctx.db).value;
239 let name = match type_def.name() {
240 Some(name) => name,
241 _ => return,
242 };
243 let detail = type_label(&type_def);
244
245 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
246 .kind(CompletionItemKind::TypeAlias)
247 .set_documentation(type_alias.docs(ctx.db))
248 .set_deprecated(is_deprecated(type_alias, ctx.db))
249 .detail(detail)
250 .add_to(self);
251 }
252
253 pub(crate) fn add_qualified_enum_variant(
254 &mut self,
255 ctx: &CompletionContext,
256 variant: hir::EnumVariant,
257 path: ModPath,
258 ) {
259 self.add_enum_variant_impl(ctx, variant, None, Some(path))
260 }
261
262 pub(crate) fn add_enum_variant(
263 &mut self,
264 ctx: &CompletionContext,
265 variant: hir::EnumVariant,
266 local_name: Option<String>,
267 ) {
268 self.add_enum_variant_impl(ctx, variant, local_name, None)
269 }
270
271 fn add_enum_variant_impl(
272 &mut self,
273 ctx: &CompletionContext,
274 variant: hir::EnumVariant,
275 local_name: Option<String>,
276 path: Option<ModPath>,
277 ) {
278 let is_deprecated = is_deprecated(variant, ctx.db);
279 let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string());
280 let qualified_name = match &path {
281 Some(it) => it.to_string(),
282 None => name.to_string(),
283 };
284 let detail_types = variant
285 .fields(ctx.db)
286 .into_iter()
287 .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db)));
288 let variant_kind = variant.kind(ctx.db);
289 let detail = match variant_kind {
290 StructKind::Tuple | StructKind::Unit => format!(
291 "({})",
292 detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ")
293 ),
294 StructKind::Record => format!(
295 "{{ {} }}",
296 detail_types
297 .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string()))
298 .format(", ")
299 ),
300 };
301 let mut res = CompletionItem::new(
302 CompletionKind::Reference,
303 ctx.source_range(),
304 qualified_name.clone(),
305 )
306 .kind(CompletionItemKind::EnumVariant)
307 .set_documentation(variant.docs(ctx.db))
308 .set_deprecated(is_deprecated)
309 .detail(detail);
310
311 if path.is_some() {
312 res = res.lookup_by(name);
313 }
314
315 if variant_kind == StructKind::Tuple {
316 mark::hit!(inserts_parens_for_tuple_enums);
317 let params = Params::Anonymous(variant.fields(ctx.db).len());
318 res = res.add_call_parens(ctx, qualified_name, params)
319 }
320
321 res.add_to(self);
322 }
323}
324
325pub(crate) fn compute_score(
326 ctx: &CompletionContext,
327 ty: &Type,
328 name: &str,
329) -> Option<CompletionScore> {
330 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
331 mark::hit!(record_field_type_match);
332 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
333 (struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db))
334 } else if let Some(active_parameter) = &ctx.active_parameter {
335 mark::hit!(active_param_type_match);
336 (active_parameter.name.clone(), active_parameter.ty.clone())
337 } else {
338 return None;
339 };
340
341 // Compute score
342 // For the same type
343 if &active_type != ty {
344 return None;
345 }
346
347 let mut res = CompletionScore::TypeMatch;
348
349 // If same type + same name then go top position
350 if active_name == name {
351 res = CompletionScore::TypeAndNameMatch
352 }
353
354 Some(res)
355}
356
357enum Params {
358 Named(Vec<String>),
359 Anonymous(usize),
360}
361
362impl Params {
363 fn len(&self) -> usize {
364 match self {
365 Params::Named(xs) => xs.len(),
366 Params::Anonymous(len) => *len,
367 }
368 }
369
370 fn is_empty(&self) -> bool {
371 self.len() == 0
372 }
373}
374
375impl Builder {
376 fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder {
377 if !ctx.config.add_call_parenthesis {
378 return self;
379 }
380 if ctx.use_item_syntax.is_some() {
381 mark::hit!(no_parens_in_use_item);
382 return self;
383 }
384 if ctx.is_pattern_call {
385 mark::hit!(dont_duplicate_pattern_parens);
386 return self;
387 }
388 if ctx.is_call {
389 return self;
390 }
391
392 // Don't add parentheses if the expected type is some function reference.
393 if let Some(ty) = &ctx.expected_type {
394 if ty.is_fn() {
395 mark::hit!(no_call_parens_if_fn_ptr_needed);
396 return self;
397 }
398 }
399
400 let cap = match ctx.config.snippet_cap {
401 Some(it) => it,
402 None => return self,
403 };
404 // If not an import, add parenthesis automatically.
405 mark::hit!(inserts_parens_for_function_calls);
406
407 let (snippet, label) = if params.is_empty() {
408 (format!("{}()$0", name), format!("{}()", name))
409 } else {
410 self = self.trigger_call_info();
411 let snippet = match (ctx.config.add_call_argument_snippets, params) {
412 (true, Params::Named(params)) => {
413 let function_params_snippet =
414 params.iter().enumerate().format_with(", ", |(index, param_name), f| {
415 f(&format_args!("${{{}:{}}}", index + 1, param_name))
416 });
417 format!("{}({})$0", name, function_params_snippet)
418 }
419 _ => {
420 mark::hit!(suppress_arg_snippets);
421 format!("{}($0)", name)
422 }
423 };
424
425 (snippet, format!("{}(…)", name))
426 };
427 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
428 }
429}
430
431fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
432 node.attrs(db).by_key("deprecated").exists()
433}
434
435fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
436 let mut votes = [0, 0, 0];
437 for (idx, s) in docs.match_indices(&macro_name) {
438 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
439 // Ensure to match the full word
440 if after.starts_with('!')
441 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
442 {
443 // It may have spaces before the braces like `foo! {}`
444 match after[1..].chars().find(|&c| !c.is_whitespace()) {
445 Some('{') => votes[0] += 1,
446 Some('[') => votes[1] += 1,
447 Some('(') => votes[2] += 1,
448 _ => {}
449 }
450 }
451 }
452
453 // Insert a space before `{}`.
454 // We prefer the last one when some votes equal.
455 let (_vote, (bra, ket)) = votes
456 .iter()
457 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
458 .max_by_key(|&(&vote, _)| vote)
459 .unwrap();
460 (*bra, *ket)
461}
462
463#[cfg(test)]
464mod tests {
465 use std::cmp::Reverse;
466
467 use expect_test::{expect, Expect};
468 use test_utils::mark;
469
470 use crate::{
471 completion::{
472 test_utils::{
473 check_edit, check_edit_with_config, do_completion, get_all_completion_items,
474 },
475 CompletionConfig, CompletionKind,
476 },
477 CompletionScore,
478 };
479
480 fn check(ra_fixture: &str, expect: Expect) {
481 let actual = do_completion(ra_fixture, CompletionKind::Reference);
482 expect.assert_debug_eq(&actual);
483 }
484
485 fn check_scores(ra_fixture: &str, expect: Expect) {
486 fn display_score(score: Option<CompletionScore>) -> &'static str {
487 match score {
488 Some(CompletionScore::TypeMatch) => "[type]",
489 Some(CompletionScore::TypeAndNameMatch) => "[type+name]",
490 None => "[]".into(),
491 }
492 }
493
494 let mut completions = get_all_completion_items(CompletionConfig::default(), ra_fixture);
495 completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string()));
496 let actual = completions
497 .into_iter()
498 .filter(|it| it.completion_kind == CompletionKind::Reference)
499 .map(|it| {
500 let tag = it.kind().unwrap().tag();
501 let score = display_score(it.score());
502 format!("{} {} {}\n", tag, it.label(), score)
503 })
504 .collect::<String>();
505 expect.assert_eq(&actual);
506 }
507
508 #[test]
509 fn enum_detail_includes_record_fields() {
510 check(
511 r#"
512enum Foo { Foo { x: i32, y: i32 } }
513
514fn main() { Foo::Fo<|> }
515"#,
516 expect![[r#"
517 [
518 CompletionItem {
519 label: "Foo",
520 source_range: 54..56,
521 delete: 54..56,
522 insert: "Foo",
523 kind: EnumVariant,
524 detail: "{ x: i32, y: i32 }",
525 },
526 ]
527 "#]],
528 );
529 }
530
531 #[test]
532 fn enum_detail_doesnt_include_tuple_fields() {
533 check(
534 r#"
535enum Foo { Foo (i32, i32) }
536
537fn main() { Foo::Fo<|> }
538"#,
539 expect![[r#"
540 [
541 CompletionItem {
542 label: "Foo(…)",
543 source_range: 46..48,
544 delete: 46..48,
545 insert: "Foo($0)",
546 kind: EnumVariant,
547 lookup: "Foo",
548 detail: "(i32, i32)",
549 trigger_call_info: true,
550 },
551 ]
552 "#]],
553 );
554 }
555
556 #[test]
557 fn enum_detail_just_parentheses_for_unit() {
558 check(
559 r#"
560enum Foo { Foo }
561
562fn main() { Foo::Fo<|> }
563"#,
564 expect![[r#"
565 [
566 CompletionItem {
567 label: "Foo",
568 source_range: 35..37,
569 delete: 35..37,
570 insert: "Foo",
571 kind: EnumVariant,
572 detail: "()",
573 },
574 ]
575 "#]],
576 );
577 }
578
579 #[test]
580 fn sets_deprecated_flag_in_completion_items() {
581 check(
582 r#"
583#[deprecated]
584fn something_deprecated() {}
585#[deprecated(since = "1.0.0")]
586fn something_else_deprecated() {}
587
588fn main() { som<|> }
589"#,
590 expect![[r#"
591 [
592 CompletionItem {
593 label: "main()",
594 source_range: 121..124,
595 delete: 121..124,
596 insert: "main()$0",
597 kind: Function,
598 lookup: "main",
599 detail: "fn main()",
600 },
601 CompletionItem {
602 label: "something_deprecated()",
603 source_range: 121..124,
604 delete: 121..124,
605 insert: "something_deprecated()$0",
606 kind: Function,
607 lookup: "something_deprecated",
608 detail: "fn something_deprecated()",
609 deprecated: true,
610 },
611 CompletionItem {
612 label: "something_else_deprecated()",
613 source_range: 121..124,
614 delete: 121..124,
615 insert: "something_else_deprecated()$0",
616 kind: Function,
617 lookup: "something_else_deprecated",
618 detail: "fn something_else_deprecated()",
619 deprecated: true,
620 },
621 ]
622 "#]],
623 );
624
625 check(
626 r#"
627struct A { #[deprecated] the_field: u32 }
628fn foo() { A { the<|> } }
629"#,
630 expect![[r#"
631 [
632 CompletionItem {
633 label: "the_field",
634 source_range: 57..60,
635 delete: 57..60,
636 insert: "the_field",
637 kind: Field,
638 detail: "u32",
639 deprecated: true,
640 },
641 ]
642 "#]],
643 );
644 }
645
646 #[test]
647 fn renders_docs() {
648 check(
649 r#"
650struct S {
651 /// Field docs
652 foo:
653}
654impl S {
655 /// Method docs
656 fn bar(self) { self.<|> }
657}"#,
658 expect![[r#"
659 [
660 CompletionItem {
661 label: "bar()",
662 source_range: 94..94,
663 delete: 94..94,
664 insert: "bar()$0",
665 kind: Method,
666 lookup: "bar",
667 detail: "fn bar(self)",
668 documentation: Documentation(
669 "Method docs",
670 ),
671 },
672 CompletionItem {
673 label: "foo",
674 source_range: 94..94,
675 delete: 94..94,
676 insert: "foo",
677 kind: Field,
678 detail: "{unknown}",
679 documentation: Documentation(
680 "Field docs",
681 ),
682 },
683 ]
684 "#]],
685 );
686
687 check(
688 r#"
689use self::my<|>;
690
691/// mod docs
692mod my { }
693
694/// enum docs
695enum E {
696 /// variant docs
697 V
698}
699use self::E::*;
700"#,
701 expect![[r#"
702 [
703 CompletionItem {
704 label: "E",
705 source_range: 10..12,
706 delete: 10..12,
707 insert: "E",
708 kind: Enum,
709 documentation: Documentation(
710 "enum docs",
711 ),
712 },
713 CompletionItem {
714 label: "V",
715 source_range: 10..12,
716 delete: 10..12,
717 insert: "V",
718 kind: EnumVariant,
719 detail: "()",
720 documentation: Documentation(
721 "variant docs",
722 ),
723 },
724 CompletionItem {
725 label: "my",
726 source_range: 10..12,
727 delete: 10..12,
728 insert: "my",
729 kind: Module,
730 documentation: Documentation(
731 "mod docs",
732 ),
733 },
734 ]
735 "#]],
736 )
737 }
738
739 #[test]
740 fn dont_render_attrs() {
741 check(
742 r#"
743struct S;
744impl S {
745 #[inline]
746 fn the_method(&self) { }
747}
748fn foo(s: S) { s.<|> }
749"#,
750 expect![[r#"
751 [
752 CompletionItem {
753 label: "the_method()",
754 source_range: 81..81,
755 delete: 81..81,
756 insert: "the_method()$0",
757 kind: Method,
758 lookup: "the_method",
759 detail: "fn the_method(&self)",
760 },
761 ]
762 "#]],
763 )
764 }
765
766 #[test]
767 fn inserts_parens_for_function_calls() {
768 mark::check!(inserts_parens_for_function_calls);
769 check_edit(
770 "no_args",
771 r#"
772fn no_args() {}
773fn main() { no_<|> }
774"#,
775 r#"
776fn no_args() {}
777fn main() { no_args()$0 }
778"#,
779 );
780
781 check_edit(
782 "with_args",
783 r#"
784fn with_args(x: i32, y: String) {}
785fn main() { with_<|> }
786"#,
787 r#"
788fn with_args(x: i32, y: String) {}
789fn main() { with_args(${1:x}, ${2:y})$0 }
790"#,
791 );
792
793 check_edit(
794 "foo",
795 r#"
796struct S;
797impl S {
798 fn foo(&self) {}
799}
800fn bar(s: &S) { s.f<|> }
801"#,
802 r#"
803struct S;
804impl S {
805 fn foo(&self) {}
806}
807fn bar(s: &S) { s.foo()$0 }
808"#,
809 );
810
811 check_edit(
812 "foo",
813 r#"
814struct S {}
815impl S {
816 fn foo(&self, x: i32) {}
817}
818fn bar(s: &S) {
819 s.f<|>
820}
821"#,
822 r#"
823struct S {}
824impl S {
825 fn foo(&self, x: i32) {}
826}
827fn bar(s: &S) {
828 s.foo(${1:x})$0
829}
830"#,
831 );
832 }
833
834 #[test]
835 fn suppress_arg_snippets() {
836 mark::check!(suppress_arg_snippets);
837 check_edit_with_config(
838 CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() },
839 "with_args",
840 r#"
841fn with_args(x: i32, y: String) {}
842fn main() { with_<|> }
843"#,
844 r#"
845fn with_args(x: i32, y: String) {}
846fn main() { with_args($0) }
847"#,
848 );
849 }
850
851 #[test]
852 fn strips_underscores_from_args() {
853 check_edit(
854 "foo",
855 r#"
856fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
857fn main() { f<|> }
858"#,
859 r#"
860fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
861fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
862"#,
863 );
864 }
865
866 #[test]
867 fn inserts_parens_for_tuple_enums() {
868 mark::check!(inserts_parens_for_tuple_enums);
869 check_edit(
870 "Some",
871 r#"
872enum Option<T> { Some(T), None }
873use Option::*;
874fn main() -> Option<i32> {
875 Som<|>
876}
877"#,
878 r#"
879enum Option<T> { Some(T), None }
880use Option::*;
881fn main() -> Option<i32> {
882 Some($0)
883}
884"#,
885 );
886 check_edit(
887 "Some",
888 r#"
889enum Option<T> { Some(T), None }
890use Option::*;
891fn main(value: Option<i32>) {
892 match value {
893 Som<|>
894 }
895}
896"#,
897 r#"
898enum Option<T> { Some(T), None }
899use Option::*;
900fn main(value: Option<i32>) {
901 match value {
902 Some($0)
903 }
904}
905"#,
906 );
907 }
908
909 #[test]
910 fn dont_duplicate_pattern_parens() {
911 mark::check!(dont_duplicate_pattern_parens);
912 check_edit(
913 "Var",
914 r#"
915enum E { Var(i32) }
916fn main() {
917 match E::Var(92) {
918 E::<|>(92) => (),
919 }
920}
921"#,
922 r#"
923enum E { Var(i32) }
924fn main() {
925 match E::Var(92) {
926 E::Var(92) => (),
927 }
928}
929"#,
930 );
931 }
932
933 #[test]
934 fn no_call_parens_if_fn_ptr_needed() {
935 mark::check!(no_call_parens_if_fn_ptr_needed);
936 check_edit(
937 "foo",
938 r#"
939fn foo(foo: u8, bar: u8) {}
940struct ManualVtable { f: fn(u8, u8) }
941
942fn main() -> ManualVtable {
943 ManualVtable { f: f<|> }
944}
945"#,
946 r#"
947fn foo(foo: u8, bar: u8) {}
948struct ManualVtable { f: fn(u8, u8) }
949
950fn main() -> ManualVtable {
951 ManualVtable { f: foo }
952}
953"#,
954 );
955 }
956
957 #[test]
958 fn no_parens_in_use_item() {
959 mark::check!(no_parens_in_use_item);
960 check_edit(
961 "foo",
962 r#"
963mod m { pub fn foo() {} }
964use crate::m::f<|>;
965"#,
966 r#"
967mod m { pub fn foo() {} }
968use crate::m::foo;
969"#,
970 );
971 }
972
973 #[test]
974 fn no_parens_in_call() {
975 check_edit(
976 "foo",
977 r#"
978fn foo(x: i32) {}
979fn main() { f<|>(); }
980"#,
981 r#"
982fn foo(x: i32) {}
983fn main() { foo(); }
984"#,
985 );
986 check_edit(
987 "foo",
988 r#"
989struct Foo;
990impl Foo { fn foo(&self){} }
991fn f(foo: &Foo) { foo.f<|>(); }
992"#,
993 r#"
994struct Foo;
995impl Foo { fn foo(&self){} }
996fn f(foo: &Foo) { foo.foo(); }
997"#,
998 );
999 }
1000
1001 #[test]
1002 fn inserts_angle_brackets_for_generics() {
1003 mark::check!(inserts_angle_brackets_for_generics);
1004 check_edit(
1005 "Vec",
1006 r#"
1007struct Vec<T> {}
1008fn foo(xs: Ve<|>)
1009"#,
1010 r#"
1011struct Vec<T> {}
1012fn foo(xs: Vec<$0>)
1013"#,
1014 );
1015 check_edit(
1016 "Vec",
1017 r#"
1018type Vec<T> = (T,);
1019fn foo(xs: Ve<|>)
1020"#,
1021 r#"
1022type Vec<T> = (T,);
1023fn foo(xs: Vec<$0>)
1024"#,
1025 );
1026 check_edit(
1027 "Vec",
1028 r#"
1029struct Vec<T = i128> {}
1030fn foo(xs: Ve<|>)
1031"#,
1032 r#"
1033struct Vec<T = i128> {}
1034fn foo(xs: Vec)
1035"#,
1036 );
1037 check_edit(
1038 "Vec",
1039 r#"
1040struct Vec<T> {}
1041fn foo(xs: Ve<|><i128>)
1042"#,
1043 r#"
1044struct Vec<T> {}
1045fn foo(xs: Vec<i128>)
1046"#,
1047 );
1048 }
1049
1050 #[test]
1051 fn dont_insert_macro_call_parens_unncessary() {
1052 mark::check!(dont_insert_macro_call_parens_unncessary);
1053 check_edit(
1054 "frobnicate!",
1055 r#"
1056//- /main.rs
1057use foo::<|>;
1058//- /foo/lib.rs
1059#[macro_export]
1060macro_rules frobnicate { () => () }
1061"#,
1062 r#"
1063use foo::frobnicate;
1064"#,
1065 );
1066
1067 check_edit(
1068 "frobnicate!",
1069 r#"
1070macro_rules frobnicate { () => () }
1071fn main() { frob<|>!(); }
1072"#,
1073 r#"
1074macro_rules frobnicate { () => () }
1075fn main() { frobnicate!(); }
1076"#,
1077 );
1078 }
1079
1080 #[test]
1081 fn active_param_score() {
1082 mark::check!(active_param_type_match);
1083 check_scores(
1084 r#"
1085struct S { foo: i64, bar: u32, baz: u32 }
1086fn test(bar: u32) { }
1087fn foo(s: S) { test(s.<|>) }
1088"#,
1089 expect![[r#"
1090 fd bar [type+name]
1091 fd baz [type]
1092 fd foo []
1093 "#]],
1094 );
1095 }
1096
1097 #[test]
1098 fn record_field_scores() {
1099 mark::check!(record_field_type_match);
1100 check_scores(
1101 r#"
1102struct A { foo: i64, bar: u32, baz: u32 }
1103struct B { x: (), y: f32, bar: u32 }
1104fn foo(a: A) { B { bar: a.<|> }; }
1105"#,
1106 expect![[r#"
1107 fd bar [type+name]
1108 fd baz [type]
1109 fd foo []
1110 "#]],
1111 )
1112 }
1113
1114 #[test]
1115 fn record_field_and_call_scores() {
1116 check_scores(
1117 r#"
1118struct A { foo: i64, bar: u32, baz: u32 }
1119struct B { x: (), y: f32, bar: u32 }
1120fn f(foo: i64) { }
1121fn foo(a: A) { B { bar: f(a.<|>) }; }
1122"#,
1123 expect![[r#"
1124 fd foo [type+name]
1125 fd bar []
1126 fd baz []
1127 "#]],
1128 );
1129 check_scores(
1130 r#"
1131struct A { foo: i64, bar: u32, baz: u32 }
1132struct B { x: (), y: f32, bar: u32 }
1133fn f(foo: i64) { }
1134fn foo(a: A) { f(B { bar: a.<|> }); }
1135"#,
1136 expect![[r#"
1137 fd bar [type+name]
1138 fd baz [type]
1139 fd foo []
1140 "#]],
1141 );
1142 }
1143
1144 #[test]
1145 fn prioritize_exact_ref_match() {
1146 check_scores(
1147 r#"
1148struct WorldSnapshot { _f: () };
1149fn go(world: &WorldSnapshot) { go(w<|>) }
1150"#,
1151 expect![[r#"
1152 bn world [type+name]
1153 st WorldSnapshot []
1154 fn go(…) []
1155 "#]],
1156 );
1157 }
1158
1159 #[test]
1160 fn too_many_arguments() {
1161 mark::check!(too_many_arguments);
1162 check_scores(
1163 r#"
1164struct Foo;
1165fn f(foo: &Foo) { f(foo, w<|>) }
1166"#,
1167 expect![[r#"
1168 st Foo []
1169 fn f(…) []
1170 bn foo []
1171 "#]],
1172 );
1173 }
1174
1175 #[test]
1176 fn guesses_macro_braces() {
1177 check_edit(
1178 "vec!",
1179 r#"
1180/// Creates a [`Vec`] containing the arguments.
1181///
1182/// ```
1183/// let v = vec![1, 2, 3];
1184/// assert_eq!(v[0], 1);
1185/// assert_eq!(v[1], 2);
1186/// assert_eq!(v[2], 3);
1187/// ```
1188macro_rules! vec { () => {} }
1189
1190fn fn main() { v<|> }
1191"#,
1192 r#"
1193/// Creates a [`Vec`] containing the arguments.
1194///
1195/// ```
1196/// let v = vec![1, 2, 3];
1197/// assert_eq!(v[0], 1);
1198/// assert_eq!(v[1], 2);
1199/// assert_eq!(v[2], 3);
1200/// ```
1201macro_rules! vec { () => {} }
1202
1203fn fn main() { vec![$0] }
1204"#,
1205 );
1206
1207 check_edit(
1208 "foo!",
1209 r#"
1210/// Foo
1211///
1212/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
1213/// call as `let _=foo! { hello world };`
1214macro_rules! foo { () => {} }
1215fn main() { <|> }
1216"#,
1217 r#"
1218/// Foo
1219///
1220/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
1221/// call as `let _=foo! { hello world };`
1222macro_rules! foo { () => {} }
1223fn main() { foo! {$0} }
1224"#,
1225 )
1226 }
1227}
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs
new file mode 100644
index 000000000..1452d7e9e
--- /dev/null
+++ b/crates/ide/src/completion/test_utils.rs
@@ -0,0 +1,114 @@
1//! Runs completion for testing purposes.
2
3use hir::Semantics;
4use itertools::Itertools;
5use stdx::{format_to, trim_indent};
6use syntax::{AstNode, NodeOrToken, SyntaxElement};
7use test_utils::assert_eq_text;
8
9use crate::{
10 completion::{completion_item::CompletionKind, CompletionConfig},
11 mock_analysis::analysis_and_position,
12 CompletionItem,
13};
14
15pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
16 do_completion_with_config(CompletionConfig::default(), code, kind)
17}
18
19pub(crate) fn do_completion_with_config(
20 config: CompletionConfig,
21 code: &str,
22 kind: CompletionKind,
23) -> Vec<CompletionItem> {
24 let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code)
25 .into_iter()
26 .filter(|c| c.completion_kind == kind)
27 .collect();
28 kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
29 kind_completions
30}
31
32pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
33 completion_list_with_config(CompletionConfig::default(), code, kind)
34}
35
36pub(crate) fn completion_list_with_config(
37 config: CompletionConfig,
38 code: &str,
39 kind: CompletionKind,
40) -> String {
41 let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code)
42 .into_iter()
43 .filter(|c| c.completion_kind == kind)
44 .collect();
45 kind_completions.sort_by_key(|c| c.label().to_owned());
46 let label_width = kind_completions
47 .iter()
48 .map(|it| monospace_width(it.label()))
49 .max()
50 .unwrap_or_default()
51 .min(16);
52 kind_completions
53 .into_iter()
54 .map(|it| {
55 let tag = it.kind().unwrap().tag();
56 let var_name = format!("{} {}", tag, it.label());
57 let mut buf = var_name;
58 if let Some(detail) = it.detail() {
59 let width = label_width.saturating_sub(monospace_width(it.label()));
60 format_to!(buf, "{:width$} {}", "", detail, width = width);
61 }
62 format_to!(buf, "\n");
63 buf
64 })
65 .collect()
66}
67
68fn monospace_width(s: &str) -> usize {
69 s.chars().count()
70}
71
72pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
73 check_edit_with_config(CompletionConfig::default(), what, ra_fixture_before, ra_fixture_after)
74}
75
76pub(crate) fn check_edit_with_config(
77 config: CompletionConfig,
78 what: &str,
79 ra_fixture_before: &str,
80 ra_fixture_after: &str,
81) {
82 let ra_fixture_after = trim_indent(ra_fixture_after);
83 let (analysis, position) = analysis_and_position(ra_fixture_before);
84 let completions: Vec<CompletionItem> =
85 analysis.completions(&config, position).unwrap().unwrap().into();
86 let (completion,) = completions
87 .iter()
88 .filter(|it| it.lookup() == what)
89 .collect_tuple()
90 .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
91 let mut actual = analysis.file_text(position.file_id).unwrap().to_string();
92 completion.text_edit().apply(&mut actual);
93 assert_eq_text!(&ra_fixture_after, &actual)
94}
95
96pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
97 let (analysis, pos) = analysis_and_position(code);
98 analysis
99 .with_db(|db| {
100 let sema = Semantics::new(db);
101 let original_file = sema.parse(pos.file_id);
102 let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
103 assert!(check(NodeOrToken::Token(token)));
104 })
105 .unwrap();
106}
107
108pub(crate) fn get_all_completion_items(
109 config: CompletionConfig,
110 code: &str,
111) -> Vec<CompletionItem> {
112 let (analysis, position) = analysis_and_position(code);
113 analysis.completions(&config, position).unwrap().unwrap().into()
114}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
new file mode 100644
index 000000000..b2b972b02
--- /dev/null
+++ b/crates/ide/src/diagnostics.rs
@@ -0,0 +1,746 @@
1//! Collects diagnostics & fixits for a single file.
2//!
3//! The tricky bit here is that diagnostics are produced by hir in terms of
4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges.
6
7mod fixes;
8
9use std::cell::RefCell;
10
11use base_db::SourceDatabase;
12use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
13use ide_db::RootDatabase;
14use itertools::Itertools;
15use rustc_hash::FxHashSet;
16use syntax::{
17 ast::{self, AstNode},
18 SyntaxNode, TextRange, T,
19};
20use text_edit::TextEdit;
21
22use crate::{FileId, Label, SourceChange, SourceFileEdit};
23
24use self::fixes::DiagnosticWithFix;
25
26#[derive(Debug)]
27pub struct Diagnostic {
28 // pub name: Option<String>,
29 pub message: String,
30 pub range: TextRange,
31 pub severity: Severity,
32 pub fix: Option<Fix>,
33}
34
35#[derive(Debug)]
36pub struct Fix {
37 pub label: Label,
38 pub source_change: SourceChange,
39 /// Allows to trigger the fix only when the caret is in the range given
40 pub fix_trigger_range: TextRange,
41}
42
43impl Fix {
44 fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self {
45 let label = Label::new(label);
46 Self { label, source_change, fix_trigger_range }
47 }
48}
49
50#[derive(Debug, Copy, Clone)]
51pub enum Severity {
52 Error,
53 WeakWarning,
54}
55
56#[derive(Default, Debug, Clone)]
57pub struct DiagnosticsConfig {
58 pub disable_experimental: bool,
59 pub disabled: FxHashSet<String>,
60}
61
62pub(crate) fn diagnostics(
63 db: &RootDatabase,
64 config: &DiagnosticsConfig,
65 file_id: FileId,
66) -> Vec<Diagnostic> {
67 let _p = profile::span("diagnostics");
68 let sema = Semantics::new(db);
69 let parse = db.parse(file_id);
70 let mut res = Vec::new();
71
72 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
73 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
74 // name: None,
75 range: err.range(),
76 message: format!("Syntax Error: {}", err),
77 severity: Severity::Error,
78 fix: None,
79 }));
80
81 for node in parse.tree().syntax().descendants() {
82 check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
83 check_struct_shorthand_initialization(&mut res, file_id, &node);
84 }
85 let res = RefCell::new(res);
86 let sink_builder = DiagnosticSinkBuilder::new()
87 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
88 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
89 })
90 .on::<hir::diagnostics::MissingFields, _>(|d| {
91 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
92 })
93 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
94 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
95 })
96 .on::<hir::diagnostics::NoSuchField, _>(|d| {
97 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
98 })
99 // Only collect experimental diagnostics when they're enabled.
100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
101 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
102
103 // Finalize the `DiagnosticSink` building process.
104 let mut sink = sink_builder
105 // Diagnostics not handled above get no fix and default treatment.
106 .build(|d| {
107 res.borrow_mut().push(Diagnostic {
108 // name: Some(d.name().into()),
109 message: d.message(),
110 range: sema.diagnostics_display_range(d).range,
111 severity: Severity::Error,
112 fix: None,
113 })
114 });
115
116 if let Some(m) = sema.to_module_def(file_id) {
117 m.diagnostics(db, &mut sink);
118 };
119 drop(sink);
120 res.into_inner()
121}
122
123fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
124 Diagnostic {
125 // name: Some(d.name().into()),
126 range: sema.diagnostics_display_range(d).range,
127 message: d.message(),
128 severity: Severity::Error,
129 fix: d.fix(&sema),
130 }
131}
132
133fn check_unnecessary_braces_in_use_statement(
134 acc: &mut Vec<Diagnostic>,
135 file_id: FileId,
136 node: &SyntaxNode,
137) -> Option<()> {
138 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
139 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
140 let use_range = use_tree_list.syntax().text_range();
141 let edit =
142 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
143 .unwrap_or_else(|| {
144 let to_replace = single_use_tree.syntax().text().to_string();
145 let mut edit_builder = TextEdit::builder();
146 edit_builder.delete(use_range);
147 edit_builder.insert(use_range.start(), to_replace);
148 edit_builder.finish()
149 });
150
151 acc.push(Diagnostic {
152 // name: None,
153 range: use_range,
154 message: "Unnecessary braces in use statement".to_string(),
155 severity: Severity::WeakWarning,
156 fix: Some(Fix::new(
157 "Remove unnecessary braces",
158 SourceFileEdit { file_id, edit }.into(),
159 use_range,
160 )),
161 });
162 }
163
164 Some(())
165}
166
167fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
168 single_use_tree: &ast::UseTree,
169) -> Option<TextEdit> {
170 let use_tree_list_node = single_use_tree.syntax().parent()?;
171 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] {
172 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
173 let end = use_tree_list_node.text_range().end();
174 return Some(TextEdit::delete(TextRange::new(start, end)));
175 }
176 None
177}
178
179fn check_struct_shorthand_initialization(
180 acc: &mut Vec<Diagnostic>,
181 file_id: FileId,
182 node: &SyntaxNode,
183) -> Option<()> {
184 let record_lit = ast::RecordExpr::cast(node.clone())?;
185 let record_field_list = record_lit.record_expr_field_list()?;
186 for record_field in record_field_list.fields() {
187 if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
188 let field_name = name_ref.syntax().text().to_string();
189 let field_expr = expr.syntax().text().to_string();
190 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
191 if field_name == field_expr && !field_name_is_tup_index {
192 let mut edit_builder = TextEdit::builder();
193 edit_builder.delete(record_field.syntax().text_range());
194 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
195 let edit = edit_builder.finish();
196
197 let field_range = record_field.syntax().text_range();
198 acc.push(Diagnostic {
199 // name: None,
200 range: field_range,
201 message: "Shorthand struct initialization".to_string(),
202 severity: Severity::WeakWarning,
203 fix: Some(Fix::new(
204 "Use struct shorthand initialization",
205 SourceFileEdit { file_id, edit }.into(),
206 field_range,
207 )),
208 });
209 }
210 }
211 }
212 Some(())
213}
214
215#[cfg(test)]
216mod tests {
217 use expect_test::{expect, Expect};
218 use stdx::trim_indent;
219 use test_utils::assert_eq_text;
220
221 use crate::{
222 mock_analysis::{analysis_and_position, single_file, MockAnalysis},
223 DiagnosticsConfig,
224 };
225
226 /// Takes a multi-file input fixture with annotated cursor positions,
227 /// and checks that:
228 /// * a diagnostic is produced
229 /// * this diagnostic fix trigger range touches the input cursor position
230 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
231 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
232 let after = trim_indent(ra_fixture_after);
233
234 let (analysis, file_position) = analysis_and_position(ra_fixture_before);
235 let diagnostic = analysis
236 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
237 .unwrap()
238 .pop()
239 .unwrap();
240 let mut fix = diagnostic.fix.unwrap();
241 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
242 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
243 let actual = {
244 let mut actual = target_file_contents.to_string();
245 edit.apply(&mut actual);
246 actual
247 };
248
249 assert_eq_text!(&after, &actual);
250 assert!(
251 fix.fix_trigger_range.start() <= file_position.offset
252 && fix.fix_trigger_range.end() >= file_position.offset,
253 "diagnostic fix range {:?} does not touch cursor position {:?}",
254 fix.fix_trigger_range,
255 file_position.offset
256 );
257 }
258
259 /// Checks that a diagnostic applies to the file containing the `<|>` cursor marker
260 /// which has a fix that can apply to other files.
261 fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) {
262 let ra_fixture_after = &trim_indent(ra_fixture_after);
263 let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
264 let current_file_id = file_pos.file_id;
265 let diagnostic = analysis
266 .diagnostics(&DiagnosticsConfig::default(), current_file_id)
267 .unwrap()
268 .pop()
269 .unwrap();
270 let mut fix = diagnostic.fix.unwrap();
271 let edit = fix.source_change.source_file_edits.pop().unwrap();
272 let changed_file_id = edit.file_id;
273 let before = analysis.file_text(changed_file_id).unwrap();
274 let actual = {
275 let mut actual = before.to_string();
276 edit.edit.apply(&mut actual);
277 actual
278 };
279 assert_eq_text!(ra_fixture_after, &actual);
280 }
281
282 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
283 /// apply to the file containing the cursor.
284 fn check_no_diagnostics(ra_fixture: &str) {
285 let mock = MockAnalysis::with_files(ra_fixture);
286 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
287 let analysis = mock.analysis();
288 let diagnostics = files
289 .into_iter()
290 .flat_map(|file_id| {
291 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
292 })
293 .collect::<Vec<_>>();
294 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
295 }
296
297 fn check_expect(ra_fixture: &str, expect: Expect) {
298 let (analysis, file_id) = single_file(ra_fixture);
299 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
300 expect.assert_debug_eq(&diagnostics)
301 }
302
303 #[test]
304 fn test_wrap_return_type() {
305 check_fix(
306 r#"
307//- /main.rs
308use core::result::Result::{self, Ok, Err};
309
310fn div(x: i32, y: i32) -> Result<i32, ()> {
311 if y == 0 {
312 return Err(());
313 }
314 x / y<|>
315}
316//- /core/lib.rs
317pub mod result {
318 pub enum Result<T, E> { Ok(T), Err(E) }
319}
320"#,
321 r#"
322use core::result::Result::{self, Ok, Err};
323
324fn div(x: i32, y: i32) -> Result<i32, ()> {
325 if y == 0 {
326 return Err(());
327 }
328 Ok(x / y)
329}
330"#,
331 );
332 }
333
334 #[test]
335 fn test_wrap_return_type_handles_generic_functions() {
336 check_fix(
337 r#"
338//- /main.rs
339use core::result::Result::{self, Ok, Err};
340
341fn div<T>(x: T) -> Result<T, i32> {
342 if x == 0 {
343 return Err(7);
344 }
345 <|>x
346}
347//- /core/lib.rs
348pub mod result {
349 pub enum Result<T, E> { Ok(T), Err(E) }
350}
351"#,
352 r#"
353use core::result::Result::{self, Ok, Err};
354
355fn div<T>(x: T) -> Result<T, i32> {
356 if x == 0 {
357 return Err(7);
358 }
359 Ok(x)
360}
361"#,
362 );
363 }
364
365 #[test]
366 fn test_wrap_return_type_handles_type_aliases() {
367 check_fix(
368 r#"
369//- /main.rs
370use core::result::Result::{self, Ok, Err};
371
372type MyResult<T> = Result<T, ()>;
373
374fn div(x: i32, y: i32) -> MyResult<i32> {
375 if y == 0 {
376 return Err(());
377 }
378 x <|>/ y
379}
380//- /core/lib.rs
381pub mod result {
382 pub enum Result<T, E> { Ok(T), Err(E) }
383}
384"#,
385 r#"
386use core::result::Result::{self, Ok, Err};
387
388type MyResult<T> = Result<T, ()>;
389
390fn div(x: i32, y: i32) -> MyResult<i32> {
391 if y == 0 {
392 return Err(());
393 }
394 Ok(x / y)
395}
396"#,
397 );
398 }
399
400 #[test]
401 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
402 check_no_diagnostics(
403 r#"
404//- /main.rs
405use core::result::Result::{self, Ok, Err};
406
407fn foo() -> Result<(), i32> { 0 }
408
409//- /core/lib.rs
410pub mod result {
411 pub enum Result<T, E> { Ok(T), Err(E) }
412}
413"#,
414 );
415 }
416
417 #[test]
418 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
419 check_no_diagnostics(
420 r#"
421//- /main.rs
422use core::result::Result::{self, Ok, Err};
423
424enum SomeOtherEnum { Ok(i32), Err(String) }
425
426fn foo() -> SomeOtherEnum { 0 }
427
428//- /core/lib.rs
429pub mod result {
430 pub enum Result<T, E> { Ok(T), Err(E) }
431}
432"#,
433 );
434 }
435
436 #[test]
437 fn test_fill_struct_fields_empty() {
438 check_fix(
439 r#"
440struct TestStruct { one: i32, two: i64 }
441
442fn test_fn() {
443 let s = TestStruct {<|>};
444}
445"#,
446 r#"
447struct TestStruct { one: i32, two: i64 }
448
449fn test_fn() {
450 let s = TestStruct { one: (), two: ()};
451}
452"#,
453 );
454 }
455
456 #[test]
457 fn test_fill_struct_fields_self() {
458 check_fix(
459 r#"
460struct TestStruct { one: i32 }
461
462impl TestStruct {
463 fn test_fn() { let s = Self {<|>}; }
464}
465"#,
466 r#"
467struct TestStruct { one: i32 }
468
469impl TestStruct {
470 fn test_fn() { let s = Self { one: ()}; }
471}
472"#,
473 );
474 }
475
476 #[test]
477 fn test_fill_struct_fields_enum() {
478 check_fix(
479 r#"
480enum Expr {
481 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
482}
483
484impl Expr {
485 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
486 Expr::Bin {<|> }
487 }
488}
489"#,
490 r#"
491enum Expr {
492 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
493}
494
495impl Expr {
496 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
497 Expr::Bin { lhs: (), rhs: () }
498 }
499}
500"#,
501 );
502 }
503
504 #[test]
505 fn test_fill_struct_fields_partial() {
506 check_fix(
507 r#"
508struct TestStruct { one: i32, two: i64 }
509
510fn test_fn() {
511 let s = TestStruct{ two: 2<|> };
512}
513"#,
514 r"
515struct TestStruct { one: i32, two: i64 }
516
517fn test_fn() {
518 let s = TestStruct{ two: 2, one: () };
519}
520",
521 );
522 }
523
524 #[test]
525 fn test_fill_struct_fields_no_diagnostic() {
526 check_no_diagnostics(
527 r"
528 struct TestStruct { one: i32, two: i64 }
529
530 fn test_fn() {
531 let one = 1;
532 let s = TestStruct{ one, two: 2 };
533 }
534 ",
535 );
536 }
537
538 #[test]
539 fn test_fill_struct_fields_no_diagnostic_on_spread() {
540 check_no_diagnostics(
541 r"
542 struct TestStruct { one: i32, two: i64 }
543
544 fn test_fn() {
545 let one = 1;
546 let s = TestStruct{ ..a };
547 }
548 ",
549 );
550 }
551
552 #[test]
553 fn test_unresolved_module_diagnostic() {
554 check_expect(
555 r#"mod foo;"#,
556 expect![[r#"
557 [
558 Diagnostic {
559 message: "unresolved module",
560 range: 0..8,
561 severity: Error,
562 fix: Some(
563 Fix {
564 label: "Create module",
565 source_change: SourceChange {
566 source_file_edits: [],
567 file_system_edits: [
568 CreateFile {
569 anchor: FileId(
570 1,
571 ),
572 dst: "foo.rs",
573 },
574 ],
575 is_snippet: false,
576 },
577 fix_trigger_range: 0..8,
578 },
579 ),
580 },
581 ]
582 "#]],
583 );
584 }
585
586 #[test]
587 fn range_mapping_out_of_macros() {
588 // FIXME: this is very wrong, but somewhat tricky to fix.
589 check_fix(
590 r#"
591fn some() {}
592fn items() {}
593fn here() {}
594
595macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
596
597fn main() {
598 let _x = id![Foo { a: <|>42 }];
599}
600
601pub struct Foo { pub a: i32, pub b: i32 }
602"#,
603 r#"
604fn {a:42, b: ()} {}
605fn items() {}
606fn here() {}
607
608macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
609
610fn main() {
611 let _x = id![Foo { a: 42 }];
612}
613
614pub struct Foo { pub a: i32, pub b: i32 }
615"#,
616 );
617 }
618
619 #[test]
620 fn test_check_unnecessary_braces_in_use_statement() {
621 check_no_diagnostics(
622 r#"
623use a;
624use a::{c, d::e};
625"#,
626 );
627 check_fix(r#"use {<|>b};"#, r#"use b;"#);
628 check_fix(r#"use {b<|>};"#, r#"use b;"#);
629 check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#);
630 check_fix(r#"use a::{self<|>};"#, r#"use a;"#);
631 check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#);
632 }
633
634 #[test]
635 fn test_check_struct_shorthand_initialization() {
636 check_no_diagnostics(
637 r#"
638struct A { a: &'static str }
639fn main() { A { a: "hello" } }
640"#,
641 );
642 check_no_diagnostics(
643 r#"
644struct A(usize);
645fn main() { A { 0: 0 } }
646"#,
647 );
648
649 check_fix(
650 r#"
651struct A { a: &'static str }
652fn main() {
653 let a = "haha";
654 A { a<|>: a }
655}
656"#,
657 r#"
658struct A { a: &'static str }
659fn main() {
660 let a = "haha";
661 A { a }
662}
663"#,
664 );
665
666 check_fix(
667 r#"
668struct A { a: &'static str, b: &'static str }
669fn main() {
670 let a = "haha";
671 let b = "bb";
672 A { a<|>: a, b }
673}
674"#,
675 r#"
676struct A { a: &'static str, b: &'static str }
677fn main() {
678 let a = "haha";
679 let b = "bb";
680 A { a, b }
681}
682"#,
683 );
684 }
685
686 #[test]
687 fn test_add_field_from_usage() {
688 check_fix(
689 r"
690fn main() {
691 Foo { bar: 3, baz<|>: false};
692}
693struct Foo {
694 bar: i32
695}
696",
697 r"
698fn main() {
699 Foo { bar: 3, baz: false};
700}
701struct Foo {
702 bar: i32,
703 baz: bool
704}
705",
706 )
707 }
708
709 #[test]
710 fn test_add_field_in_other_file_from_usage() {
711 check_apply_diagnostic_fix_in_other_file(
712 r"
713 //- /main.rs
714 mod foo;
715
716 fn main() {
717 <|>foo::Foo { bar: 3, baz: false};
718 }
719 //- /foo.rs
720 struct Foo {
721 bar: i32
722 }
723 ",
724 r"
725 struct Foo {
726 bar: i32,
727 pub(crate) baz: bool
728 }
729 ",
730 )
731 }
732
733 #[test]
734 fn test_disabled_diagnostics() {
735 let mut config = DiagnosticsConfig::default();
736 config.disabled.insert("unresolved-module".into());
737
738 let (analysis, file_id) = single_file(r#"mod foo;"#);
739
740 let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
741 assert!(diagnostics.is_empty());
742
743 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
744 assert!(!diagnostics.is_empty());
745 }
746}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
new file mode 100644
index 000000000..68ae1c239
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -0,0 +1,175 @@
1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use base_db::FileId;
4use hir::{
5 db::AstDatabase,
6 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
7 HasSource, HirDisplay, Semantics, VariantDef,
8};
9use ide_db::{
10 source_change::{FileSystemEdit, SourceFileEdit},
11 RootDatabase,
12};
13use syntax::{
14 algo,
15 ast::{self, edit::IndentLevel, make},
16 AstNode,
17};
18use text_edit::TextEdit;
19
20use crate::diagnostics::Fix;
21
22/// A [Diagnostic] that potentially has a fix available.
23///
24/// [Diagnostic]: hir::diagnostics::Diagnostic
25pub trait DiagnosticWithFix: Diagnostic {
26 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
27}
28
29impl DiagnosticWithFix for UnresolvedModule {
30 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
31 let root = sema.db.parse_or_expand(self.file)?;
32 let unresolved_module = self.decl.to_node(&root);
33 Some(Fix::new(
34 "Create module",
35 FileSystemEdit::CreateFile {
36 anchor: self.file.original_file(sema.db),
37 dst: self.candidate.clone(),
38 }
39 .into(),
40 unresolved_module.syntax().text_range(),
41 ))
42 }
43}
44
45impl DiagnosticWithFix for NoSuchField {
46 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
47 let root = sema.db.parse_or_expand(self.file)?;
48 missing_record_expr_field_fix(
49 &sema,
50 self.file.original_file(sema.db),
51 &self.field.to_node(&root),
52 )
53 }
54}
55
56impl DiagnosticWithFix for MissingFields {
57 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
58 // Note that although we could add a diagnostics to
59 // fill the missing tuple field, e.g :
60 // `struct A(usize);`
61 // `let a = A { 0: () }`
62 // but it is uncommon usage and it should not be encouraged.
63 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
64 return None;
65 }
66
67 let root = sema.db.parse_or_expand(self.file)?;
68 let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
69 let mut new_field_list = old_field_list.clone();
70 for f in self.missed_fields.iter() {
71 let field =
72 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
73 new_field_list = new_field_list.append_field(&field);
74 }
75
76 let edit = {
77 let mut builder = TextEdit::builder();
78 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
79 .into_text_edit(&mut builder);
80 builder.finish()
81 };
82 Some(Fix::new(
83 "Fill struct fields",
84 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
85 sema.original_range(&old_field_list.syntax()).range,
86 ))
87 }
88}
89
90impl DiagnosticWithFix for MissingOkInTailExpr {
91 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
92 let root = sema.db.parse_or_expand(self.file)?;
93 let tail_expr = self.expr.to_node(&root);
94 let tail_expr_range = tail_expr.syntax().text_range();
95 let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
96 let source_change =
97 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
98 Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
99 }
100}
101
102fn missing_record_expr_field_fix(
103 sema: &Semantics<RootDatabase>,
104 usage_file_id: FileId,
105 record_expr_field: &ast::RecordExprField,
106) -> Option<Fix> {
107 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
108 let def_id = sema.resolve_variant(record_lit)?;
109 let module;
110 let def_file_id;
111 let record_fields = match VariantDef::from(def_id) {
112 VariantDef::Struct(s) => {
113 module = s.module(sema.db);
114 let source = s.source(sema.db);
115 def_file_id = source.file_id;
116 let fields = source.value.field_list()?;
117 record_field_list(fields)?
118 }
119 VariantDef::Union(u) => {
120 module = u.module(sema.db);
121 let source = u.source(sema.db);
122 def_file_id = source.file_id;
123 source.value.record_field_list()?
124 }
125 VariantDef::EnumVariant(e) => {
126 module = e.module(sema.db);
127 let source = e.source(sema.db);
128 def_file_id = source.file_id;
129 let fields = source.value.field_list()?;
130 record_field_list(fields)?
131 }
132 };
133 let def_file_id = def_file_id.original_file(sema.db);
134
135 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
136 if new_field_type.is_unknown() {
137 return None;
138 }
139 let new_field = make::record_field(
140 record_expr_field.field_name()?,
141 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
142 );
143
144 let last_field = record_fields.fields().last()?;
145 let last_field_syntax = last_field.syntax();
146 let indent = IndentLevel::from_node(last_field_syntax);
147
148 let mut new_field = new_field.to_string();
149 if usage_file_id != def_file_id {
150 new_field = format!("pub(crate) {}", new_field);
151 }
152 new_field = format!("\n{}{}", indent, new_field);
153
154 let needs_comma = !last_field_syntax.to_string().ends_with(',');
155 if needs_comma {
156 new_field = format!(",{}", new_field);
157 }
158
159 let source_change = SourceFileEdit {
160 file_id: def_file_id,
161 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
162 };
163 return Some(Fix::new(
164 "Create field",
165 source_change.into(),
166 record_expr_field.syntax().text_range(),
167 ));
168
169 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
170 match field_def_list {
171 ast::FieldList::RecordFieldList(it) => Some(it),
172 ast::FieldList::TupleFieldList(_) => None,
173 }
174 }
175}
diff --git a/crates/ide/src/display.rs b/crates/ide/src/display.rs
new file mode 100644
index 000000000..41b5bdc49
--- /dev/null
+++ b/crates/ide/src/display.rs
@@ -0,0 +1,83 @@
1//! This module contains utilities for turning SyntaxNodes and HIR types
2//! into types that may be used to render in a UI.
3
4mod navigation_target;
5mod short_label;
6
7use syntax::{
8 ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner},
9 SyntaxKind::{ATTR, COMMENT},
10};
11
12use ast::VisibilityOwner;
13use stdx::format_to;
14
15pub use navigation_target::NavigationTarget;
16pub(crate) use navigation_target::{ToNav, TryToNav};
17pub(crate) use short_label::ShortLabel;
18
19pub(crate) fn function_declaration(node: &ast::Fn) -> String {
20 let mut buf = String::new();
21 if let Some(vis) = node.visibility() {
22 format_to!(buf, "{} ", vis);
23 }
24 if node.async_token().is_some() {
25 format_to!(buf, "async ");
26 }
27 if node.const_token().is_some() {
28 format_to!(buf, "const ");
29 }
30 if node.unsafe_token().is_some() {
31 format_to!(buf, "unsafe ");
32 }
33 if let Some(abi) = node.abi() {
34 // Keyword `extern` is included in the string.
35 format_to!(buf, "{} ", abi);
36 }
37 if let Some(name) = node.name() {
38 format_to!(buf, "fn {}", name)
39 }
40 if let Some(type_params) = node.generic_param_list() {
41 format_to!(buf, "{}", type_params);
42 }
43 if let Some(param_list) = node.param_list() {
44 format_to!(buf, "{}", param_list);
45 }
46 if let Some(ret_type) = node.ret_type() {
47 if ret_type.ty().is_some() {
48 format_to!(buf, " {}", ret_type);
49 }
50 }
51 if let Some(where_clause) = node.where_clause() {
52 format_to!(buf, "\n{}", where_clause);
53 }
54 buf
55}
56
57pub(crate) fn const_label(node: &ast::Const) -> String {
58 let label: String = node
59 .syntax()
60 .children_with_tokens()
61 .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR))
62 .map(|node| node.to_string())
63 .collect();
64
65 label.trim().to_owned()
66}
67
68pub(crate) fn type_label(node: &ast::TypeAlias) -> String {
69 let label: String = node
70 .syntax()
71 .children_with_tokens()
72 .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR))
73 .map(|node| node.to_string())
74 .collect();
75
76 label.trim().to_owned()
77}
78
79pub(crate) fn macro_label(node: &ast::MacroCall) -> String {
80 let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default();
81 let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" };
82 format!("{}macro_rules! {}", vis, name)
83}
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
new file mode 100644
index 000000000..1ee80c2dd
--- /dev/null
+++ b/crates/ide/src/display/navigation_target.rs
@@ -0,0 +1,491 @@
1//! FIXME: write short doc here
2
3use base_db::{FileId, SourceDatabase};
4use either::Either;
5use hir::{original_range, AssocItem, FieldSource, HasSource, InFile, ModuleSource};
6use ide_db::{defs::Definition, RootDatabase};
7use syntax::{
8 ast::{self, DocCommentsOwner, NameOwner},
9 match_ast, AstNode, SmolStr,
10 SyntaxKind::{self, IDENT_PAT, TYPE_PARAM},
11 TextRange,
12};
13
14use crate::FileSymbol;
15
16use super::short_label::ShortLabel;
17
18/// `NavigationTarget` represents and element in the editor's UI which you can
19/// click on to navigate to a particular piece of code.
20///
21/// Typically, a `NavigationTarget` corresponds to some element in the source
22/// code, like a function or a struct, but this is not strictly required.
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct NavigationTarget {
25 pub file_id: FileId,
26 /// Range which encompasses the whole element.
27 ///
28 /// Should include body, doc comments, attributes, etc.
29 ///
30 /// Clients should use this range to answer "is the cursor inside the
31 /// element?" question.
32 pub full_range: TextRange,
33 /// A "most interesting" range withing the `full_range`.
34 ///
35 /// Typically, `full_range` is the whole syntax node, including doc
36 /// comments, and `focus_range` is the range of the identifier. "Most
37 /// interesting" range within the full range, typically the range of
38 /// identifier.
39 ///
40 /// Clients should place the cursor on this range when navigating to this target.
41 pub focus_range: Option<TextRange>,
42 pub name: SmolStr,
43 pub kind: SyntaxKind,
44 pub container_name: Option<SmolStr>,
45 pub description: Option<String>,
46 pub docs: Option<String>,
47}
48
49pub(crate) trait ToNav {
50 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
51}
52
53pub(crate) trait TryToNav {
54 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
55}
56
57impl NavigationTarget {
58 pub fn focus_or_full_range(&self) -> TextRange {
59 self.focus_range.unwrap_or(self.full_range)
60 }
61
62 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
63 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
64 if let Some(src) = module.declaration_source(db) {
65 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
66 let mut res = NavigationTarget::from_syntax(
67 frange.file_id,
68 name,
69 None,
70 frange.range,
71 src.value.syntax().kind(),
72 );
73 res.docs = src.value.doc_comment_text();
74 res.description = src.value.short_label();
75 return res;
76 }
77 module.to_nav(db)
78 }
79
80 #[cfg(test)]
81 pub(crate) fn assert_match(&self, expected: &str) {
82 let actual = self.debug_render();
83 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
84 }
85
86 #[cfg(test)]
87 pub(crate) fn debug_render(&self) -> String {
88 let mut buf =
89 format!("{} {:?} {:?} {:?}", self.name, self.kind, self.file_id, self.full_range);
90 if let Some(focus_range) = self.focus_range {
91 buf.push_str(&format!(" {:?}", focus_range))
92 }
93 if let Some(container_name) = &self.container_name {
94 buf.push_str(&format!(" {}", container_name))
95 }
96 buf
97 }
98
99 /// Allows `NavigationTarget` to be created from a `NameOwner`
100 pub(crate) fn from_named(
101 db: &RootDatabase,
102 node: InFile<&dyn ast::NameOwner>,
103 ) -> NavigationTarget {
104 let name =
105 node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
106 let focus_range =
107 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
108 let frange = original_range(db, node.map(|it| it.syntax()));
109
110 NavigationTarget::from_syntax(
111 frange.file_id,
112 name,
113 focus_range,
114 frange.range,
115 node.value.syntax().kind(),
116 )
117 }
118
119 /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
120 pub(crate) fn from_doc_commented(
121 db: &RootDatabase,
122 named: InFile<&dyn ast::NameOwner>,
123 node: InFile<&dyn ast::DocCommentsOwner>,
124 ) -> NavigationTarget {
125 let name =
126 named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
127 let frange = original_range(db, node.map(|it| it.syntax()));
128
129 NavigationTarget::from_syntax(
130 frange.file_id,
131 name,
132 None,
133 frange.range,
134 node.value.syntax().kind(),
135 )
136 }
137
138 fn from_syntax(
139 file_id: FileId,
140 name: SmolStr,
141 focus_range: Option<TextRange>,
142 full_range: TextRange,
143 kind: SyntaxKind,
144 ) -> NavigationTarget {
145 NavigationTarget {
146 file_id,
147 name,
148 kind,
149 full_range,
150 focus_range,
151 container_name: None,
152 description: None,
153 docs: None,
154 }
155 }
156}
157
158impl ToNav for FileSymbol {
159 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
160 NavigationTarget {
161 file_id: self.file_id,
162 name: self.name.clone(),
163 kind: self.kind,
164 full_range: self.range,
165 focus_range: self.name_range,
166 container_name: self.container_name.clone(),
167 description: description_from_symbol(db, self),
168 docs: docs_from_symbol(db, self),
169 }
170 }
171}
172
173impl TryToNav for Definition {
174 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
175 match self {
176 Definition::Macro(it) => Some(it.to_nav(db)),
177 Definition::Field(it) => Some(it.to_nav(db)),
178 Definition::ModuleDef(it) => it.try_to_nav(db),
179 Definition::SelfType(it) => Some(it.to_nav(db)),
180 Definition::Local(it) => Some(it.to_nav(db)),
181 Definition::TypeParam(it) => Some(it.to_nav(db)),
182 }
183 }
184}
185
186impl TryToNav for hir::ModuleDef {
187 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
188 let res = match self {
189 hir::ModuleDef::Module(it) => it.to_nav(db),
190 hir::ModuleDef::Function(it) => it.to_nav(db),
191 hir::ModuleDef::Adt(it) => it.to_nav(db),
192 hir::ModuleDef::EnumVariant(it) => it.to_nav(db),
193 hir::ModuleDef::Const(it) => it.to_nav(db),
194 hir::ModuleDef::Static(it) => it.to_nav(db),
195 hir::ModuleDef::Trait(it) => it.to_nav(db),
196 hir::ModuleDef::TypeAlias(it) => it.to_nav(db),
197 hir::ModuleDef::BuiltinType(_) => return None,
198 };
199 Some(res)
200 }
201}
202
203pub(crate) trait ToNavFromAst {}
204impl ToNavFromAst for hir::Function {}
205impl ToNavFromAst for hir::Const {}
206impl ToNavFromAst for hir::Static {}
207impl ToNavFromAst for hir::Struct {}
208impl ToNavFromAst for hir::Enum {}
209impl ToNavFromAst for hir::EnumVariant {}
210impl ToNavFromAst for hir::Union {}
211impl ToNavFromAst for hir::TypeAlias {}
212impl ToNavFromAst for hir::Trait {}
213
214impl<D> ToNav for D
215where
216 D: HasSource + ToNavFromAst + Copy,
217 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
218{
219 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
220 let src = self.source(db);
221 let mut res =
222 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
223 res.docs = src.value.doc_comment_text();
224 res.description = src.value.short_label();
225 res
226 }
227}
228
229impl ToNav for hir::Module {
230 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
231 let src = self.definition_source(db);
232 let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
233 let (syntax, focus) = match &src.value {
234 ModuleSource::SourceFile(node) => (node.syntax(), None),
235 ModuleSource::Module(node) => {
236 (node.syntax(), node.name().map(|it| it.syntax().text_range()))
237 }
238 };
239 let frange = original_range(db, src.with_value(syntax));
240 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind())
241 }
242}
243
244impl ToNav for hir::ImplDef {
245 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
246 let src = self.source(db);
247 let derive_attr = self.is_builtin_derive(db);
248 let frange = if let Some(item) = &derive_attr {
249 original_range(db, item.syntax())
250 } else {
251 original_range(db, src.as_ref().map(|it| it.syntax()))
252 };
253 let focus_range = if derive_attr.is_some() {
254 None
255 } else {
256 src.value.self_ty().map(|ty| original_range(db, src.with_value(ty.syntax())).range)
257 };
258
259 NavigationTarget::from_syntax(
260 frange.file_id,
261 "impl".into(),
262 focus_range,
263 frange.range,
264 src.value.syntax().kind(),
265 )
266 }
267}
268
269impl ToNav for hir::Field {
270 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
271 let src = self.source(db);
272
273 match &src.value {
274 FieldSource::Named(it) => {
275 let mut res = NavigationTarget::from_named(db, src.with_value(it));
276 res.docs = it.doc_comment_text();
277 res.description = it.short_label();
278 res
279 }
280 FieldSource::Pos(it) => {
281 let frange = original_range(db, src.with_value(it.syntax()));
282 NavigationTarget::from_syntax(
283 frange.file_id,
284 "".into(),
285 None,
286 frange.range,
287 it.syntax().kind(),
288 )
289 }
290 }
291 }
292}
293
294impl ToNav for hir::MacroDef {
295 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
296 let src = self.source(db);
297 log::debug!("nav target {:#?}", src.value.syntax());
298 let mut res =
299 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
300 res.docs = src.value.doc_comment_text();
301 res
302 }
303}
304
305impl ToNav for hir::Adt {
306 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
307 match self {
308 hir::Adt::Struct(it) => it.to_nav(db),
309 hir::Adt::Union(it) => it.to_nav(db),
310 hir::Adt::Enum(it) => it.to_nav(db),
311 }
312 }
313}
314
315impl ToNav for hir::AssocItem {
316 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
317 match self {
318 AssocItem::Function(it) => it.to_nav(db),
319 AssocItem::Const(it) => it.to_nav(db),
320 AssocItem::TypeAlias(it) => it.to_nav(db),
321 }
322 }
323}
324
325impl ToNav for hir::Local {
326 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
327 let src = self.source(db);
328 let node = match &src.value {
329 Either::Left(bind_pat) => {
330 bind_pat.name().map_or_else(|| bind_pat.syntax().clone(), |it| it.syntax().clone())
331 }
332 Either::Right(it) => it.syntax().clone(),
333 };
334 let full_range = original_range(db, src.with_value(&node));
335 let name = match self.name(db) {
336 Some(it) => it.to_string().into(),
337 None => "".into(),
338 };
339 NavigationTarget {
340 file_id: full_range.file_id,
341 name,
342 kind: IDENT_PAT,
343 full_range: full_range.range,
344 focus_range: None,
345 container_name: None,
346 description: None,
347 docs: None,
348 }
349 }
350}
351
352impl ToNav for hir::TypeParam {
353 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
354 let src = self.source(db);
355 let full_range = match &src.value {
356 Either::Left(it) => it.syntax().text_range(),
357 Either::Right(it) => it.syntax().text_range(),
358 };
359 let focus_range = match &src.value {
360 Either::Left(_) => None,
361 Either::Right(it) => it.name().map(|it| it.syntax().text_range()),
362 };
363 NavigationTarget {
364 file_id: src.file_id.original_file(db),
365 name: self.name(db).to_string().into(),
366 kind: TYPE_PARAM,
367 full_range,
368 focus_range,
369 container_name: None,
370 description: None,
371 docs: None,
372 }
373 }
374}
375
376pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
377 let parse = db.parse(symbol.file_id);
378 let node = symbol.ptr.to_node(parse.tree().syntax());
379
380 match_ast! {
381 match node {
382 ast::Fn(it) => it.doc_comment_text(),
383 ast::Struct(it) => it.doc_comment_text(),
384 ast::Enum(it) => it.doc_comment_text(),
385 ast::Trait(it) => it.doc_comment_text(),
386 ast::Module(it) => it.doc_comment_text(),
387 ast::TypeAlias(it) => it.doc_comment_text(),
388 ast::Const(it) => it.doc_comment_text(),
389 ast::Static(it) => it.doc_comment_text(),
390 ast::RecordField(it) => it.doc_comment_text(),
391 ast::Variant(it) => it.doc_comment_text(),
392 ast::MacroCall(it) => it.doc_comment_text(),
393 _ => None,
394 }
395 }
396}
397
398/// Get a description of a symbol.
399///
400/// e.g. `struct Name`, `enum Name`, `fn Name`
401pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
402 let parse = db.parse(symbol.file_id);
403 let node = symbol.ptr.to_node(parse.tree().syntax());
404
405 match_ast! {
406 match node {
407 ast::Fn(it) => it.short_label(),
408 ast::Struct(it) => it.short_label(),
409 ast::Enum(it) => it.short_label(),
410 ast::Trait(it) => it.short_label(),
411 ast::Module(it) => it.short_label(),
412 ast::TypeAlias(it) => it.short_label(),
413 ast::Const(it) => it.short_label(),
414 ast::Static(it) => it.short_label(),
415 ast::RecordField(it) => it.short_label(),
416 ast::Variant(it) => it.short_label(),
417 _ => None,
418 }
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use expect_test::expect;
425
426 use crate::{mock_analysis::single_file, Query};
427
428 #[test]
429 fn test_nav_for_symbol() {
430 let (analysis, _) = single_file(
431 r#"
432enum FooInner { }
433fn foo() { enum FooInner { } }
434"#,
435 );
436
437 let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap();
438 expect![[r#"
439 [
440 NavigationTarget {
441 file_id: FileId(
442 1,
443 ),
444 full_range: 0..17,
445 focus_range: Some(
446 5..13,
447 ),
448 name: "FooInner",
449 kind: ENUM,
450 container_name: None,
451 description: Some(
452 "enum FooInner",
453 ),
454 docs: None,
455 },
456 NavigationTarget {
457 file_id: FileId(
458 1,
459 ),
460 full_range: 29..46,
461 focus_range: Some(
462 34..42,
463 ),
464 name: "FooInner",
465 kind: ENUM,
466 container_name: Some(
467 "foo",
468 ),
469 description: Some(
470 "enum FooInner",
471 ),
472 docs: None,
473 },
474 ]
475 "#]]
476 .assert_debug_eq(&navs);
477 }
478
479 #[test]
480 fn test_world_symbols_are_case_sensitive() {
481 let (analysis, _) = single_file(
482 r#"
483fn foo() {}
484struct Foo;
485"#,
486 );
487
488 let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
489 assert_eq!(navs.len(), 2)
490 }
491}
diff --git a/crates/ide/src/display/short_label.rs b/crates/ide/src/display/short_label.rs
new file mode 100644
index 000000000..ea49d9f97
--- /dev/null
+++ b/crates/ide/src/display/short_label.rs
@@ -0,0 +1,111 @@
1//! FIXME: write short doc here
2
3use stdx::format_to;
4use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
5
6pub(crate) trait ShortLabel {
7 fn short_label(&self) -> Option<String>;
8}
9
10impl ShortLabel for ast::Fn {
11 fn short_label(&self) -> Option<String> {
12 Some(crate::display::function_declaration(self))
13 }
14}
15
16impl ShortLabel for ast::Struct {
17 fn short_label(&self) -> Option<String> {
18 short_label_from_node(self, "struct ")
19 }
20}
21
22impl ShortLabel for ast::Union {
23 fn short_label(&self) -> Option<String> {
24 short_label_from_node(self, "union ")
25 }
26}
27
28impl ShortLabel for ast::Enum {
29 fn short_label(&self) -> Option<String> {
30 short_label_from_node(self, "enum ")
31 }
32}
33
34impl ShortLabel for ast::Trait {
35 fn short_label(&self) -> Option<String> {
36 if self.unsafe_token().is_some() {
37 short_label_from_node(self, "unsafe trait ")
38 } else {
39 short_label_from_node(self, "trait ")
40 }
41 }
42}
43
44impl ShortLabel for ast::Module {
45 fn short_label(&self) -> Option<String> {
46 short_label_from_node(self, "mod ")
47 }
48}
49
50impl ShortLabel for ast::SourceFile {
51 fn short_label(&self) -> Option<String> {
52 None
53 }
54}
55
56impl ShortLabel for ast::TypeAlias {
57 fn short_label(&self) -> Option<String> {
58 short_label_from_node(self, "type ")
59 }
60}
61
62impl ShortLabel for ast::Const {
63 fn short_label(&self) -> Option<String> {
64 let mut new_buf = short_label_from_ty(self, self.ty(), "const ")?;
65 if let Some(expr) = self.body() {
66 format_to!(new_buf, " = {}", expr.syntax());
67 }
68 Some(new_buf)
69 }
70}
71
72impl ShortLabel for ast::Static {
73 fn short_label(&self) -> Option<String> {
74 short_label_from_ty(self, self.ty(), "static ")
75 }
76}
77
78impl ShortLabel for ast::RecordField {
79 fn short_label(&self) -> Option<String> {
80 short_label_from_ty(self, self.ty(), "")
81 }
82}
83
84impl ShortLabel for ast::Variant {
85 fn short_label(&self) -> Option<String> {
86 Some(self.name()?.text().to_string())
87 }
88}
89
90fn short_label_from_ty<T>(node: &T, ty: Option<ast::Type>, prefix: &str) -> Option<String>
91where
92 T: NameOwner + VisibilityOwner,
93{
94 let mut buf = short_label_from_node(node, prefix)?;
95
96 if let Some(type_ref) = ty {
97 format_to!(buf, ": {}", type_ref.syntax());
98 }
99
100 Some(buf)
101}
102
103fn short_label_from_node<T>(node: &T, label: &str) -> Option<String>
104where
105 T: NameOwner + VisibilityOwner,
106{
107 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default();
108 buf.push_str(label);
109 buf.push_str(node.name()?.text().as_str());
110 Some(buf)
111}
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
new file mode 100644
index 000000000..8a285bcf7
--- /dev/null
+++ b/crates/ide/src/expand_macro.rs
@@ -0,0 +1,283 @@
1use hir::Semantics;
2use ide_db::RootDatabase;
3use syntax::{
4 algo::{find_node_at_offset, SyntaxRewriter},
5 ast, AstNode, NodeOrToken, SyntaxKind,
6 SyntaxKind::*,
7 SyntaxNode, WalkEvent, T,
8};
9
10use crate::FilePosition;
11
12pub struct ExpandedMacro {
13 pub name: String,
14 pub expansion: String,
15}
16
17// Feature: Expand Macro Recursively
18//
19// Shows the full macro expansion of the macro at current cursor.
20//
21// |===
22// | Editor | Action Name
23//
24// | VS Code | **Rust Analyzer: Expand macro recursively**
25// |===
26pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
27 let sema = Semantics::new(db);
28 let file = sema.parse(position.file_id);
29 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
30 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
31
32 let expanded = expand_macro_recur(&sema, &mac)?;
33
34 // FIXME:
35 // macro expansion may lose all white space information
36 // But we hope someday we can use ra_fmt for that
37 let expansion = insert_whitespaces(expanded);
38 Some(ExpandedMacro { name: name_ref.text().to_string(), expansion })
39}
40
41fn expand_macro_recur(
42 sema: &Semantics<RootDatabase>,
43 macro_call: &ast::MacroCall,
44) -> Option<SyntaxNode> {
45 let mut expanded = sema.expand(macro_call)?;
46
47 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
48 let mut rewriter = SyntaxRewriter::default();
49
50 for child in children.into_iter() {
51 if let Some(new_node) = expand_macro_recur(sema, &child) {
52 // Replace the whole node if it is root
53 // `replace_descendants` will not replace the parent node
54 // but `SyntaxNode::descendants include itself
55 if expanded == *child.syntax() {
56 expanded = new_node;
57 } else {
58 rewriter.replace(child.syntax(), &new_node)
59 }
60 }
61 }
62
63 let res = rewriter.rewrite(&expanded);
64 Some(res)
65}
66
67// FIXME: It would also be cool to share logic here and in the mbe tests,
68// which are pretty unreadable at the moment.
69fn insert_whitespaces(syn: SyntaxNode) -> String {
70 let mut res = String::new();
71 let mut token_iter = syn
72 .preorder_with_tokens()
73 .filter_map(|event| {
74 if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
75 Some(token)
76 } else {
77 None
78 }
79 })
80 .peekable();
81
82 let mut indent = 0;
83 let mut last: Option<SyntaxKind> = None;
84
85 while let Some(token) = token_iter.next() {
86 let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
87 token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
88 };
89 let is_last =
90 |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
91
92 res += &match token.kind() {
93 k if is_text(k) && is_next(|it| !it.is_punct(), true) => token.text().to_string() + " ",
94 L_CURLY if is_next(|it| it != R_CURLY, true) => {
95 indent += 1;
96 let leading_space = if is_last(is_text, false) { " " } else { "" };
97 format!("{}{{\n{}", leading_space, " ".repeat(indent))
98 }
99 R_CURLY if is_last(|it| it != L_CURLY, true) => {
100 indent = indent.saturating_sub(1);
101 format!("\n{}}}", " ".repeat(indent))
102 }
103 R_CURLY => format!("}}\n{}", " ".repeat(indent)),
104 T![;] => format!(";\n{}", " ".repeat(indent)),
105 T![->] => " -> ".to_string(),
106 T![=] => " = ".to_string(),
107 T![=>] => " => ".to_string(),
108 _ => token.text().to_string(),
109 };
110
111 last = Some(token.kind());
112 }
113
114 return res;
115
116 fn is_text(k: SyntaxKind) -> bool {
117 k.is_keyword() || k.is_literal() || k == IDENT
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use expect_test::{expect, Expect};
124
125 use crate::mock_analysis::analysis_and_position;
126
127 fn check(ra_fixture: &str, expect: Expect) {
128 let (analysis, pos) = analysis_and_position(ra_fixture);
129 let expansion = analysis.expand_macro(pos).unwrap().unwrap();
130 let actual = format!("{}\n{}", expansion.name, expansion.expansion);
131 expect.assert_eq(&actual);
132 }
133
134 #[test]
135 fn macro_expand_recursive_expansion() {
136 check(
137 r#"
138macro_rules! bar {
139 () => { fn b() {} }
140}
141macro_rules! foo {
142 () => { bar!(); }
143}
144macro_rules! baz {
145 () => { foo!(); }
146}
147f<|>oo!();
148"#,
149 expect![[r#"
150 foo
151 fn b(){}
152 "#]],
153 );
154 }
155
156 #[test]
157 fn macro_expand_multiple_lines() {
158 check(
159 r#"
160macro_rules! foo {
161 () => {
162 fn some_thing() -> u32 {
163 let a = 0;
164 a + 10
165 }
166 }
167}
168f<|>oo!();
169 "#,
170 expect![[r#"
171 foo
172 fn some_thing() -> u32 {
173 let a = 0;
174 a+10
175 }"#]],
176 );
177 }
178
179 #[test]
180 fn macro_expand_match_ast() {
181 check(
182 r#"
183macro_rules! match_ast {
184 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
185 (match ($node:expr) {
186 $( ast::$ast:ident($it:ident) => $res:block, )*
187 _ => $catch_all:expr $(,)?
188 }) => {{
189 $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
190 { $catch_all }
191 }};
192}
193
194fn main() {
195 mat<|>ch_ast! {
196 match container {
197 ast::TraitDef(it) => {},
198 ast::ImplDef(it) => {},
199 _ => { continue },
200 }
201 }
202}
203"#,
204 expect![[r#"
205 match_ast
206 {
207 if let Some(it) = ast::TraitDef::cast(container.clone()){}
208 else if let Some(it) = ast::ImplDef::cast(container.clone()){}
209 else {
210 {
211 continue
212 }
213 }
214 }"#]],
215 );
216 }
217
218 #[test]
219 fn macro_expand_match_ast_inside_let_statement() {
220 check(
221 r#"
222macro_rules! match_ast {
223 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
224 (match ($node:expr) {}) => {{}};
225}
226
227fn main() {
228 let p = f(|it| {
229 let res = mat<|>ch_ast! { match c {}};
230 Some(res)
231 })?;
232}
233"#,
234 expect![[r#"
235 match_ast
236 {}
237 "#]],
238 );
239 }
240
241 #[test]
242 fn macro_expand_inner_macro_fail_to_expand() {
243 check(
244 r#"
245macro_rules! bar {
246 (BAD) => {};
247}
248macro_rules! foo {
249 () => {bar!()};
250}
251
252fn main() {
253 let res = fo<|>o!();
254}
255"#,
256 expect![[r#"
257 foo
258 "#]],
259 );
260 }
261
262 #[test]
263 fn macro_expand_with_dollar_crate() {
264 check(
265 r#"
266#[macro_export]
267macro_rules! bar {
268 () => {0};
269}
270macro_rules! foo {
271 () => {$crate::bar!()};
272}
273
274fn main() {
275 let res = fo<|>o!();
276}
277"#,
278 expect![[r#"
279 foo
280 0 "#]],
281 );
282 }
283}
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
new file mode 100644
index 000000000..34563a026
--- /dev/null
+++ b/crates/ide/src/extend_selection.rs
@@ -0,0 +1,654 @@
1use std::iter::successors;
2
3use hir::Semantics;
4use ide_db::RootDatabase;
5use syntax::{
6 algo::{self, find_covering_element, skip_trivia_token},
7 ast::{self, AstNode, AstToken},
8 Direction, NodeOrToken,
9 SyntaxKind::{self, *},
10 SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T,
11};
12
13use crate::FileRange;
14
15// Feature: Extend Selection
16//
17// Extends the current selection to the encompassing syntactic construct
18// (expression, statement, item, module, etc). It works with multiple cursors.
19//
20// |===
21// | Editor | Shortcut
22//
23// | VS Code | kbd:[Ctrl+Shift+→]
24// |===
25pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
26 let sema = Semantics::new(db);
27 let src = sema.parse(frange.file_id);
28 try_extend_selection(&sema, src.syntax(), frange).unwrap_or(frange.range)
29}
30
31fn try_extend_selection(
32 sema: &Semantics<RootDatabase>,
33 root: &SyntaxNode,
34 frange: FileRange,
35) -> Option<TextRange> {
36 let range = frange.range;
37
38 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
39 let list_kinds = [
40 RECORD_PAT_FIELD_LIST,
41 MATCH_ARM_LIST,
42 RECORD_FIELD_LIST,
43 TUPLE_FIELD_LIST,
44 RECORD_EXPR_FIELD_LIST,
45 VARIANT_LIST,
46 USE_TREE_LIST,
47 GENERIC_PARAM_LIST,
48 GENERIC_ARG_LIST,
49 TYPE_BOUND_LIST,
50 PARAM_LIST,
51 ARG_LIST,
52 ARRAY_EXPR,
53 TUPLE_EXPR,
54 TUPLE_TYPE,
55 TUPLE_PAT,
56 WHERE_CLAUSE,
57 ];
58
59 if range.is_empty() {
60 let offset = range.start();
61 let mut leaves = root.token_at_offset(offset);
62 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
63 return Some(extend_ws(root, leaves.next()?, offset));
64 }
65 let leaf_range = match leaves {
66 TokenAtOffset::None => return None,
67 TokenAtOffset::Single(l) => {
68 if string_kinds.contains(&l.kind()) {
69 extend_single_word_in_comment_or_string(&l, offset)
70 .unwrap_or_else(|| l.text_range())
71 } else {
72 l.text_range()
73 }
74 }
75 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
76 };
77 return Some(leaf_range);
78 };
79 let node = match find_covering_element(root, range) {
80 NodeOrToken::Token(token) => {
81 if token.text_range() != range {
82 return Some(token.text_range());
83 }
84 if let Some(comment) = ast::Comment::cast(token.clone()) {
85 if let Some(range) = extend_comments(comment) {
86 return Some(range);
87 }
88 }
89 token.parent()
90 }
91 NodeOrToken::Node(node) => node,
92 };
93
94 // if we are in single token_tree, we maybe live in macro or attr
95 if node.kind() == TOKEN_TREE {
96 if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
97 if let Some(range) = extend_tokens_from_range(sema, macro_call, range) {
98 return Some(range);
99 }
100 }
101 }
102
103 if node.text_range() != range {
104 return Some(node.text_range());
105 }
106
107 let node = shallowest_node(&node);
108
109 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
110 if let Some(range) = extend_list_item(&node) {
111 return Some(range);
112 }
113 }
114
115 node.parent().map(|it| it.text_range())
116}
117
118fn extend_tokens_from_range(
119 sema: &Semantics<RootDatabase>,
120 macro_call: ast::MacroCall,
121 original_range: TextRange,
122) -> Option<TextRange> {
123 let src = find_covering_element(&macro_call.syntax(), original_range);
124 let (first_token, last_token) = match src {
125 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
126 NodeOrToken::Token(it) => (it.clone(), it),
127 };
128
129 let mut first_token = skip_trivia_token(first_token, Direction::Next)?;
130 let mut last_token = skip_trivia_token(last_token, Direction::Prev)?;
131
132 while !original_range.contains_range(first_token.text_range()) {
133 first_token = skip_trivia_token(first_token.next_token()?, Direction::Next)?;
134 }
135 while !original_range.contains_range(last_token.text_range()) {
136 last_token = skip_trivia_token(last_token.prev_token()?, Direction::Prev)?;
137 }
138
139 // compute original mapped token range
140 let extended = {
141 let fst_expanded = sema.descend_into_macros(first_token.clone());
142 let lst_expanded = sema.descend_into_macros(last_token.clone());
143 let mut lca = algo::least_common_ancestor(&fst_expanded.parent(), &lst_expanded.parent())?;
144 lca = shallowest_node(&lca);
145 if lca.first_token() == Some(fst_expanded) && lca.last_token() == Some(lst_expanded) {
146 lca = lca.parent()?;
147 }
148 lca
149 };
150
151 // Compute parent node range
152 let validate = |token: &SyntaxToken| {
153 let expanded = sema.descend_into_macros(token.clone());
154 algo::least_common_ancestor(&extended, &expanded.parent()).as_ref() == Some(&extended)
155 };
156
157 // Find the first and last text range under expanded parent
158 let first = successors(Some(first_token), |token| {
159 let token = token.prev_token()?;
160 skip_trivia_token(token, Direction::Prev)
161 })
162 .take_while(validate)
163 .last()?;
164
165 let last = successors(Some(last_token), |token| {
166 let token = token.next_token()?;
167 skip_trivia_token(token, Direction::Next)
168 })
169 .take_while(validate)
170 .last()?;
171
172 let range = first.text_range().cover(last.text_range());
173 if range.contains_range(original_range) && original_range != range {
174 Some(range)
175 } else {
176 None
177 }
178}
179
180/// Find the shallowest node with same range, which allows us to traverse siblings.
181fn shallowest_node(node: &SyntaxNode) -> SyntaxNode {
182 node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap()
183}
184
185fn extend_single_word_in_comment_or_string(
186 leaf: &SyntaxToken,
187 offset: TextSize,
188) -> Option<TextRange> {
189 let text: &str = leaf.text();
190 let cursor_position: u32 = (offset - leaf.text_range().start()).into();
191
192 let (before, after) = text.split_at(cursor_position as usize);
193
194 fn non_word_char(c: char) -> bool {
195 !(c.is_alphanumeric() || c == '_')
196 }
197
198 let start_idx = before.rfind(non_word_char)? as u32;
199 let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
200
201 let from: TextSize = (start_idx + 1).into();
202 let to: TextSize = (cursor_position + end_idx).into();
203
204 let range = TextRange::new(from, to);
205 if range.is_empty() {
206 None
207 } else {
208 Some(range + leaf.text_range().start())
209 }
210}
211
212fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange {
213 let ws_text = ws.text();
214 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
215 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
216 let ws_suffix = &ws_text.as_str()[suffix];
217 let ws_prefix = &ws_text.as_str()[prefix];
218 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
219 if let Some(node) = ws.next_sibling_or_token() {
220 let start = match ws_prefix.rfind('\n') {
221 Some(idx) => ws.text_range().start() + TextSize::from((idx + 1) as u32),
222 None => node.text_range().start(),
223 };
224 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
225 node.text_range().end() + TextSize::of('\n')
226 } else {
227 node.text_range().end()
228 };
229 return TextRange::new(start, end);
230 }
231 }
232 ws.text_range()
233}
234
235fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
236 return if priority(&r) > priority(&l) { r } else { l };
237 fn priority(n: &SyntaxToken) -> usize {
238 match n.kind() {
239 WHITESPACE => 0,
240 IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2,
241 _ => 1,
242 }
243 }
244}
245
246/// Extend list item selection to include nearby delimiter and whitespace.
247fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
248 fn is_single_line_ws(node: &SyntaxToken) -> bool {
249 node.kind() == WHITESPACE && !node.text().contains('\n')
250 }
251
252 fn nearby_delimiter(
253 delimiter_kind: SyntaxKind,
254 node: &SyntaxNode,
255 dir: Direction,
256 ) -> Option<SyntaxToken> {
257 node.siblings_with_tokens(dir)
258 .skip(1)
259 .skip_while(|node| match node {
260 NodeOrToken::Node(_) => false,
261 NodeOrToken::Token(it) => is_single_line_ws(it),
262 })
263 .next()
264 .and_then(|it| it.into_token())
265 .filter(|node| node.kind() == delimiter_kind)
266 }
267
268 let delimiter = match node.kind() {
269 TYPE_BOUND => T![+],
270 _ => T![,],
271 };
272
273 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
274 // Include any following whitespace when delimiter is after list item.
275 let final_node = delimiter_node
276 .next_sibling_or_token()
277 .and_then(|it| it.into_token())
278 .filter(|node| is_single_line_ws(node))
279 .unwrap_or(delimiter_node);
280
281 return Some(TextRange::new(node.text_range().start(), final_node.text_range().end()));
282 }
283 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
284 return Some(TextRange::new(delimiter_node.text_range().start(), node.text_range().end()));
285 }
286
287 None
288}
289
290fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
291 let prev = adj_comments(&comment, Direction::Prev);
292 let next = adj_comments(&comment, Direction::Next);
293 if prev != next {
294 Some(TextRange::new(prev.syntax().text_range().start(), next.syntax().text_range().end()))
295 } else {
296 None
297 }
298}
299
300fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
301 let mut res = comment.clone();
302 for element in comment.syntax().siblings_with_tokens(dir) {
303 let token = match element.as_token() {
304 None => break,
305 Some(token) => token,
306 };
307 if let Some(c) = ast::Comment::cast(token.clone()) {
308 res = c
309 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
310 break;
311 }
312 }
313 res
314}
315
316#[cfg(test)]
317mod tests {
318 use crate::mock_analysis::analysis_and_position;
319
320 use super::*;
321
322 fn do_check(before: &str, afters: &[&str]) {
323 let (analysis, position) = analysis_and_position(&before);
324 let before = analysis.file_text(position.file_id).unwrap();
325 let range = TextRange::empty(position.offset);
326 let mut frange = FileRange { file_id: position.file_id, range };
327
328 for &after in afters {
329 frange.range = analysis.extend_selection(frange).unwrap();
330 let actual = &before[frange.range];
331 assert_eq!(after, actual);
332 }
333 }
334
335 #[test]
336 fn test_extend_selection_arith() {
337 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
338 }
339
340 #[test]
341 fn test_extend_selection_list() {
342 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
343 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
344 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
345 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
346 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
347 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
348
349 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
350 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
351 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
352
353 do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]);
354
355 do_check(
356 r#"
357const FOO: [usize; 2] = [
358 22,
359 <|>33,
360]"#,
361 &["33", "33,"],
362 );
363
364 do_check(
365 r#"
366const FOO: [usize; 2] = [
367 22
368 , 33<|>,
369]"#,
370 &["33", "33,"],
371 );
372 }
373
374 #[test]
375 fn test_extend_selection_start_of_the_line() {
376 do_check(
377 r#"
378impl S {
379<|> fn foo() {
380
381 }
382}"#,
383 &[" fn foo() {\n\n }\n"],
384 );
385 }
386
387 #[test]
388 fn test_extend_selection_doc_comments() {
389 do_check(
390 r#"
391struct A;
392
393/// bla
394/// bla
395struct B {
396 <|>
397}
398 "#,
399 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
400 )
401 }
402
403 #[test]
404 fn test_extend_selection_comments() {
405 do_check(
406 r#"
407fn bar(){}
408
409// fn foo() {
410// 1 + <|>1
411// }
412
413// fn foo(){}
414 "#,
415 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
416 );
417
418 do_check(
419 r#"
420// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
421// pub enum Direction {
422// <|> Next,
423// Prev
424// }
425"#,
426 &[
427 "// Next,",
428 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
429 ],
430 );
431
432 do_check(
433 r#"
434/*
435foo
436_bar1<|>*/
437"#,
438 &["_bar1", "/*\nfoo\n_bar1*/"],
439 );
440
441 do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
442
443 do_check(r#"/<|>/foo bar"#, &["//foo bar"]);
444 }
445
446 #[test]
447 fn test_extend_selection_prefer_idents() {
448 do_check(
449 r#"
450fn main() { foo<|>+bar;}
451"#,
452 &["foo", "foo+bar"],
453 );
454 do_check(
455 r#"
456fn main() { foo+<|>bar;}
457"#,
458 &["bar", "foo+bar"],
459 );
460 }
461
462 #[test]
463 fn test_extend_selection_prefer_lifetimes() {
464 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
465 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
466 }
467
468 #[test]
469 fn test_extend_selection_select_first_word() {
470 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
471 do_check(
472 r#"
473impl S {
474fn foo() {
475// hel<|>lo world
476}
477}
478"#,
479 &["hello", "// hello world"],
480 );
481 }
482
483 #[test]
484 fn test_extend_selection_string() {
485 do_check(
486 r#"
487fn bar(){}
488
489" fn f<|>oo() {"
490"#,
491 &["foo", "\" fn foo() {\""],
492 );
493 }
494
495 #[test]
496 fn test_extend_trait_bounds_list_in_where_clause() {
497 do_check(
498 r#"
499fn foo<R>()
500 where
501 R: req::Request + 'static,
502 R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
503 R::Result: Serialize + 'static,
504"#,
505 &[
506 "DeserializeOwned",
507 "DeserializeOwned + ",
508 "DeserializeOwned + panic::UnwindSafe + 'static",
509 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
510 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
511 ],
512 );
513 do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
514 do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
515 do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
516 do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
517 do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
518 do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "Display + "]);
519 do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
520 }
521
522 #[test]
523 fn test_extend_trait_bounds_list_inline() {
524 do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
525 do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
526 do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
527 do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
528 do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
529 do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "Display + "]);
530 do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
531 do_check(
532 r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
533 &[
534 "Copy",
535 "Copy + ",
536 "Copy + Display",
537 "T: Copy + Display",
538 "T: Copy + Display, ",
539 "<T: Copy + Display, U: Copy>",
540 ],
541 );
542 }
543
544 #[test]
545 fn test_extend_selection_on_tuple_in_type() {
546 do_check(
547 r#"fn main() { let _: (krate, <|>_crate_def_map, module_id) = (); }"#,
548 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
549 );
550 // white space variations
551 do_check(
552 r#"fn main() { let _: (krate,<|>_crate_def_map,module_id) = (); }"#,
553 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
554 );
555 do_check(
556 r#"
557fn main() { let _: (
558 krate,
559 _crate<|>_def_map,
560 module_id
561) = (); }"#,
562 &[
563 "_crate_def_map",
564 "_crate_def_map,",
565 "(\n krate,\n _crate_def_map,\n module_id\n)",
566 ],
567 );
568 }
569
570 #[test]
571 fn test_extend_selection_on_tuple_in_rvalue() {
572 do_check(
573 r#"fn main() { let var = (krate, _crate_def_map<|>, module_id); }"#,
574 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
575 );
576 // white space variations
577 do_check(
578 r#"fn main() { let var = (krate,_crate<|>_def_map,module_id); }"#,
579 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
580 );
581 do_check(
582 r#"
583fn main() { let var = (
584 krate,
585 _crate_def_map<|>,
586 module_id
587); }"#,
588 &[
589 "_crate_def_map",
590 "_crate_def_map,",
591 "(\n krate,\n _crate_def_map,\n module_id\n)",
592 ],
593 );
594 }
595
596 #[test]
597 fn test_extend_selection_on_tuple_pat() {
598 do_check(
599 r#"fn main() { let (krate, _crate_def_map<|>, module_id) = var; }"#,
600 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
601 );
602 // white space variations
603 do_check(
604 r#"fn main() { let (krate,_crate<|>_def_map,module_id) = var; }"#,
605 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
606 );
607 do_check(
608 r#"
609fn main() { let (
610 krate,
611 _crate_def_map<|>,
612 module_id
613) = var; }"#,
614 &[
615 "_crate_def_map",
616 "_crate_def_map,",
617 "(\n krate,\n _crate_def_map,\n module_id\n)",
618 ],
619 );
620 }
621
622 #[test]
623 fn extend_selection_inside_macros() {
624 do_check(
625 r#"macro_rules! foo { ($item:item) => {$item} }
626 foo!{fn hello(na<|>me:usize){}}"#,
627 &[
628 "name",
629 "name:usize",
630 "(name:usize)",
631 "fn hello(name:usize){}",
632 "{fn hello(name:usize){}}",
633 "foo!{fn hello(name:usize){}}",
634 ],
635 );
636 }
637
638 #[test]
639 fn extend_selection_inside_recur_macros() {
640 do_check(
641 r#" macro_rules! foo2 { ($item:item) => {$item} }
642 macro_rules! foo { ($item:item) => {foo2!($item);} }
643 foo!{fn hello(na<|>me:usize){}}"#,
644 &[
645 "name",
646 "name:usize",
647 "(name:usize)",
648 "fn hello(name:usize){}",
649 "{fn hello(name:usize){}}",
650 "foo!{fn hello(name:usize){}}",
651 ],
652 );
653 }
654}
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs
new file mode 100644
index 000000000..6168fb837
--- /dev/null
+++ b/crates/ide/src/file_structure.rs
@@ -0,0 +1,431 @@
1use syntax::{
2 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner},
3 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent,
4};
5
6#[derive(Debug, Clone)]
7pub struct StructureNode {
8 pub parent: Option<usize>,
9 pub label: String,
10 pub navigation_range: TextRange,
11 pub node_range: TextRange,
12 pub kind: SyntaxKind,
13 pub detail: Option<String>,
14 pub deprecated: bool,
15}
16
17// Feature: File Structure
18//
19// Provides a tree of the symbols defined in the file. Can be used to
20//
21// * fuzzy search symbol in a file (super useful)
22// * draw breadcrumbs to describe the context around the cursor
23// * draw outline of the file
24//
25// |===
26// | Editor | Shortcut
27//
28// | VS Code | kbd:[Ctrl+Shift+O]
29// |===
30pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
31 let mut res = Vec::new();
32 let mut stack = Vec::new();
33
34 for event in file.syntax().preorder() {
35 match event {
36 WalkEvent::Enter(node) => {
37 if let Some(mut symbol) = structure_node(&node) {
38 symbol.parent = stack.last().copied();
39 stack.push(res.len());
40 res.push(symbol);
41 }
42 }
43 WalkEvent::Leave(node) => {
44 if structure_node(&node).is_some() {
45 stack.pop().unwrap();
46 }
47 }
48 }
49 }
50 res
51}
52
53fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
54 fn decl<N: NameOwner + AttrsOwner>(node: N) -> Option<StructureNode> {
55 decl_with_detail(&node, None)
56 }
57
58 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
59 node: &N,
60 type_ref: Option<ast::Type>,
61 ) -> Option<StructureNode> {
62 let detail = type_ref.map(|type_ref| {
63 let mut detail = String::new();
64 collapse_ws(type_ref.syntax(), &mut detail);
65 detail
66 });
67 decl_with_detail(node, detail)
68 }
69
70 fn decl_with_detail<N: NameOwner + AttrsOwner>(
71 node: &N,
72 detail: Option<String>,
73 ) -> Option<StructureNode> {
74 let name = node.name()?;
75
76 Some(StructureNode {
77 parent: None,
78 label: name.text().to_string(),
79 navigation_range: name.syntax().text_range(),
80 node_range: node.syntax().text_range(),
81 kind: node.syntax().kind(),
82 detail,
83 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
84 })
85 }
86
87 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
88 let mut can_insert_ws = false;
89 node.text().for_each_chunk(|chunk| {
90 for line in chunk.lines() {
91 let line = line.trim();
92 if line.is_empty() {
93 if can_insert_ws {
94 output.push(' ');
95 can_insert_ws = false;
96 }
97 } else {
98 output.push_str(line);
99 can_insert_ws = true;
100 }
101 }
102 })
103 }
104
105 match_ast! {
106 match node {
107 ast::Fn(it) => {
108 let mut detail = String::from("fn");
109 if let Some(type_param_list) = it.generic_param_list() {
110 collapse_ws(type_param_list.syntax(), &mut detail);
111 }
112 if let Some(param_list) = it.param_list() {
113 collapse_ws(param_list.syntax(), &mut detail);
114 }
115 if let Some(ret_type) = it.ret_type() {
116 detail.push_str(" ");
117 collapse_ws(ret_type.syntax(), &mut detail);
118 }
119
120 decl_with_detail(&it, Some(detail))
121 },
122 ast::Struct(it) => decl(it),
123 ast::Union(it) => decl(it),
124 ast::Enum(it) => decl(it),
125 ast::Variant(it) => decl(it),
126 ast::Trait(it) => decl(it),
127 ast::Module(it) => decl(it),
128 ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty()),
129 ast::RecordField(it) => decl_with_type_ref(&it, it.ty()),
130 ast::Const(it) => decl_with_type_ref(&it, it.ty()),
131 ast::Static(it) => decl_with_type_ref(&it, it.ty()),
132 ast::Impl(it) => {
133 let target_type = it.self_ty()?;
134 let target_trait = it.trait_();
135 let label = match target_trait {
136 None => format!("impl {}", target_type.syntax().text()),
137 Some(t) => {
138 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
139 }
140 };
141
142 let node = StructureNode {
143 parent: None,
144 label,
145 navigation_range: target_type.syntax().text_range(),
146 node_range: it.syntax().text_range(),
147 kind: it.syntax().kind(),
148 detail: None,
149 deprecated: false,
150 };
151 Some(node)
152 },
153 ast::MacroCall(it) => {
154 match it.path().and_then(|it| it.segment()).and_then(|it| it.name_ref()) {
155 Some(path_segment) if path_segment.text() == "macro_rules"
156 => decl(it),
157 _ => None,
158 }
159 },
160 _ => None,
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use expect_test::{expect, Expect};
168
169 use super::*;
170
171 fn check(ra_fixture: &str, expect: Expect) {
172 let file = SourceFile::parse(ra_fixture).ok().unwrap();
173 let structure = file_structure(&file);
174 expect.assert_debug_eq(&structure)
175 }
176
177 #[test]
178 fn test_file_structure() {
179 check(
180 r#"
181struct Foo {
182 x: i32
183}
184
185mod m {
186 fn bar1() {}
187 fn bar2<T>(t: T) -> T {}
188 fn bar3<A,
189 B>(a: A,
190 b: B) -> Vec<
191 u32
192 > {}
193}
194
195enum E { X, Y(i32) }
196type T = ();
197static S: i32 = 92;
198const C: i32 = 92;
199
200impl E {}
201
202impl fmt::Debug for E {}
203
204macro_rules! mc {
205 () => {}
206}
207
208#[macro_export]
209macro_rules! mcexp {
210 () => {}
211}
212
213/// Doc comment
214macro_rules! mcexp {
215 () => {}
216}
217
218#[deprecated]
219fn obsolete() {}
220
221#[deprecated(note = "for awhile")]
222fn very_obsolete() {}
223"#,
224 expect![[r#"
225 [
226 StructureNode {
227 parent: None,
228 label: "Foo",
229 navigation_range: 8..11,
230 node_range: 1..26,
231 kind: STRUCT,
232 detail: None,
233 deprecated: false,
234 },
235 StructureNode {
236 parent: Some(
237 0,
238 ),
239 label: "x",
240 navigation_range: 18..19,
241 node_range: 18..24,
242 kind: RECORD_FIELD,
243 detail: Some(
244 "i32",
245 ),
246 deprecated: false,
247 },
248 StructureNode {
249 parent: None,
250 label: "m",
251 navigation_range: 32..33,
252 node_range: 28..158,
253 kind: MODULE,
254 detail: None,
255 deprecated: false,
256 },
257 StructureNode {
258 parent: Some(
259 2,
260 ),
261 label: "bar1",
262 navigation_range: 43..47,
263 node_range: 40..52,
264 kind: FN,
265 detail: Some(
266 "fn()",
267 ),
268 deprecated: false,
269 },
270 StructureNode {
271 parent: Some(
272 2,
273 ),
274 label: "bar2",
275 navigation_range: 60..64,
276 node_range: 57..81,
277 kind: FN,
278 detail: Some(
279 "fn<T>(t: T) -> T",
280 ),
281 deprecated: false,
282 },
283 StructureNode {
284 parent: Some(
285 2,
286 ),
287 label: "bar3",
288 navigation_range: 89..93,
289 node_range: 86..156,
290 kind: FN,
291 detail: Some(
292 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
293 ),
294 deprecated: false,
295 },
296 StructureNode {
297 parent: None,
298 label: "E",
299 navigation_range: 165..166,
300 node_range: 160..180,
301 kind: ENUM,
302 detail: None,
303 deprecated: false,
304 },
305 StructureNode {
306 parent: Some(
307 6,
308 ),
309 label: "X",
310 navigation_range: 169..170,
311 node_range: 169..170,
312 kind: VARIANT,
313 detail: None,
314 deprecated: false,
315 },
316 StructureNode {
317 parent: Some(
318 6,
319 ),
320 label: "Y",
321 navigation_range: 172..173,
322 node_range: 172..178,
323 kind: VARIANT,
324 detail: None,
325 deprecated: false,
326 },
327 StructureNode {
328 parent: None,
329 label: "T",
330 navigation_range: 186..187,
331 node_range: 181..193,
332 kind: TYPE_ALIAS,
333 detail: Some(
334 "()",
335 ),
336 deprecated: false,
337 },
338 StructureNode {
339 parent: None,
340 label: "S",
341 navigation_range: 201..202,
342 node_range: 194..213,
343 kind: STATIC,
344 detail: Some(
345 "i32",
346 ),
347 deprecated: false,
348 },
349 StructureNode {
350 parent: None,
351 label: "C",
352 navigation_range: 220..221,
353 node_range: 214..232,
354 kind: CONST,
355 detail: Some(
356 "i32",
357 ),
358 deprecated: false,
359 },
360 StructureNode {
361 parent: None,
362 label: "impl E",
363 navigation_range: 239..240,
364 node_range: 234..243,
365 kind: IMPL,
366 detail: None,
367 deprecated: false,
368 },
369 StructureNode {
370 parent: None,
371 label: "impl fmt::Debug for E",
372 navigation_range: 265..266,
373 node_range: 245..269,
374 kind: IMPL,
375 detail: None,
376 deprecated: false,
377 },
378 StructureNode {
379 parent: None,
380 label: "mc",
381 navigation_range: 284..286,
382 node_range: 271..303,
383 kind: MACRO_CALL,
384 detail: None,
385 deprecated: false,
386 },
387 StructureNode {
388 parent: None,
389 label: "mcexp",
390 navigation_range: 334..339,
391 node_range: 305..356,
392 kind: MACRO_CALL,
393 detail: None,
394 deprecated: false,
395 },
396 StructureNode {
397 parent: None,
398 label: "mcexp",
399 navigation_range: 387..392,
400 node_range: 358..409,
401 kind: MACRO_CALL,
402 detail: None,
403 deprecated: false,
404 },
405 StructureNode {
406 parent: None,
407 label: "obsolete",
408 navigation_range: 428..436,
409 node_range: 411..441,
410 kind: FN,
411 detail: Some(
412 "fn()",
413 ),
414 deprecated: true,
415 },
416 StructureNode {
417 parent: None,
418 label: "very_obsolete",
419 navigation_range: 481..494,
420 node_range: 443..499,
421 kind: FN,
422 detail: Some(
423 "fn()",
424 ),
425 deprecated: true,
426 },
427 ]
428 "#]],
429 );
430 }
431}
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs
new file mode 100644
index 000000000..7523aec55
--- /dev/null
+++ b/crates/ide/src/folding_ranges.rs
@@ -0,0 +1,422 @@
1//! FIXME: write short doc here
2
3use rustc_hash::FxHashSet;
4
5use syntax::{
6 ast::{self, AstNode, AstToken, VisibilityOwner},
7 Direction, NodeOrToken, SourceFile,
8 SyntaxKind::{self, *},
9 SyntaxNode, TextRange,
10};
11
12#[derive(Debug, PartialEq, Eq)]
13pub enum FoldKind {
14 Comment,
15 Imports,
16 Mods,
17 Block,
18 ArgList,
19}
20
21#[derive(Debug)]
22pub struct Fold {
23 pub range: TextRange,
24 pub kind: FoldKind,
25}
26
27pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
28 let mut res = vec![];
29 let mut visited_comments = FxHashSet::default();
30 let mut visited_imports = FxHashSet::default();
31 let mut visited_mods = FxHashSet::default();
32
33 for element in file.syntax().descendants_with_tokens() {
34 // Fold items that span multiple lines
35 if let Some(kind) = fold_kind(element.kind()) {
36 let is_multiline = match &element {
37 NodeOrToken::Node(node) => node.text().contains_char('\n'),
38 NodeOrToken::Token(token) => token.text().contains('\n'),
39 };
40 if is_multiline {
41 res.push(Fold { range: element.text_range(), kind });
42 continue;
43 }
44 }
45
46 match element {
47 NodeOrToken::Token(token) => {
48 // Fold groups of comments
49 if let Some(comment) = ast::Comment::cast(token) {
50 if !visited_comments.contains(&comment) {
51 if let Some(range) =
52 contiguous_range_for_comment(comment, &mut visited_comments)
53 {
54 res.push(Fold { range, kind: FoldKind::Comment })
55 }
56 }
57 }
58 }
59 NodeOrToken::Node(node) => {
60 // Fold groups of imports
61 if node.kind() == USE && !visited_imports.contains(&node) {
62 if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) {
63 res.push(Fold { range, kind: FoldKind::Imports })
64 }
65 }
66
67 // Fold groups of mods
68 if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node)
69 {
70 if let Some(range) =
71 contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods)
72 {
73 res.push(Fold { range, kind: FoldKind::Mods })
74 }
75 }
76 }
77 }
78 }
79
80 res
81}
82
83fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
84 match kind {
85 COMMENT => Some(FoldKind::Comment),
86 USE => Some(FoldKind::Imports),
87 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList),
88 ASSOC_ITEM_LIST
89 | RECORD_FIELD_LIST
90 | RECORD_PAT_FIELD_LIST
91 | RECORD_EXPR_FIELD_LIST
92 | ITEM_LIST
93 | EXTERN_ITEM_LIST
94 | USE_TREE_LIST
95 | BLOCK_EXPR
96 | MATCH_ARM_LIST
97 | VARIANT_LIST
98 | TOKEN_TREE => Some(FoldKind::Block),
99 _ => None,
100 }
101}
102
103fn has_visibility(node: &SyntaxNode) -> bool {
104 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
105}
106
107fn contiguous_range_for_group(
108 first: &SyntaxNode,
109 visited: &mut FxHashSet<SyntaxNode>,
110) -> Option<TextRange> {
111 contiguous_range_for_group_unless(first, |_| false, visited)
112}
113
114fn contiguous_range_for_group_unless(
115 first: &SyntaxNode,
116 unless: impl Fn(&SyntaxNode) -> bool,
117 visited: &mut FxHashSet<SyntaxNode>,
118) -> Option<TextRange> {
119 visited.insert(first.clone());
120
121 let mut last = first.clone();
122 for element in first.siblings_with_tokens(Direction::Next) {
123 let node = match element {
124 NodeOrToken::Token(token) => {
125 if let Some(ws) = ast::Whitespace::cast(token) {
126 if !ws.spans_multiple_lines() {
127 // Ignore whitespace without blank lines
128 continue;
129 }
130 }
131 // There is a blank line or another token, which means that the
132 // group ends here
133 break;
134 }
135 NodeOrToken::Node(node) => node,
136 };
137
138 // Stop if we find a node that doesn't belong to the group
139 if node.kind() != first.kind() || unless(&node) {
140 break;
141 }
142
143 visited.insert(node.clone());
144 last = node;
145 }
146
147 if first != &last {
148 Some(TextRange::new(first.text_range().start(), last.text_range().end()))
149 } else {
150 // The group consists of only one element, therefore it cannot be folded
151 None
152 }
153}
154
155fn contiguous_range_for_comment(
156 first: ast::Comment,
157 visited: &mut FxHashSet<ast::Comment>,
158) -> Option<TextRange> {
159 visited.insert(first.clone());
160
161 // Only fold comments of the same flavor
162 let group_kind = first.kind();
163 if !group_kind.shape.is_line() {
164 return None;
165 }
166
167 let mut last = first.clone();
168 for element in first.syntax().siblings_with_tokens(Direction::Next) {
169 match element {
170 NodeOrToken::Token(token) => {
171 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
172 if !ws.spans_multiple_lines() {
173 // Ignore whitespace without blank lines
174 continue;
175 }
176 }
177 if let Some(c) = ast::Comment::cast(token) {
178 if c.kind() == group_kind {
179 visited.insert(c.clone());
180 last = c;
181 continue;
182 }
183 }
184 // The comment group ends because either:
185 // * An element of a different kind was reached
186 // * A comment of a different flavor was reached
187 break;
188 }
189 NodeOrToken::Node(_) => break,
190 };
191 }
192
193 if first != last {
194 Some(TextRange::new(first.syntax().text_range().start(), last.syntax().text_range().end()))
195 } else {
196 // The group consists of only one element, therefore it cannot be folded
197 None
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use test_utils::extract_tags;
204
205 use super::*;
206
207 fn check(ra_fixture: &str) {
208 let (ranges, text) = extract_tags(ra_fixture, "fold");
209
210 let parse = SourceFile::parse(&text);
211 let folds = folding_ranges(&parse.tree());
212 assert_eq!(
213 folds.len(),
214 ranges.len(),
215 "The amount of folds is different than the expected amount"
216 );
217
218 for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) {
219 assert_eq!(fold.range.start(), range.start());
220 assert_eq!(fold.range.end(), range.end());
221
222 let kind = match fold.kind {
223 FoldKind::Comment => "comment",
224 FoldKind::Imports => "imports",
225 FoldKind::Mods => "mods",
226 FoldKind::Block => "block",
227 FoldKind::ArgList => "arglist",
228 };
229 assert_eq!(kind, &attr.unwrap());
230 }
231 }
232
233 #[test]
234 fn test_fold_comments() {
235 check(
236 r#"
237<fold comment>// Hello
238// this is a multiline
239// comment
240//</fold>
241
242// But this is not
243
244fn main() <fold block>{
245 <fold comment>// We should
246 // also
247 // fold
248 // this one.</fold>
249 <fold comment>//! But this one is different
250 //! because it has another flavor</fold>
251 <fold comment>/* As does this
252 multiline comment */</fold>
253}</fold>"#,
254 );
255 }
256
257 #[test]
258 fn test_fold_imports() {
259 check(
260 r#"
261<fold imports>use std::<fold block>{
262 str,
263 vec,
264 io as iop
265}</fold>;</fold>
266
267fn main() <fold block>{
268}</fold>"#,
269 );
270 }
271
272 #[test]
273 fn test_fold_mods() {
274 check(
275 r#"
276
277pub mod foo;
278<fold mods>mod after_pub;
279mod after_pub_next;</fold>
280
281<fold mods>mod before_pub;
282mod before_pub_next;</fold>
283pub mod bar;
284
285mod not_folding_single;
286pub mod foobar;
287pub not_folding_single_next;
288
289<fold mods>#[cfg(test)]
290mod with_attribute;
291mod with_attribute_next;</fold>
292
293fn main() <fold block>{
294}</fold>"#,
295 );
296 }
297
298 #[test]
299 fn test_fold_import_groups() {
300 check(
301 r#"
302<fold imports>use std::str;
303use std::vec;
304use std::io as iop;</fold>
305
306<fold imports>use std::mem;
307use std::f64;</fold>
308
309use std::collections::HashMap;
310// Some random comment
311use std::collections::VecDeque;
312
313fn main() <fold block>{
314}</fold>"#,
315 );
316 }
317
318 #[test]
319 fn test_fold_import_and_groups() {
320 check(
321 r#"
322<fold imports>use std::str;
323use std::vec;
324use std::io as iop;</fold>
325
326<fold imports>use std::mem;
327use std::f64;</fold>
328
329<fold imports>use std::collections::<fold block>{
330 HashMap,
331 VecDeque,
332}</fold>;</fold>
333// Some random comment
334
335fn main() <fold block>{
336}</fold>"#,
337 );
338 }
339
340 #[test]
341 fn test_folds_structs() {
342 check(
343 r#"
344struct Foo <fold block>{
345}</fold>
346"#,
347 );
348 }
349
350 #[test]
351 fn test_folds_traits() {
352 check(
353 r#"
354trait Foo <fold block>{
355}</fold>
356"#,
357 );
358 }
359
360 #[test]
361 fn test_folds_macros() {
362 check(
363 r#"
364macro_rules! foo <fold block>{
365 ($($tt:tt)*) => { $($tt)* }
366}</fold>
367"#,
368 );
369 }
370
371 #[test]
372 fn test_fold_match_arms() {
373 check(
374 r#"
375fn main() <fold block>{
376 match 0 <fold block>{
377 0 => 0,
378 _ => 1,
379 }</fold>
380}</fold>
381"#,
382 );
383 }
384
385 #[test]
386 fn fold_big_calls() {
387 check(
388 r#"
389fn main() <fold block>{
390 frobnicate<fold arglist>(
391 1,
392 2,
393 3,
394 )</fold>
395}</fold>
396"#,
397 )
398 }
399
400 #[test]
401 fn fold_record_literals() {
402 check(
403 r#"
404const _: S = S <fold block>{
405
406}</fold>;
407"#,
408 )
409 }
410
411 #[test]
412 fn fold_multiline_params() {
413 check(
414 r#"
415fn foo<fold arglist>(
416 x: i32,
417 y: String,
418)</fold> {}
419"#,
420 )
421 }
422}
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
new file mode 100644
index 000000000..15e9b7fad
--- /dev/null
+++ b/crates/ide/src/goto_definition.rs
@@ -0,0 +1,989 @@
1use hir::Semantics;
2use ide_db::{
3 defs::{classify_name, classify_name_ref},
4 symbol_index, RootDatabase,
5};
6use syntax::{
7 ast::{self},
8 match_ast, AstNode,
9 SyntaxKind::*,
10 SyntaxToken, TokenAtOffset, T,
11};
12
13use crate::{
14 display::{ToNav, TryToNav},
15 FilePosition, NavigationTarget, RangeInfo,
16};
17
18// Feature: Go to Definition
19//
20// Navigates to the definition of an identifier.
21//
22// |===
23// | Editor | Shortcut
24//
25// | VS Code | kbd:[F12]
26// |===
27pub(crate) fn goto_definition(
28 db: &RootDatabase,
29 position: FilePosition,
30) -> Option<RangeInfo<Vec<NavigationTarget>>> {
31 let sema = Semantics::new(db);
32 let file = sema.parse(position.file_id).syntax().clone();
33 let original_token = pick_best(file.token_at_offset(position.offset))?;
34 let token = sema.descend_into_macros(original_token.clone());
35 let parent = token.parent();
36
37 let nav_targets = match_ast! {
38 match parent {
39 ast::NameRef(name_ref) => {
40 reference_definition(&sema, &name_ref).to_vec()
41 },
42 ast::Name(name) => {
43 let def = classify_name(&sema, &name)?.definition(sema.db);
44 let nav = def.try_to_nav(sema.db)?;
45 vec![nav]
46 },
47 _ => return None,
48 }
49 };
50
51 Some(RangeInfo::new(original_token.text_range(), nav_targets))
52}
53
54fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
55 return tokens.max_by_key(priority);
56 fn priority(n: &SyntaxToken) -> usize {
57 match n.kind() {
58 IDENT | INT_NUMBER | T![self] => 2,
59 kind if kind.is_trivia() => 0,
60 _ => 1,
61 }
62 }
63}
64
65#[derive(Debug)]
66pub(crate) enum ReferenceResult {
67 Exact(NavigationTarget),
68 Approximate(Vec<NavigationTarget>),
69}
70
71impl ReferenceResult {
72 fn to_vec(self) -> Vec<NavigationTarget> {
73 match self {
74 ReferenceResult::Exact(target) => vec![target],
75 ReferenceResult::Approximate(vec) => vec,
76 }
77 }
78}
79
80pub(crate) fn reference_definition(
81 sema: &Semantics<RootDatabase>,
82 name_ref: &ast::NameRef,
83) -> ReferenceResult {
84 let name_kind = classify_name_ref(sema, name_ref);
85 if let Some(def) = name_kind {
86 let def = def.definition(sema.db);
87 return match def.try_to_nav(sema.db) {
88 Some(nav) => ReferenceResult::Exact(nav),
89 None => ReferenceResult::Approximate(Vec::new()),
90 };
91 }
92
93 // Fallback index based approach:
94 let navs = symbol_index::index_resolve(sema.db, name_ref)
95 .into_iter()
96 .map(|s| s.to_nav(sema.db))
97 .collect();
98 ReferenceResult::Approximate(navs)
99}
100
101#[cfg(test)]
102mod tests {
103 use base_db::FileRange;
104 use syntax::{TextRange, TextSize};
105
106 use crate::mock_analysis::MockAnalysis;
107
108 fn check(ra_fixture: &str) {
109 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
110 let (mut expected, data) = mock.annotation();
111 let analysis = mock.analysis();
112 match data.as_str() {
113 "" => (),
114 "file" => {
115 expected.range =
116 TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap()))
117 }
118 data => panic!("bad data: {}", data),
119 }
120
121 let mut navs =
122 analysis.goto_definition(position).unwrap().expect("no definition found").info;
123 if navs.len() == 0 {
124 panic!("unresolved reference")
125 }
126 assert_eq!(navs.len(), 1);
127
128 let nav = navs.pop().unwrap();
129 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() });
130 }
131
132 #[test]
133 fn goto_def_for_extern_crate() {
134 check(
135 r#"
136 //- /main.rs
137 extern crate std<|>;
138 //- /std/lib.rs
139 // empty
140 //^ file
141 "#,
142 )
143 }
144
145 #[test]
146 fn goto_def_for_renamed_extern_crate() {
147 check(
148 r#"
149 //- /main.rs
150 extern crate std as abc<|>;
151 //- /std/lib.rs
152 // empty
153 //^ file
154 "#,
155 )
156 }
157
158 #[test]
159 fn goto_def_in_items() {
160 check(
161 r#"
162struct Foo;
163 //^^^
164enum E { X(Foo<|>) }
165"#,
166 );
167 }
168
169 #[test]
170 fn goto_def_at_start_of_item() {
171 check(
172 r#"
173struct Foo;
174 //^^^
175enum E { X(<|>Foo) }
176"#,
177 );
178 }
179
180 #[test]
181 fn goto_definition_resolves_correct_name() {
182 check(
183 r#"
184//- /lib.rs
185use a::Foo;
186mod a;
187mod b;
188enum E { X(Foo<|>) }
189
190//- /a.rs
191struct Foo;
192 //^^^
193//- /b.rs
194struct Foo;
195"#,
196 );
197 }
198
199 #[test]
200 fn goto_def_for_module_declaration() {
201 check(
202 r#"
203//- /lib.rs
204mod <|>foo;
205
206//- /foo.rs
207// empty
208//^ file
209"#,
210 );
211
212 check(
213 r#"
214//- /lib.rs
215mod <|>foo;
216
217//- /foo/mod.rs
218// empty
219//^ file
220"#,
221 );
222 }
223
224 #[test]
225 fn goto_def_for_macros() {
226 check(
227 r#"
228macro_rules! foo { () => { () } }
229 //^^^
230fn bar() {
231 <|>foo!();
232}
233"#,
234 );
235 }
236
237 #[test]
238 fn goto_def_for_macros_from_other_crates() {
239 check(
240 r#"
241//- /lib.rs
242use foo::foo;
243fn bar() {
244 <|>foo!();
245}
246
247//- /foo/lib.rs
248#[macro_export]
249macro_rules! foo { () => { () } }
250 //^^^
251"#,
252 );
253 }
254
255 #[test]
256 fn goto_def_for_macros_in_use_tree() {
257 check(
258 r#"
259//- /lib.rs
260use foo::foo<|>;
261
262//- /foo/lib.rs
263#[macro_export]
264macro_rules! foo { () => { () } }
265 //^^^
266"#,
267 );
268 }
269
270 #[test]
271 fn goto_def_for_macro_defined_fn_with_arg() {
272 check(
273 r#"
274//- /lib.rs
275macro_rules! define_fn {
276 ($name:ident) => (fn $name() {})
277}
278
279define_fn!(foo);
280 //^^^
281
282fn bar() {
283 <|>foo();
284}
285"#,
286 );
287 }
288
289 #[test]
290 fn goto_def_for_macro_defined_fn_no_arg() {
291 check(
292 r#"
293//- /lib.rs
294macro_rules! define_fn {
295 () => (fn foo() {})
296}
297
298 define_fn!();
299//^^^^^^^^^^^^^
300
301fn bar() {
302 <|>foo();
303}
304"#,
305 );
306 }
307
308 #[test]
309 fn goto_definition_works_for_macro_inside_pattern() {
310 check(
311 r#"
312//- /lib.rs
313macro_rules! foo {() => {0}}
314 //^^^
315
316fn bar() {
317 match (0,1) {
318 (<|>foo!(), _) => {}
319 }
320}
321"#,
322 );
323 }
324
325 #[test]
326 fn goto_definition_works_for_macro_inside_match_arm_lhs() {
327 check(
328 r#"
329//- /lib.rs
330macro_rules! foo {() => {0}}
331 //^^^
332fn bar() {
333 match 0 {
334 <|>foo!() => {}
335 }
336}
337"#,
338 );
339 }
340
341 #[test]
342 fn goto_def_for_use_alias() {
343 check(
344 r#"
345//- /lib.rs
346use foo as bar<|>;
347
348//- /foo/lib.rs
349// empty
350//^ file
351"#,
352 );
353 }
354
355 #[test]
356 fn goto_def_for_use_alias_foo_macro() {
357 check(
358 r#"
359//- /lib.rs
360use foo::foo as bar<|>;
361
362//- /foo/lib.rs
363#[macro_export]
364macro_rules! foo { () => { () } }
365 //^^^
366"#,
367 );
368 }
369
370 #[test]
371 fn goto_def_for_methods() {
372 check(
373 r#"
374//- /lib.rs
375struct Foo;
376impl Foo {
377 fn frobnicate(&self) { }
378 //^^^^^^^^^^
379}
380
381fn bar(foo: &Foo) {
382 foo.frobnicate<|>();
383}
384"#,
385 );
386 }
387
388 #[test]
389 fn goto_def_for_fields() {
390 check(
391 r#"
392struct Foo {
393 spam: u32,
394} //^^^^
395
396fn bar(foo: &Foo) {
397 foo.spam<|>;
398}
399"#,
400 );
401 }
402
403 #[test]
404 fn goto_def_for_record_fields() {
405 check(
406 r#"
407//- /lib.rs
408struct Foo {
409 spam: u32,
410} //^^^^
411
412fn bar() -> Foo {
413 Foo {
414 spam<|>: 0,
415 }
416}
417"#,
418 );
419 }
420
421 #[test]
422 fn goto_def_for_record_pat_fields() {
423 check(
424 r#"
425//- /lib.rs
426struct Foo {
427 spam: u32,
428} //^^^^
429
430fn bar(foo: Foo) -> Foo {
431 let Foo { spam<|>: _, } = foo
432}
433"#,
434 );
435 }
436
437 #[test]
438 fn goto_def_for_record_fields_macros() {
439 check(
440 r"
441macro_rules! m { () => { 92 };}
442struct Foo { spam: u32 }
443 //^^^^
444
445fn bar() -> Foo {
446 Foo { spam<|>: m!() }
447}
448",
449 );
450 }
451
452 #[test]
453 fn goto_for_tuple_fields() {
454 check(
455 r#"
456struct Foo(u32);
457 //^^^
458
459fn bar() {
460 let foo = Foo(0);
461 foo.<|>0;
462}
463"#,
464 );
465 }
466
467 #[test]
468 fn goto_def_for_ufcs_inherent_methods() {
469 check(
470 r#"
471struct Foo;
472impl Foo {
473 fn frobnicate() { }
474} //^^^^^^^^^^
475
476fn bar(foo: &Foo) {
477 Foo::frobnicate<|>();
478}
479"#,
480 );
481 }
482
483 #[test]
484 fn goto_def_for_ufcs_trait_methods_through_traits() {
485 check(
486 r#"
487trait Foo {
488 fn frobnicate();
489} //^^^^^^^^^^
490
491fn bar() {
492 Foo::frobnicate<|>();
493}
494"#,
495 );
496 }
497
498 #[test]
499 fn goto_def_for_ufcs_trait_methods_through_self() {
500 check(
501 r#"
502struct Foo;
503trait Trait {
504 fn frobnicate();
505} //^^^^^^^^^^
506impl Trait for Foo {}
507
508fn bar() {
509 Foo::frobnicate<|>();
510}
511"#,
512 );
513 }
514
515 #[test]
516 fn goto_definition_on_self() {
517 check(
518 r#"
519struct Foo;
520impl Foo {
521 //^^^
522 pub fn new() -> Self {
523 Self<|> {}
524 }
525}
526"#,
527 );
528 check(
529 r#"
530struct Foo;
531impl Foo {
532 //^^^
533 pub fn new() -> Self<|> {
534 Self {}
535 }
536}
537"#,
538 );
539
540 check(
541 r#"
542enum Foo { A }
543impl Foo {
544 //^^^
545 pub fn new() -> Self<|> {
546 Foo::A
547 }
548}
549"#,
550 );
551
552 check(
553 r#"
554enum Foo { A }
555impl Foo {
556 //^^^
557 pub fn thing(a: &Self<|>) {
558 }
559}
560"#,
561 );
562 }
563
564 #[test]
565 fn goto_definition_on_self_in_trait_impl() {
566 check(
567 r#"
568struct Foo;
569trait Make {
570 fn new() -> Self;
571}
572impl Make for Foo {
573 //^^^
574 fn new() -> Self {
575 Self<|> {}
576 }
577}
578"#,
579 );
580
581 check(
582 r#"
583struct Foo;
584trait Make {
585 fn new() -> Self;
586}
587impl Make for Foo {
588 //^^^
589 fn new() -> Self<|> {
590 Self {}
591 }
592}
593"#,
594 );
595 }
596
597 #[test]
598 fn goto_def_when_used_on_definition_name_itself() {
599 check(
600 r#"
601struct Foo<|> { value: u32 }
602 //^^^
603 "#,
604 );
605
606 check(
607 r#"
608struct Foo {
609 field<|>: string,
610} //^^^^^
611"#,
612 );
613
614 check(
615 r#"
616fn foo_test<|>() { }
617 //^^^^^^^^
618"#,
619 );
620
621 check(
622 r#"
623enum Foo<|> { Variant }
624 //^^^
625"#,
626 );
627
628 check(
629 r#"
630enum Foo {
631 Variant1,
632 Variant2<|>,
633 //^^^^^^^^
634 Variant3,
635}
636"#,
637 );
638
639 check(
640 r#"
641static INNER<|>: &str = "";
642 //^^^^^
643"#,
644 );
645
646 check(
647 r#"
648const INNER<|>: &str = "";
649 //^^^^^
650"#,
651 );
652
653 check(
654 r#"
655type Thing<|> = Option<()>;
656 //^^^^^
657"#,
658 );
659
660 check(
661 r#"
662trait Foo<|> { }
663 //^^^
664"#,
665 );
666
667 check(
668 r#"
669mod bar<|> { }
670 //^^^
671"#,
672 );
673 }
674
675 #[test]
676 fn goto_from_macro() {
677 check(
678 r#"
679macro_rules! id {
680 ($($tt:tt)*) => { $($tt)* }
681}
682fn foo() {}
683 //^^^
684id! {
685 fn bar() {
686 fo<|>o();
687 }
688}
689mod confuse_index { fn foo(); }
690"#,
691 );
692 }
693
694 #[test]
695 fn goto_through_format() {
696 check(
697 r#"
698#[macro_export]
699macro_rules! format {
700 ($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
701}
702#[rustc_builtin_macro]
703#[macro_export]
704macro_rules! format_args {
705 ($fmt:expr) => ({ /* compiler built-in */ });
706 ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
707}
708pub mod __export {
709 pub use crate::format_args;
710 fn foo() {} // for index confusion
711}
712fn foo() -> i8 {}
713 //^^^
714fn test() {
715 format!("{}", fo<|>o())
716}
717"#,
718 );
719 }
720
721 #[test]
722 fn goto_for_type_param() {
723 check(
724 r#"
725struct Foo<T: Clone> { t: <|>T }
726 //^
727"#,
728 );
729 }
730
731 #[test]
732 fn goto_within_macro() {
733 check(
734 r#"
735macro_rules! id {
736 ($($tt:tt)*) => ($($tt)*)
737}
738
739fn foo() {
740 let x = 1;
741 //^
742 id!({
743 let y = <|>x;
744 let z = y;
745 });
746}
747"#,
748 );
749
750 check(
751 r#"
752macro_rules! id {
753 ($($tt:tt)*) => ($($tt)*)
754}
755
756fn foo() {
757 let x = 1;
758 id!({
759 let y = x;
760 //^
761 let z = <|>y;
762 });
763}
764"#,
765 );
766 }
767
768 #[test]
769 fn goto_def_in_local_fn() {
770 check(
771 r#"
772fn main() {
773 fn foo() {
774 let x = 92;
775 //^
776 <|>x;
777 }
778}
779"#,
780 );
781 }
782
783 #[test]
784 fn goto_def_in_local_macro() {
785 check(
786 r#"
787fn bar() {
788 macro_rules! foo { () => { () } }
789 //^^^
790 <|>foo!();
791}
792"#,
793 );
794 }
795
796 #[test]
797 fn goto_def_for_field_init_shorthand() {
798 check(
799 r#"
800struct Foo { x: i32 }
801fn main() {
802 let x = 92;
803 //^
804 Foo { x<|> };
805}
806"#,
807 )
808 }
809
810 #[test]
811 fn goto_def_for_enum_variant_field() {
812 check(
813 r#"
814enum Foo {
815 Bar { x: i32 }
816} //^
817fn baz(foo: Foo) {
818 match foo {
819 Foo::Bar { x<|> } => x
820 };
821}
822"#,
823 );
824 }
825
826 #[test]
827 fn goto_def_for_enum_variant_self_pattern_const() {
828 check(
829 r#"
830enum Foo { Bar }
831 //^^^
832impl Foo {
833 fn baz(self) {
834 match self { Self::Bar<|> => {} }
835 }
836}
837"#,
838 );
839 }
840
841 #[test]
842 fn goto_def_for_enum_variant_self_pattern_record() {
843 check(
844 r#"
845enum Foo { Bar { val: i32 } }
846 //^^^
847impl Foo {
848 fn baz(self) -> i32 {
849 match self { Self::Bar<|> { val } => {} }
850 }
851}
852"#,
853 );
854 }
855
856 #[test]
857 fn goto_def_for_enum_variant_self_expr_const() {
858 check(
859 r#"
860enum Foo { Bar }
861 //^^^
862impl Foo {
863 fn baz(self) { Self::Bar<|>; }
864}
865"#,
866 );
867 }
868
869 #[test]
870 fn goto_def_for_enum_variant_self_expr_record() {
871 check(
872 r#"
873enum Foo { Bar { val: i32 } }
874 //^^^
875impl Foo {
876 fn baz(self) { Self::Bar<|> {val: 4}; }
877}
878"#,
879 );
880 }
881
882 #[test]
883 fn goto_def_for_type_alias_generic_parameter() {
884 check(
885 r#"
886type Alias<T> = T<|>;
887 //^
888"#,
889 )
890 }
891
892 #[test]
893 fn goto_def_for_macro_container() {
894 check(
895 r#"
896//- /lib.rs
897foo::module<|>::mac!();
898
899//- /foo/lib.rs
900pub mod module {
901 //^^^^^^
902 #[macro_export]
903 macro_rules! _mac { () => { () } }
904 pub use crate::_mac as mac;
905}
906"#,
907 );
908 }
909
910 #[test]
911 fn goto_def_for_assoc_ty_in_path() {
912 check(
913 r#"
914trait Iterator {
915 type Item;
916 //^^^^
917}
918
919fn f() -> impl Iterator<Item<|> = u8> {}
920"#,
921 );
922 }
923
924 #[test]
925 fn goto_def_for_assoc_ty_in_path_multiple() {
926 check(
927 r#"
928trait Iterator {
929 type A;
930 //^
931 type B;
932}
933
934fn f() -> impl Iterator<A<|> = u8, B = ()> {}
935"#,
936 );
937 check(
938 r#"
939trait Iterator {
940 type A;
941 type B;
942 //^
943}
944
945fn f() -> impl Iterator<A = u8, B<|> = ()> {}
946"#,
947 );
948 }
949
950 #[test]
951 fn goto_def_for_assoc_ty_ufcs() {
952 check(
953 r#"
954trait Iterator {
955 type Item;
956 //^^^^
957}
958
959fn g() -> <() as Iterator<Item<|> = ()>>::Item {}
960"#,
961 );
962 }
963
964 #[test]
965 fn goto_def_for_assoc_ty_ufcs_multiple() {
966 check(
967 r#"
968trait Iterator {
969 type A;
970 //^
971 type B;
972}
973
974fn g() -> <() as Iterator<A<|> = (), B = u8>>::B {}
975"#,
976 );
977 check(
978 r#"
979trait Iterator {
980 type A;
981 type B;
982 //^
983}
984
985fn g() -> <() as Iterator<A = (), B<|> = u8>>::A {}
986"#,
987 );
988 }
989}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
new file mode 100644
index 000000000..f503f4ec5
--- /dev/null
+++ b/crates/ide/src/goto_implementation.rs
@@ -0,0 +1,229 @@
1use hir::{Crate, ImplDef, Semantics};
2use ide_db::RootDatabase;
3use syntax::{algo::find_node_at_offset, ast, AstNode};
4
5use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
6
7// Feature: Go to Implementation
8//
9// Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
10//
11// |===
12// | Editor | Shortcut
13//
14// | VS Code | kbd:[Ctrl+F12]
15// |===
16pub(crate) fn goto_implementation(
17 db: &RootDatabase,
18 position: FilePosition,
19) -> Option<RangeInfo<Vec<NavigationTarget>>> {
20 let sema = Semantics::new(db);
21 let source_file = sema.parse(position.file_id);
22 let syntax = source_file.syntax().clone();
23
24 let krate = sema.to_module_def(position.file_id)?.krate();
25
26 if let Some(nominal_def) = find_node_at_offset::<ast::AdtDef>(&syntax, position.offset) {
27 return Some(RangeInfo::new(
28 nominal_def.syntax().text_range(),
29 impls_for_def(&sema, &nominal_def, krate)?,
30 ));
31 } else if let Some(trait_def) = find_node_at_offset::<ast::Trait>(&syntax, position.offset) {
32 return Some(RangeInfo::new(
33 trait_def.syntax().text_range(),
34 impls_for_trait(&sema, &trait_def, krate)?,
35 ));
36 }
37
38 None
39}
40
41fn impls_for_def(
42 sema: &Semantics<RootDatabase>,
43 node: &ast::AdtDef,
44 krate: Crate,
45) -> Option<Vec<NavigationTarget>> {
46 let ty = match node {
47 ast::AdtDef::Struct(def) => sema.to_def(def)?.ty(sema.db),
48 ast::AdtDef::Enum(def) => sema.to_def(def)?.ty(sema.db),
49 ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db),
50 };
51
52 let impls = ImplDef::all_in_crate(sema.db, krate);
53
54 Some(
55 impls
56 .into_iter()
57 .filter(|impl_def| ty.is_equal_for_find_impls(&impl_def.target_ty(sema.db)))
58 .map(|imp| imp.to_nav(sema.db))
59 .collect(),
60 )
61}
62
63fn impls_for_trait(
64 sema: &Semantics<RootDatabase>,
65 node: &ast::Trait,
66 krate: Crate,
67) -> Option<Vec<NavigationTarget>> {
68 let tr = sema.to_def(node)?;
69
70 let impls = ImplDef::for_trait(sema.db, krate, tr);
71
72 Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect())
73}
74
75#[cfg(test)]
76mod tests {
77 use base_db::FileRange;
78
79 use crate::mock_analysis::MockAnalysis;
80
81 fn check(ra_fixture: &str) {
82 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
83 let annotations = mock.annotations();
84 let analysis = mock.analysis();
85
86 let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
87
88 let key = |frange: &FileRange| (frange.file_id, frange.range.start());
89
90 let mut expected = annotations
91 .into_iter()
92 .map(|(range, data)| {
93 assert!(data.is_empty());
94 range
95 })
96 .collect::<Vec<_>>();
97 expected.sort_by_key(key);
98
99 let mut actual = navs
100 .into_iter()
101 .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
102 .collect::<Vec<_>>();
103 actual.sort_by_key(key);
104
105 assert_eq!(expected, actual);
106 }
107
108 #[test]
109 fn goto_implementation_works() {
110 check(
111 r#"
112struct Foo<|>;
113impl Foo {}
114 //^^^
115"#,
116 );
117 }
118
119 #[test]
120 fn goto_implementation_works_multiple_blocks() {
121 check(
122 r#"
123struct Foo<|>;
124impl Foo {}
125 //^^^
126impl Foo {}
127 //^^^
128"#,
129 );
130 }
131
132 #[test]
133 fn goto_implementation_works_multiple_mods() {
134 check(
135 r#"
136struct Foo<|>;
137mod a {
138 impl super::Foo {}
139 //^^^^^^^^^^
140}
141mod b {
142 impl super::Foo {}
143 //^^^^^^^^^^
144}
145"#,
146 );
147 }
148
149 #[test]
150 fn goto_implementation_works_multiple_files() {
151 check(
152 r#"
153//- /lib.rs
154struct Foo<|>;
155mod a;
156mod b;
157//- /a.rs
158impl crate::Foo {}
159 //^^^^^^^^^^
160//- /b.rs
161impl crate::Foo {}
162 //^^^^^^^^^^
163"#,
164 );
165 }
166
167 #[test]
168 fn goto_implementation_for_trait() {
169 check(
170 r#"
171trait T<|> {}
172struct Foo;
173impl T for Foo {}
174 //^^^
175"#,
176 );
177 }
178
179 #[test]
180 fn goto_implementation_for_trait_multiple_files() {
181 check(
182 r#"
183//- /lib.rs
184trait T<|> {};
185struct Foo;
186mod a;
187mod b;
188//- /a.rs
189impl crate::T for crate::Foo {}
190 //^^^^^^^^^^
191//- /b.rs
192impl crate::T for crate::Foo {}
193 //^^^^^^^^^^
194 "#,
195 );
196 }
197
198 #[test]
199 fn goto_implementation_all_impls() {
200 check(
201 r#"
202//- /lib.rs
203trait T {}
204struct Foo<|>;
205impl Foo {}
206 //^^^
207impl T for Foo {}
208 //^^^
209impl T for &Foo {}
210 //^^^^
211"#,
212 );
213 }
214
215 #[test]
216 fn goto_implementation_to_builtin_derive() {
217 check(
218 r#"
219 #[derive(Copy)]
220//^^^^^^^^^^^^^^^
221struct Foo<|>;
222
223mod marker {
224 trait Copy {}
225}
226"#,
227 );
228 }
229}
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
new file mode 100644
index 000000000..4a151b150
--- /dev/null
+++ b/crates/ide/src/goto_type_definition.rs
@@ -0,0 +1,151 @@
1use ide_db::RootDatabase;
2use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
3
4use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
5
6// Feature: Go to Type Definition
7//
8// Navigates to the type of an identifier.
9//
10// |===
11// | Editor | Action Name
12//
13// | VS Code | **Go to Type Definition*
14// |===
15pub(crate) fn goto_type_definition(
16 db: &RootDatabase,
17 position: FilePosition,
18) -> Option<RangeInfo<Vec<NavigationTarget>>> {
19 let sema = hir::Semantics::new(db);
20
21 let file: ast::SourceFile = sema.parse(position.file_id);
22 let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?;
23 let token: SyntaxToken = sema.descend_into_macros(token);
24
25 let (ty, node) = sema.ancestors_with_macros(token.parent()).find_map(|node| {
26 let ty = match_ast! {
27 match node {
28 ast::Expr(it) => sema.type_of_expr(&it)?,
29 ast::Pat(it) => sema.type_of_pat(&it)?,
30 ast::SelfParam(it) => sema.type_of_self(&it)?,
31 _ => return None,
32 }
33 };
34
35 Some((ty, node))
36 })?;
37
38 let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?;
39
40 let nav = adt_def.to_nav(db);
41 Some(RangeInfo::new(node.text_range(), vec![nav]))
42}
43
44fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
45 return tokens.max_by_key(priority);
46 fn priority(n: &SyntaxToken) -> usize {
47 match n.kind() {
48 IDENT | INT_NUMBER | T![self] => 2,
49 kind if kind.is_trivia() => 0,
50 _ => 1,
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use base_db::FileRange;
58
59 use crate::mock_analysis::MockAnalysis;
60
61 fn check(ra_fixture: &str) {
62 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
63 let (expected, data) = mock.annotation();
64 assert!(data.is_empty());
65 let analysis = mock.analysis();
66
67 let mut navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
68 assert_eq!(navs.len(), 1);
69 let nav = navs.pop().unwrap();
70 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() });
71 }
72
73 #[test]
74 fn goto_type_definition_works_simple() {
75 check(
76 r#"
77struct Foo;
78 //^^^
79fn foo() {
80 let f: Foo; f<|>
81}
82"#,
83 );
84 }
85
86 #[test]
87 fn goto_type_definition_works_simple_ref() {
88 check(
89 r#"
90struct Foo;
91 //^^^
92fn foo() {
93 let f: &Foo; f<|>
94}
95"#,
96 );
97 }
98
99 #[test]
100 fn goto_type_definition_works_through_macro() {
101 check(
102 r#"
103macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
104struct Foo {}
105 //^^^
106id! {
107 fn bar() { let f<|> = Foo {}; }
108}
109"#,
110 );
111 }
112
113 #[test]
114 fn goto_type_definition_for_param() {
115 check(
116 r#"
117struct Foo;
118 //^^^
119fn foo(<|>f: Foo) {}
120"#,
121 );
122 }
123
124 #[test]
125 fn goto_type_definition_for_tuple_field() {
126 check(
127 r#"
128struct Foo;
129 //^^^
130struct Bar(Foo);
131fn foo() {
132 let bar = Bar(Foo);
133 bar.<|>0;
134}
135"#,
136 );
137 }
138
139 #[test]
140 fn goto_def_for_self_param() {
141 check(
142 r#"
143struct Foo;
144 //^^^
145impl Foo {
146 fn f(&self<|>) {}
147}
148"#,
149 )
150 }
151}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
new file mode 100644
index 000000000..b012e4900
--- /dev/null
+++ b/crates/ide/src/hover.rs
@@ -0,0 +1,2967 @@
1use base_db::SourceDatabase;
2use hir::{
3 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
4 Module, ModuleDef, ModuleSource, Semantics,
5};
6use ide_db::{
7 defs::{classify_name, classify_name_ref, Definition},
8 RootDatabase,
9};
10use itertools::Itertools;
11use stdx::format_to;
12use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
13use test_utils::mark;
14
15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::rewrite_links,
18 markup::Markup,
19 runnables::runnable,
20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
21};
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct HoverConfig {
25 pub implementations: bool,
26 pub run: bool,
27 pub debug: bool,
28 pub goto_type_def: bool,
29}
30
31impl Default for HoverConfig {
32 fn default() -> Self {
33 Self { implementations: true, run: true, debug: true, goto_type_def: true }
34 }
35}
36
37impl HoverConfig {
38 pub const NO_ACTIONS: Self =
39 Self { implementations: false, run: false, debug: false, goto_type_def: false };
40
41 pub fn any(&self) -> bool {
42 self.implementations || self.runnable() || self.goto_type_def
43 }
44
45 pub fn none(&self) -> bool {
46 !self.any()
47 }
48
49 pub fn runnable(&self) -> bool {
50 self.run || self.debug
51 }
52}
53
54#[derive(Debug, Clone)]
55pub enum HoverAction {
56 Runnable(Runnable),
57 Implementaion(FilePosition),
58 GoToType(Vec<HoverGotoTypeData>),
59}
60
61#[derive(Debug, Clone, Eq, PartialEq)]
62pub struct HoverGotoTypeData {
63 pub mod_path: String,
64 pub nav: NavigationTarget,
65}
66
67/// Contains the results when hovering over an item
68#[derive(Debug, Default)]
69pub struct HoverResult {
70 pub markup: Markup,
71 pub actions: Vec<HoverAction>,
72}
73
74// Feature: Hover
75//
76// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
77// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
78pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
79 let sema = Semantics::new(db);
80 let file = sema.parse(position.file_id).syntax().clone();
81 let token = pick_best(file.token_at_offset(position.offset))?;
82 let token = sema.descend_into_macros(token);
83
84 let mut res = HoverResult::default();
85
86 let node = token.parent();
87 let definition = match_ast! {
88 match node {
89 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
90 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
91 _ => None,
92 }
93 };
94 if let Some(definition) = definition {
95 if let Some(markup) = hover_for_definition(db, definition) {
96 let markup = rewrite_links(db, &markup.as_str(), &definition);
97 res.markup = Markup::from(markup);
98 if let Some(action) = show_implementations_action(db, definition) {
99 res.actions.push(action);
100 }
101
102 if let Some(action) = runnable_action(&sema, definition, position.file_id) {
103 res.actions.push(action);
104 }
105
106 if let Some(action) = goto_type_action(db, definition) {
107 res.actions.push(action);
108 }
109
110 let range = sema.original_range(&node).range;
111 return Some(RangeInfo::new(range, res));
112 }
113 }
114
115 let node = token
116 .ancestors()
117 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
118
119 let ty = match_ast! {
120 match node {
121 ast::Expr(it) => sema.type_of_expr(&it)?,
122 ast::Pat(it) => sema.type_of_pat(&it)?,
123 // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
124 // (e.g expanding a builtin macro). So we give up here.
125 ast::MacroCall(_it) => return None,
126 _ => return None,
127 }
128 };
129
130 res.markup = Markup::fenced_block(&ty.display(db));
131 let range = sema.original_range(&node).range;
132 Some(RangeInfo::new(range, res))
133}
134
135fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
136 fn to_action(nav_target: NavigationTarget) -> HoverAction {
137 HoverAction::Implementaion(FilePosition {
138 file_id: nav_target.file_id,
139 offset: nav_target.focus_or_full_range().start(),
140 })
141 }
142
143 match def {
144 Definition::ModuleDef(it) => match it {
145 ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
146 ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
147 ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
148 ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
149 _ => None,
150 },
151 _ => None,
152 }
153}
154
155fn runnable_action(
156 sema: &Semantics<RootDatabase>,
157 def: Definition,
158 file_id: FileId,
159) -> Option<HoverAction> {
160 match def {
161 Definition::ModuleDef(it) => match it {
162 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
163 ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
164 .map(|it| HoverAction::Runnable(it)),
165 _ => None,
166 },
167 ModuleDef::Function(it) => {
168 let src = it.source(sema.db);
169 if src.file_id != file_id.into() {
170 mark::hit!(hover_macro_generated_struct_fn_doc_comment);
171 mark::hit!(hover_macro_generated_struct_fn_doc_attr);
172
173 return None;
174 }
175
176 runnable(&sema, src.value.syntax().clone(), file_id)
177 .map(|it| HoverAction::Runnable(it))
178 }
179 _ => None,
180 },
181 _ => None,
182 }
183}
184
185fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
186 match def {
187 Definition::Local(it) => {
188 let mut targets: Vec<ModuleDef> = Vec::new();
189 let mut push_new_def = |item: ModuleDef| {
190 if !targets.contains(&item) {
191 targets.push(item);
192 }
193 };
194
195 it.ty(db).walk(db, |t| {
196 if let Some(adt) = t.as_adt() {
197 push_new_def(adt.into());
198 } else if let Some(trait_) = t.as_dyn_trait() {
199 push_new_def(trait_.into());
200 } else if let Some(traits) = t.as_impl_traits(db) {
201 traits.into_iter().for_each(|it| push_new_def(it.into()));
202 } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
203 push_new_def(trait_.into());
204 }
205 });
206
207 let targets = targets
208 .into_iter()
209 .filter_map(|it| {
210 Some(HoverGotoTypeData {
211 mod_path: render_path(
212 db,
213 it.module(db)?,
214 it.name(db).map(|name| name.to_string()),
215 ),
216 nav: it.try_to_nav(db)?,
217 })
218 })
219 .collect();
220
221 Some(HoverAction::GoToType(targets))
222 }
223 _ => None,
224 }
225}
226
227fn hover_markup(
228 docs: Option<String>,
229 desc: Option<String>,
230 mod_path: Option<String>,
231) -> Option<Markup> {
232 match desc {
233 Some(desc) => {
234 let mut buf = String::new();
235
236 if let Some(mod_path) = mod_path {
237 if !mod_path.is_empty() {
238 format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
239 }
240 }
241 format_to!(buf, "```rust\n{}\n```", desc);
242
243 if let Some(doc) = docs {
244 format_to!(buf, "\n___\n\n{}", doc);
245 }
246 Some(buf.into())
247 }
248 None => docs.map(Markup::from),
249 }
250}
251
252fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
253 match def {
254 Definition::Field(f) => Some(f.parent_def(db).name(db)),
255 Definition::Local(l) => l.parent(db).name(db),
256 Definition::ModuleDef(md) => match md {
257 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
258 AssocItemContainer::Trait(t) => Some(t.name(db)),
259 AssocItemContainer::ImplDef(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
260 },
261 ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)),
262 _ => None,
263 },
264 Definition::SelfType(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
265 _ => None,
266 }
267 .map(|name| name.to_string())
268}
269
270fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String {
271 let crate_name =
272 db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string);
273 let module_path = module
274 .path_to_root(db)
275 .into_iter()
276 .rev()
277 .flat_map(|it| it.name(db).map(|name| name.to_string()));
278 crate_name.into_iter().chain(module_path).chain(item_name).join("::")
279}
280
281fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
282 def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
283}
284
285fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
286 let mod_path = definition_mod_path(db, &def);
287 return match def {
288 Definition::Macro(it) => {
289 let src = it.source(db);
290 let docs = Documentation::from_ast(&src.value).map(Into::into);
291 hover_markup(docs, Some(macro_label(&src.value)), mod_path)
292 }
293 Definition::Field(it) => {
294 let src = it.source(db);
295 match src.value {
296 FieldSource::Named(it) => {
297 let docs = Documentation::from_ast(&it).map(Into::into);
298 hover_markup(docs, it.short_label(), mod_path)
299 }
300 _ => None,
301 }
302 }
303 Definition::ModuleDef(it) => match it {
304 ModuleDef::Module(it) => match it.definition_source(db).value {
305 ModuleSource::Module(it) => {
306 let docs = Documentation::from_ast(&it).map(Into::into);
307 hover_markup(docs, it.short_label(), mod_path)
308 }
309 ModuleSource::SourceFile(it) => {
310 let docs = Documentation::from_ast(&it).map(Into::into);
311 hover_markup(docs, it.short_label(), mod_path)
312 }
313 },
314 ModuleDef::Function(it) => from_def_source(db, it, mod_path),
315 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
316 ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path),
317 ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it, mod_path),
318 ModuleDef::EnumVariant(it) => from_def_source(db, it, mod_path),
319 ModuleDef::Const(it) => from_def_source(db, it, mod_path),
320 ModuleDef::Static(it) => from_def_source(db, it, mod_path),
321 ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
322 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
323 ModuleDef::BuiltinType(it) => return Some(it.to_string().into()),
324 },
325 Definition::Local(it) => return Some(Markup::fenced_block(&it.ty(db).display(db))),
326 Definition::TypeParam(_) | Definition::SelfType(_) => {
327 // FIXME: Hover for generic param
328 None
329 }
330 };
331
332 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
333 where
334 D: HasSource<Ast = A>,
335 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
336 {
337 let src = def.source(db);
338 let docs = Documentation::from_ast(&src.value).map(Into::into);
339 hover_markup(docs, src.value.short_label(), mod_path)
340 }
341}
342
343fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
344 return tokens.max_by_key(priority);
345 fn priority(n: &SyntaxToken) -> usize {
346 match n.kind() {
347 IDENT | INT_NUMBER => 3,
348 T!['('] | T![')'] => 2,
349 kind if kind.is_trivia() => 0,
350 _ => 1,
351 }
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use base_db::FileLoader;
358 use expect_test::{expect, Expect};
359
360 use crate::mock_analysis::analysis_and_position;
361
362 use super::*;
363
364 fn check_hover_no_result(ra_fixture: &str) {
365 let (analysis, position) = analysis_and_position(ra_fixture);
366 assert!(analysis.hover(position).unwrap().is_none());
367 }
368
369 fn check(ra_fixture: &str, expect: Expect) {
370 let (analysis, position) = analysis_and_position(ra_fixture);
371 let hover = analysis.hover(position).unwrap().unwrap();
372
373 let content = analysis.db.file_text(position.file_id);
374 let hovered_element = &content[hover.range];
375
376 let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
377 expect.assert_eq(&actual)
378 }
379
380 fn check_actions(ra_fixture: &str, expect: Expect) {
381 let (analysis, position) = analysis_and_position(ra_fixture);
382 let hover = analysis.hover(position).unwrap().unwrap();
383 expect.assert_debug_eq(&hover.info.actions)
384 }
385
386 #[test]
387 fn hover_shows_type_of_an_expression() {
388 check(
389 r#"
390pub fn foo() -> u32 { 1 }
391
392fn main() {
393 let foo_test = foo()<|>;
394}
395"#,
396 expect![[r#"
397 *foo()*
398 ```rust
399 u32
400 ```
401 "#]],
402 );
403 }
404
405 #[test]
406 fn hover_shows_long_type_of_an_expression() {
407 check(
408 r#"
409struct Scan<A, B, C> { a: A, b: B, c: C }
410struct Iter<I> { inner: I }
411enum Option<T> { Some(T), None }
412
413struct OtherStruct<T> { i: T }
414
415fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
416 Iter { inner: Scan { a, b, c } }
417}
418
419fn main() {
420 let num: i32 = 55;
421 let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
422 Option::Some(*memo + value)
423 };
424 let number = 5u32;
425 let mut iter<|> = scan(OtherStruct { i: num }, closure, number);
426}
427"#,
428 expect![[r#"
429 *iter*
430
431 ```rust
432 Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
433 ```
434 "#]],
435 );
436 }
437
438 #[test]
439 fn hover_shows_fn_signature() {
440 // Single file with result
441 check(
442 r#"
443pub fn foo() -> u32 { 1 }
444
445fn main() { let foo_test = fo<|>o(); }
446"#,
447 expect![[r#"
448 *foo*
449
450 ```rust
451 test
452 ```
453
454 ```rust
455 pub fn foo() -> u32
456 ```
457 "#]],
458 );
459
460 // Multiple candidates but results are ambiguous.
461 check(
462 r#"
463//- /a.rs
464pub fn foo() -> u32 { 1 }
465
466//- /b.rs
467pub fn foo() -> &str { "" }
468
469//- /c.rs
470pub fn foo(a: u32, b: u32) {}
471
472//- /main.rs
473mod a;
474mod b;
475mod c;
476
477fn main() { let foo_test = fo<|>o(); }
478 "#,
479 expect![[r#"
480 *foo*
481 ```rust
482 {unknown}
483 ```
484 "#]],
485 );
486 }
487
488 #[test]
489 fn hover_shows_fn_signature_with_type_params() {
490 check(
491 r#"
492pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
493
494fn main() { let foo_test = fo<|>o(); }
495 "#,
496 expect![[r#"
497 *foo*
498
499 ```rust
500 test
501 ```
502
503 ```rust
504 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str
505 ```
506 "#]],
507 );
508 }
509
510 #[test]
511 fn hover_shows_fn_signature_on_fn_name() {
512 check(
513 r#"
514pub fn foo<|>(a: u32, b: u32) -> u32 {}
515
516fn main() { }
517"#,
518 expect![[r#"
519 *foo*
520
521 ```rust
522 test
523 ```
524
525 ```rust
526 pub fn foo(a: u32, b: u32) -> u32
527 ```
528 "#]],
529 );
530 }
531
532 #[test]
533 fn hover_shows_fn_doc() {
534 check(
535 r#"
536/// # Example
537/// ```
538/// # use std::path::Path;
539/// #
540/// foo(Path::new("hello, world!"))
541/// ```
542pub fn foo<|>(_: &Path) {}
543
544fn main() { }
545"#,
546 expect![[r##"
547 *foo*
548
549 ```rust
550 test
551 ```
552
553 ```rust
554 pub fn foo(_: &Path)
555 ```
556
557 ---
558
559 # Example
560
561 ```
562 # use std::path::Path;
563 #
564 foo(Path::new("hello, world!"))
565 ```
566 "##]],
567 );
568 }
569
570 #[test]
571 fn hover_shows_struct_field_info() {
572 // Hovering over the field when instantiating
573 check(
574 r#"
575struct Foo { field_a: u32 }
576
577fn main() {
578 let foo = Foo { field_a<|>: 0, };
579}
580"#,
581 expect![[r#"
582 *field_a*
583
584 ```rust
585 test::Foo
586 ```
587
588 ```rust
589 field_a: u32
590 ```
591 "#]],
592 );
593
594 // Hovering over the field in the definition
595 check(
596 r#"
597struct Foo { field_a<|>: u32 }
598
599fn main() {
600 let foo = Foo { field_a: 0 };
601}
602"#,
603 expect![[r#"
604 *field_a*
605
606 ```rust
607 test::Foo
608 ```
609
610 ```rust
611 field_a: u32
612 ```
613 "#]],
614 );
615 }
616
617 #[test]
618 fn hover_const_static() {
619 check(
620 r#"const foo<|>: u32 = 123;"#,
621 expect![[r#"
622 *foo*
623
624 ```rust
625 test
626 ```
627
628 ```rust
629 const foo: u32 = 123
630 ```
631 "#]],
632 );
633 check(
634 r#"static foo<|>: u32 = 456;"#,
635 expect![[r#"
636 *foo*
637
638 ```rust
639 test
640 ```
641
642 ```rust
643 static foo: u32
644 ```
645 "#]],
646 );
647 }
648
649 #[test]
650 fn hover_default_generic_types() {
651 check(
652 r#"
653struct Test<K, T = u8> { k: K, t: T }
654
655fn main() {
656 let zz<|> = Test { t: 23u8, k: 33 };
657}"#,
658 expect![[r#"
659 *zz*
660
661 ```rust
662 Test<i32, u8>
663 ```
664 "#]],
665 );
666 }
667
668 #[test]
669 fn hover_some() {
670 check(
671 r#"
672enum Option<T> { Some(T) }
673use Option::Some;
674
675fn main() { So<|>me(12); }
676"#,
677 expect![[r#"
678 *Some*
679
680 ```rust
681 test::Option
682 ```
683
684 ```rust
685 Some
686 ```
687 "#]],
688 );
689
690 check(
691 r#"
692enum Option<T> { Some(T) }
693use Option::Some;
694
695fn main() { let b<|>ar = Some(12); }
696"#,
697 expect![[r#"
698 *bar*
699
700 ```rust
701 Option<i32>
702 ```
703 "#]],
704 );
705 }
706
707 #[test]
708 fn hover_enum_variant() {
709 check(
710 r#"
711enum Option<T> {
712 /// The None variant
713 Non<|>e
714}
715"#,
716 expect![[r#"
717 *None*
718
719 ```rust
720 test::Option
721 ```
722
723 ```rust
724 None
725 ```
726
727 ---
728
729 The None variant
730 "#]],
731 );
732
733 check(
734 r#"
735enum Option<T> {
736 /// The Some variant
737 Some(T)
738}
739fn main() {
740 let s = Option::Som<|>e(12);
741}
742"#,
743 expect![[r#"
744 *Some*
745
746 ```rust
747 test::Option
748 ```
749
750 ```rust
751 Some
752 ```
753
754 ---
755
756 The Some variant
757 "#]],
758 );
759 }
760
761 #[test]
762 fn hover_for_local_variable() {
763 check(
764 r#"fn func(foo: i32) { fo<|>o; }"#,
765 expect![[r#"
766 *foo*
767
768 ```rust
769 i32
770 ```
771 "#]],
772 )
773 }
774
775 #[test]
776 fn hover_for_local_variable_pat() {
777 check(
778 r#"fn func(fo<|>o: i32) {}"#,
779 expect![[r#"
780 *foo*
781
782 ```rust
783 i32
784 ```
785 "#]],
786 )
787 }
788
789 #[test]
790 fn hover_local_var_edge() {
791 check(
792 r#"fn func(foo: i32) { if true { <|>foo; }; }"#,
793 expect![[r#"
794 *foo*
795
796 ```rust
797 i32
798 ```
799 "#]],
800 )
801 }
802
803 #[test]
804 fn hover_for_param_edge() {
805 check(
806 r#"fn func(<|>foo: i32) {}"#,
807 expect![[r#"
808 *foo*
809
810 ```rust
811 i32
812 ```
813 "#]],
814 )
815 }
816
817 #[test]
818 fn hover_for_param_with_multiple_traits() {
819 check(
820 r#"trait Deref {
821 type Target: ?Sized;
822 }
823 trait DerefMut {
824 type Target: ?Sized;
825 }
826 fn f(_x<|>: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
827 expect![[r#"
828 *_x*
829
830 ```rust
831 impl Deref<Target = u8> + DerefMut<Target = u8>
832 ```
833 "#]],
834 )
835 }
836
837 #[test]
838 fn test_hover_infer_associated_method_result() {
839 check(
840 r#"
841struct Thing { x: u32 }
842
843impl Thing {
844 fn new() -> Thing { Thing { x: 0 } }
845}
846
847fn main() { let foo_<|>test = Thing::new(); }
848 "#,
849 expect![[r#"
850 *foo_test*
851
852 ```rust
853 Thing
854 ```
855 "#]],
856 )
857 }
858
859 #[test]
860 fn test_hover_infer_associated_method_exact() {
861 check(
862 r#"
863mod wrapper {
864 struct Thing { x: u32 }
865
866 impl Thing {
867 fn new() -> Thing { Thing { x: 0 } }
868 }
869}
870
871fn main() { let foo_test = wrapper::Thing::new<|>(); }
872"#,
873 expect![[r#"
874 *new*
875
876 ```rust
877 test::wrapper::Thing
878 ```
879
880 ```rust
881 fn new() -> Thing
882 ```
883 "#]],
884 )
885 }
886
887 #[test]
888 fn test_hover_infer_associated_const_in_pattern() {
889 check(
890 r#"
891struct X;
892impl X {
893 const C: u32 = 1;
894}
895
896fn main() {
897 match 1 {
898 X::C<|> => {},
899 2 => {},
900 _ => {}
901 };
902}
903"#,
904 expect![[r#"
905 *C*
906
907 ```rust
908 test
909 ```
910
911 ```rust
912 const C: u32 = 1
913 ```
914 "#]],
915 )
916 }
917
918 #[test]
919 fn test_hover_self() {
920 check(
921 r#"
922struct Thing { x: u32 }
923impl Thing {
924 fn new() -> Self { Self<|> { x: 0 } }
925}
926"#,
927 expect![[r#"
928 *Self { x: 0 }*
929 ```rust
930 Thing
931 ```
932 "#]],
933 )
934 } /* FIXME: revive these tests
935 let (analysis, position) = analysis_and_position(
936 "
937 struct Thing { x: u32 }
938 impl Thing {
939 fn new() -> Self<|> {
940 Self { x: 0 }
941 }
942 }
943 ",
944 );
945
946 let hover = analysis.hover(position).unwrap().unwrap();
947 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
948
949 let (analysis, position) = analysis_and_position(
950 "
951 enum Thing { A }
952 impl Thing {
953 pub fn new() -> Self<|> {
954 Thing::A
955 }
956 }
957 ",
958 );
959 let hover = analysis.hover(position).unwrap().unwrap();
960 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
961
962 let (analysis, position) = analysis_and_position(
963 "
964 enum Thing { A }
965 impl Thing {
966 pub fn thing(a: Self<|>) {
967 }
968 }
969 ",
970 );
971 let hover = analysis.hover(position).unwrap().unwrap();
972 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
973 */
974
975 #[test]
976 fn test_hover_shadowing_pat() {
977 check(
978 r#"
979fn x() {}
980
981fn y() {
982 let x = 0i32;
983 x<|>;
984}
985"#,
986 expect![[r#"
987 *x*
988
989 ```rust
990 i32
991 ```
992 "#]],
993 )
994 }
995
996 #[test]
997 fn test_hover_macro_invocation() {
998 check(
999 r#"
1000macro_rules! foo { () => {} }
1001
1002fn f() { fo<|>o!(); }
1003"#,
1004 expect![[r#"
1005 *foo*
1006
1007 ```rust
1008 test
1009 ```
1010
1011 ```rust
1012 macro_rules! foo
1013 ```
1014 "#]],
1015 )
1016 }
1017
1018 #[test]
1019 fn test_hover_tuple_field() {
1020 check(
1021 r#"struct TS(String, i32<|>);"#,
1022 expect![[r#"
1023 *i32*
1024 i32
1025 "#]],
1026 )
1027 }
1028
1029 #[test]
1030 fn test_hover_through_macro() {
1031 check(
1032 r#"
1033macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
1034fn foo() {}
1035id! {
1036 fn bar() { fo<|>o(); }
1037}
1038"#,
1039 expect![[r#"
1040 *foo*
1041
1042 ```rust
1043 test
1044 ```
1045
1046 ```rust
1047 fn foo()
1048 ```
1049 "#]],
1050 );
1051 }
1052
1053 #[test]
1054 fn test_hover_through_expr_in_macro() {
1055 check(
1056 r#"
1057macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
1058fn foo(bar:u32) { let a = id!(ba<|>r); }
1059"#,
1060 expect![[r#"
1061 *bar*
1062
1063 ```rust
1064 u32
1065 ```
1066 "#]],
1067 );
1068 }
1069
1070 #[test]
1071 fn test_hover_through_expr_in_macro_recursive() {
1072 check(
1073 r#"
1074macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
1075macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
1076fn foo(bar:u32) { let a = id!(ba<|>r); }
1077"#,
1078 expect![[r#"
1079 *bar*
1080
1081 ```rust
1082 u32
1083 ```
1084 "#]],
1085 );
1086 }
1087
1088 #[test]
1089 fn test_hover_through_func_in_macro_recursive() {
1090 check(
1091 r#"
1092macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
1093macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
1094fn bar() -> u32 { 0 }
1095fn foo() { let a = id!([0u32, bar(<|>)] ); }
1096"#,
1097 expect![[r#"
1098 *bar()*
1099 ```rust
1100 u32
1101 ```
1102 "#]],
1103 );
1104 }
1105
1106 #[test]
1107 fn test_hover_through_literal_string_in_macro() {
1108 check(
1109 r#"
1110macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } }
1111fn foo() {
1112 let mastered_for_itunes = "";
1113 let _ = arr!("Tr<|>acks", &mastered_for_itunes);
1114}
1115"#,
1116 expect![[r#"
1117 *"Tracks"*
1118 ```rust
1119 &str
1120 ```
1121 "#]],
1122 );
1123 }
1124
1125 #[test]
1126 fn test_hover_through_assert_macro() {
1127 check(
1128 r#"
1129#[rustc_builtin_macro]
1130macro_rules! assert {}
1131
1132fn bar() -> bool { true }
1133fn foo() {
1134 assert!(ba<|>r());
1135}
1136"#,
1137 expect![[r#"
1138 *bar*
1139
1140 ```rust
1141 test
1142 ```
1143
1144 ```rust
1145 fn bar() -> bool
1146 ```
1147 "#]],
1148 );
1149 }
1150
1151 #[test]
1152 fn test_hover_through_literal_string_in_builtin_macro() {
1153 check_hover_no_result(
1154 r#"
1155 #[rustc_builtin_macro]
1156 macro_rules! format {}
1157
1158 fn foo() {
1159 format!("hel<|>lo {}", 0);
1160 }
1161 "#,
1162 );
1163 }
1164
1165 #[test]
1166 fn test_hover_non_ascii_space_doc() {
1167 check(
1168 "
1169/// <- `\u{3000}` here
1170fn foo() { }
1171
1172fn bar() { fo<|>o(); }
1173",
1174 expect![[r#"
1175 *foo*
1176
1177 ```rust
1178 test
1179 ```
1180
1181 ```rust
1182 fn foo()
1183 ```
1184
1185 ---
1186
1187 \<- ` ` here
1188 "#]],
1189 );
1190 }
1191
1192 #[test]
1193 fn test_hover_function_show_qualifiers() {
1194 check(
1195 r#"async fn foo<|>() {}"#,
1196 expect![[r#"
1197 *foo*
1198
1199 ```rust
1200 test
1201 ```
1202
1203 ```rust
1204 async fn foo()
1205 ```
1206 "#]],
1207 );
1208 check(
1209 r#"pub const unsafe fn foo<|>() {}"#,
1210 expect![[r#"
1211 *foo*
1212
1213 ```rust
1214 test
1215 ```
1216
1217 ```rust
1218 pub const unsafe fn foo()
1219 ```
1220 "#]],
1221 );
1222 check(
1223 r#"pub(crate) async unsafe extern "C" fn foo<|>() {}"#,
1224 expect![[r#"
1225 *foo*
1226
1227 ```rust
1228 test
1229 ```
1230
1231 ```rust
1232 pub(crate) async unsafe extern "C" fn foo()
1233 ```
1234 "#]],
1235 );
1236 }
1237
1238 #[test]
1239 fn test_hover_trait_show_qualifiers() {
1240 check_actions(
1241 r"unsafe trait foo<|>() {}",
1242 expect![[r#"
1243 [
1244 Implementaion(
1245 FilePosition {
1246 file_id: FileId(
1247 1,
1248 ),
1249 offset: 13,
1250 },
1251 ),
1252 ]
1253 "#]],
1254 );
1255 }
1256
1257 #[test]
1258 fn test_hover_extern_crate() {
1259 check(
1260 r#"
1261//- /main.rs
1262extern crate st<|>d;
1263//- /std/lib.rs
1264//! Standard library for this test
1265//!
1266//! Printed?
1267//! abc123
1268 "#,
1269 expect![[r#"
1270 *std*
1271 Standard library for this test
1272
1273 Printed?
1274 abc123
1275 "#]],
1276 );
1277 check(
1278 r#"
1279//- /main.rs
1280extern crate std as ab<|>c;
1281//- /std/lib.rs
1282//! Standard library for this test
1283//!
1284//! Printed?
1285//! abc123
1286 "#,
1287 expect![[r#"
1288 *abc*
1289 Standard library for this test
1290
1291 Printed?
1292 abc123
1293 "#]],
1294 );
1295 }
1296
1297 #[test]
1298 fn test_hover_mod_with_same_name_as_function() {
1299 check(
1300 r#"
1301use self::m<|>y::Bar;
1302mod my { pub struct Bar; }
1303
1304fn my() {}
1305"#,
1306 expect![[r#"
1307 *my*
1308
1309 ```rust
1310 test
1311 ```
1312
1313 ```rust
1314 mod my
1315 ```
1316 "#]],
1317 );
1318 }
1319
1320 #[test]
1321 fn test_hover_struct_doc_comment() {
1322 check(
1323 r#"
1324/// bar docs
1325struct Bar;
1326
1327fn foo() { let bar = Ba<|>r; }
1328"#,
1329 expect![[r#"
1330 *Bar*
1331
1332 ```rust
1333 test
1334 ```
1335
1336 ```rust
1337 struct Bar
1338 ```
1339
1340 ---
1341
1342 bar docs
1343 "#]],
1344 );
1345 }
1346
1347 #[test]
1348 fn test_hover_struct_doc_attr() {
1349 check(
1350 r#"
1351#[doc = "bar docs"]
1352struct Bar;
1353
1354fn foo() { let bar = Ba<|>r; }
1355"#,
1356 expect![[r#"
1357 *Bar*
1358
1359 ```rust
1360 test
1361 ```
1362
1363 ```rust
1364 struct Bar
1365 ```
1366
1367 ---
1368
1369 bar docs
1370 "#]],
1371 );
1372 }
1373
1374 #[test]
1375 fn test_hover_struct_doc_attr_multiple_and_mixed() {
1376 check(
1377 r#"
1378/// bar docs 0
1379#[doc = "bar docs 1"]
1380#[doc = "bar docs 2"]
1381struct Bar;
1382
1383fn foo() { let bar = Ba<|>r; }
1384"#,
1385 expect![[r#"
1386 *Bar*
1387
1388 ```rust
1389 test
1390 ```
1391
1392 ```rust
1393 struct Bar
1394 ```
1395
1396 ---
1397
1398 bar docs 0
1399
1400 bar docs 1
1401
1402 bar docs 2
1403 "#]],
1404 );
1405 }
1406
1407 #[test]
1408 fn test_hover_path_link() {
1409 check(
1410 r"
1411 //- /lib.rs
1412 pub struct Foo;
1413 /// [Foo](struct.Foo.html)
1414 pub struct B<|>ar
1415 ",
1416 expect![[r#"
1417 *Bar*
1418
1419 ```rust
1420 test
1421 ```
1422
1423 ```rust
1424 pub struct Bar
1425 ```
1426
1427 ---
1428
1429 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1430 "#]],
1431 );
1432 }
1433
1434 #[test]
1435 fn test_hover_path_link_no_strip() {
1436 check(
1437 r"
1438 //- /lib.rs
1439 pub struct Foo;
1440 /// [struct Foo](struct.Foo.html)
1441 pub struct B<|>ar
1442 ",
1443 expect![[r#"
1444 *Bar*
1445
1446 ```rust
1447 test
1448 ```
1449
1450 ```rust
1451 pub struct Bar
1452 ```
1453
1454 ---
1455
1456 [struct Foo](https://docs.rs/test/*/test/struct.Foo.html)
1457 "#]],
1458 );
1459 }
1460
1461 #[ignore = "path based links currently only support documentation on ModuleDef items"]
1462 #[test]
1463 fn test_hover_path_link_field() {
1464 check(
1465 r"
1466 //- /lib.rs
1467 pub struct Foo;
1468 pub struct Bar {
1469 /// [Foo](struct.Foo.html)
1470 fie<|>ld: ()
1471 }
1472 ",
1473 expect![[r#"
1474 *field*
1475
1476 ```rust
1477 test::Bar
1478 ```
1479
1480 ```rust
1481 field: ()
1482 ```
1483
1484 ---
1485
1486 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1487 "#]],
1488 );
1489 }
1490
1491 #[test]
1492 fn test_hover_intra_link() {
1493 check(
1494 r"
1495 //- /lib.rs
1496 pub mod foo {
1497 pub struct Foo;
1498 }
1499 /// [Foo](foo::Foo)
1500 pub struct B<|>ar
1501 ",
1502 expect![[r#"
1503 *Bar*
1504
1505 ```rust
1506 test
1507 ```
1508
1509 ```rust
1510 pub struct Bar
1511 ```
1512
1513 ---
1514
1515 [Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)
1516 "#]],
1517 );
1518 }
1519
1520 #[test]
1521 fn test_hover_intra_link_html_root_url() {
1522 check(
1523 r#"
1524 //- /lib.rs
1525
1526 #![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
1527
1528 pub mod foo {
1529 pub struct Foo;
1530 }
1531 /// [Foo](foo::Foo)
1532 pub struct B<|>ar
1533 "#,
1534 expect![[r#"
1535 *Bar*
1536
1537 ```rust
1538 test
1539 ```
1540
1541 ```rust
1542 pub struct Bar
1543 ```
1544
1545 ---
1546
1547 [Foo](https://example.com/test/foo/struct.Foo.html)
1548 "#]],
1549 );
1550 }
1551
1552 #[test]
1553 fn test_hover_intra_link_shortlink() {
1554 check(
1555 r"
1556 //- /lib.rs
1557 pub struct Foo;
1558 /// [Foo]
1559 pub struct B<|>ar
1560 ",
1561 expect![[r#"
1562 *Bar*
1563
1564 ```rust
1565 test
1566 ```
1567
1568 ```rust
1569 pub struct Bar
1570 ```
1571
1572 ---
1573
1574 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1575 "#]],
1576 );
1577 }
1578
1579 #[test]
1580 fn test_hover_intra_link_shortlink_code() {
1581 check(
1582 r"
1583 //- /lib.rs
1584 pub struct Foo;
1585 /// [`Foo`]
1586 pub struct B<|>ar
1587 ",
1588 expect![[r#"
1589 *Bar*
1590
1591 ```rust
1592 test
1593 ```
1594
1595 ```rust
1596 pub struct Bar
1597 ```
1598
1599 ---
1600
1601 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1602 "#]],
1603 );
1604 }
1605
1606 #[test]
1607 fn test_hover_intra_link_namespaced() {
1608 check(
1609 r"
1610 //- /lib.rs
1611 pub struct Foo;
1612 fn Foo() {}
1613 /// [Foo()]
1614 pub struct B<|>ar
1615 ",
1616 expect![[r#"
1617 *Bar*
1618
1619 ```rust
1620 test
1621 ```
1622
1623 ```rust
1624 pub struct Bar
1625 ```
1626
1627 ---
1628
1629 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1630 "#]],
1631 );
1632 }
1633
1634 #[test]
1635 fn test_hover_intra_link_shortlink_namspaced_code() {
1636 check(
1637 r"
1638 //- /lib.rs
1639 pub struct Foo;
1640 /// [`struct Foo`]
1641 pub struct B<|>ar
1642 ",
1643 expect![[r#"
1644 *Bar*
1645
1646 ```rust
1647 test
1648 ```
1649
1650 ```rust
1651 pub struct Bar
1652 ```
1653
1654 ---
1655
1656 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1657 "#]],
1658 );
1659 }
1660
1661 #[test]
1662 fn test_hover_intra_link_shortlink_namspaced_code_with_at() {
1663 check(
1664 r"
1665 //- /lib.rs
1666 pub struct Foo;
1667 /// [`struct@Foo`]
1668 pub struct B<|>ar
1669 ",
1670 expect![[r#"
1671 *Bar*
1672
1673 ```rust
1674 test
1675 ```
1676
1677 ```rust
1678 pub struct Bar
1679 ```
1680
1681 ---
1682
1683 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1684 "#]],
1685 );
1686 }
1687
1688 #[test]
1689 fn test_hover_intra_link_reference() {
1690 check(
1691 r"
1692 //- /lib.rs
1693 pub struct Foo;
1694 /// [my Foo][foo]
1695 ///
1696 /// [foo]: Foo
1697 pub struct B<|>ar
1698 ",
1699 expect![[r#"
1700 *Bar*
1701
1702 ```rust
1703 test
1704 ```
1705
1706 ```rust
1707 pub struct Bar
1708 ```
1709
1710 ---
1711
1712 [my Foo](https://docs.rs/test/*/test/struct.Foo.html)
1713 "#]],
1714 );
1715 }
1716
1717 #[test]
1718 fn test_hover_external_url() {
1719 check(
1720 r"
1721 //- /lib.rs
1722 pub struct Foo;
1723 /// [external](https://www.google.com)
1724 pub struct B<|>ar
1725 ",
1726 expect![[r#"
1727 *Bar*
1728
1729 ```rust
1730 test
1731 ```
1732
1733 ```rust
1734 pub struct Bar
1735 ```
1736
1737 ---
1738
1739 [external](https://www.google.com)
1740 "#]],
1741 );
1742 }
1743
1744 // Check that we don't rewrite links which we can't identify
1745 #[test]
1746 fn test_hover_unknown_target() {
1747 check(
1748 r"
1749 //- /lib.rs
1750 pub struct Foo;
1751 /// [baz](Baz)
1752 pub struct B<|>ar
1753 ",
1754 expect![[r#"
1755 *Bar*
1756
1757 ```rust
1758 test
1759 ```
1760
1761 ```rust
1762 pub struct Bar
1763 ```
1764
1765 ---
1766
1767 [baz](Baz)
1768 "#]],
1769 );
1770 }
1771
1772 #[test]
1773 fn test_hover_macro_generated_struct_fn_doc_comment() {
1774 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1775
1776 check(
1777 r#"
1778macro_rules! bar {
1779 () => {
1780 struct Bar;
1781 impl Bar {
1782 /// Do the foo
1783 fn foo(&self) {}
1784 }
1785 }
1786}
1787
1788bar!();
1789
1790fn foo() { let bar = Bar; bar.fo<|>o(); }
1791"#,
1792 expect![[r#"
1793 *foo*
1794
1795 ```rust
1796 test::Bar
1797 ```
1798
1799 ```rust
1800 fn foo(&self)
1801 ```
1802
1803 ---
1804
1805 Do the foo
1806 "#]],
1807 );
1808 }
1809
1810 #[test]
1811 fn test_hover_macro_generated_struct_fn_doc_attr() {
1812 mark::check!(hover_macro_generated_struct_fn_doc_attr);
1813
1814 check(
1815 r#"
1816macro_rules! bar {
1817 () => {
1818 struct Bar;
1819 impl Bar {
1820 #[doc = "Do the foo"]
1821 fn foo(&self) {}
1822 }
1823 }
1824}
1825
1826bar!();
1827
1828fn foo() { let bar = Bar; bar.fo<|>o(); }
1829"#,
1830 expect![[r#"
1831 *foo*
1832
1833 ```rust
1834 test::Bar
1835 ```
1836
1837 ```rust
1838 fn foo(&self)
1839 ```
1840
1841 ---
1842
1843 Do the foo
1844 "#]],
1845 );
1846 }
1847
1848 #[test]
1849 fn test_hover_trait_has_impl_action() {
1850 check_actions(
1851 r#"trait foo<|>() {}"#,
1852 expect![[r#"
1853 [
1854 Implementaion(
1855 FilePosition {
1856 file_id: FileId(
1857 1,
1858 ),
1859 offset: 6,
1860 },
1861 ),
1862 ]
1863 "#]],
1864 );
1865 }
1866
1867 #[test]
1868 fn test_hover_struct_has_impl_action() {
1869 check_actions(
1870 r"struct foo<|>() {}",
1871 expect![[r#"
1872 [
1873 Implementaion(
1874 FilePosition {
1875 file_id: FileId(
1876 1,
1877 ),
1878 offset: 7,
1879 },
1880 ),
1881 ]
1882 "#]],
1883 );
1884 }
1885
1886 #[test]
1887 fn test_hover_union_has_impl_action() {
1888 check_actions(
1889 r#"union foo<|>() {}"#,
1890 expect![[r#"
1891 [
1892 Implementaion(
1893 FilePosition {
1894 file_id: FileId(
1895 1,
1896 ),
1897 offset: 6,
1898 },
1899 ),
1900 ]
1901 "#]],
1902 );
1903 }
1904
1905 #[test]
1906 fn test_hover_enum_has_impl_action() {
1907 check_actions(
1908 r"enum foo<|>() { A, B }",
1909 expect![[r#"
1910 [
1911 Implementaion(
1912 FilePosition {
1913 file_id: FileId(
1914 1,
1915 ),
1916 offset: 5,
1917 },
1918 ),
1919 ]
1920 "#]],
1921 );
1922 }
1923
1924 #[test]
1925 fn test_hover_test_has_action() {
1926 check_actions(
1927 r#"
1928#[test]
1929fn foo_<|>test() {}
1930"#,
1931 expect![[r#"
1932 [
1933 Runnable(
1934 Runnable {
1935 nav: NavigationTarget {
1936 file_id: FileId(
1937 1,
1938 ),
1939 full_range: 0..24,
1940 focus_range: Some(
1941 11..19,
1942 ),
1943 name: "foo_test",
1944 kind: FN,
1945 container_name: None,
1946 description: None,
1947 docs: None,
1948 },
1949 kind: Test {
1950 test_id: Path(
1951 "foo_test",
1952 ),
1953 attr: TestAttr {
1954 ignore: false,
1955 },
1956 },
1957 cfg_exprs: [],
1958 },
1959 ),
1960 ]
1961 "#]],
1962 );
1963 }
1964
1965 #[test]
1966 fn test_hover_test_mod_has_action() {
1967 check_actions(
1968 r#"
1969mod tests<|> {
1970 #[test]
1971 fn foo_test() {}
1972}
1973"#,
1974 expect![[r#"
1975 [
1976 Runnable(
1977 Runnable {
1978 nav: NavigationTarget {
1979 file_id: FileId(
1980 1,
1981 ),
1982 full_range: 0..46,
1983 focus_range: Some(
1984 4..9,
1985 ),
1986 name: "tests",
1987 kind: MODULE,
1988 container_name: None,
1989 description: None,
1990 docs: None,
1991 },
1992 kind: TestMod {
1993 path: "tests",
1994 },
1995 cfg_exprs: [],
1996 },
1997 ),
1998 ]
1999 "#]],
2000 );
2001 }
2002
2003 #[test]
2004 fn test_hover_struct_has_goto_type_action() {
2005 check_actions(
2006 r#"
2007struct S{ f1: u32 }
2008
2009fn main() { let s<|>t = S{ f1:0 }; }
2010 "#,
2011 expect![[r#"
2012 [
2013 GoToType(
2014 [
2015 HoverGotoTypeData {
2016 mod_path: "test::S",
2017 nav: NavigationTarget {
2018 file_id: FileId(
2019 1,
2020 ),
2021 full_range: 0..19,
2022 focus_range: Some(
2023 7..8,
2024 ),
2025 name: "S",
2026 kind: STRUCT,
2027 container_name: None,
2028 description: Some(
2029 "struct S",
2030 ),
2031 docs: None,
2032 },
2033 },
2034 ],
2035 ),
2036 ]
2037 "#]],
2038 );
2039 }
2040
2041 #[test]
2042 fn test_hover_generic_struct_has_goto_type_actions() {
2043 check_actions(
2044 r#"
2045struct Arg(u32);
2046struct S<T>{ f1: T }
2047
2048fn main() { let s<|>t = S{ f1:Arg(0) }; }
2049"#,
2050 expect![[r#"
2051 [
2052 GoToType(
2053 [
2054 HoverGotoTypeData {
2055 mod_path: "test::S",
2056 nav: NavigationTarget {
2057 file_id: FileId(
2058 1,
2059 ),
2060 full_range: 17..37,
2061 focus_range: Some(
2062 24..25,
2063 ),
2064 name: "S",
2065 kind: STRUCT,
2066 container_name: None,
2067 description: Some(
2068 "struct S",
2069 ),
2070 docs: None,
2071 },
2072 },
2073 HoverGotoTypeData {
2074 mod_path: "test::Arg",
2075 nav: NavigationTarget {
2076 file_id: FileId(
2077 1,
2078 ),
2079 full_range: 0..16,
2080 focus_range: Some(
2081 7..10,
2082 ),
2083 name: "Arg",
2084 kind: STRUCT,
2085 container_name: None,
2086 description: Some(
2087 "struct Arg",
2088 ),
2089 docs: None,
2090 },
2091 },
2092 ],
2093 ),
2094 ]
2095 "#]],
2096 );
2097 }
2098
2099 #[test]
2100 fn test_hover_generic_struct_has_flattened_goto_type_actions() {
2101 check_actions(
2102 r#"
2103struct Arg(u32);
2104struct S<T>{ f1: T }
2105
2106fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
2107 "#,
2108 expect![[r#"
2109 [
2110 GoToType(
2111 [
2112 HoverGotoTypeData {
2113 mod_path: "test::S",
2114 nav: NavigationTarget {
2115 file_id: FileId(
2116 1,
2117 ),
2118 full_range: 17..37,
2119 focus_range: Some(
2120 24..25,
2121 ),
2122 name: "S",
2123 kind: STRUCT,
2124 container_name: None,
2125 description: Some(
2126 "struct S",
2127 ),
2128 docs: None,
2129 },
2130 },
2131 HoverGotoTypeData {
2132 mod_path: "test::Arg",
2133 nav: NavigationTarget {
2134 file_id: FileId(
2135 1,
2136 ),
2137 full_range: 0..16,
2138 focus_range: Some(
2139 7..10,
2140 ),
2141 name: "Arg",
2142 kind: STRUCT,
2143 container_name: None,
2144 description: Some(
2145 "struct Arg",
2146 ),
2147 docs: None,
2148 },
2149 },
2150 ],
2151 ),
2152 ]
2153 "#]],
2154 );
2155 }
2156
2157 #[test]
2158 fn test_hover_tuple_has_goto_type_actions() {
2159 check_actions(
2160 r#"
2161struct A(u32);
2162struct B(u32);
2163mod M {
2164 pub struct C(u32);
2165}
2166
2167fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
2168"#,
2169 expect![[r#"
2170 [
2171 GoToType(
2172 [
2173 HoverGotoTypeData {
2174 mod_path: "test::A",
2175 nav: NavigationTarget {
2176 file_id: FileId(
2177 1,
2178 ),
2179 full_range: 0..14,
2180 focus_range: Some(
2181 7..8,
2182 ),
2183 name: "A",
2184 kind: STRUCT,
2185 container_name: None,
2186 description: Some(
2187 "struct A",
2188 ),
2189 docs: None,
2190 },
2191 },
2192 HoverGotoTypeData {
2193 mod_path: "test::B",
2194 nav: NavigationTarget {
2195 file_id: FileId(
2196 1,
2197 ),
2198 full_range: 15..29,
2199 focus_range: Some(
2200 22..23,
2201 ),
2202 name: "B",
2203 kind: STRUCT,
2204 container_name: None,
2205 description: Some(
2206 "struct B",
2207 ),
2208 docs: None,
2209 },
2210 },
2211 HoverGotoTypeData {
2212 mod_path: "test::M::C",
2213 nav: NavigationTarget {
2214 file_id: FileId(
2215 1,
2216 ),
2217 full_range: 42..60,
2218 focus_range: Some(
2219 53..54,
2220 ),
2221 name: "C",
2222 kind: STRUCT,
2223 container_name: None,
2224 description: Some(
2225 "pub struct C",
2226 ),
2227 docs: None,
2228 },
2229 },
2230 ],
2231 ),
2232 ]
2233 "#]],
2234 );
2235 }
2236
2237 #[test]
2238 fn test_hover_return_impl_trait_has_goto_type_action() {
2239 check_actions(
2240 r#"
2241trait Foo {}
2242fn foo() -> impl Foo {}
2243
2244fn main() { let s<|>t = foo(); }
2245"#,
2246 expect![[r#"
2247 [
2248 GoToType(
2249 [
2250 HoverGotoTypeData {
2251 mod_path: "test::Foo",
2252 nav: NavigationTarget {
2253 file_id: FileId(
2254 1,
2255 ),
2256 full_range: 0..12,
2257 focus_range: Some(
2258 6..9,
2259 ),
2260 name: "Foo",
2261 kind: TRAIT,
2262 container_name: None,
2263 description: Some(
2264 "trait Foo",
2265 ),
2266 docs: None,
2267 },
2268 },
2269 ],
2270 ),
2271 ]
2272 "#]],
2273 );
2274 }
2275
2276 #[test]
2277 fn test_hover_generic_return_impl_trait_has_goto_type_action() {
2278 check_actions(
2279 r#"
2280trait Foo<T> {}
2281struct S;
2282fn foo() -> impl Foo<S> {}
2283
2284fn main() { let s<|>t = foo(); }
2285"#,
2286 expect![[r#"
2287 [
2288 GoToType(
2289 [
2290 HoverGotoTypeData {
2291 mod_path: "test::Foo",
2292 nav: NavigationTarget {
2293 file_id: FileId(
2294 1,
2295 ),
2296 full_range: 0..15,
2297 focus_range: Some(
2298 6..9,
2299 ),
2300 name: "Foo",
2301 kind: TRAIT,
2302 container_name: None,
2303 description: Some(
2304 "trait Foo",
2305 ),
2306 docs: None,
2307 },
2308 },
2309 HoverGotoTypeData {
2310 mod_path: "test::S",
2311 nav: NavigationTarget {
2312 file_id: FileId(
2313 1,
2314 ),
2315 full_range: 16..25,
2316 focus_range: Some(
2317 23..24,
2318 ),
2319 name: "S",
2320 kind: STRUCT,
2321 container_name: None,
2322 description: Some(
2323 "struct S",
2324 ),
2325 docs: None,
2326 },
2327 },
2328 ],
2329 ),
2330 ]
2331 "#]],
2332 );
2333 }
2334
2335 #[test]
2336 fn test_hover_return_impl_traits_has_goto_type_action() {
2337 check_actions(
2338 r#"
2339trait Foo {}
2340trait Bar {}
2341fn foo() -> impl Foo + Bar {}
2342
2343fn main() { let s<|>t = foo(); }
2344 "#,
2345 expect![[r#"
2346 [
2347 GoToType(
2348 [
2349 HoverGotoTypeData {
2350 mod_path: "test::Foo",
2351 nav: NavigationTarget {
2352 file_id: FileId(
2353 1,
2354 ),
2355 full_range: 0..12,
2356 focus_range: Some(
2357 6..9,
2358 ),
2359 name: "Foo",
2360 kind: TRAIT,
2361 container_name: None,
2362 description: Some(
2363 "trait Foo",
2364 ),
2365 docs: None,
2366 },
2367 },
2368 HoverGotoTypeData {
2369 mod_path: "test::Bar",
2370 nav: NavigationTarget {
2371 file_id: FileId(
2372 1,
2373 ),
2374 full_range: 13..25,
2375 focus_range: Some(
2376 19..22,
2377 ),
2378 name: "Bar",
2379 kind: TRAIT,
2380 container_name: None,
2381 description: Some(
2382 "trait Bar",
2383 ),
2384 docs: None,
2385 },
2386 },
2387 ],
2388 ),
2389 ]
2390 "#]],
2391 );
2392 }
2393
2394 #[test]
2395 fn test_hover_generic_return_impl_traits_has_goto_type_action() {
2396 check_actions(
2397 r#"
2398trait Foo<T> {}
2399trait Bar<T> {}
2400struct S1 {}
2401struct S2 {}
2402
2403fn foo() -> impl Foo<S1> + Bar<S2> {}
2404
2405fn main() { let s<|>t = foo(); }
2406"#,
2407 expect![[r#"
2408 [
2409 GoToType(
2410 [
2411 HoverGotoTypeData {
2412 mod_path: "test::Foo",
2413 nav: NavigationTarget {
2414 file_id: FileId(
2415 1,
2416 ),
2417 full_range: 0..15,
2418 focus_range: Some(
2419 6..9,
2420 ),
2421 name: "Foo",
2422 kind: TRAIT,
2423 container_name: None,
2424 description: Some(
2425 "trait Foo",
2426 ),
2427 docs: None,
2428 },
2429 },
2430 HoverGotoTypeData {
2431 mod_path: "test::Bar",
2432 nav: NavigationTarget {
2433 file_id: FileId(
2434 1,
2435 ),
2436 full_range: 16..31,
2437 focus_range: Some(
2438 22..25,
2439 ),
2440 name: "Bar",
2441 kind: TRAIT,
2442 container_name: None,
2443 description: Some(
2444 "trait Bar",
2445 ),
2446 docs: None,
2447 },
2448 },
2449 HoverGotoTypeData {
2450 mod_path: "test::S1",
2451 nav: NavigationTarget {
2452 file_id: FileId(
2453 1,
2454 ),
2455 full_range: 32..44,
2456 focus_range: Some(
2457 39..41,
2458 ),
2459 name: "S1",
2460 kind: STRUCT,
2461 container_name: None,
2462 description: Some(
2463 "struct S1",
2464 ),
2465 docs: None,
2466 },
2467 },
2468 HoverGotoTypeData {
2469 mod_path: "test::S2",
2470 nav: NavigationTarget {
2471 file_id: FileId(
2472 1,
2473 ),
2474 full_range: 45..57,
2475 focus_range: Some(
2476 52..54,
2477 ),
2478 name: "S2",
2479 kind: STRUCT,
2480 container_name: None,
2481 description: Some(
2482 "struct S2",
2483 ),
2484 docs: None,
2485 },
2486 },
2487 ],
2488 ),
2489 ]
2490 "#]],
2491 );
2492 }
2493
2494 #[test]
2495 fn test_hover_arg_impl_trait_has_goto_type_action() {
2496 check_actions(
2497 r#"
2498trait Foo {}
2499fn foo(ar<|>g: &impl Foo) {}
2500"#,
2501 expect![[r#"
2502 [
2503 GoToType(
2504 [
2505 HoverGotoTypeData {
2506 mod_path: "test::Foo",
2507 nav: NavigationTarget {
2508 file_id: FileId(
2509 1,
2510 ),
2511 full_range: 0..12,
2512 focus_range: Some(
2513 6..9,
2514 ),
2515 name: "Foo",
2516 kind: TRAIT,
2517 container_name: None,
2518 description: Some(
2519 "trait Foo",
2520 ),
2521 docs: None,
2522 },
2523 },
2524 ],
2525 ),
2526 ]
2527 "#]],
2528 );
2529 }
2530
2531 #[test]
2532 fn test_hover_arg_impl_traits_has_goto_type_action() {
2533 check_actions(
2534 r#"
2535trait Foo {}
2536trait Bar<T> {}
2537struct S{}
2538
2539fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2540"#,
2541 expect![[r#"
2542 [
2543 GoToType(
2544 [
2545 HoverGotoTypeData {
2546 mod_path: "test::Foo",
2547 nav: NavigationTarget {
2548 file_id: FileId(
2549 1,
2550 ),
2551 full_range: 0..12,
2552 focus_range: Some(
2553 6..9,
2554 ),
2555 name: "Foo",
2556 kind: TRAIT,
2557 container_name: None,
2558 description: Some(
2559 "trait Foo",
2560 ),
2561 docs: None,
2562 },
2563 },
2564 HoverGotoTypeData {
2565 mod_path: "test::Bar",
2566 nav: NavigationTarget {
2567 file_id: FileId(
2568 1,
2569 ),
2570 full_range: 13..28,
2571 focus_range: Some(
2572 19..22,
2573 ),
2574 name: "Bar",
2575 kind: TRAIT,
2576 container_name: None,
2577 description: Some(
2578 "trait Bar",
2579 ),
2580 docs: None,
2581 },
2582 },
2583 HoverGotoTypeData {
2584 mod_path: "test::S",
2585 nav: NavigationTarget {
2586 file_id: FileId(
2587 1,
2588 ),
2589 full_range: 29..39,
2590 focus_range: Some(
2591 36..37,
2592 ),
2593 name: "S",
2594 kind: STRUCT,
2595 container_name: None,
2596 description: Some(
2597 "struct S",
2598 ),
2599 docs: None,
2600 },
2601 },
2602 ],
2603 ),
2604 ]
2605 "#]],
2606 );
2607 }
2608
2609 #[test]
2610 fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
2611 check_actions(
2612 r#"
2613trait Foo<T> {}
2614struct S {}
2615fn foo(ar<|>g: &impl Foo<S>) {}
2616"#,
2617 expect![[r#"
2618 [
2619 GoToType(
2620 [
2621 HoverGotoTypeData {
2622 mod_path: "test::Foo",
2623 nav: NavigationTarget {
2624 file_id: FileId(
2625 1,
2626 ),
2627 full_range: 0..15,
2628 focus_range: Some(
2629 6..9,
2630 ),
2631 name: "Foo",
2632 kind: TRAIT,
2633 container_name: None,
2634 description: Some(
2635 "trait Foo",
2636 ),
2637 docs: None,
2638 },
2639 },
2640 HoverGotoTypeData {
2641 mod_path: "test::S",
2642 nav: NavigationTarget {
2643 file_id: FileId(
2644 1,
2645 ),
2646 full_range: 16..27,
2647 focus_range: Some(
2648 23..24,
2649 ),
2650 name: "S",
2651 kind: STRUCT,
2652 container_name: None,
2653 description: Some(
2654 "struct S",
2655 ),
2656 docs: None,
2657 },
2658 },
2659 ],
2660 ),
2661 ]
2662 "#]],
2663 );
2664 }
2665
2666 #[test]
2667 fn test_hover_dyn_return_has_goto_type_action() {
2668 check_actions(
2669 r#"
2670trait Foo {}
2671struct S;
2672impl Foo for S {}
2673
2674struct B<T>{}
2675fn foo() -> B<dyn Foo> {}
2676
2677fn main() { let s<|>t = foo(); }
2678"#,
2679 expect![[r#"
2680 [
2681 GoToType(
2682 [
2683 HoverGotoTypeData {
2684 mod_path: "test::B",
2685 nav: NavigationTarget {
2686 file_id: FileId(
2687 1,
2688 ),
2689 full_range: 42..55,
2690 focus_range: Some(
2691 49..50,
2692 ),
2693 name: "B",
2694 kind: STRUCT,
2695 container_name: None,
2696 description: Some(
2697 "struct B",
2698 ),
2699 docs: None,
2700 },
2701 },
2702 HoverGotoTypeData {
2703 mod_path: "test::Foo",
2704 nav: NavigationTarget {
2705 file_id: FileId(
2706 1,
2707 ),
2708 full_range: 0..12,
2709 focus_range: Some(
2710 6..9,
2711 ),
2712 name: "Foo",
2713 kind: TRAIT,
2714 container_name: None,
2715 description: Some(
2716 "trait Foo",
2717 ),
2718 docs: None,
2719 },
2720 },
2721 ],
2722 ),
2723 ]
2724 "#]],
2725 );
2726 }
2727
2728 #[test]
2729 fn test_hover_dyn_arg_has_goto_type_action() {
2730 check_actions(
2731 r#"
2732trait Foo {}
2733fn foo(ar<|>g: &dyn Foo) {}
2734"#,
2735 expect![[r#"
2736 [
2737 GoToType(
2738 [
2739 HoverGotoTypeData {
2740 mod_path: "test::Foo",
2741 nav: NavigationTarget {
2742 file_id: FileId(
2743 1,
2744 ),
2745 full_range: 0..12,
2746 focus_range: Some(
2747 6..9,
2748 ),
2749 name: "Foo",
2750 kind: TRAIT,
2751 container_name: None,
2752 description: Some(
2753 "trait Foo",
2754 ),
2755 docs: None,
2756 },
2757 },
2758 ],
2759 ),
2760 ]
2761 "#]],
2762 );
2763 }
2764
2765 #[test]
2766 fn test_hover_generic_dyn_arg_has_goto_type_action() {
2767 check_actions(
2768 r#"
2769trait Foo<T> {}
2770struct S {}
2771fn foo(ar<|>g: &dyn Foo<S>) {}
2772"#,
2773 expect![[r#"
2774 [
2775 GoToType(
2776 [
2777 HoverGotoTypeData {
2778 mod_path: "test::Foo",
2779 nav: NavigationTarget {
2780 file_id: FileId(
2781 1,
2782 ),
2783 full_range: 0..15,
2784 focus_range: Some(
2785 6..9,
2786 ),
2787 name: "Foo",
2788 kind: TRAIT,
2789 container_name: None,
2790 description: Some(
2791 "trait Foo",
2792 ),
2793 docs: None,
2794 },
2795 },
2796 HoverGotoTypeData {
2797 mod_path: "test::S",
2798 nav: NavigationTarget {
2799 file_id: FileId(
2800 1,
2801 ),
2802 full_range: 16..27,
2803 focus_range: Some(
2804 23..24,
2805 ),
2806 name: "S",
2807 kind: STRUCT,
2808 container_name: None,
2809 description: Some(
2810 "struct S",
2811 ),
2812 docs: None,
2813 },
2814 },
2815 ],
2816 ),
2817 ]
2818 "#]],
2819 );
2820 }
2821
2822 #[test]
2823 fn test_hover_goto_type_action_links_order() {
2824 check_actions(
2825 r#"
2826trait ImplTrait<T> {}
2827trait DynTrait<T> {}
2828struct B<T> {}
2829struct S {}
2830
2831fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2832 "#,
2833 expect![[r#"
2834 [
2835 GoToType(
2836 [
2837 HoverGotoTypeData {
2838 mod_path: "test::ImplTrait",
2839 nav: NavigationTarget {
2840 file_id: FileId(
2841 1,
2842 ),
2843 full_range: 0..21,
2844 focus_range: Some(
2845 6..15,
2846 ),
2847 name: "ImplTrait",
2848 kind: TRAIT,
2849 container_name: None,
2850 description: Some(
2851 "trait ImplTrait",
2852 ),
2853 docs: None,
2854 },
2855 },
2856 HoverGotoTypeData {
2857 mod_path: "test::B",
2858 nav: NavigationTarget {
2859 file_id: FileId(
2860 1,
2861 ),
2862 full_range: 43..57,
2863 focus_range: Some(
2864 50..51,
2865 ),
2866 name: "B",
2867 kind: STRUCT,
2868 container_name: None,
2869 description: Some(
2870 "struct B",
2871 ),
2872 docs: None,
2873 },
2874 },
2875 HoverGotoTypeData {
2876 mod_path: "test::DynTrait",
2877 nav: NavigationTarget {
2878 file_id: FileId(
2879 1,
2880 ),
2881 full_range: 22..42,
2882 focus_range: Some(
2883 28..36,
2884 ),
2885 name: "DynTrait",
2886 kind: TRAIT,
2887 container_name: None,
2888 description: Some(
2889 "trait DynTrait",
2890 ),
2891 docs: None,
2892 },
2893 },
2894 HoverGotoTypeData {
2895 mod_path: "test::S",
2896 nav: NavigationTarget {
2897 file_id: FileId(
2898 1,
2899 ),
2900 full_range: 58..69,
2901 focus_range: Some(
2902 65..66,
2903 ),
2904 name: "S",
2905 kind: STRUCT,
2906 container_name: None,
2907 description: Some(
2908 "struct S",
2909 ),
2910 docs: None,
2911 },
2912 },
2913 ],
2914 ),
2915 ]
2916 "#]],
2917 );
2918 }
2919
2920 #[test]
2921 fn test_hover_associated_type_has_goto_type_action() {
2922 check_actions(
2923 r#"
2924trait Foo {
2925 type Item;
2926 fn get(self) -> Self::Item {}
2927}
2928
2929struct Bar{}
2930struct S{}
2931
2932impl Foo for S { type Item = Bar; }
2933
2934fn test() -> impl Foo { S {} }
2935
2936fn main() { let s<|>t = test().get(); }
2937"#,
2938 expect![[r#"
2939 [
2940 GoToType(
2941 [
2942 HoverGotoTypeData {
2943 mod_path: "test::Foo",
2944 nav: NavigationTarget {
2945 file_id: FileId(
2946 1,
2947 ),
2948 full_range: 0..62,
2949 focus_range: Some(
2950 6..9,
2951 ),
2952 name: "Foo",
2953 kind: TRAIT,
2954 container_name: None,
2955 description: Some(
2956 "trait Foo",
2957 ),
2958 docs: None,
2959 },
2960 },
2961 ],
2962 ),
2963 ]
2964 "#]],
2965 );
2966 }
2967}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
new file mode 100644
index 000000000..583f39d85
--- /dev/null
+++ b/crates/ide/src/inlay_hints.rs
@@ -0,0 +1,927 @@
1use hir::{Adt, Callable, HirDisplay, Semantics, Type};
2use ide_db::RootDatabase;
3use stdx::to_lower_snake_case;
4use syntax::{
5 ast::{self, ArgListOwner, AstNode},
6 match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
7};
8
9use crate::FileId;
10use ast::NameOwner;
11use either::Either;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsConfig {
15 pub type_hints: bool,
16 pub parameter_hints: bool,
17 pub chaining_hints: bool,
18 pub max_length: Option<usize>,
19}
20
21impl Default for InlayHintsConfig {
22 fn default() -> Self {
23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
24 }
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum InlayKind {
29 TypeHint,
30 ParameterHint,
31 ChainingHint,
32}
33
34#[derive(Debug)]
35pub struct InlayHint {
36 pub range: TextRange,
37 pub kind: InlayKind,
38 pub label: SmolStr,
39}
40
41// Feature: Inlay Hints
42//
43// rust-analyzer shows additional information inline with the source code.
44// Editors usually render this using read-only virtual text snippets interspersed with code.
45//
46// rust-analyzer shows hints for
47//
48// * types of local variables
49// * names of function arguments
50// * types of chained expressions
51//
52// **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations.
53// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
54// https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
55//
56// |===
57// | Editor | Action Name
58//
59// | VS Code | **Rust Analyzer: Toggle inlay hints*
60// |===
61pub(crate) fn inlay_hints(
62 db: &RootDatabase,
63 file_id: FileId,
64 config: &InlayHintsConfig,
65) -> Vec<InlayHint> {
66 let _p = profile::span("inlay_hints");
67 let sema = Semantics::new(db);
68 let file = sema.parse(file_id);
69
70 let mut res = Vec::new();
71 for node in file.syntax().descendants() {
72 if let Some(expr) = ast::Expr::cast(node.clone()) {
73 get_chaining_hints(&mut res, &sema, config, expr);
74 }
75
76 match_ast! {
77 match node {
78 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
79 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
80 ast::IdentPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); },
81 _ => (),
82 }
83 }
84 }
85 res
86}
87
88fn get_chaining_hints(
89 acc: &mut Vec<InlayHint>,
90 sema: &Semantics<RootDatabase>,
91 config: &InlayHintsConfig,
92 expr: ast::Expr,
93) -> Option<()> {
94 if !config.chaining_hints {
95 return None;
96 }
97
98 if matches!(expr, ast::Expr::RecordExpr(_)) {
99 return None;
100 }
101
102 let mut tokens = expr
103 .syntax()
104 .siblings_with_tokens(Direction::Next)
105 .filter_map(NodeOrToken::into_token)
106 .filter(|t| match t.kind() {
107 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
108 SyntaxKind::COMMENT => false,
109 _ => true,
110 });
111
112 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
113 // Ignoring extra whitespace and comments
114 let next = tokens.next()?.kind();
115 let next_next = tokens.next()?.kind();
116 if next == SyntaxKind::WHITESPACE && next_next == T![.] {
117 let ty = sema.type_of_expr(&expr)?;
118 if ty.is_unknown() {
119 return None;
120 }
121 if matches!(expr, ast::Expr::PathExpr(_)) {
122 if let Some(Adt::Struct(st)) = ty.as_adt() {
123 if st.fields(sema.db).is_empty() {
124 return None;
125 }
126 }
127 }
128 let label = ty.display_truncated(sema.db, config.max_length).to_string();
129 acc.push(InlayHint {
130 range: expr.syntax().text_range(),
131 kind: InlayKind::ChainingHint,
132 label: label.into(),
133 });
134 }
135 Some(())
136}
137
138fn get_param_name_hints(
139 acc: &mut Vec<InlayHint>,
140 sema: &Semantics<RootDatabase>,
141 config: &InlayHintsConfig,
142 expr: ast::Expr,
143) -> Option<()> {
144 if !config.parameter_hints {
145 return None;
146 }
147
148 let args = match &expr {
149 ast::Expr::CallExpr(expr) => expr.arg_list()?.args(),
150 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
151 _ => return None,
152 };
153
154 let callable = get_callable(sema, &expr)?;
155 let hints = callable
156 .params(sema.db)
157 .into_iter()
158 .zip(args)
159 .filter_map(|((param, _ty), arg)| {
160 let param_name = match param? {
161 Either::Left(self_param) => self_param.to_string(),
162 Either::Right(pat) => match pat {
163 ast::Pat::IdentPat(it) => it.name()?.to_string(),
164 _ => return None,
165 },
166 };
167 Some((param_name, arg))
168 })
169 .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, &param_name, &arg))
170 .map(|(param_name, arg)| InlayHint {
171 range: arg.syntax().text_range(),
172 kind: InlayKind::ParameterHint,
173 label: param_name.into(),
174 });
175
176 acc.extend(hints);
177 Some(())
178}
179
180fn get_bind_pat_hints(
181 acc: &mut Vec<InlayHint>,
182 sema: &Semantics<RootDatabase>,
183 config: &InlayHintsConfig,
184 pat: ast::IdentPat,
185) -> Option<()> {
186 if !config.type_hints {
187 return None;
188 }
189
190 let ty = sema.type_of_pat(&pat.clone().into())?;
191
192 if should_not_display_type_hint(sema.db, &pat, &ty) {
193 return None;
194 }
195
196 acc.push(InlayHint {
197 range: pat.syntax().text_range(),
198 kind: InlayKind::TypeHint,
199 label: ty.display_truncated(sema.db, config.max_length).to_string().into(),
200 });
201 Some(())
202}
203
204fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Type) -> bool {
205 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() {
206 let pat_text = bind_pat.to_string();
207 enum_data
208 .variants(db)
209 .into_iter()
210 .map(|variant| variant.name(db).to_string())
211 .any(|enum_name| enum_name == pat_text)
212 } else {
213 false
214 }
215}
216
217fn should_not_display_type_hint(
218 db: &RootDatabase,
219 bind_pat: &ast::IdentPat,
220 pat_ty: &Type,
221) -> bool {
222 if pat_ty.is_unknown() {
223 return true;
224 }
225
226 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
227 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
228 return true;
229 }
230 }
231
232 for node in bind_pat.syntax().ancestors() {
233 match_ast! {
234 match node {
235 ast::LetStmt(it) => {
236 return it.ty().is_some()
237 },
238 ast::Param(it) => {
239 return it.ty().is_some()
240 },
241 ast::MatchArm(_it) => {
242 return pat_is_enum_variant(db, bind_pat, pat_ty);
243 },
244 ast::IfExpr(it) => {
245 return it.condition().and_then(|condition| condition.pat()).is_some()
246 && pat_is_enum_variant(db, bind_pat, pat_ty);
247 },
248 ast::WhileExpr(it) => {
249 return it.condition().and_then(|condition| condition.pat()).is_some()
250 && pat_is_enum_variant(db, bind_pat, pat_ty);
251 },
252 _ => (),
253 }
254 }
255 }
256 false
257}
258
259fn should_show_param_name_hint(
260 sema: &Semantics<RootDatabase>,
261 callable: &Callable,
262 param_name: &str,
263 argument: &ast::Expr,
264) -> bool {
265 let param_name = param_name.trim_start_matches('_');
266 let fn_name = match callable.kind() {
267 hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()),
268 hir::CallableKind::TupleStruct(_)
269 | hir::CallableKind::TupleEnumVariant(_)
270 | hir::CallableKind::Closure => None,
271 };
272 if param_name.is_empty()
273 || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_'))
274 || is_argument_similar_to_param_name(sema, argument, param_name)
275 || param_name.starts_with("ra_fixture")
276 {
277 return false;
278 }
279
280 // avoid displaying hints for common functions like map, filter, etc.
281 // or other obvious words used in std
282 !(callable.n_params() == 1 && is_obvious_param(param_name))
283}
284
285fn is_argument_similar_to_param_name(
286 sema: &Semantics<RootDatabase>,
287 argument: &ast::Expr,
288 param_name: &str,
289) -> bool {
290 if is_enum_name_similar_to_param_name(sema, argument, param_name) {
291 return true;
292 }
293 match get_string_representation(argument) {
294 None => false,
295 Some(repr) => {
296 let argument_string = repr.trim_start_matches('_');
297 argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
298 }
299 }
300}
301
302fn is_enum_name_similar_to_param_name(
303 sema: &Semantics<RootDatabase>,
304 argument: &ast::Expr,
305 param_name: &str,
306) -> bool {
307 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
308 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
309 _ => false,
310 }
311}
312
313fn get_string_representation(expr: &ast::Expr) -> Option<String> {
314 match expr {
315 ast::Expr::MethodCallExpr(method_call_expr) => {
316 Some(method_call_expr.name_ref()?.to_string())
317 }
318 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
319 _ => Some(expr.to_string()),
320 }
321}
322
323fn is_obvious_param(param_name: &str) -> bool {
324 let is_obvious_param_name =
325 matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
326 param_name.len() == 1 || is_obvious_param_name
327}
328
329fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> {
330 match expr {
331 ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db),
332 ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr),
333 _ => None,
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use expect_test::{expect, Expect};
340 use test_utils::extract_annotations;
341
342 use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file};
343
344 fn check(ra_fixture: &str) {
345 check_with_config(InlayHintsConfig::default(), ra_fixture);
346 }
347
348 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
349 let (analysis, file_id) = single_file(ra_fixture);
350 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
351 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
352 let actual =
353 inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
354 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
355 }
356
357 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
358 let (analysis, file_id) = single_file(ra_fixture);
359 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
360 expect.assert_debug_eq(&inlay_hints)
361 }
362
363 #[test]
364 fn param_hints_only() {
365 check_with_config(
366 InlayHintsConfig {
367 parameter_hints: true,
368 type_hints: false,
369 chaining_hints: false,
370 max_length: None,
371 },
372 r#"
373fn foo(a: i32, b: i32) -> i32 { a + b }
374fn main() {
375 let _x = foo(
376 4,
377 //^ a
378 4,
379 //^ b
380 );
381}"#,
382 );
383 }
384
385 #[test]
386 fn hints_disabled() {
387 check_with_config(
388 InlayHintsConfig {
389 type_hints: false,
390 parameter_hints: false,
391 chaining_hints: false,
392 max_length: None,
393 },
394 r#"
395fn foo(a: i32, b: i32) -> i32 { a + b }
396fn main() {
397 let _x = foo(4, 4);
398}"#,
399 );
400 }
401
402 #[test]
403 fn type_hints_only() {
404 check_with_config(
405 InlayHintsConfig {
406 type_hints: true,
407 parameter_hints: false,
408 chaining_hints: false,
409 max_length: None,
410 },
411 r#"
412fn foo(a: i32, b: i32) -> i32 { a + b }
413fn main() {
414 let _x = foo(4, 4);
415 //^^ i32
416}"#,
417 );
418 }
419
420 #[test]
421 fn default_generic_types_should_not_be_displayed() {
422 check(
423 r#"
424struct Test<K, T = u8> { k: K, t: T }
425
426fn main() {
427 let zz = Test { t: 23u8, k: 33 };
428 //^^ Test<i32>
429 let zz_ref = &zz;
430 //^^^^^^ &Test<i32>
431 let test = || zz;
432 //^^^^ || -> Test<i32>
433}"#,
434 );
435 }
436
437 #[test]
438 fn let_statement() {
439 check(
440 r#"
441#[derive(PartialEq)]
442enum Option<T> { None, Some(T) }
443
444#[derive(PartialEq)]
445struct Test { a: Option<u32>, b: u8 }
446
447fn main() {
448 struct InnerStruct {}
449
450 let test = 54;
451 //^^^^ i32
452 let test: i32 = 33;
453 let mut test = 33;
454 //^^^^^^^^ i32
455 let _ = 22;
456 let test = "test";
457 //^^^^ &str
458 let test = InnerStruct {};
459
460 let test = unresolved();
461
462 let test = (42, 'a');
463 //^^^^ (i32, char)
464 let (a, (b, (c,)) = (2, (3, (9.2,));
465 //^ i32 ^ i32 ^ f64
466 let &x = &92;
467 //^ i32
468}"#,
469 );
470 }
471
472 #[test]
473 fn closure_parameters() {
474 check(
475 r#"
476fn main() {
477 let mut start = 0;
478 //^^^^^^^^^ i32
479 (0..2).for_each(|increment| { start += increment; });
480 //^^^^^^^^^ i32
481
482 let multiply =
483 //^^^^^^^^ |…| -> i32
484 | a, b| a * b
485 //^ i32 ^ i32
486 ;
487
488 let _: i32 = multiply(1, 2);
489 let multiply_ref = &multiply;
490 //^^^^^^^^^^^^ &|…| -> i32
491
492 let return_42 = || 42;
493 //^^^^^^^^^ || -> i32
494}"#,
495 );
496 }
497
498 #[test]
499 fn for_expression() {
500 check(
501 r#"
502fn main() {
503 let mut start = 0;
504 //^^^^^^^^^ i32
505 for increment in 0..2 { start += increment; }
506 //^^^^^^^^^ i32
507}"#,
508 );
509 }
510
511 #[test]
512 fn if_expr() {
513 check(
514 r#"
515enum Option<T> { None, Some(T) }
516use Option::*;
517
518struct Test { a: Option<u32>, b: u8 }
519
520fn main() {
521 let test = Some(Test { a: Some(3), b: 1 });
522 //^^^^ Option<Test>
523 if let None = &test {};
524 if let test = &test {};
525 //^^^^ &Option<Test>
526 if let Some(test) = &test {};
527 //^^^^ &Test
528 if let Some(Test { a, b }) = &test {};
529 //^ &Option<u32> ^ &u8
530 if let Some(Test { a: x, b: y }) = &test {};
531 //^ &Option<u32> ^ &u8
532 if let Some(Test { a: Some(x), b: y }) = &test {};
533 //^ &u32 ^ &u8
534 if let Some(Test { a: None, b: y }) = &test {};
535 //^ &u8
536 if let Some(Test { b: y, .. }) = &test {};
537 //^ &u8
538 if test == None {}
539}"#,
540 );
541 }
542
543 #[test]
544 fn while_expr() {
545 check(
546 r#"
547enum Option<T> { None, Some(T) }
548use Option::*;
549
550struct Test { a: Option<u32>, b: u8 }
551
552fn main() {
553 let test = Some(Test { a: Some(3), b: 1 });
554 //^^^^ Option<Test>
555 while let Some(Test { a: Some(x), b: y }) = &test {};
556 //^ &u32 ^ &u8
557}"#,
558 );
559 }
560
561 #[test]
562 fn match_arm_list() {
563 check(
564 r#"
565enum Option<T> { None, Some(T) }
566use Option::*;
567
568struct Test { a: Option<u32>, b: u8 }
569
570fn main() {
571 match Some(Test { a: Some(3), b: 1 }) {
572 None => (),
573 test => (),
574 //^^^^ Option<Test>
575 Some(Test { a: Some(x), b: y }) => (),
576 //^ u32 ^ u8
577 _ => {}
578 }
579}"#,
580 );
581 }
582
583 #[test]
584 fn hint_truncation() {
585 check_with_config(
586 InlayHintsConfig { max_length: Some(8), ..Default::default() },
587 r#"
588struct Smol<T>(T);
589
590struct VeryLongOuterName<T>(T);
591
592fn main() {
593 let a = Smol(0u32);
594 //^ Smol<u32>
595 let b = VeryLongOuterName(0usize);
596 //^ VeryLongOuterName<…>
597 let c = Smol(Smol(0u32))
598 //^ Smol<Smol<…>>
599}"#,
600 );
601 }
602
603 #[test]
604 fn function_call_parameter_hint() {
605 check(
606 r#"
607enum Option<T> { None, Some(T) }
608use Option::*;
609
610struct FileId {}
611struct SmolStr {}
612
613struct TextRange {}
614struct SyntaxKind {}
615struct NavigationTarget {}
616
617struct Test {}
618
619impl Test {
620 fn method(&self, mut param: i32) -> i32 { param * 2 }
621
622 fn from_syntax(
623 file_id: FileId,
624 name: SmolStr,
625 focus_range: Option<TextRange>,
626 full_range: TextRange,
627 kind: SyntaxKind,
628 docs: Option<String>,
629 ) -> NavigationTarget {
630 NavigationTarget {}
631 }
632}
633
634fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
635 foo + bar
636}
637
638fn main() {
639 let not_literal = 1;
640 //^^^^^^^^^^^ i32
641 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
642 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
643 let t: Test = Test {};
644 t.method(123);
645 //^^^ param
646 Test::method(&t, 3456);
647 //^^ &self ^^^^ param
648 Test::from_syntax(
649 FileId {},
650 //^^^^^^^^^ file_id
651 "impl".into(),
652 //^^^^^^^^^^^^^ name
653 None,
654 //^^^^ focus_range
655 TextRange {},
656 //^^^^^^^^^^^^ full_range
657 SyntaxKind {},
658 //^^^^^^^^^^^^^ kind
659 None,
660 //^^^^ docs
661 );
662}"#,
663 );
664 }
665
666 #[test]
667 fn omitted_parameters_hints_heuristics() {
668 check_with_config(
669 InlayHintsConfig { max_length: Some(8), ..Default::default() },
670 r#"
671fn map(f: i32) {}
672fn filter(predicate: i32) {}
673
674struct TestVarContainer {
675 test_var: i32,
676}
677
678impl TestVarContainer {
679 fn test_var(&self) -> i32 {
680 self.test_var
681 }
682}
683
684struct Test {}
685
686impl Test {
687 fn map(self, f: i32) -> Self {
688 self
689 }
690
691 fn filter(self, predicate: i32) -> Self {
692 self
693 }
694
695 fn field(self, value: i32) -> Self {
696 self
697 }
698
699 fn no_hints_expected(&self, _: i32, test_var: i32) {}
700
701 fn frob(&self, frob: bool) {}
702}
703
704struct Param {}
705
706fn different_order(param: &Param) {}
707fn different_order_mut(param: &mut Param) {}
708fn has_underscore(_param: bool) {}
709fn enum_matches_param_name(completion_kind: CompletionKind) {}
710fn param_destructuring_omitted_1((a, b): (u32, u32)) {}
711fn param_destructuring_omitted_2(TestVarContainer { test_var: _ }: TestVarContainer) {}
712
713fn twiddle(twiddle: bool) {}
714fn doo(_doo: bool) {}
715
716enum CompletionKind {
717 Keyword,
718}
719
720fn main() {
721 let container: TestVarContainer = TestVarContainer { test_var: 42 };
722 let test: Test = Test {};
723
724 map(22);
725 filter(33);
726
727 let test_processed: Test = test.map(1).filter(2).field(3);
728
729 let test_var: i32 = 55;
730 test_processed.no_hints_expected(22, test_var);
731 test_processed.no_hints_expected(33, container.test_var);
732 test_processed.no_hints_expected(44, container.test_var());
733 test_processed.frob(false);
734
735 twiddle(true);
736 doo(true);
737
738 let mut param_begin: Param = Param {};
739 different_order(&param_begin);
740 different_order(&mut param_begin);
741
742 let param: bool = true;
743 has_underscore(param);
744
745 enum_matches_param_name(CompletionKind::Keyword);
746
747 let a: f64 = 7.0;
748 let b: f64 = 4.0;
749 let _: f64 = a.div_euclid(b);
750 let _: f64 = a.abs_sub(b);
751
752 let range: (u32, u32) = (3, 5);
753 param_destructuring_omitted_1(range);
754 param_destructuring_omitted_2(container);
755}"#,
756 );
757 }
758
759 #[test]
760 fn unit_structs_have_no_type_hints() {
761 check_with_config(
762 InlayHintsConfig { max_length: Some(8), ..Default::default() },
763 r#"
764enum Result<T, E> { Ok(T), Err(E) }
765use Result::*;
766
767struct SyntheticSyntax;
768
769fn main() {
770 match Ok(()) {
771 Ok(_) => (),
772 Err(SyntheticSyntax) => (),
773 }
774}"#,
775 );
776 }
777
778 #[test]
779 fn chaining_hints_ignore_comments() {
780 check_expect(
781 InlayHintsConfig {
782 parameter_hints: false,
783 type_hints: false,
784 chaining_hints: true,
785 max_length: None,
786 },
787 r#"
788struct A(B);
789impl A { fn into_b(self) -> B { self.0 } }
790struct B(C);
791impl B { fn into_c(self) -> C { self.0 } }
792struct C;
793
794fn main() {
795 let c = A(B(C))
796 .into_b() // This is a comment
797 .into_c();
798}
799"#,
800 expect![[r#"
801 [
802 InlayHint {
803 range: 147..172,
804 kind: ChainingHint,
805 label: "B",
806 },
807 InlayHint {
808 range: 147..154,
809 kind: ChainingHint,
810 label: "A",
811 },
812 ]
813 "#]],
814 );
815 }
816
817 #[test]
818 fn chaining_hints_without_newlines() {
819 check_with_config(
820 InlayHintsConfig {
821 parameter_hints: false,
822 type_hints: false,
823 chaining_hints: true,
824 max_length: None,
825 },
826 r#"
827struct A(B);
828impl A { fn into_b(self) -> B { self.0 } }
829struct B(C);
830impl B { fn into_c(self) -> C { self.0 } }
831struct C;
832
833fn main() {
834 let c = A(B(C)).into_b().into_c();
835}"#,
836 );
837 }
838
839 #[test]
840 fn struct_access_chaining_hints() {
841 check_expect(
842 InlayHintsConfig {
843 parameter_hints: false,
844 type_hints: false,
845 chaining_hints: true,
846 max_length: None,
847 },
848 r#"
849struct A { pub b: B }
850struct B { pub c: C }
851struct C(pub bool);
852struct D;
853
854impl D {
855 fn foo(&self) -> i32 { 42 }
856}
857
858fn main() {
859 let x = A { b: B { c: C(true) } }
860 .b
861 .c
862 .0;
863 let x = D
864 .foo();
865}"#,
866 expect![[r#"
867 [
868 InlayHint {
869 range: 143..190,
870 kind: ChainingHint,
871 label: "C",
872 },
873 InlayHint {
874 range: 143..179,
875 kind: ChainingHint,
876 label: "B",
877 },
878 ]
879 "#]],
880 );
881 }
882
883 #[test]
884 fn generic_chaining_hints() {
885 check_expect(
886 InlayHintsConfig {
887 parameter_hints: false,
888 type_hints: false,
889 chaining_hints: true,
890 max_length: None,
891 },
892 r#"
893struct A<T>(T);
894struct B<T>(T);
895struct C<T>(T);
896struct X<T,R>(T, R);
897
898impl<T> A<T> {
899 fn new(t: T) -> Self { A(t) }
900 fn into_b(self) -> B<T> { B(self.0) }
901}
902impl<T> B<T> {
903 fn into_c(self) -> C<T> { C(self.0) }
904}
905fn main() {
906 let c = A::new(X(42, true))
907 .into_b()
908 .into_c();
909}
910"#,
911 expect![[r#"
912 [
913 InlayHint {
914 range: 246..283,
915 kind: ChainingHint,
916 label: "B<X<i32, bool>>",
917 },
918 InlayHint {
919 range: 246..265,
920 kind: ChainingHint,
921 label: "A<X<i32, bool>>",
922 },
923 ]
924 "#]],
925 );
926 }
927}
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
new file mode 100644
index 000000000..e37702acd
--- /dev/null
+++ b/crates/ide/src/join_lines.rs
@@ -0,0 +1,773 @@
1use assists::utils::extract_trivial_expression;
2use itertools::Itertools;
3use syntax::{
4 algo::{find_covering_element, non_trivia_sibling},
5 ast::{self, AstNode, AstToken},
6 Direction, NodeOrToken, SourceFile,
7 SyntaxKind::{self, USE_TREE, WHITESPACE},
8 SyntaxNode, SyntaxToken, TextRange, TextSize, T,
9};
10use text_edit::{TextEdit, TextEditBuilder};
11
12// Feature: Join Lines
13//
14// Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
15//
16// |===
17// | Editor | Action Name
18//
19// | VS Code | **Rust Analyzer: Join lines**
20// |===
21pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
22 let range = if range.is_empty() {
23 let syntax = file.syntax();
24 let text = syntax.text().slice(range.start()..);
25 let pos = match text.find_char('\n') {
26 None => return TextEdit::builder().finish(),
27 Some(pos) => pos,
28 };
29 TextRange::at(range.start() + pos, TextSize::of('\n'))
30 } else {
31 range
32 };
33
34 let node = match find_covering_element(file.syntax(), range) {
35 NodeOrToken::Node(node) => node,
36 NodeOrToken::Token(token) => token.parent(),
37 };
38 let mut edit = TextEdit::builder();
39 for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) {
40 let range = match range.intersect(token.text_range()) {
41 Some(range) => range,
42 None => continue,
43 } - token.text_range().start();
44 let text = token.text();
45 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
46 let pos: TextSize = (pos as u32).into();
47 let off = token.text_range().start() + range.start() + pos;
48 if !edit.invalidates_offset(off) {
49 remove_newline(&mut edit, &token, off);
50 }
51 }
52 }
53
54 edit.finish()
55}
56
57fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) {
58 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
59 // The node is either the first or the last in the file
60 let suff = &token.text()[TextRange::new(
61 offset - token.text_range().start() + TextSize::of('\n'),
62 TextSize::of(token.text().as_str()),
63 )];
64 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
65
66 edit.replace(TextRange::at(offset, ((spaces + 1) as u32).into()), " ".to_string());
67 return;
68 }
69
70 // The node is between two other nodes
71 let prev = token.prev_sibling_or_token().unwrap();
72 let next = token.next_sibling_or_token().unwrap();
73 if is_trailing_comma(prev.kind(), next.kind()) {
74 // Removes: trailing comma, newline (incl. surrounding whitespace)
75 edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end()));
76 return;
77 }
78 if prev.kind() == T![,] && next.kind() == T!['}'] {
79 // Removes: comma, newline (incl. surrounding whitespace)
80 let space = if let Some(left) = prev.prev_sibling_or_token() {
81 compute_ws(left.kind(), next.kind())
82 } else {
83 " "
84 };
85 edit.replace(
86 TextRange::new(prev.text_range().start(), token.text_range().end()),
87 space.to_string(),
88 );
89 return;
90 }
91
92 if let (Some(_), Some(next)) = (
93 prev.as_token().cloned().and_then(ast::Comment::cast),
94 next.as_token().cloned().and_then(ast::Comment::cast),
95 ) {
96 // Removes: newline (incl. surrounding whitespace), start of the next comment
97 edit.delete(TextRange::new(
98 token.text_range().start(),
99 next.syntax().text_range().start() + TextSize::of(next.prefix()),
100 ));
101 return;
102 }
103
104 // Special case that turns something like:
105 //
106 // ```
107 // my_function({<|>
108 // <some-expr>
109 // })
110 // ```
111 //
112 // into `my_function(<some-expr>)`
113 if join_single_expr_block(edit, token).is_some() {
114 return;
115 }
116 // ditto for
117 //
118 // ```
119 // use foo::{<|>
120 // bar
121 // };
122 // ```
123 if join_single_use_tree(edit, token).is_some() {
124 return;
125 }
126
127 // Remove newline but add a computed amount of whitespace characters
128 edit.replace(token.text_range(), compute_ws(prev.kind(), next.kind()).to_string());
129}
130
131fn has_comma_after(node: &SyntaxNode) -> bool {
132 match non_trivia_sibling(node.clone().into(), Direction::Next) {
133 Some(n) => n.kind() == T![,],
134 _ => false,
135 }
136}
137
138fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
139 let block_expr = ast::BlockExpr::cast(token.parent())?;
140 if !block_expr.is_standalone() {
141 return None;
142 }
143 let expr = extract_trivial_expression(&block_expr)?;
144
145 let block_range = block_expr.syntax().text_range();
146 let mut buf = expr.syntax().text().to_string();
147
148 // Match block needs to have a comma after the block
149 if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) {
150 if !has_comma_after(match_arm.syntax()) {
151 buf.push(',');
152 }
153 }
154
155 edit.replace(block_range, buf);
156
157 Some(())
158}
159
160fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
161 let use_tree_list = ast::UseTreeList::cast(token.parent())?;
162 let (tree,) = use_tree_list.use_trees().collect_tuple()?;
163 edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string());
164 Some(())
165}
166
167fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
168 matches!((left, right), (T![,], T![')']) | (T![,], T![']']))
169}
170
171fn compute_ws(left: SyntaxKind, right: SyntaxKind) -> &'static str {
172 match left {
173 T!['('] | T!['['] => return "",
174 T!['{'] => {
175 if let USE_TREE = right {
176 return "";
177 }
178 }
179 _ => (),
180 }
181 match right {
182 T![')'] | T![']'] => return "",
183 T!['}'] => {
184 if let USE_TREE = left {
185 return "";
186 }
187 }
188 T![.] => return "",
189 _ => (),
190 }
191 " "
192}
193
194#[cfg(test)]
195mod tests {
196 use syntax::SourceFile;
197 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
198
199 use super::*;
200
201 fn check_join_lines(before: &str, after: &str) {
202 let (before_cursor_pos, before) = extract_offset(before);
203 let file = SourceFile::parse(&before).ok().unwrap();
204
205 let range = TextRange::empty(before_cursor_pos);
206 let result = join_lines(&file, range);
207
208 let actual = {
209 let mut actual = before.to_string();
210 result.apply(&mut actual);
211 actual
212 };
213 let actual_cursor_pos = result
214 .apply_to_offset(before_cursor_pos)
215 .expect("cursor position is affected by the edit");
216 let actual = add_cursor(&actual, actual_cursor_pos);
217 assert_eq_text!(after, &actual);
218 }
219
220 #[test]
221 fn test_join_lines_comma() {
222 check_join_lines(
223 r"
224fn foo() {
225 <|>foo(1,
226 )
227}
228",
229 r"
230fn foo() {
231 <|>foo(1)
232}
233",
234 );
235 }
236
237 #[test]
238 fn test_join_lines_lambda_block() {
239 check_join_lines(
240 r"
241pub fn reparse(&self, edit: &AtomTextEdit) -> File {
242 <|>self.incremental_reparse(edit).unwrap_or_else(|| {
243 self.full_reparse(edit)
244 })
245}
246",
247 r"
248pub fn reparse(&self, edit: &AtomTextEdit) -> File {
249 <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
250}
251",
252 );
253 }
254
255 #[test]
256 fn test_join_lines_block() {
257 check_join_lines(
258 r"
259fn foo() {
260 foo(<|>{
261 92
262 })
263}",
264 r"
265fn foo() {
266 foo(<|>92)
267}",
268 );
269 }
270
271 #[test]
272 fn test_join_lines_diverging_block() {
273 let before = r"
274 fn foo() {
275 loop {
276 match x {
277 92 => <|>{
278 continue;
279 }
280 }
281 }
282 }
283 ";
284 let after = r"
285 fn foo() {
286 loop {
287 match x {
288 92 => <|>continue,
289 }
290 }
291 }
292 ";
293 check_join_lines(before, after);
294 }
295
296 #[test]
297 fn join_lines_adds_comma_for_block_in_match_arm() {
298 check_join_lines(
299 r"
300fn foo(e: Result<U, V>) {
301 match e {
302 Ok(u) => <|>{
303 u.foo()
304 }
305 Err(v) => v,
306 }
307}",
308 r"
309fn foo(e: Result<U, V>) {
310 match e {
311 Ok(u) => <|>u.foo(),
312 Err(v) => v,
313 }
314}",
315 );
316 }
317
318 #[test]
319 fn join_lines_multiline_in_block() {
320 check_join_lines(
321 r"
322fn foo() {
323 match ty {
324 <|> Some(ty) => {
325 match ty {
326 _ => false,
327 }
328 }
329 _ => true,
330 }
331}
332",
333 r"
334fn foo() {
335 match ty {
336 <|> Some(ty) => match ty {
337 _ => false,
338 },
339 _ => true,
340 }
341}
342",
343 );
344 }
345
346 #[test]
347 fn join_lines_keeps_comma_for_block_in_match_arm() {
348 // We already have a comma
349 check_join_lines(
350 r"
351fn foo(e: Result<U, V>) {
352 match e {
353 Ok(u) => <|>{
354 u.foo()
355 },
356 Err(v) => v,
357 }
358}",
359 r"
360fn foo(e: Result<U, V>) {
361 match e {
362 Ok(u) => <|>u.foo(),
363 Err(v) => v,
364 }
365}",
366 );
367
368 // comma with whitespace between brace and ,
369 check_join_lines(
370 r"
371fn foo(e: Result<U, V>) {
372 match e {
373 Ok(u) => <|>{
374 u.foo()
375 } ,
376 Err(v) => v,
377 }
378}",
379 r"
380fn foo(e: Result<U, V>) {
381 match e {
382 Ok(u) => <|>u.foo() ,
383 Err(v) => v,
384 }
385}",
386 );
387
388 // comma with newline between brace and ,
389 check_join_lines(
390 r"
391fn foo(e: Result<U, V>) {
392 match e {
393 Ok(u) => <|>{
394 u.foo()
395 }
396 ,
397 Err(v) => v,
398 }
399}",
400 r"
401fn foo(e: Result<U, V>) {
402 match e {
403 Ok(u) => <|>u.foo()
404 ,
405 Err(v) => v,
406 }
407}",
408 );
409 }
410
411 #[test]
412 fn join_lines_keeps_comma_with_single_arg_tuple() {
413 // A single arg tuple
414 check_join_lines(
415 r"
416fn foo() {
417 let x = (<|>{
418 4
419 },);
420}",
421 r"
422fn foo() {
423 let x = (<|>4,);
424}",
425 );
426
427 // single arg tuple with whitespace between brace and comma
428 check_join_lines(
429 r"
430fn foo() {
431 let x = (<|>{
432 4
433 } ,);
434}",
435 r"
436fn foo() {
437 let x = (<|>4 ,);
438}",
439 );
440
441 // single arg tuple with newline between brace and comma
442 check_join_lines(
443 r"
444fn foo() {
445 let x = (<|>{
446 4
447 }
448 ,);
449}",
450 r"
451fn foo() {
452 let x = (<|>4
453 ,);
454}",
455 );
456 }
457
458 #[test]
459 fn test_join_lines_use_items_left() {
460 // No space after the '{'
461 check_join_lines(
462 r"
463<|>use syntax::{
464 TextSize, TextRange,
465};",
466 r"
467<|>use syntax::{TextSize, TextRange,
468};",
469 );
470 }
471
472 #[test]
473 fn test_join_lines_use_items_right() {
474 // No space after the '}'
475 check_join_lines(
476 r"
477use syntax::{
478<|> TextSize, TextRange
479};",
480 r"
481use syntax::{
482<|> TextSize, TextRange};",
483 );
484 }
485
486 #[test]
487 fn test_join_lines_use_items_right_comma() {
488 // No space after the '}'
489 check_join_lines(
490 r"
491use syntax::{
492<|> TextSize, TextRange,
493};",
494 r"
495use syntax::{
496<|> TextSize, TextRange};",
497 );
498 }
499
500 #[test]
501 fn test_join_lines_use_tree() {
502 check_join_lines(
503 r"
504use syntax::{
505 algo::<|>{
506 find_token_at_offset,
507 },
508 ast,
509};",
510 r"
511use syntax::{
512 algo::<|>find_token_at_offset,
513 ast,
514};",
515 );
516 }
517
518 #[test]
519 fn test_join_lines_normal_comments() {
520 check_join_lines(
521 r"
522fn foo() {
523 // Hello<|>
524 // world!
525}
526",
527 r"
528fn foo() {
529 // Hello<|> world!
530}
531",
532 );
533 }
534
535 #[test]
536 fn test_join_lines_doc_comments() {
537 check_join_lines(
538 r"
539fn foo() {
540 /// Hello<|>
541 /// world!
542}
543",
544 r"
545fn foo() {
546 /// Hello<|> world!
547}
548",
549 );
550 }
551
552 #[test]
553 fn test_join_lines_mod_comments() {
554 check_join_lines(
555 r"
556fn foo() {
557 //! Hello<|>
558 //! world!
559}
560",
561 r"
562fn foo() {
563 //! Hello<|> world!
564}
565",
566 );
567 }
568
569 #[test]
570 fn test_join_lines_multiline_comments_1() {
571 check_join_lines(
572 r"
573fn foo() {
574 // Hello<|>
575 /* world! */
576}
577",
578 r"
579fn foo() {
580 // Hello<|> world! */
581}
582",
583 );
584 }
585
586 #[test]
587 fn test_join_lines_multiline_comments_2() {
588 check_join_lines(
589 r"
590fn foo() {
591 // The<|>
592 /* quick
593 brown
594 fox! */
595}
596",
597 r"
598fn foo() {
599 // The<|> quick
600 brown
601 fox! */
602}
603",
604 );
605 }
606
607 fn check_join_lines_sel(before: &str, after: &str) {
608 let (sel, before) = extract_range(before);
609 let parse = SourceFile::parse(&before);
610 let result = join_lines(&parse.tree(), sel);
611 let actual = {
612 let mut actual = before.to_string();
613 result.apply(&mut actual);
614 actual
615 };
616 assert_eq_text!(after, &actual);
617 }
618
619 #[test]
620 fn test_join_lines_selection_fn_args() {
621 check_join_lines_sel(
622 r"
623fn foo() {
624 <|>foo(1,
625 2,
626 3,
627 <|>)
628}
629 ",
630 r"
631fn foo() {
632 foo(1, 2, 3)
633}
634 ",
635 );
636 }
637
638 #[test]
639 fn test_join_lines_selection_struct() {
640 check_join_lines_sel(
641 r"
642struct Foo <|>{
643 f: u32,
644}<|>
645 ",
646 r"
647struct Foo { f: u32 }
648 ",
649 );
650 }
651
652 #[test]
653 fn test_join_lines_selection_dot_chain() {
654 check_join_lines_sel(
655 r"
656fn foo() {
657 join(<|>type_params.type_params()
658 .filter_map(|it| it.name())
659 .map(|it| it.text())<|>)
660}",
661 r"
662fn foo() {
663 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
664}",
665 );
666 }
667
668 #[test]
669 fn test_join_lines_selection_lambda_block_body() {
670 check_join_lines_sel(
671 r"
672pub fn handle_find_matching_brace() {
673 params.offsets
674 .map(|offset| <|>{
675 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
676 }<|>)
677 .collect();
678}",
679 r"
680pub fn handle_find_matching_brace() {
681 params.offsets
682 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
683 .collect();
684}",
685 );
686 }
687
688 #[test]
689 fn test_join_lines_commented_block() {
690 check_join_lines(
691 r"
692fn main() {
693 let _ = {
694 // <|>foo
695 // bar
696 92
697 };
698}
699 ",
700 r"
701fn main() {
702 let _ = {
703 // <|>foo bar
704 92
705 };
706}
707 ",
708 )
709 }
710
711 #[test]
712 fn join_lines_mandatory_blocks_block() {
713 check_join_lines(
714 r"
715<|>fn foo() {
716 92
717}
718 ",
719 r"
720<|>fn foo() { 92
721}
722 ",
723 );
724
725 check_join_lines(
726 r"
727fn foo() {
728 <|>if true {
729 92
730 }
731}
732 ",
733 r"
734fn foo() {
735 <|>if true { 92
736 }
737}
738 ",
739 );
740
741 check_join_lines(
742 r"
743fn foo() {
744 <|>loop {
745 92
746 }
747}
748 ",
749 r"
750fn foo() {
751 <|>loop { 92
752 }
753}
754 ",
755 );
756
757 check_join_lines(
758 r"
759fn foo() {
760 <|>unsafe {
761 92
762 }
763}
764 ",
765 r"
766fn foo() {
767 <|>unsafe { 92
768 }
769}
770 ",
771 );
772 }
773}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
new file mode 100644
index 000000000..570790384
--- /dev/null
+++ b/crates/ide/src/lib.rs
@@ -0,0 +1,516 @@
1//! ide crate provides "ide-centric" APIs for the rust-analyzer. That is,
2//! it generally operates with files and text ranges, and returns results as
3//! Strings, suitable for displaying to the human.
4//!
5//! What powers this API are the `RootDatabase` struct, which defines a `salsa`
6//! database, and the `hir` crate, where majority of the analysis happens.
7//! However, IDE specific bits of the analysis (most notably completion) happen
8//! in this crate.
9
10// For proving that RootDatabase is RefUnwindSafe.
11#![recursion_limit = "128"]
12
13#[allow(unused)]
14macro_rules! eprintln {
15 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
16}
17
18pub mod mock_analysis;
19
20mod markup;
21mod prime_caches;
22mod display;
23
24mod call_hierarchy;
25mod call_info;
26mod completion;
27mod diagnostics;
28mod expand_macro;
29mod extend_selection;
30mod file_structure;
31mod folding_ranges;
32mod goto_definition;
33mod goto_implementation;
34mod goto_type_definition;
35mod hover;
36mod inlay_hints;
37mod join_lines;
38mod matching_brace;
39mod parent_module;
40mod references;
41mod runnables;
42mod status;
43mod syntax_highlighting;
44mod syntax_tree;
45mod typing;
46mod link_rewrite;
47
48use std::sync::Arc;
49
50use base_db::{
51 salsa::{self, ParallelDatabase},
52 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
53};
54use cfg::CfgOptions;
55use ide_db::{
56 symbol_index::{self, FileSymbol},
57 LineIndexDatabase,
58};
59use syntax::{SourceFile, TextRange, TextSize};
60
61use crate::display::ToNav;
62
63pub use crate::{
64 call_hierarchy::CallItem,
65 call_info::CallInfo,
66 completion::{
67 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
68 },
69 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
70 display::NavigationTarget,
71 expand_macro::ExpandedMacro,
72 file_structure::StructureNode,
73 folding_ranges::{Fold, FoldKind},
74 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
75 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
76 markup::Markup,
77 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
78 runnables::{Runnable, RunnableKind, TestId},
79 syntax_highlighting::{
80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
81 },
82};
83
84pub use assists::{Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist};
85pub use base_db::{
86 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
87 SourceRootId,
88};
89pub use hir::{Documentation, Semantics};
90pub use ide_db::{
91 change::AnalysisChange,
92 label::Label,
93 line_index::{LineCol, LineIndex},
94 search::SearchScope,
95 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
96 symbol_index::Query,
97 RootDatabase,
98};
99pub use ssr::SsrError;
100pub use text_edit::{Indel, TextEdit};
101
102pub type Cancelable<T> = Result<T, Canceled>;
103
104/// Info associated with a text range.
105#[derive(Debug)]
106pub struct RangeInfo<T> {
107 pub range: TextRange,
108 pub info: T,
109}
110
111impl<T> RangeInfo<T> {
112 pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
113 RangeInfo { range, info }
114 }
115}
116
117/// `AnalysisHost` stores the current state of the world.
118#[derive(Debug)]
119pub struct AnalysisHost {
120 db: RootDatabase,
121}
122
123impl AnalysisHost {
124 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
125 AnalysisHost { db: RootDatabase::new(lru_capacity) }
126 }
127
128 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
129 self.db.update_lru_capacity(lru_capacity);
130 }
131
132 /// Returns a snapshot of the current state, which you can query for
133 /// semantic information.
134 pub fn analysis(&self) -> Analysis {
135 Analysis { db: self.db.snapshot() }
136 }
137
138 /// Applies changes to the current state of the world. If there are
139 /// outstanding snapshots, they will be canceled.
140 pub fn apply_change(&mut self, change: AnalysisChange) {
141 self.db.apply_change(change)
142 }
143
144 pub fn maybe_collect_garbage(&mut self) {
145 self.db.maybe_collect_garbage();
146 }
147
148 pub fn collect_garbage(&mut self) {
149 self.db.collect_garbage();
150 }
151 /// NB: this clears the database
152 pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes)> {
153 self.db.per_query_memory_usage()
154 }
155 pub fn request_cancellation(&mut self) {
156 self.db.request_cancellation();
157 }
158 pub fn raw_database(&self) -> &RootDatabase {
159 &self.db
160 }
161 pub fn raw_database_mut(&mut self) -> &mut RootDatabase {
162 &mut self.db
163 }
164}
165
166impl Default for AnalysisHost {
167 fn default() -> AnalysisHost {
168 AnalysisHost::new(None)
169 }
170}
171
172/// Analysis is a snapshot of a world state at a moment in time. It is the main
173/// entry point for asking semantic information about the world. When the world
174/// state is advanced using `AnalysisHost::apply_change` method, all existing
175/// `Analysis` are canceled (most method return `Err(Canceled)`).
176#[derive(Debug)]
177pub struct Analysis {
178 db: salsa::Snapshot<RootDatabase>,
179}
180
181// As a general design guideline, `Analysis` API are intended to be independent
182// from the language server protocol. That is, when exposing some functionality
183// we should think in terms of "what API makes most sense" and not in terms of
184// "what types LSP uses". Although currently LSP is the only consumer of the
185// API, the API should in theory be usable as a library, or via a different
186// protocol.
187impl Analysis {
188 // Creates an analysis instance for a single file, without any extenal
189 // dependencies, stdlib support or ability to apply changes. See
190 // `AnalysisHost` for creating a fully-featured analysis.
191 pub fn from_single_file(text: String) -> (Analysis, FileId) {
192 let mut host = AnalysisHost::default();
193 let file_id = FileId(0);
194 let mut file_set = FileSet::default();
195 file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
196 let source_root = SourceRoot::new_local(file_set);
197
198 let mut change = AnalysisChange::new();
199 change.set_roots(vec![source_root]);
200 let mut crate_graph = CrateGraph::default();
201 // FIXME: cfg options
202 // Default to enable test for single file.
203 let mut cfg_options = CfgOptions::default();
204 cfg_options.insert_atom("test".into());
205 crate_graph.add_crate_root(
206 file_id,
207 Edition::Edition2018,
208 None,
209 cfg_options,
210 Env::default(),
211 Default::default(),
212 );
213 change.change_file(file_id, Some(Arc::new(text)));
214 change.set_crate_graph(crate_graph);
215 host.apply_change(change);
216 (host.analysis(), file_id)
217 }
218
219 /// Debug info about the current state of the analysis.
220 pub fn status(&self) -> Cancelable<String> {
221 self.with_db(|db| status::status(&*db))
222 }
223
224 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
225 self.with_db(|db| prime_caches::prime_caches(db, files))
226 }
227
228 /// Gets the text of the source file.
229 pub fn file_text(&self, file_id: FileId) -> Cancelable<Arc<String>> {
230 self.with_db(|db| db.file_text(file_id))
231 }
232
233 /// Gets the syntax tree of the file.
234 pub fn parse(&self, file_id: FileId) -> Cancelable<SourceFile> {
235 self.with_db(|db| db.parse(file_id).tree())
236 }
237
238 /// Gets the file's `LineIndex`: data structure to convert between absolute
239 /// offsets and line/column representation.
240 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
241 self.with_db(|db| db.line_index(file_id))
242 }
243
244 /// Selects the next syntactic nodes encompassing the range.
245 pub fn extend_selection(&self, frange: FileRange) -> Cancelable<TextRange> {
246 self.with_db(|db| extend_selection::extend_selection(db, frange))
247 }
248
249 /// Returns position of the matching brace (all types of braces are
250 /// supported).
251 pub fn matching_brace(&self, position: FilePosition) -> Cancelable<Option<TextSize>> {
252 self.with_db(|db| {
253 let parse = db.parse(position.file_id);
254 let file = parse.tree();
255 matching_brace::matching_brace(&file, position.offset)
256 })
257 }
258
259 /// Returns a syntax tree represented as `String`, for debug purposes.
260 // FIXME: use a better name here.
261 pub fn syntax_tree(
262 &self,
263 file_id: FileId,
264 text_range: Option<TextRange>,
265 ) -> Cancelable<String> {
266 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
267 }
268
269 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
270 self.with_db(|db| expand_macro::expand_macro(db, position))
271 }
272
273 /// Returns an edit to remove all newlines in the range, cleaning up minor
274 /// stuff like trailing commas.
275 pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
276 self.with_db(|db| {
277 let parse = db.parse(frange.file_id);
278 join_lines::join_lines(&parse.tree(), frange.range)
279 })
280 }
281
282 /// Returns an edit which should be applied when opening a new line, fixing
283 /// up minor stuff like continuing the comment.
284 /// The edit will be a snippet (with `$0`).
285 pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> {
286 self.with_db(|db| typing::on_enter(&db, position))
287 }
288
289 /// Returns an edit which should be applied after a character was typed.
290 ///
291 /// This is useful for some on-the-fly fixups, like adding `;` to `let =`
292 /// automatically.
293 pub fn on_char_typed(
294 &self,
295 position: FilePosition,
296 char_typed: char,
297 ) -> Cancelable<Option<SourceChange>> {
298 // Fast path to not even parse the file.
299 if !typing::TRIGGER_CHARS.contains(char_typed) {
300 return Ok(None);
301 }
302 self.with_db(|db| typing::on_char_typed(&db, position, char_typed))
303 }
304
305 /// Returns a tree representation of symbols in the file. Useful to draw a
306 /// file outline.
307 pub fn file_structure(&self, file_id: FileId) -> Cancelable<Vec<StructureNode>> {
308 self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree()))
309 }
310
311 /// Returns a list of the places in the file where type hints can be displayed.
312 pub fn inlay_hints(
313 &self,
314 file_id: FileId,
315 config: &InlayHintsConfig,
316 ) -> Cancelable<Vec<InlayHint>> {
317 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
318 }
319
320 /// Returns the set of folding ranges.
321 pub fn folding_ranges(&self, file_id: FileId) -> Cancelable<Vec<Fold>> {
322 self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
323 }
324
325 /// Fuzzy searches for a symbol.
326 pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> {
327 self.with_db(|db| {
328 symbol_index::world_symbols(db, query)
329 .into_iter()
330 .map(|s| s.to_nav(db))
331 .collect::<Vec<_>>()
332 })
333 }
334
335 /// Returns the definitions from the symbol at `position`.
336 pub fn goto_definition(
337 &self,
338 position: FilePosition,
339 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
340 self.with_db(|db| goto_definition::goto_definition(db, position))
341 }
342
343 /// Returns the impls from the symbol at `position`.
344 pub fn goto_implementation(
345 &self,
346 position: FilePosition,
347 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
348 self.with_db(|db| goto_implementation::goto_implementation(db, position))
349 }
350
351 /// Returns the type definitions for the symbol at `position`.
352 pub fn goto_type_definition(
353 &self,
354 position: FilePosition,
355 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
356 self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
357 }
358
359 /// Finds all usages of the reference at point.
360 pub fn find_all_refs(
361 &self,
362 position: FilePosition,
363 search_scope: Option<SearchScope>,
364 ) -> Cancelable<Option<ReferenceSearchResult>> {
365 self.with_db(|db| {
366 references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info)
367 })
368 }
369
370 /// Returns a short text describing element at position.
371 pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> {
372 self.with_db(|db| hover::hover(db, position))
373 }
374
375 /// Computes parameter information for the given call expression.
376 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
377 self.with_db(|db| call_info::call_info(db, position))
378 }
379
380 /// Computes call hierarchy candidates for the given file position.
381 pub fn call_hierarchy(
382 &self,
383 position: FilePosition,
384 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
385 self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
386 }
387
388 /// Computes incoming calls for the given file position.
389 pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
390 self.with_db(|db| call_hierarchy::incoming_calls(db, position))
391 }
392
393 /// Computes incoming calls for the given file position.
394 pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
395 self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
396 }
397
398 /// Returns a `mod name;` declaration which created the current module.
399 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
400 self.with_db(|db| parent_module::parent_module(db, position))
401 }
402
403 /// Returns crates this file belongs too.
404 pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
405 self.with_db(|db| parent_module::crate_for(db, file_id))
406 }
407
408 /// Returns the edition of the given crate.
409 pub fn crate_edition(&self, crate_id: CrateId) -> Cancelable<Edition> {
410 self.with_db(|db| db.crate_graph()[crate_id].edition)
411 }
412
413 /// Returns the root file of the given crate.
414 pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> {
415 self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
416 }
417
418 /// Returns the set of possible targets to run for the current file.
419 pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
420 self.with_db(|db| runnables::runnables(db, file_id))
421 }
422
423 /// Computes syntax highlighting for the given file
424 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
425 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
426 }
427
428 /// Computes syntax highlighting for the given file range.
429 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> {
430 self.with_db(|db| {
431 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
432 })
433 }
434
435 /// Computes syntax highlighting for the given file.
436 pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable<String> {
437 self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
438 }
439
440 /// Computes completions at the given position.
441 pub fn completions(
442 &self,
443 config: &CompletionConfig,
444 position: FilePosition,
445 ) -> Cancelable<Option<Vec<CompletionItem>>> {
446 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
447 }
448
449 /// Computes resolved assists with source changes for the given position.
450 pub fn resolved_assists(
451 &self,
452 config: &AssistConfig,
453 frange: FileRange,
454 ) -> Cancelable<Vec<ResolvedAssist>> {
455 self.with_db(|db| assists::Assist::resolved(db, config, frange))
456 }
457
458 /// Computes unresolved assists (aka code actions aka intentions) for the given
459 /// position.
460 pub fn unresolved_assists(
461 &self,
462 config: &AssistConfig,
463 frange: FileRange,
464 ) -> Cancelable<Vec<Assist>> {
465 self.with_db(|db| Assist::unresolved(db, config, frange))
466 }
467
468 /// Computes the set of diagnostics for the given file.
469 pub fn diagnostics(
470 &self,
471 config: &DiagnosticsConfig,
472 file_id: FileId,
473 ) -> Cancelable<Vec<Diagnostic>> {
474 self.with_db(|db| diagnostics::diagnostics(db, config, file_id))
475 }
476
477 /// Returns the edit required to rename reference at the position to the new
478 /// name.
479 pub fn rename(
480 &self,
481 position: FilePosition,
482 new_name: &str,
483 ) -> Cancelable<Option<RangeInfo<SourceChange>>> {
484 self.with_db(|db| references::rename(db, position, new_name))
485 }
486
487 pub fn structural_search_replace(
488 &self,
489 query: &str,
490 parse_only: bool,
491 resolve_context: FilePosition,
492 selections: Vec<FileRange>,
493 ) -> Cancelable<Result<SourceChange, SsrError>> {
494 self.with_db(|db| {
495 let rule: ssr::SsrRule = query.parse()?;
496 let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections);
497 match_finder.add_rule(rule)?;
498 let edits = if parse_only { Vec::new() } else { match_finder.edits() };
499 Ok(SourceChange::from(edits))
500 })
501 }
502
503 /// Performs an operation on that may be Canceled.
504 fn with_db<F, T>(&self, f: F) -> Cancelable<T>
505 where
506 F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe,
507 {
508 self.db.catch_canceled(f)
509 }
510}
511
512#[test]
513fn analysis_is_send() {
514 fn is_send<T: Send>() {}
515 is_send::<Analysis>();
516}
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
new file mode 100644
index 000000000..a826220e3
--- /dev/null
+++ b/crates/ide/src/link_rewrite.rs
@@ -0,0 +1,79 @@
1//! This is a wrapper around [`hir::link_rewrite`] connecting it to the markdown parser.
2
3use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
4use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
5
6use hir::resolve_doc_link;
7use ide_db::{defs::Definition, RootDatabase};
8
9/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
10pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
11 let doc = Parser::new_with_broken_link_callback(
12 markdown,
13 Options::empty(),
14 Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
15 );
16
17 let doc = map_links(doc, |target, title: &str| {
18 // This check is imperfect, there's some overlap between valid intra-doc links
19 // and valid URLs so we choose to be too eager to try to resolve what might be
20 // a URL.
21 if target.contains("://") {
22 (target.to_string(), title.to_string())
23 } else {
24 // Two posibilities:
25 // * path-based links: `../../module/struct.MyStruct.html`
26 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
27 let resolved = match definition {
28 Definition::ModuleDef(t) => resolve_doc_link(db, t, title, target),
29 Definition::Macro(t) => resolve_doc_link(db, t, title, target),
30 Definition::Field(t) => resolve_doc_link(db, t, title, target),
31 Definition::SelfType(t) => resolve_doc_link(db, t, title, target),
32 Definition::Local(t) => resolve_doc_link(db, t, title, target),
33 Definition::TypeParam(t) => resolve_doc_link(db, t, title, target),
34 };
35
36 match resolved {
37 Some((target, title)) => (target, title),
38 None => (target.to_string(), title.to_string()),
39 }
40 }
41 });
42 let mut out = String::new();
43 let mut options = CmarkOptions::default();
44 options.code_block_backticks = 3;
45 cmark_with_options(doc, &mut out, None, options).ok();
46 out
47}
48
49// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
50fn map_links<'e>(
51 events: impl Iterator<Item = Event<'e>>,
52 callback: impl Fn(&str, &str) -> (String, String),
53) -> impl Iterator<Item = Event<'e>> {
54 let mut in_link = false;
55 let mut link_target: Option<CowStr> = None;
56
57 events.map(move |evt| match evt {
58 Event::Start(Tag::Link(_link_type, ref target, _)) => {
59 in_link = true;
60 link_target = Some(target.clone());
61 evt
62 }
63 Event::End(Tag::Link(link_type, _target, _)) => {
64 in_link = false;
65 Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
66 }
67 Event::Text(s) if in_link => {
68 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
69 link_target = Some(CowStr::Boxed(link_target_s.into()));
70 Event::Text(CowStr::Boxed(link_name.into()))
71 }
72 Event::Code(s) if in_link => {
73 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
74 link_target = Some(CowStr::Boxed(link_target_s.into()));
75 Event::Code(CowStr::Boxed(link_name.into()))
76 }
77 _ => evt,
78 })
79}
diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs
new file mode 100644
index 000000000..60c193c40
--- /dev/null
+++ b/crates/ide/src/markup.rs
@@ -0,0 +1,38 @@
1//! Markdown formatting.
2//!
3//! Sometimes, we want to display a "rich text" in the UI. At the moment, we use
4//! markdown for this purpose. It doesn't feel like a right option, but that's
5//! what is used by LSP, so let's keep it simple.
6use std::fmt;
7
8#[derive(Default, Debug)]
9pub struct Markup {
10 text: String,
11}
12
13impl From<Markup> for String {
14 fn from(markup: Markup) -> Self {
15 markup.text
16 }
17}
18
19impl From<String> for Markup {
20 fn from(text: String) -> Self {
21 Markup { text }
22 }
23}
24
25impl fmt::Display for Markup {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 fmt::Display::fmt(&self.text, f)
28 }
29}
30
31impl Markup {
32 pub fn as_str(&self) -> &str {
33 self.text.as_str()
34 }
35 pub fn fenced_block(contents: &impl fmt::Display) -> Markup {
36 format!("```rust\n{}\n```", contents).into()
37 }
38}
diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs
new file mode 100644
index 000000000..cb6abb0db
--- /dev/null
+++ b/crates/ide/src/matching_brace.rs
@@ -0,0 +1,73 @@
1use syntax::{
2 ast::{self, AstNode},
3 SourceFile, SyntaxKind, TextSize, T,
4};
5use test_utils::mark;
6
7// Feature: Matching Brace
8//
9// If the cursor is on any brace (`<>(){}[]||`) which is a part of a brace-pair,
10// moves cursor to the matching brace. It uses the actual parser to determine
11// braces, so it won't confuse generics with comparisons.
12//
13// |===
14// | Editor | Action Name
15//
16// | VS Code | **Rust Analyzer: Find matching brace**
17// |===
18pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
19 const BRACES: &[SyntaxKind] =
20 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
21 let (brace_token, brace_idx) = file
22 .syntax()
23 .token_at_offset(offset)
24 .filter_map(|node| {
25 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
26 Some((node, idx))
27 })
28 .next()?;
29 let parent = brace_token.parent();
30 if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
31 mark::hit!(pipes_not_braces);
32 return None;
33 }
34 let matching_kind = BRACES[brace_idx ^ 1];
35 let matching_node = parent
36 .children_with_tokens()
37 .filter_map(|it| it.into_token())
38 .find(|node| node.kind() == matching_kind && node != &brace_token)?;
39 Some(matching_node.text_range().start())
40}
41
42#[cfg(test)]
43mod tests {
44 use test_utils::{add_cursor, assert_eq_text, extract_offset};
45
46 use super::*;
47
48 #[test]
49 fn test_matching_brace() {
50 fn do_check(before: &str, after: &str) {
51 let (pos, before) = extract_offset(before);
52 let parse = SourceFile::parse(&before);
53 let new_pos = match matching_brace(&parse.tree(), pos) {
54 None => pos,
55 Some(pos) => pos,
56 };
57 let actual = add_cursor(&before, new_pos);
58 assert_eq_text!(after, &actual);
59 }
60
61 do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }");
62 do_check("fn main() { |x: i32|<|> x * 2;}", "fn main() { <|>|x: i32| x * 2;}");
63 do_check("fn main() { <|>|x: i32| x * 2;}", "fn main() { |x: i32<|>| x * 2;}");
64
65 {
66 mark::check!(pipes_not_braces);
67 do_check(
68 "fn main() { match 92 { 1 | 2 |<|> 3 => 92 } }",
69 "fn main() { match 92 { 1 | 2 |<|> 3 => 92 } }",
70 );
71 }
72 }
73}
diff --git a/crates/ide/src/mock_analysis.rs b/crates/ide/src/mock_analysis.rs
new file mode 100644
index 000000000..235796dbc
--- /dev/null
+++ b/crates/ide/src/mock_analysis.rs
@@ -0,0 +1,176 @@
1//! FIXME: write short doc here
2use std::sync::Arc;
3
4use base_db::{CrateName, FileSet, SourceRoot, VfsPath};
5use cfg::CfgOptions;
6use test_utils::{
7 extract_annotations, extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER,
8};
9
10use crate::{
11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
12};
13
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files.
16#[derive(Debug, Default)]
17pub struct MockAnalysis {
18 files: Vec<Fixture>,
19}
20
21impl MockAnalysis {
22 /// Creates `MockAnalysis` using a fixture data in the following format:
23 ///
24 /// ```not_rust
25 /// //- /main.rs
26 /// mod foo;
27 /// fn main() {}
28 ///
29 /// //- /foo.rs
30 /// struct Baz;
31 /// ```
32 pub fn with_files(ra_fixture: &str) -> MockAnalysis {
33 let (res, pos) = MockAnalysis::with_fixture(ra_fixture);
34 assert!(pos.is_none());
35 res
36 }
37
38 /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
39 /// whose position is also returned.
40 pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
41 let (res, position) = MockAnalysis::with_fixture(fixture);
42 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
43 let offset = match range_or_offset {
44 RangeOrOffset::Range(_) => panic!(),
45 RangeOrOffset::Offset(it) => it,
46 };
47 (res, FilePosition { file_id, offset })
48 }
49
50 fn with_fixture(fixture: &str) -> (MockAnalysis, Option<(FileId, RangeOrOffset)>) {
51 let mut position = None;
52 let mut res = MockAnalysis::default();
53 for mut entry in Fixture::parse(fixture) {
54 if entry.text.contains(CURSOR_MARKER) {
55 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
56 let (range_or_offset, text) = extract_range_or_offset(&entry.text);
57 entry.text = text;
58 let file_id = res.add_file_fixture(entry);
59 position = Some((file_id, range_or_offset));
60 } else {
61 res.add_file_fixture(entry);
62 }
63 }
64 (res, position)
65 }
66
67 fn add_file_fixture(&mut self, fixture: Fixture) -> FileId {
68 let file_id = FileId((self.files.len() + 1) as u32);
69 self.files.push(fixture);
70 file_id
71 }
72
73 pub fn id_of(&self, path: &str) -> FileId {
74 let (file_id, _) =
75 self.files().find(|(_, data)| path == data.path).expect("no file in this mock");
76 file_id
77 }
78 pub fn annotations(&self) -> Vec<(FileRange, String)> {
79 self.files()
80 .flat_map(|(file_id, fixture)| {
81 let annotations = extract_annotations(&fixture.text);
82 annotations
83 .into_iter()
84 .map(move |(range, data)| (FileRange { file_id, range }, data))
85 })
86 .collect()
87 }
88 pub fn files(&self) -> impl Iterator<Item = (FileId, &Fixture)> + '_ {
89 self.files.iter().enumerate().map(|(idx, fixture)| (FileId(idx as u32 + 1), fixture))
90 }
91 pub fn annotation(&self) -> (FileRange, String) {
92 let mut all = self.annotations();
93 assert_eq!(all.len(), 1);
94 all.pop().unwrap()
95 }
96 pub fn analysis_host(self) -> AnalysisHost {
97 let mut host = AnalysisHost::default();
98 let mut change = AnalysisChange::new();
99 let mut file_set = FileSet::default();
100 let mut crate_graph = CrateGraph::default();
101 let mut root_crate = None;
102 for (i, data) in self.files.into_iter().enumerate() {
103 let path = data.path;
104 assert!(path.starts_with('/'));
105
106 let mut cfg = CfgOptions::default();
107 data.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
108 data.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
109 let edition: Edition =
110 data.edition.and_then(|it| it.parse().ok()).unwrap_or(Edition::Edition2018);
111
112 let file_id = FileId(i as u32 + 1);
113 let env = data.env.into_iter().collect();
114 if path == "/lib.rs" || path == "/main.rs" {
115 root_crate = Some(crate_graph.add_crate_root(
116 file_id,
117 edition,
118 Some("test".to_string()),
119 cfg,
120 env,
121 Default::default(),
122 ));
123 } else if path.ends_with("/lib.rs") {
124 let base = &path[..path.len() - "/lib.rs".len()];
125 let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
126 let other_crate = crate_graph.add_crate_root(
127 file_id,
128 edition,
129 Some(crate_name.to_string()),
130 cfg,
131 env,
132 Default::default(),
133 );
134 if let Some(root_crate) = root_crate {
135 crate_graph
136 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
137 .unwrap();
138 }
139 }
140 let path = VfsPath::new_virtual_path(path.to_string());
141 file_set.insert(file_id, path);
142 change.change_file(file_id, Some(Arc::new(data.text).to_owned()));
143 }
144 change.set_crate_graph(crate_graph);
145 change.set_roots(vec![SourceRoot::new_local(file_set)]);
146 host.apply_change(change);
147 host
148 }
149 pub fn analysis(self) -> Analysis {
150 self.analysis_host().analysis()
151 }
152}
153
154/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
155pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
156 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
157 (mock.analysis(), position)
158}
159
160/// Creates analysis for a single file.
161pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
162 let mock = MockAnalysis::with_files(ra_fixture);
163 let file_id = mock.id_of("/main.rs");
164 (mock.analysis(), file_id)
165}
166
167/// Creates analysis for a single file, returns range marked with a pair of <|>.
168pub fn analysis_and_range(ra_fixture: &str) -> (Analysis, FileRange) {
169 let (res, position) = MockAnalysis::with_fixture(ra_fixture);
170 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
171 let range = match range_or_offset {
172 RangeOrOffset::Range(it) => it,
173 RangeOrOffset::Offset(_) => panic!(),
174 };
175 (res.analysis(), FileRange { file_id, range })
176}
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
new file mode 100644
index 000000000..59ed2967c
--- /dev/null
+++ b/crates/ide/src/parent_module.rs
@@ -0,0 +1,155 @@
1use base_db::{CrateId, FileId, FilePosition};
2use hir::Semantics;
3use ide_db::RootDatabase;
4use syntax::{
5 algo::find_node_at_offset,
6 ast::{self, AstNode},
7};
8use test_utils::mark;
9
10use crate::NavigationTarget;
11
12// Feature: Parent Module
13//
14// Navigates to the parent module of the current module.
15//
16// |===
17// | Editor | Action Name
18//
19// | VS Code | **Rust Analyzer: Locate parent module**
20// |===
21
22/// This returns `Vec` because a module may be included from several places. We
23/// don't handle this case yet though, so the Vec has length at most one.
24pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
25 let sema = Semantics::new(db);
26 let source_file = sema.parse(position.file_id);
27
28 let mut module = find_node_at_offset::<ast::Module>(source_file.syntax(), position.offset);
29
30 // If cursor is literally on `mod foo`, go to the grandpa.
31 if let Some(m) = &module {
32 if !m
33 .item_list()
34 .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset))
35 {
36 mark::hit!(test_resolve_parent_module_on_module_decl);
37 module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast);
38 }
39 }
40
41 let module = match module {
42 Some(module) => sema.to_def(&module),
43 None => sema.to_module_def(position.file_id),
44 };
45 let module = match module {
46 None => return Vec::new(),
47 Some(it) => it,
48 };
49 let nav = NavigationTarget::from_module_to_decl(db, module);
50 vec![nav]
51}
52
53/// Returns `Vec` for the same reason as `parent_module`
54pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
55 let sema = Semantics::new(db);
56 let module = match sema.to_module_def(file_id) {
57 Some(it) => it,
58 None => return Vec::new(),
59 };
60 let krate = module.krate();
61 vec![krate.into()]
62}
63
64#[cfg(test)]
65mod tests {
66 use base_db::Env;
67 use cfg::CfgOptions;
68 use test_utils::mark;
69
70 use crate::{
71 mock_analysis::{analysis_and_position, MockAnalysis},
72 AnalysisChange, CrateGraph,
73 Edition::Edition2018,
74 };
75
76 #[test]
77 fn test_resolve_parent_module() {
78 let (analysis, pos) = analysis_and_position(
79 "
80 //- /lib.rs
81 mod foo;
82 //- /foo.rs
83 <|>// empty
84 ",
85 );
86 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
87 nav.assert_match("foo MODULE FileId(1) 0..8");
88 }
89
90 #[test]
91 fn test_resolve_parent_module_on_module_decl() {
92 mark::check!(test_resolve_parent_module_on_module_decl);
93 let (analysis, pos) = analysis_and_position(
94 "
95 //- /lib.rs
96 mod foo;
97
98 //- /foo.rs
99 mod <|>bar;
100
101 //- /foo/bar.rs
102 // empty
103 ",
104 );
105 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
106 nav.assert_match("foo MODULE FileId(1) 0..8");
107 }
108
109 #[test]
110 fn test_resolve_parent_module_for_inline() {
111 let (analysis, pos) = analysis_and_position(
112 "
113 //- /lib.rs
114 mod foo {
115 mod bar {
116 mod baz { <|> }
117 }
118 }
119 ",
120 );
121 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
122 nav.assert_match("baz MODULE FileId(1) 32..44");
123 }
124
125 #[test]
126 fn test_resolve_crate_root() {
127 let mock = MockAnalysis::with_files(
128 r#"
129//- /bar.rs
130mod foo;
131//- /foo.rs
132// empty
133"#,
134 );
135 let root_file = mock.id_of("/bar.rs");
136 let mod_file = mock.id_of("/foo.rs");
137 let mut host = mock.analysis_host();
138 assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
139
140 let mut crate_graph = CrateGraph::default();
141 let crate_id = crate_graph.add_crate_root(
142 root_file,
143 Edition2018,
144 None,
145 CfgOptions::default(),
146 Env::default(),
147 Default::default(),
148 );
149 let mut change = AnalysisChange::new();
150 change.set_crate_graph(crate_graph);
151 host.apply_change(change);
152
153 assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]);
154 }
155}
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
new file mode 100644
index 000000000..c5ab5a1d8
--- /dev/null
+++ b/crates/ide/src/prime_caches.rs
@@ -0,0 +1,12 @@
1//! rust-analyzer is lazy and doesn't not compute anything unless asked. This
2//! sometimes is counter productive when, for example, the first goto definition
3//! request takes longer to compute. This modules implemented prepopulating of
4//! various caches, it's not really advanced at the moment.
5
6use crate::{FileId, RootDatabase};
7
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
9 for file in files {
10 let _ = crate::syntax_highlighting::highlight(db, file, None, false);
11 }
12}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
new file mode 100644
index 000000000..722c8f406
--- /dev/null
+++ b/crates/ide/src/references.rs
@@ -0,0 +1,696 @@
1//! This module implements a reference search.
2//! First, the element at the cursor position must be either an `ast::Name`
3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching.
7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference.
11
12mod rename;
13
14use hir::Semantics;
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 search::SearchScope,
18 RootDatabase,
19};
20use syntax::{
21 algo::find_node_at_offset,
22 ast::{self, NameOwner},
23 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
24};
25
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27
28pub(crate) use self::rename::rename;
29
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31
32#[derive(Debug, Clone)]
33pub struct ReferenceSearchResult {
34 declaration: Declaration,
35 references: Vec<Reference>,
36}
37
38#[derive(Debug, Clone)]
39pub struct Declaration {
40 pub nav: NavigationTarget,
41 pub kind: ReferenceKind,
42 pub access: Option<ReferenceAccess>,
43}
44
45impl ReferenceSearchResult {
46 pub fn declaration(&self) -> &Declaration {
47 &self.declaration
48 }
49
50 pub fn decl_target(&self) -> &NavigationTarget {
51 &self.declaration.nav
52 }
53
54 pub fn references(&self) -> &[Reference] {
55 &self.references
56 }
57
58 /// Total number of references
59 /// At least 1 since all valid references should
60 /// Have a declaration
61 pub fn len(&self) -> usize {
62 self.references.len() + 1
63 }
64}
65
66// allow turning ReferenceSearchResult into an iterator
67// over References
68impl IntoIterator for ReferenceSearchResult {
69 type Item = Reference;
70 type IntoIter = std::vec::IntoIter<Reference>;
71
72 fn into_iter(mut self) -> Self::IntoIter {
73 let mut v = Vec::with_capacity(self.len());
74 v.push(Reference {
75 file_range: FileRange {
76 file_id: self.declaration.nav.file_id,
77 range: self.declaration.nav.focus_or_full_range(),
78 },
79 kind: self.declaration.kind,
80 access: self.declaration.access,
81 });
82 v.append(&mut self.references);
83 v.into_iter()
84 }
85}
86
87pub(crate) fn find_all_refs(
88 sema: &Semantics<RootDatabase>,
89 position: FilePosition,
90 search_scope: Option<SearchScope>,
91) -> Option<RangeInfo<ReferenceSearchResult>> {
92 let _p = profile::span("find_all_refs");
93 let syntax = sema.parse(position.file_id).syntax().clone();
94
95 let (opt_name, search_kind) = if let Some(name) =
96 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
97 {
98 (Some(name), ReferenceKind::StructLiteral)
99 } else {
100 (
101 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset),
102 ReferenceKind::Other,
103 )
104 };
105
106 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
107
108 let references = def
109 .usages(sema)
110 .set_scope(search_scope)
111 .all()
112 .into_iter()
113 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
114 .collect();
115
116 let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range();
117
118 let declaration = Declaration {
119 nav: def.try_to_nav(sema.db)?,
120 kind: ReferenceKind::Other,
121 access: decl_access(&def, &syntax, decl_range),
122 };
123
124 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
125}
126
127fn find_name(
128 sema: &Semantics<RootDatabase>,
129 syntax: &SyntaxNode,
130 position: FilePosition,
131 opt_name: Option<ast::Name>,
132) -> Option<RangeInfo<Definition>> {
133 if let Some(name) = opt_name {
134 let def = classify_name(sema, &name)?.definition(sema.db);
135 let range = name.syntax().text_range();
136 return Some(RangeInfo::new(range, def));
137 }
138 let name_ref =
139 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
140 let def = classify_name_ref(sema, &name_ref)?.definition(sema.db);
141 let range = name_ref.syntax().text_range();
142 Some(RangeInfo::new(range, def))
143}
144
145fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
146 match def {
147 Definition::Local(_) | Definition::Field(_) => {}
148 _ => return None,
149 };
150
151 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
152 if stmt.initializer().is_some() {
153 let pat = stmt.pat()?;
154 if let ast::Pat::IdentPat(it) = pat {
155 if it.mut_token().is_some() {
156 return Some(ReferenceAccess::Write);
157 }
158 }
159 }
160
161 None
162}
163
164fn get_struct_def_name_for_struct_literal_search(
165 sema: &Semantics<RootDatabase>,
166 syntax: &SyntaxNode,
167 position: FilePosition,
168) -> Option<ast::Name> {
169 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
170 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN {
171 return None;
172 }
173 if let Some(name) =
174 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start())
175 {
176 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
177 }
178 if sema
179 .find_node_at_offset_with_descend::<ast::GenericParamList>(
180 &syntax,
181 left.text_range().start(),
182 )
183 .is_some()
184 {
185 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
186 }
187 }
188 None
189}
190
191#[cfg(test)]
192mod tests {
193 use crate::{
194 mock_analysis::{analysis_and_position, MockAnalysis},
195 Declaration, Reference, ReferenceSearchResult, SearchScope,
196 };
197
198 #[test]
199 fn test_struct_literal_after_space() {
200 let refs = get_all_refs(
201 r#"
202struct Foo <|>{
203 a: i32,
204}
205impl Foo {
206 fn f() -> i32 { 42 }
207}
208fn main() {
209 let f: Foo;
210 f = Foo {a: Foo::f()};
211}
212"#,
213 );
214 check_result(
215 refs,
216 "Foo STRUCT FileId(1) 0..26 7..10 Other",
217 &["FileId(1) 101..104 StructLiteral"],
218 );
219 }
220
221 #[test]
222 fn test_struct_literal_before_space() {
223 let refs = get_all_refs(
224 r#"
225struct Foo<|> {}
226 fn main() {
227 let f: Foo;
228 f = Foo {};
229}
230"#,
231 );
232 check_result(
233 refs,
234 "Foo STRUCT FileId(1) 0..13 7..10 Other",
235 &["FileId(1) 41..44 Other", "FileId(1) 54..57 StructLiteral"],
236 );
237 }
238
239 #[test]
240 fn test_struct_literal_with_generic_type() {
241 let refs = get_all_refs(
242 r#"
243struct Foo<T> <|>{}
244 fn main() {
245 let f: Foo::<i32>;
246 f = Foo {};
247}
248"#,
249 );
250 check_result(
251 refs,
252 "Foo STRUCT FileId(1) 0..16 7..10 Other",
253 &["FileId(1) 64..67 StructLiteral"],
254 );
255 }
256
257 #[test]
258 fn test_struct_literal_for_tuple() {
259 let refs = get_all_refs(
260 r#"
261struct Foo<|>(i32);
262
263fn main() {
264 let f: Foo;
265 f = Foo(1);
266}
267"#,
268 );
269 check_result(
270 refs,
271 "Foo STRUCT FileId(1) 0..16 7..10 Other",
272 &["FileId(1) 54..57 StructLiteral"],
273 );
274 }
275
276 #[test]
277 fn test_find_all_refs_for_local() {
278 let refs = get_all_refs(
279 r#"
280fn main() {
281 let mut i = 1;
282 let j = 1;
283 i = i<|> + j;
284
285 {
286 i = 0;
287 }
288
289 i = 5;
290}"#,
291 );
292 check_result(
293 refs,
294 "i IDENT_PAT FileId(1) 24..25 Other Write",
295 &[
296 "FileId(1) 50..51 Other Write",
297 "FileId(1) 54..55 Other Read",
298 "FileId(1) 76..77 Other Write",
299 "FileId(1) 94..95 Other Write",
300 ],
301 );
302 }
303
304 #[test]
305 fn search_filters_by_range() {
306 let refs = get_all_refs(
307 r#"
308fn foo() {
309 let spam<|> = 92;
310 spam + spam
311}
312fn bar() {
313 let spam = 92;
314 spam + spam
315}
316"#,
317 );
318 check_result(
319 refs,
320 "spam IDENT_PAT FileId(1) 19..23 Other",
321 &["FileId(1) 34..38 Other Read", "FileId(1) 41..45 Other Read"],
322 );
323 }
324
325 #[test]
326 fn test_find_all_refs_for_param_inside() {
327 let refs = get_all_refs(
328 r#"
329fn foo(i : u32) -> u32 {
330 i<|>
331}
332"#,
333 );
334 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
335 }
336
337 #[test]
338 fn test_find_all_refs_for_fn_param() {
339 let refs = get_all_refs(
340 r#"
341fn foo(i<|> : u32) -> u32 {
342 i
343}
344"#,
345 );
346 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
347 }
348
349 #[test]
350 fn test_find_all_refs_field_name() {
351 let refs = get_all_refs(
352 r#"
353//- /lib.rs
354struct Foo {
355 pub spam<|>: u32,
356}
357
358fn main(s: Foo) {
359 let f = s.spam;
360}
361"#,
362 );
363 check_result(
364 refs,
365 "spam RECORD_FIELD FileId(1) 17..30 21..25 Other",
366 &["FileId(1) 67..71 Other Read"],
367 );
368 }
369
370 #[test]
371 fn test_find_all_refs_impl_item_name() {
372 let refs = get_all_refs(
373 r#"
374struct Foo;
375impl Foo {
376 fn f<|>(&self) { }
377}
378"#,
379 );
380 check_result(refs, "f FN FileId(1) 27..43 30..31 Other", &[]);
381 }
382
383 #[test]
384 fn test_find_all_refs_enum_var_name() {
385 let refs = get_all_refs(
386 r#"
387enum Foo {
388 A,
389 B<|>,
390 C,
391}
392"#,
393 );
394 check_result(refs, "B VARIANT FileId(1) 22..23 22..23 Other", &[]);
395 }
396
397 #[test]
398 fn test_find_all_refs_two_modules() {
399 let (analysis, pos) = analysis_and_position(
400 r#"
401//- /lib.rs
402pub mod foo;
403pub mod bar;
404
405fn f() {
406 let i = foo::Foo { n: 5 };
407}
408
409//- /foo.rs
410use crate::bar;
411
412pub struct Foo {
413 pub n: u32,
414}
415
416fn f() {
417 let i = bar::Bar { n: 5 };
418}
419
420//- /bar.rs
421use crate::foo;
422
423pub struct Bar {
424 pub n: u32,
425}
426
427fn f() {
428 let i = foo::Foo<|> { n: 5 };
429}
430"#,
431 );
432 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
433 check_result(
434 refs,
435 "Foo STRUCT FileId(2) 17..51 28..31 Other",
436 &["FileId(1) 53..56 StructLiteral", "FileId(3) 79..82 StructLiteral"],
437 );
438 }
439
440 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
441 // So, there are two references: the first one is a definition of the `foo` module,
442 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
443 #[test]
444 fn test_find_all_refs_decl_module() {
445 let (analysis, pos) = analysis_and_position(
446 r#"
447//- /lib.rs
448mod foo<|>;
449
450use foo::Foo;
451
452fn f() {
453 let i = Foo { n: 5 };
454}
455
456//- /foo.rs
457pub struct Foo {
458 pub n: u32,
459}
460"#,
461 );
462 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
463 check_result(refs, "foo SOURCE_FILE FileId(2) 0..35 Other", &["FileId(1) 14..17 Other"]);
464 }
465
466 #[test]
467 fn test_find_all_refs_super_mod_vis() {
468 let (analysis, pos) = analysis_and_position(
469 r#"
470//- /lib.rs
471mod foo;
472
473//- /foo.rs
474mod some;
475use some::Foo;
476
477fn f() {
478 let i = Foo { n: 5 };
479}
480
481//- /foo/some.rs
482pub(super) struct Foo<|> {
483 pub n: u32,
484}
485"#,
486 );
487 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
488 check_result(
489 refs,
490 "Foo STRUCT FileId(3) 0..41 18..21 Other",
491 &["FileId(2) 20..23 Other", "FileId(2) 47..50 StructLiteral"],
492 );
493 }
494
495 #[test]
496 fn test_find_all_refs_with_scope() {
497 let code = r#"
498 //- /lib.rs
499 mod foo;
500 mod bar;
501
502 pub fn quux<|>() {}
503
504 //- /foo.rs
505 fn f() { super::quux(); }
506
507 //- /bar.rs
508 fn f() { super::quux(); }
509 "#;
510
511 let (mock, pos) = MockAnalysis::with_files_and_position(code);
512 let bar = mock.id_of("/bar.rs");
513 let analysis = mock.analysis();
514
515 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
516 check_result(
517 refs,
518 "quux FN FileId(1) 19..35 26..30 Other",
519 &["FileId(2) 16..20 StructLiteral", "FileId(3) 16..20 StructLiteral"],
520 );
521
522 let refs =
523 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
524 check_result(
525 refs,
526 "quux FN FileId(1) 19..35 26..30 Other",
527 &["FileId(3) 16..20 StructLiteral"],
528 );
529 }
530
531 #[test]
532 fn test_find_all_refs_macro_def() {
533 let refs = get_all_refs(
534 r#"
535#[macro_export]
536macro_rules! m1<|> { () => (()) }
537
538fn foo() {
539 m1();
540 m1();
541}
542"#,
543 );
544 check_result(
545 refs,
546 "m1 MACRO_CALL FileId(1) 0..46 29..31 Other",
547 &["FileId(1) 63..65 StructLiteral", "FileId(1) 73..75 StructLiteral"],
548 );
549 }
550
551 #[test]
552 fn test_basic_highlight_read_write() {
553 let refs = get_all_refs(
554 r#"
555fn foo() {
556 let mut i<|> = 0;
557 i = i + 1;
558}
559"#,
560 );
561 check_result(
562 refs,
563 "i IDENT_PAT FileId(1) 23..24 Other Write",
564 &["FileId(1) 34..35 Other Write", "FileId(1) 38..39 Other Read"],
565 );
566 }
567
568 #[test]
569 fn test_basic_highlight_field_read_write() {
570 let refs = get_all_refs(
571 r#"
572struct S {
573 f: u32,
574}
575
576fn foo() {
577 let mut s = S{f: 0};
578 s.f<|> = 0;
579}
580"#,
581 );
582 check_result(
583 refs,
584 "f RECORD_FIELD FileId(1) 15..21 15..16 Other",
585 &["FileId(1) 55..56 Other Read", "FileId(1) 68..69 Other Write"],
586 );
587 }
588
589 #[test]
590 fn test_basic_highlight_decl_no_write() {
591 let refs = get_all_refs(
592 r#"
593fn foo() {
594 let i<|>;
595 i = 1;
596}
597"#,
598 );
599 check_result(refs, "i IDENT_PAT FileId(1) 19..20 Other", &["FileId(1) 26..27 Other Write"]);
600 }
601
602 #[test]
603 fn test_find_struct_function_refs_outside_module() {
604 let refs = get_all_refs(
605 r#"
606mod foo {
607 pub struct Foo;
608
609 impl Foo {
610 pub fn new<|>() -> Foo {
611 Foo
612 }
613 }
614}
615
616fn main() {
617 let _f = foo::Foo::new();
618}
619"#,
620 );
621 check_result(
622 refs,
623 "new FN FileId(1) 54..101 61..64 Other",
624 &["FileId(1) 146..149 StructLiteral"],
625 );
626 }
627
628 #[test]
629 fn test_find_all_refs_nested_module() {
630 let code = r#"
631 //- /lib.rs
632 mod foo {
633 mod bar;
634 }
635
636 fn f<|>() {}
637
638 //- /foo/bar.rs
639 use crate::f;
640
641 fn g() {
642 f();
643 }
644 "#;
645
646 let (analysis, pos) = analysis_and_position(code);
647 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
648 check_result(
649 refs,
650 "f FN FileId(1) 26..35 29..30 Other",
651 &["FileId(2) 11..12 Other", "FileId(2) 28..29 StructLiteral"],
652 );
653 }
654
655 fn get_all_refs(ra_fixture: &str) -> ReferenceSearchResult {
656 let (analysis, position) = analysis_and_position(ra_fixture);
657 analysis.find_all_refs(position, None).unwrap().unwrap()
658 }
659
660 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
661 res.declaration().assert_match(expected_decl);
662 assert_eq!(res.references.len(), expected_refs.len());
663 res.references()
664 .iter()
665 .enumerate()
666 .for_each(|(i, r)| ref_assert_match(r, expected_refs[i]));
667 }
668
669 impl Declaration {
670 fn debug_render(&self) -> String {
671 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
672 if let Some(access) = self.access {
673 s.push_str(&format!(" {:?}", access));
674 }
675 s
676 }
677
678 fn assert_match(&self, expected: &str) {
679 let actual = self.debug_render();
680 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
681 }
682 }
683
684 fn ref_debug_render(r: &Reference) -> String {
685 let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
686 if let Some(access) = r.access {
687 s.push_str(&format!(" {:?}", access));
688 }
689 s
690 }
691
692 fn ref_assert_match(r: &Reference, expected: &str) {
693 let actual = ref_debug_render(r);
694 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
695 }
696}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
new file mode 100644
index 000000000..301629763
--- /dev/null
+++ b/crates/ide/src/references/rename.rs
@@ -0,0 +1,1010 @@
1//! FIXME: write short doc here
2
3use base_db::SourceDatabaseExt;
4use hir::{Module, ModuleDef, ModuleSource, Semantics};
5use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase,
8};
9use std::convert::TryInto;
10use syntax::{
11 algo::find_node_at_offset,
12 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14};
15use test_utils::mark;
16use text_edit::TextEdit;
17
18use crate::{
19 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
20 SourceChange, SourceFileEdit, TextRange, TextSize,
21};
22
23pub(crate) fn rename(
24 db: &RootDatabase,
25 position: FilePosition,
26 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> {
28 let sema = Semantics::new(db);
29
30 match lex_single_valid_syntax_kind(new_name)? {
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position),
33 _ => return None,
34 }
35
36 let source_file = sema.parse(position.file_id);
37 let syntax = source_file.syntax();
38 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
39 rename_mod(&sema, position, module, new_name)
40 } else if let Some(self_token) =
41 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
42 {
43 rename_self_to_param(&sema, position, self_token, new_name)
44 } else {
45 rename_reference(&sema, position, new_name)
46 }
47}
48
49fn find_module_at_offset(
50 sema: &Semantics<RootDatabase>,
51 position: FilePosition,
52 syntax: &SyntaxNode,
53) -> Option<Module> {
54 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
55
56 let module = match_ast! {
57 match (ident.parent()) {
58 ast::NameRef(name_ref) => {
59 match classify_name_ref(sema, &name_ref)? {
60 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
61 _ => return None,
62 }
63 },
64 ast::Name(name) => {
65 match classify_name(&sema, &name)? {
66 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
67 _ => return None,
68 }
69 },
70 _ => return None,
71 }
72 };
73
74 Some(module)
75}
76
77fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
78 let mut replacement_text = String::new();
79 let file_id = reference.file_range.file_id;
80 let range = match reference.kind {
81 ReferenceKind::FieldShorthandForField => {
82 mark::hit!(test_rename_struct_field_for_shorthand);
83 replacement_text.push_str(new_name);
84 replacement_text.push_str(": ");
85 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
86 }
87 ReferenceKind::FieldShorthandForLocal => {
88 mark::hit!(test_rename_local_for_field_shorthand);
89 replacement_text.push_str(": ");
90 replacement_text.push_str(new_name);
91 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
92 }
93 _ => {
94 replacement_text.push_str(new_name);
95 reference.file_range.range
96 }
97 };
98 SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) }
99}
100
101fn rename_mod(
102 sema: &Semantics<RootDatabase>,
103 position: FilePosition,
104 module: Module,
105 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> {
107 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new();
109
110 let src = module.definition_source(sema.db);
111 let file_id = src.file_id.original_file(sema.db);
112 match src.value {
113 ModuleSource::SourceFile(..) => {
114 // mod is defined in path/to/dir/mod.rs
115 let dst = if module.is_mod_rs(sema.db) {
116 format!("../{}/mod.rs", new_name)
117 } else {
118 format!("{}.rs", new_name)
119 };
120 let move_file = FileSystemEdit::MoveFile { src: file_id, anchor: file_id, dst };
121 file_system_edits.push(move_file);
122 }
123 ModuleSource::Module(..) => {}
124 }
125
126 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?;
129 let edit = SourceFileEdit {
130 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
132 };
133 source_file_edits.push(edit);
134 }
135
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?;
137 let ref_edits = refs
138 .references
139 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits);
142
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144}
145
146fn rename_to_self(
147 sema: &Semantics<RootDatabase>,
148 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> {
150 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax();
152
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?;
154 let params = fn_def.param_list()?;
155 if params.self_param().is_some() {
156 return None; // method already has self param
157 }
158 let first_param = params.params().next()?;
159 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types
162 };
163
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?;
165
166 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
168 .into_iter()
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170
171 if param_ref.is_empty() {
172 return None;
173 }
174
175 let mut edits = usages
176 .into_iter()
177 .map(|reference| source_edit_from_reference(reference, "self"))
178 .collect::<Vec<_>>();
179
180 edits.push(SourceFileEdit {
181 file_id: position.file_id,
182 edit: TextEdit::replace(
183 param_range,
184 String::from(if mutable { "&mut self" } else { "&self" }),
185 ),
186 });
187
188 Some(RangeInfo::new(range, SourceChange::from(edits)))
189}
190
191fn text_edit_from_self_param(
192 syn: &SyntaxNode,
193 self_param: &ast::SelfParam,
194 new_name: &str,
195) -> Option<TextEdit> {
196 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
197 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
198 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
199 }
200 None
201 }
202
203 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?;
204 let type_name = target_type_name(&impl_def)?;
205
206 let mut replacement_text = String::from(new_name);
207 replacement_text.push_str(": ");
208 replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
209 replacement_text.push_str(type_name.as_str());
210
211 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
212}
213
214fn rename_self_to_param(
215 sema: &Semantics<RootDatabase>,
216 position: FilePosition,
217 self_token: SyntaxToken,
218 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> {
220 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax();
222
223 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?;
225 let search_range = fn_def.syntax().text_range();
226
227 let mut edits: Vec<SourceFileEdit> = vec![];
228
229 for (idx, _) in text.match_indices("self") {
230 let offset: TextSize = idx.try_into().unwrap();
231 if !search_range.contains_inclusive(offset) {
232 continue;
233 }
234 if let Some(ref usage) =
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)?
239 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name))
241 };
242 edits.push(SourceFileEdit { file_id: position.file_id, edit });
243 }
244 }
245
246 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248
249 Some(RangeInfo::new(range, SourceChange::from(edits)))
250}
251
252fn rename_reference(
253 sema: &Semantics<RootDatabase>,
254 position: FilePosition,
255 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?;
258
259 let edit = refs
260 .into_iter()
261 .map(|reference| source_edit_from_reference(reference, new_name))
262 .collect::<Vec<_>>();
263
264 if edit.is_empty() {
265 return None;
266 }
267
268 Some(RangeInfo::new(range, SourceChange::from(edit)))
269}
270
271#[cfg(test)]
272mod tests {
273 use expect_test::{expect, Expect};
274 use stdx::trim_indent;
275 use test_utils::{assert_eq_text, mark};
276 use text_edit::TextEdit;
277
278 use crate::{mock_analysis::analysis_and_position, FileId};
279
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = analysis_and_position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap();
284 let mut text_edit_builder = TextEdit::builder();
285 let mut file_id: Option<FileId> = None;
286 if let Some(change) = source_change {
287 for edit in change.info.source_file_edits {
288 file_id = Some(edit.file_id);
289 for indel in edit.edit.into_iter() {
290 text_edit_builder.replace(indel.delete, indel.insert);
291 }
292 }
293 }
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
295 text_edit_builder.finish().apply(&mut result);
296 assert_eq_text!(ra_fixture_after, &*result);
297 }
298
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = analysis_and_position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap();
302 expect.assert_debug_eq(&source_change)
303 }
304
305 #[test]
306 fn test_rename_to_underscore() {
307 check("_", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let _ = 1; }"#);
308 }
309
310 #[test]
311 fn test_rename_to_raw_identifier() {
312 check("r#fn", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let r#fn = 1; }"#);
313 }
314
315 #[test]
316 fn test_rename_to_invalid_identifier() {
317 let (analysis, position) = analysis_and_position(r#"fn main() { let i<|> = 1; }"#);
318 let new_name = "invalid!";
319 let source_change = analysis.rename(position, new_name).unwrap();
320 assert!(source_change.is_none());
321 }
322
323 #[test]
324 fn test_rename_for_local() {
325 check(
326 "k",
327 r#"
328fn main() {
329 let mut i = 1;
330 let j = 1;
331 i = i<|> + j;
332
333 { i = 0; }
334
335 i = 5;
336}
337"#,
338 r#"
339fn main() {
340 let mut k = 1;
341 let j = 1;
342 k = k + j;
343
344 { k = 0; }
345
346 k = 5;
347}
348"#,
349 );
350 }
351
352 #[test]
353 fn test_rename_for_macro_args() {
354 check(
355 "b",
356 r#"
357macro_rules! foo {($i:ident) => {$i} }
358fn main() {
359 let a<|> = "test";
360 foo!(a);
361}
362"#,
363 r#"
364macro_rules! foo {($i:ident) => {$i} }
365fn main() {
366 let b = "test";
367 foo!(b);
368}
369"#,
370 );
371 }
372
373 #[test]
374 fn test_rename_for_macro_args_rev() {
375 check(
376 "b",
377 r#"
378macro_rules! foo {($i:ident) => {$i} }
379fn main() {
380 let a = "test";
381 foo!(a<|>);
382}
383"#,
384 r#"
385macro_rules! foo {($i:ident) => {$i} }
386fn main() {
387 let b = "test";
388 foo!(b);
389}
390"#,
391 );
392 }
393
394 #[test]
395 fn test_rename_for_macro_define_fn() {
396 check(
397 "bar",
398 r#"
399macro_rules! define_fn {($id:ident) => { fn $id{} }}
400define_fn!(foo);
401fn main() {
402 fo<|>o();
403}
404"#,
405 r#"
406macro_rules! define_fn {($id:ident) => { fn $id{} }}
407define_fn!(bar);
408fn main() {
409 bar();
410}
411"#,
412 );
413 }
414
415 #[test]
416 fn test_rename_for_macro_define_fn_rev() {
417 check(
418 "bar",
419 r#"
420macro_rules! define_fn {($id:ident) => { fn $id{} }}
421define_fn!(fo<|>o);
422fn main() {
423 foo();
424}
425"#,
426 r#"
427macro_rules! define_fn {($id:ident) => { fn $id{} }}
428define_fn!(bar);
429fn main() {
430 bar();
431}
432"#,
433 );
434 }
435
436 #[test]
437 fn test_rename_for_param_inside() {
438 check("j", r#"fn foo(i : u32) -> u32 { i<|> }"#, r#"fn foo(j : u32) -> u32 { j }"#);
439 }
440
441 #[test]
442 fn test_rename_refs_for_fn_param() {
443 check("j", r#"fn foo(i<|> : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);
444 }
445
446 #[test]
447 fn test_rename_for_mut_param() {
448 check("j", r#"fn foo(mut i<|> : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);
449 }
450
451 #[test]
452 fn test_rename_struct_field() {
453 check(
454 "j",
455 r#"
456struct Foo { i<|>: i32 }
457
458impl Foo {
459 fn new(i: i32) -> Self {
460 Self { i: i }
461 }
462}
463"#,
464 r#"
465struct Foo { j: i32 }
466
467impl Foo {
468 fn new(i: i32) -> Self {
469 Self { j: i }
470 }
471}
472"#,
473 );
474 }
475
476 #[test]
477 fn test_rename_struct_field_for_shorthand() {
478 mark::check!(test_rename_struct_field_for_shorthand);
479 check(
480 "j",
481 r#"
482struct Foo { i<|>: i32 }
483
484impl Foo {
485 fn new(i: i32) -> Self {
486 Self { i }
487 }
488}
489"#,
490 r#"
491struct Foo { j: i32 }
492
493impl Foo {
494 fn new(i: i32) -> Self {
495 Self { j: i }
496 }
497}
498"#,
499 );
500 }
501
502 #[test]
503 fn test_rename_local_for_field_shorthand() {
504 mark::check!(test_rename_local_for_field_shorthand);
505 check(
506 "j",
507 r#"
508struct Foo { i: i32 }
509
510impl Foo {
511 fn new(i<|>: i32) -> Self {
512 Self { i }
513 }
514}
515"#,
516 r#"
517struct Foo { i: i32 }
518
519impl Foo {
520 fn new(j: i32) -> Self {
521 Self { i: j }
522 }
523}
524"#,
525 );
526 }
527
528 #[test]
529 fn test_field_shorthand_correct_struct() {
530 check(
531 "j",
532 r#"
533struct Foo { i<|>: i32 }
534struct Bar { i: i32 }
535
536impl Bar {
537 fn new(i: i32) -> Self {
538 Self { i }
539 }
540}
541"#,
542 r#"
543struct Foo { j: i32 }
544struct Bar { i: i32 }
545
546impl Bar {
547 fn new(i: i32) -> Self {
548 Self { i }
549 }
550}
551"#,
552 );
553 }
554
555 #[test]
556 fn test_shadow_local_for_struct_shorthand() {
557 check(
558 "j",
559 r#"
560struct Foo { i: i32 }
561
562fn baz(i<|>: i32) -> Self {
563 let x = Foo { i };
564 {
565 let i = 0;
566 Foo { i }
567 }
568}
569"#,
570 r#"
571struct Foo { i: i32 }
572
573fn baz(j: i32) -> Self {
574 let x = Foo { i: j };
575 {
576 let i = 0;
577 Foo { i }
578 }
579}
580"#,
581 );
582 }
583
584 #[test]
585 fn test_rename_mod() {
586 check_expect(
587 "foo2",
588 r#"
589//- /lib.rs
590mod bar;
591
592//- /bar.rs
593mod foo<|>;
594
595//- /bar/foo.rs
596// empty
597"#,
598 expect![[r#"
599 RangeInfo {
600 range: 4..7,
601 info: SourceChange {
602 source_file_edits: [
603 SourceFileEdit {
604 file_id: FileId(
605 2,
606 ),
607 edit: TextEdit {
608 indels: [
609 Indel {
610 insert: "foo2",
611 delete: 4..7,
612 },
613 ],
614 },
615 },
616 ],
617 file_system_edits: [
618 MoveFile {
619 src: FileId(
620 3,
621 ),
622 anchor: FileId(
623 3,
624 ),
625 dst: "foo2.rs",
626 },
627 ],
628 is_snippet: false,
629 },
630 }
631 "#]],
632 );
633 }
634
635 #[test]
636 fn test_rename_mod_in_use_tree() {
637 check_expect(
638 "quux",
639 r#"
640//- /main.rs
641pub mod foo;
642pub mod bar;
643fn main() {}
644
645//- /foo.rs
646pub struct FooContent;
647
648//- /bar.rs
649use crate::foo<|>::FooContent;
650"#,
651 expect![[r#"
652 RangeInfo {
653 range: 11..14,
654 info: SourceChange {
655 source_file_edits: [
656 SourceFileEdit {
657 file_id: FileId(
658 1,
659 ),
660 edit: TextEdit {
661 indels: [
662 Indel {
663 insert: "quux",
664 delete: 8..11,
665 },
666 ],
667 },
668 },
669 SourceFileEdit {
670 file_id: FileId(
671 3,
672 ),
673 edit: TextEdit {
674 indels: [
675 Indel {
676 insert: "quux",
677 delete: 11..14,
678 },
679 ],
680 },
681 },
682 ],
683 file_system_edits: [
684 MoveFile {
685 src: FileId(
686 2,
687 ),
688 anchor: FileId(
689 2,
690 ),
691 dst: "quux.rs",
692 },
693 ],
694 is_snippet: false,
695 },
696 }
697 "#]],
698 );
699 }
700
701 #[test]
702 fn test_rename_mod_in_dir() {
703 check_expect(
704 "foo2",
705 r#"
706//- /lib.rs
707mod fo<|>o;
708//- /foo/mod.rs
709// emtpy
710"#,
711 expect![[r#"
712 RangeInfo {
713 range: 4..7,
714 info: SourceChange {
715 source_file_edits: [
716 SourceFileEdit {
717 file_id: FileId(
718 1,
719 ),
720 edit: TextEdit {
721 indels: [
722 Indel {
723 insert: "foo2",
724 delete: 4..7,
725 },
726 ],
727 },
728 },
729 ],
730 file_system_edits: [
731 MoveFile {
732 src: FileId(
733 2,
734 ),
735 anchor: FileId(
736 2,
737 ),
738 dst: "../foo2/mod.rs",
739 },
740 ],
741 is_snippet: false,
742 },
743 }
744 "#]],
745 );
746 }
747
748 #[test]
749 fn test_rename_unusually_nested_mod() {
750 check_expect(
751 "bar",
752 r#"
753//- /lib.rs
754mod outer { mod fo<|>o; }
755
756//- /outer/foo.rs
757// emtpy
758"#,
759 expect![[r#"
760 RangeInfo {
761 range: 16..19,
762 info: SourceChange {
763 source_file_edits: [
764 SourceFileEdit {
765 file_id: FileId(
766 1,
767 ),
768 edit: TextEdit {
769 indels: [
770 Indel {
771 insert: "bar",
772 delete: 16..19,
773 },
774 ],
775 },
776 },
777 ],
778 file_system_edits: [
779 MoveFile {
780 src: FileId(
781 2,
782 ),
783 anchor: FileId(
784 2,
785 ),
786 dst: "bar.rs",
787 },
788 ],
789 is_snippet: false,
790 },
791 }
792 "#]],
793 );
794 }
795
796 #[test]
797 fn test_module_rename_in_path() {
798 check(
799 "baz",
800 r#"
801mod <|>foo { pub fn bar() {} }
802
803fn main() { foo::bar(); }
804"#,
805 r#"
806mod baz { pub fn bar() {} }
807
808fn main() { baz::bar(); }
809"#,
810 );
811 }
812
813 #[test]
814 fn test_rename_mod_filename_and_path() {
815 check_expect(
816 "foo2",
817 r#"
818//- /lib.rs
819mod bar;
820fn f() {
821 bar::foo::fun()
822}
823
824//- /bar.rs
825pub mod foo<|>;
826
827//- /bar/foo.rs
828// pub fn fun() {}
829"#,
830 expect![[r#"
831 RangeInfo {
832 range: 8..11,
833 info: SourceChange {
834 source_file_edits: [
835 SourceFileEdit {
836 file_id: FileId(
837 2,
838 ),
839 edit: TextEdit {
840 indels: [
841 Indel {
842 insert: "foo2",
843 delete: 8..11,
844 },
845 ],
846 },
847 },
848 SourceFileEdit {
849 file_id: FileId(
850 1,
851 ),
852 edit: TextEdit {
853 indels: [
854 Indel {
855 insert: "foo2",
856 delete: 27..30,
857 },
858 ],
859 },
860 },
861 ],
862 file_system_edits: [
863 MoveFile {
864 src: FileId(
865 3,
866 ),
867 anchor: FileId(
868 3,
869 ),
870 dst: "foo2.rs",
871 },
872 ],
873 is_snippet: false,
874 },
875 }
876 "#]],
877 );
878 }
879
880 #[test]
881 fn test_enum_variant_from_module_1() {
882 check(
883 "Baz",
884 r#"
885mod foo {
886 pub enum Foo { Bar<|> }
887}
888
889fn func(f: foo::Foo) {
890 match f {
891 foo::Foo::Bar => {}
892 }
893}
894"#,
895 r#"
896mod foo {
897 pub enum Foo { Baz }
898}
899
900fn func(f: foo::Foo) {
901 match f {
902 foo::Foo::Baz => {}
903 }
904}
905"#,
906 );
907 }
908
909 #[test]
910 fn test_enum_variant_from_module_2() {
911 check(
912 "baz",
913 r#"
914mod foo {
915 pub struct Foo { pub bar<|>: uint }
916}
917
918fn foo(f: foo::Foo) {
919 let _ = f.bar;
920}
921"#,
922 r#"
923mod foo {
924 pub struct Foo { pub baz: uint }
925}
926
927fn foo(f: foo::Foo) {
928 let _ = f.baz;
929}
930"#,
931 );
932 }
933
934 #[test]
935 fn test_parameter_to_self() {
936 check(
937 "self",
938 r#"
939struct Foo { i: i32 }
940
941impl Foo {
942 fn f(foo<|>: &mut Foo) -> i32 {
943 foo.i
944 }
945}
946"#,
947 r#"
948struct Foo { i: i32 }
949
950impl Foo {
951 fn f(&mut self) -> i32 {
952 self.i
953 }
954}
955"#,
956 );
957 }
958
959 #[test]
960 fn test_self_to_parameter() {
961 check(
962 "foo",
963 r#"
964struct Foo { i: i32 }
965
966impl Foo {
967 fn f(&mut <|>self) -> i32 {
968 self.i
969 }
970}
971"#,
972 r#"
973struct Foo { i: i32 }
974
975impl Foo {
976 fn f(foo: &mut Foo) -> i32 {
977 foo.i
978 }
979}
980"#,
981 );
982 }
983
984 #[test]
985 fn test_self_in_path_to_parameter() {
986 check(
987 "foo",
988 r#"
989struct Foo { i: i32 }
990
991impl Foo {
992 fn f(&self) -> i32 {
993 let self_var = 1;
994 self<|>.i
995 }
996}
997"#,
998 r#"
999struct Foo { i: i32 }
1000
1001impl Foo {
1002 fn f(foo: &Foo) -> i32 {
1003 let self_var = 1;
1004 foo.i
1005 }
1006}
1007"#,
1008 );
1009 }
1010}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
new file mode 100644
index 000000000..4139f329e
--- /dev/null
+++ b/crates/ide/src/runnables.rs
@@ -0,0 +1,883 @@
1use std::fmt;
2
3use cfg::CfgExpr;
4use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
5use ide_db::RootDatabase;
6use itertools::Itertools;
7use syntax::{
8 ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner},
9 match_ast, SyntaxNode,
10};
11
12use crate::{display::ToNav, FileId, NavigationTarget};
13
14#[derive(Debug, Clone)]
15pub struct Runnable {
16 pub nav: NavigationTarget,
17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>,
19}
20
21#[derive(Debug, Clone)]
22pub enum TestId {
23 Name(String),
24 Path(String),
25}
26
27impl fmt::Display for TestId {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self {
30 TestId::Name(name) => write!(f, "{}", name),
31 TestId::Path(path) => write!(f, "{}", path),
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
37pub enum RunnableKind {
38 Test { test_id: TestId, attr: TestAttr },
39 TestMod { path: String },
40 Bench { test_id: TestId },
41 DocTest { test_id: TestId },
42 Bin,
43}
44
45#[derive(Debug, Eq, PartialEq)]
46pub struct RunnableAction {
47 pub run_title: &'static str,
48 pub debugee: bool,
49}
50
51const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
52const DOCTEST: RunnableAction =
53 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
54const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
55const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
56
57impl Runnable {
58 // test package::module::testname
59 pub fn label(&self, target: Option<String>) -> String {
60 match &self.kind {
61 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
62 RunnableKind::TestMod { path } => format!("test-mod {}", path),
63 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
64 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
65 RunnableKind::Bin => {
66 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
67 }
68 }
69 }
70
71 pub fn action(&self) -> &'static RunnableAction {
72 match &self.kind {
73 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
74 RunnableKind::DocTest { .. } => &DOCTEST,
75 RunnableKind::Bench { .. } => &BENCH,
76 RunnableKind::Bin => &BIN,
77 }
78 }
79}
80
81// Feature: Run
82//
83// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
84// location**. Super useful for repeatedly running just a single test. Do bind this
85// to a shortcut!
86//
87// |===
88// | Editor | Action Name
89//
90// | VS Code | **Rust Analyzer: Run**
91// |===
92pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
93 let sema = Semantics::new(db);
94 let source_file = sema.parse(file_id);
95 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
96}
97
98pub(crate) fn runnable(
99 sema: &Semantics<RootDatabase>,
100 item: SyntaxNode,
101 file_id: FileId,
102) -> Option<Runnable> {
103 match_ast! {
104 match item {
105 ast::Fn(it) => runnable_fn(sema, it, file_id),
106 ast::Module(it) => runnable_mod(sema, it, file_id),
107 _ => None,
108 }
109 }
110}
111
112fn runnable_fn(
113 sema: &Semantics<RootDatabase>,
114 fn_def: ast::Fn,
115 file_id: FileId,
116) -> Option<Runnable> {
117 let name_string = fn_def.name()?.text().to_string();
118
119 let kind = if name_string == "main" {
120 RunnableKind::Bin
121 } else {
122 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
123 Some(module) => {
124 let def = sema.to_def(&fn_def)?;
125 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
126 match assoc_item.container(sema.db) {
127 hir::AssocItemContainer::Trait(trait_item) => {
128 Some(trait_item.name(sema.db).to_string())
129 }
130 hir::AssocItemContainer::ImplDef(impl_def) => impl_def
131 .target_ty(sema.db)
132 .as_adt()
133 .map(|adt| adt.name(sema.db).to_string()),
134 }
135 });
136
137 let path_iter = module
138 .path_to_root(sema.db)
139 .into_iter()
140 .rev()
141 .filter_map(|it| it.name(sema.db))
142 .map(|name| name.to_string());
143
144 let path = if let Some(impl_trait_name) = impl_trait_name {
145 path_iter
146 .chain(std::iter::once(impl_trait_name))
147 .chain(std::iter::once(name_string))
148 .join("::")
149 } else {
150 path_iter.chain(std::iter::once(name_string)).join("::")
151 };
152
153 TestId::Path(path)
154 }
155 None => TestId::Name(name_string),
156 };
157
158 if has_test_related_attribute(&fn_def) {
159 let attr = TestAttr::from_fn(&fn_def);
160 RunnableKind::Test { test_id, attr }
161 } else if fn_def.has_atom_attr("bench") {
162 RunnableKind::Bench { test_id }
163 } else if has_doc_test(&fn_def) {
164 RunnableKind::DocTest { test_id }
165 } else {
166 return None;
167 }
168 };
169
170 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
171 let cfg_exprs = attrs.cfg().collect();
172
173 let nav = if let RunnableKind::DocTest { .. } = kind {
174 NavigationTarget::from_doc_commented(
175 sema.db,
176 InFile::new(file_id.into(), &fn_def),
177 InFile::new(file_id.into(), &fn_def),
178 )
179 } else {
180 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
181 };
182 Some(Runnable { nav, kind, cfg_exprs })
183}
184
185#[derive(Debug, Copy, Clone)]
186pub struct TestAttr {
187 pub ignore: bool,
188}
189
190impl TestAttr {
191 fn from_fn(fn_def: &ast::Fn) -> TestAttr {
192 let ignore = fn_def
193 .attrs()
194 .filter_map(|attr| attr.simple_name())
195 .any(|attribute_text| attribute_text == "ignore");
196 TestAttr { ignore }
197 }
198}
199
200/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
201/// `#[test_case(...)]`, `#[tokio::test]` and similar.
202/// Also a regular `#[test]` annotation is supported.
203///
204/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
205/// but it's better than not to have the runnables for the tests at all.
206fn has_test_related_attribute(fn_def: &ast::Fn) -> bool {
207 fn_def
208 .attrs()
209 .filter_map(|attr| attr.path())
210 .map(|path| path.syntax().to_string().to_lowercase())
211 .any(|attribute_text| attribute_text.contains("test"))
212}
213
214fn has_doc_test(fn_def: &ast::Fn) -> bool {
215 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
216}
217
218fn runnable_mod(
219 sema: &Semantics<RootDatabase>,
220 module: ast::Module,
221 file_id: FileId,
222) -> Option<Runnable> {
223 if !has_test_function_or_multiple_test_submodules(&module) {
224 return None;
225 }
226 let module_def = sema.to_def(&module)?;
227
228 let path = module_def
229 .path_to_root(sema.db)
230 .into_iter()
231 .rev()
232 .filter_map(|it| it.name(sema.db))
233 .join("::");
234
235 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
236 let cfg_exprs = attrs.cfg().collect();
237 let nav = module_def.to_nav(sema.db);
238 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
239}
240
241// We could create runnables for modules with number_of_test_submodules > 0,
242// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
243fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
244 if let Some(item_list) = module.item_list() {
245 let mut number_of_test_submodules = 0;
246
247 for item in item_list.items() {
248 match item {
249 ast::Item::Fn(f) => {
250 if has_test_related_attribute(&f) {
251 return true;
252 }
253 }
254 ast::Item::Module(submodule) => {
255 if has_test_function_or_multiple_test_submodules(&submodule) {
256 number_of_test_submodules += 1;
257 }
258 }
259 _ => (),
260 }
261 }
262
263 number_of_test_submodules > 1
264 } else {
265 false
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use expect_test::{expect, Expect};
272
273 use crate::mock_analysis::analysis_and_position;
274
275 use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST};
276
277 fn check(
278 ra_fixture: &str,
279 // FIXME: fold this into `expect` as well
280 actions: &[&RunnableAction],
281 expect: Expect,
282 ) {
283 let (analysis, position) = analysis_and_position(ra_fixture);
284 let runnables = analysis.runnables(position.file_id).unwrap();
285 expect.assert_debug_eq(&runnables);
286 assert_eq!(
287 actions,
288 runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
289 );
290 }
291
292 #[test]
293 fn test_runnables() {
294 check(
295 r#"
296//- /lib.rs
297<|>
298fn main() {}
299
300#[test]
301fn test_foo() {}
302
303#[test]
304#[ignore]
305fn test_foo() {}
306
307#[bench]
308fn bench() {}
309"#,
310 &[&BIN, &TEST, &TEST, &BENCH],
311 expect![[r#"
312 [
313 Runnable {
314 nav: NavigationTarget {
315 file_id: FileId(
316 1,
317 ),
318 full_range: 1..13,
319 focus_range: Some(
320 4..8,
321 ),
322 name: "main",
323 kind: FN,
324 container_name: None,
325 description: None,
326 docs: None,
327 },
328 kind: Bin,
329 cfg_exprs: [],
330 },
331 Runnable {
332 nav: NavigationTarget {
333 file_id: FileId(
334 1,
335 ),
336 full_range: 15..39,
337 focus_range: Some(
338 26..34,
339 ),
340 name: "test_foo",
341 kind: FN,
342 container_name: None,
343 description: None,
344 docs: None,
345 },
346 kind: Test {
347 test_id: Path(
348 "test_foo",
349 ),
350 attr: TestAttr {
351 ignore: false,
352 },
353 },
354 cfg_exprs: [],
355 },
356 Runnable {
357 nav: NavigationTarget {
358 file_id: FileId(
359 1,
360 ),
361 full_range: 41..75,
362 focus_range: Some(
363 62..70,
364 ),
365 name: "test_foo",
366 kind: FN,
367 container_name: None,
368 description: None,
369 docs: None,
370 },
371 kind: Test {
372 test_id: Path(
373 "test_foo",
374 ),
375 attr: TestAttr {
376 ignore: true,
377 },
378 },
379 cfg_exprs: [],
380 },
381 Runnable {
382 nav: NavigationTarget {
383 file_id: FileId(
384 1,
385 ),
386 full_range: 77..99,
387 focus_range: Some(
388 89..94,
389 ),
390 name: "bench",
391 kind: FN,
392 container_name: None,
393 description: None,
394 docs: None,
395 },
396 kind: Bench {
397 test_id: Path(
398 "bench",
399 ),
400 },
401 cfg_exprs: [],
402 },
403 ]
404 "#]],
405 );
406 }
407
408 #[test]
409 fn test_runnables_doc_test() {
410 check(
411 r#"
412//- /lib.rs
413<|>
414fn main() {}
415
416/// ```
417/// let x = 5;
418/// ```
419fn foo() {}
420"#,
421 &[&BIN, &DOCTEST],
422 expect![[r#"
423 [
424 Runnable {
425 nav: NavigationTarget {
426 file_id: FileId(
427 1,
428 ),
429 full_range: 1..13,
430 focus_range: Some(
431 4..8,
432 ),
433 name: "main",
434 kind: FN,
435 container_name: None,
436 description: None,
437 docs: None,
438 },
439 kind: Bin,
440 cfg_exprs: [],
441 },
442 Runnable {
443 nav: NavigationTarget {
444 file_id: FileId(
445 1,
446 ),
447 full_range: 15..57,
448 focus_range: None,
449 name: "foo",
450 kind: FN,
451 container_name: None,
452 description: None,
453 docs: None,
454 },
455 kind: DocTest {
456 test_id: Path(
457 "foo",
458 ),
459 },
460 cfg_exprs: [],
461 },
462 ]
463 "#]],
464 );
465 }
466
467 #[test]
468 fn test_runnables_doc_test_in_impl() {
469 check(
470 r#"
471//- /lib.rs
472<|>
473fn main() {}
474
475struct Data;
476impl Data {
477 /// ```
478 /// let x = 5;
479 /// ```
480 fn foo() {}
481}
482"#,
483 &[&BIN, &DOCTEST],
484 expect![[r#"
485 [
486 Runnable {
487 nav: NavigationTarget {
488 file_id: FileId(
489 1,
490 ),
491 full_range: 1..13,
492 focus_range: Some(
493 4..8,
494 ),
495 name: "main",
496 kind: FN,
497 container_name: None,
498 description: None,
499 docs: None,
500 },
501 kind: Bin,
502 cfg_exprs: [],
503 },
504 Runnable {
505 nav: NavigationTarget {
506 file_id: FileId(
507 1,
508 ),
509 full_range: 44..98,
510 focus_range: None,
511 name: "foo",
512 kind: FN,
513 container_name: None,
514 description: None,
515 docs: None,
516 },
517 kind: DocTest {
518 test_id: Path(
519 "Data::foo",
520 ),
521 },
522 cfg_exprs: [],
523 },
524 ]
525 "#]],
526 );
527 }
528
529 #[test]
530 fn test_runnables_module() {
531 check(
532 r#"
533//- /lib.rs
534<|>
535mod test_mod {
536 #[test]
537 fn test_foo1() {}
538}
539"#,
540 &[&TEST, &TEST],
541 expect![[r#"
542 [
543 Runnable {
544 nav: NavigationTarget {
545 file_id: FileId(
546 1,
547 ),
548 full_range: 1..51,
549 focus_range: Some(
550 5..13,
551 ),
552 name: "test_mod",
553 kind: MODULE,
554 container_name: None,
555 description: None,
556 docs: None,
557 },
558 kind: TestMod {
559 path: "test_mod",
560 },
561 cfg_exprs: [],
562 },
563 Runnable {
564 nav: NavigationTarget {
565 file_id: FileId(
566 1,
567 ),
568 full_range: 20..49,
569 focus_range: Some(
570 35..44,
571 ),
572 name: "test_foo1",
573 kind: FN,
574 container_name: None,
575 description: None,
576 docs: None,
577 },
578 kind: Test {
579 test_id: Path(
580 "test_mod::test_foo1",
581 ),
582 attr: TestAttr {
583 ignore: false,
584 },
585 },
586 cfg_exprs: [],
587 },
588 ]
589 "#]],
590 );
591 }
592
593 #[test]
594 fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
595 check(
596 r#"
597//- /lib.rs
598<|>
599mod root_tests {
600 mod nested_tests_0 {
601 mod nested_tests_1 {
602 #[test]
603 fn nested_test_11() {}
604
605 #[test]
606 fn nested_test_12() {}
607 }
608
609 mod nested_tests_2 {
610 #[test]
611 fn nested_test_2() {}
612 }
613
614 mod nested_tests_3 {}
615 }
616
617 mod nested_tests_4 {}
618}
619"#,
620 &[&TEST, &TEST, &TEST, &TEST, &TEST, &TEST],
621 expect![[r#"
622 [
623 Runnable {
624 nav: NavigationTarget {
625 file_id: FileId(
626 1,
627 ),
628 full_range: 22..323,
629 focus_range: Some(
630 26..40,
631 ),
632 name: "nested_tests_0",
633 kind: MODULE,
634 container_name: None,
635 description: None,
636 docs: None,
637 },
638 kind: TestMod {
639 path: "root_tests::nested_tests_0",
640 },
641 cfg_exprs: [],
642 },
643 Runnable {
644 nav: NavigationTarget {
645 file_id: FileId(
646 1,
647 ),
648 full_range: 51..192,
649 focus_range: Some(
650 55..69,
651 ),
652 name: "nested_tests_1",
653 kind: MODULE,
654 container_name: None,
655 description: None,
656 docs: None,
657 },
658 kind: TestMod {
659 path: "root_tests::nested_tests_0::nested_tests_1",
660 },
661 cfg_exprs: [],
662 },
663 Runnable {
664 nav: NavigationTarget {
665 file_id: FileId(
666 1,
667 ),
668 full_range: 84..126,
669 focus_range: Some(
670 107..121,
671 ),
672 name: "nested_test_11",
673 kind: FN,
674 container_name: None,
675 description: None,
676 docs: None,
677 },
678 kind: Test {
679 test_id: Path(
680 "root_tests::nested_tests_0::nested_tests_1::nested_test_11",
681 ),
682 attr: TestAttr {
683 ignore: false,
684 },
685 },
686 cfg_exprs: [],
687 },
688 Runnable {
689 nav: NavigationTarget {
690 file_id: FileId(
691 1,
692 ),
693 full_range: 140..182,
694 focus_range: Some(
695 163..177,
696 ),
697 name: "nested_test_12",
698 kind: FN,
699 container_name: None,
700 description: None,
701 docs: None,
702 },
703 kind: Test {
704 test_id: Path(
705 "root_tests::nested_tests_0::nested_tests_1::nested_test_12",
706 ),
707 attr: TestAttr {
708 ignore: false,
709 },
710 },
711 cfg_exprs: [],
712 },
713 Runnable {
714 nav: NavigationTarget {
715 file_id: FileId(
716 1,
717 ),
718 full_range: 202..286,
719 focus_range: Some(
720 206..220,
721 ),
722 name: "nested_tests_2",
723 kind: MODULE,
724 container_name: None,
725 description: None,
726 docs: None,
727 },
728 kind: TestMod {
729 path: "root_tests::nested_tests_0::nested_tests_2",
730 },
731 cfg_exprs: [],
732 },
733 Runnable {
734 nav: NavigationTarget {
735 file_id: FileId(
736 1,
737 ),
738 full_range: 235..276,
739 focus_range: Some(
740 258..271,
741 ),
742 name: "nested_test_2",
743 kind: FN,
744 container_name: None,
745 description: None,
746 docs: None,
747 },
748 kind: Test {
749 test_id: Path(
750 "root_tests::nested_tests_0::nested_tests_2::nested_test_2",
751 ),
752 attr: TestAttr {
753 ignore: false,
754 },
755 },
756 cfg_exprs: [],
757 },
758 ]
759 "#]],
760 );
761 }
762
763 #[test]
764 fn test_runnables_with_feature() {
765 check(
766 r#"
767//- /lib.rs crate:foo cfg:feature=foo
768<|>
769#[test]
770#[cfg(feature = "foo")]
771fn test_foo1() {}
772"#,
773 &[&TEST],
774 expect![[r#"
775 [
776 Runnable {
777 nav: NavigationTarget {
778 file_id: FileId(
779 1,
780 ),
781 full_range: 1..50,
782 focus_range: Some(
783 36..45,
784 ),
785 name: "test_foo1",
786 kind: FN,
787 container_name: None,
788 description: None,
789 docs: None,
790 },
791 kind: Test {
792 test_id: Path(
793 "test_foo1",
794 ),
795 attr: TestAttr {
796 ignore: false,
797 },
798 },
799 cfg_exprs: [
800 KeyValue {
801 key: "feature",
802 value: "foo",
803 },
804 ],
805 },
806 ]
807 "#]],
808 );
809 }
810
811 #[test]
812 fn test_runnables_with_features() {
813 check(
814 r#"
815//- /lib.rs crate:foo cfg:feature=foo,feature=bar
816<|>
817#[test]
818#[cfg(all(feature = "foo", feature = "bar"))]
819fn test_foo1() {}
820"#,
821 &[&TEST],
822 expect![[r#"
823 [
824 Runnable {
825 nav: NavigationTarget {
826 file_id: FileId(
827 1,
828 ),
829 full_range: 1..72,
830 focus_range: Some(
831 58..67,
832 ),
833 name: "test_foo1",
834 kind: FN,
835 container_name: None,
836 description: None,
837 docs: None,
838 },
839 kind: Test {
840 test_id: Path(
841 "test_foo1",
842 ),
843 attr: TestAttr {
844 ignore: false,
845 },
846 },
847 cfg_exprs: [
848 All(
849 [
850 KeyValue {
851 key: "feature",
852 value: "foo",
853 },
854 KeyValue {
855 key: "feature",
856 value: "bar",
857 },
858 ],
859 ),
860 ],
861 },
862 ]
863 "#]],
864 );
865 }
866
867 #[test]
868 fn test_runnables_no_test_function_in_module() {
869 check(
870 r#"
871//- /lib.rs
872<|>
873mod test_mod {
874 fn foo1() {}
875}
876"#,
877 &[],
878 expect![[r#"
879 []
880 "#]],
881 );
882 }
883}
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
new file mode 100644
index 000000000..c23708181
--- /dev/null
+++ b/crates/ide/src/status.rs
@@ -0,0 +1,145 @@
1use std::{fmt, iter::FromIterator, sync::Arc};
2
3use base_db::{
4 salsa::debug::{DebugQueryTable, TableEntry},
5 FileTextQuery, SourceRootId,
6};
7use hir::MacroFile;
8use ide_db::{
9 symbol_index::{LibrarySymbolsQuery, SymbolIndex},
10 RootDatabase,
11};
12use profile::{memory_usage, Bytes};
13use rustc_hash::FxHashMap;
14use syntax::{ast, Parse, SyntaxNode};
15
16use crate::FileId;
17
18fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
19 base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>()
20}
21fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
22 hir::db::ParseMacroQuery.in_db(db).entries::<SyntaxTreeStats>()
23}
24
25// Feature: Status
26//
27// Shows internal statistic about memory usage of rust-analyzer.
28//
29// |===
30// | Editor | Action Name
31//
32// | VS Code | **Rust Analyzer: Status**
33// |===
34pub(crate) fn status(db: &RootDatabase) -> String {
35 let files_stats = FileTextQuery.in_db(db).entries::<FilesStats>();
36 let syntax_tree_stats = syntax_tree_stats(db);
37 let macro_syntax_tree_stats = macro_syntax_tree_stats(db);
38 let symbols_stats = LibrarySymbolsQuery.in_db(db).entries::<LibrarySymbolsStats>();
39 format!(
40 "{}\n{}\n{}\n{} (macros)\n\n\nmemory:\n{}\ngc {:?} seconds ago",
41 files_stats,
42 symbols_stats,
43 syntax_tree_stats,
44 macro_syntax_tree_stats,
45 memory_usage(),
46 db.last_gc.elapsed().as_secs(),
47 )
48}
49
50#[derive(Default)]
51struct FilesStats {
52 total: usize,
53 size: Bytes,
54}
55
56impl fmt::Display for FilesStats {
57 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
58 write!(fmt, "{} ({}) files", self.total, self.size)
59 }
60}
61
62impl FromIterator<TableEntry<FileId, Arc<String>>> for FilesStats {
63 fn from_iter<T>(iter: T) -> FilesStats
64 where
65 T: IntoIterator<Item = TableEntry<FileId, Arc<String>>>,
66 {
67 let mut res = FilesStats::default();
68 for entry in iter {
69 res.total += 1;
70 res.size += entry.value.unwrap().len();
71 }
72 res
73 }
74}
75
76#[derive(Default)]
77pub(crate) struct SyntaxTreeStats {
78 total: usize,
79 pub(crate) retained: usize,
80}
81
82impl fmt::Display for SyntaxTreeStats {
83 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
84 write!(fmt, "{} trees, {} retained", self.total, self.retained)
85 }
86}
87
88impl FromIterator<TableEntry<FileId, Parse<ast::SourceFile>>> for SyntaxTreeStats {
89 fn from_iter<T>(iter: T) -> SyntaxTreeStats
90 where
91 T: IntoIterator<Item = TableEntry<FileId, Parse<ast::SourceFile>>>,
92 {
93 let mut res = SyntaxTreeStats::default();
94 for entry in iter {
95 res.total += 1;
96 res.retained += entry.value.is_some() as usize;
97 }
98 res
99 }
100}
101
102impl<M> FromIterator<TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>> for SyntaxTreeStats {
103 fn from_iter<T>(iter: T) -> SyntaxTreeStats
104 where
105 T: IntoIterator<Item = TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>>,
106 {
107 let mut res = SyntaxTreeStats::default();
108 for entry in iter {
109 res.total += 1;
110 res.retained += entry.value.is_some() as usize;
111 }
112 res
113 }
114}
115
116#[derive(Default)]
117struct LibrarySymbolsStats {
118 total: usize,
119 size: Bytes,
120}
121
122impl fmt::Display for LibrarySymbolsStats {
123 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
124 write!(fmt, "{} ({}) symbols", self.total, self.size)
125 }
126}
127
128impl FromIterator<TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>
129 for LibrarySymbolsStats
130{
131 fn from_iter<T>(iter: T) -> LibrarySymbolsStats
132 where
133 T: IntoIterator<Item = TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>,
134 {
135 let mut res = LibrarySymbolsStats::default();
136 for entry in iter {
137 let value = entry.value.unwrap();
138 for symbols in value.values() {
139 res.total += symbols.len();
140 res.size += symbols.memory_size();
141 }
142 }
143 res
144 }
145}
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
new file mode 100644
index 000000000..25d6f7abd
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -0,0 +1,879 @@
1mod tags;
2mod html;
3mod injection;
4#[cfg(test)]
5mod tests;
6
7use hir::{Name, Semantics, VariantDef};
8use ide_db::{
9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
10 RootDatabase,
11};
12use rustc_hash::FxHashMap;
13use syntax::{
14 ast::{self, HasFormatSpecifier},
15 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
16 SyntaxKind::*,
17 TextRange, WalkEvent, T,
18};
19
20use crate::FileId;
21
22use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html;
24pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
25
26#[derive(Debug, Clone)]
27pub struct HighlightedRange {
28 pub range: TextRange,
29 pub highlight: Highlight,
30 pub binding_hash: Option<u64>,
31}
32
33// Feature: Semantic Syntax Highlighting
34//
35// rust-analyzer highlights the code semantically.
36// For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
37// rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
38// It's up to the client to map those to specific colors.
39//
40// The general rule is that a reference to an entity gets colored the same way as the entity itself.
41// We also give special modifier for `mut` and `&mut` local variables.
42pub(crate) fn highlight(
43 db: &RootDatabase,
44 file_id: FileId,
45 range_to_highlight: Option<TextRange>,
46 syntactic_name_ref_highlighting: bool,
47) -> Vec<HighlightedRange> {
48 let _p = profile::span("highlight");
49 let sema = Semantics::new(db);
50
51 // Determine the root based on the given range.
52 let (root, range_to_highlight) = {
53 let source_file = sema.parse(file_id);
54 match range_to_highlight {
55 Some(range) => {
56 let node = match source_file.syntax().covering_element(range) {
57 NodeOrToken::Node(it) => it,
58 NodeOrToken::Token(it) => it.parent(),
59 };
60 (node, range)
61 }
62 None => (source_file.syntax().clone(), source_file.syntax().text_range()),
63 }
64 };
65
66 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
67 // We use a stack for the DFS traversal below.
68 // When we leave a node, the we use it to flatten the highlighted ranges.
69 let mut stack = HighlightedRangeStack::new();
70
71 let mut current_macro_call: Option<ast::MacroCall> = None;
72 let mut format_string: Option<SyntaxElement> = None;
73
74 // Walk all nodes, keeping track of whether we are inside a macro or not.
75 // If in macro, expand it first and highlight the expanded code.
76 for event in root.preorder_with_tokens() {
77 match &event {
78 WalkEvent::Enter(_) => stack.push(),
79 WalkEvent::Leave(_) => stack.pop(),
80 };
81
82 let event_range = match &event {
83 WalkEvent::Enter(it) => it.text_range(),
84 WalkEvent::Leave(it) => it.text_range(),
85 };
86
87 // Element outside of the viewport, no need to highlight
88 if range_to_highlight.intersect(event_range).is_none() {
89 continue;
90 }
91
92 // Track "inside macro" state
93 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
94 WalkEvent::Enter(Some(mc)) => {
95 current_macro_call = Some(mc.clone());
96 if let Some(range) = macro_call_range(&mc) {
97 stack.add(HighlightedRange {
98 range,
99 highlight: HighlightTag::Macro.into(),
100 binding_hash: None,
101 });
102 }
103 if let Some(name) = mc.is_macro_rules() {
104 if let Some((highlight, binding_hash)) = highlight_element(
105 &sema,
106 &mut bindings_shadow_count,
107 syntactic_name_ref_highlighting,
108 name.syntax().clone().into(),
109 ) {
110 stack.add(HighlightedRange {
111 range: name.syntax().text_range(),
112 highlight,
113 binding_hash,
114 });
115 }
116 }
117 continue;
118 }
119 WalkEvent::Leave(Some(mc)) => {
120 assert!(current_macro_call == Some(mc));
121 current_macro_call = None;
122 format_string = None;
123 }
124 _ => (),
125 }
126
127 // Check for Rust code in documentation
128 match &event {
129 WalkEvent::Leave(NodeOrToken::Node(node)) => {
130 if let Some((doctest, range_mapping, new_comments)) =
131 injection::extract_doc_comments(node)
132 {
133 injection::highlight_doc_comment(
134 doctest,
135 range_mapping,
136 new_comments,
137 &mut stack,
138 );
139 }
140 }
141 _ => (),
142 }
143
144 let element = match event {
145 WalkEvent::Enter(it) => it,
146 WalkEvent::Leave(_) => continue,
147 };
148
149 let range = element.text_range();
150
151 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
152 // Inside a macro -- expand it first
153 let token = match element.clone().into_token() {
154 Some(it) if it.parent().kind() == TOKEN_TREE => it,
155 _ => continue,
156 };
157 let token = sema.descend_into_macros(token.clone());
158 let parent = token.parent();
159
160 // Check if macro takes a format string and remember it for highlighting later.
161 // The macros that accept a format string expand to a compiler builtin macros
162 // `format_args` and `format_args_nl`.
163 if let Some(name) = parent
164 .parent()
165 .and_then(ast::MacroCall::cast)
166 .and_then(|mc| mc.path())
167 .and_then(|p| p.segment())
168 .and_then(|s| s.name_ref())
169 {
170 match name.text().as_str() {
171 "format_args" | "format_args_nl" => {
172 format_string = parent
173 .children_with_tokens()
174 .filter(|t| t.kind() != WHITESPACE)
175 .nth(1)
176 .filter(|e| {
177 ast::String::can_cast(e.kind())
178 || ast::RawString::can_cast(e.kind())
179 })
180 }
181 _ => {}
182 }
183 }
184
185 // We only care Name and Name_ref
186 match (token.kind(), parent.kind()) {
187 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
188 _ => token.into(),
189 }
190 } else {
191 element.clone()
192 };
193
194 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
195 let expanded = element_to_highlight.as_token().unwrap().clone();
196 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
197 continue;
198 }
199 }
200
201 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
202
203 if let Some((highlight, binding_hash)) = highlight_element(
204 &sema,
205 &mut bindings_shadow_count,
206 syntactic_name_ref_highlighting,
207 element_to_highlight.clone(),
208 ) {
209 stack.add(HighlightedRange { range, highlight, binding_hash });
210 if let Some(string) =
211 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
212 {
213 if is_format_string {
214 stack.push();
215 string.lex_format_specifier(|piece_range, kind| {
216 if let Some(highlight) = highlight_format_specifier(kind) {
217 stack.add(HighlightedRange {
218 range: piece_range + range.start(),
219 highlight: highlight.into(),
220 binding_hash: None,
221 });
222 }
223 });
224 stack.pop();
225 }
226 // Highlight escape sequences
227 if let Some(char_ranges) = string.char_ranges() {
228 stack.push();
229 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
230 if string.text()[piece_range.start().into()..].starts_with('\\') {
231 stack.add(HighlightedRange {
232 range: piece_range + range.start(),
233 highlight: HighlightTag::EscapeSequence.into(),
234 binding_hash: None,
235 });
236 }
237 }
238 stack.pop_and_inject(None);
239 }
240 } else if let Some(string) =
241 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
242 {
243 if is_format_string {
244 stack.push();
245 string.lex_format_specifier(|piece_range, kind| {
246 if let Some(highlight) = highlight_format_specifier(kind) {
247 stack.add(HighlightedRange {
248 range: piece_range + range.start(),
249 highlight: highlight.into(),
250 binding_hash: None,
251 });
252 }
253 });
254 stack.pop();
255 }
256 }
257 }
258 }
259
260 stack.flattened()
261}
262
263#[derive(Debug)]
264struct HighlightedRangeStack {
265 stack: Vec<Vec<HighlightedRange>>,
266}
267
268/// We use a stack to implement the flattening logic for the highlighted
269/// syntax ranges.
270impl HighlightedRangeStack {
271 fn new() -> Self {
272 Self { stack: vec![Vec::new()] }
273 }
274
275 fn push(&mut self) {
276 self.stack.push(Vec::new());
277 }
278
279 /// Flattens the highlighted ranges.
280 ///
281 /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
282 /// 1) parent-range: Attribute [0, 23)
283 /// 2) child-range: String [16, 21)
284 ///
285 /// The following code implements the flattening, for our example this results to:
286 /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
287 fn pop(&mut self) {
288 let children = self.stack.pop().unwrap();
289 let prev = self.stack.last_mut().unwrap();
290 let needs_flattening = !children.is_empty()
291 && !prev.is_empty()
292 && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
293 if !needs_flattening {
294 prev.extend(children);
295 } else {
296 let mut parent = prev.pop().unwrap();
297 for ele in children {
298 assert!(parent.range.contains_range(ele.range));
299
300 let cloned = Self::intersect(&mut parent, &ele);
301 if !parent.range.is_empty() {
302 prev.push(parent);
303 }
304 prev.push(ele);
305 parent = cloned;
306 }
307 if !parent.range.is_empty() {
308 prev.push(parent);
309 }
310 }
311 }
312
313 /// Intersects the `HighlightedRange` `parent` with `child`.
314 /// `parent` is mutated in place, becoming the range before `child`.
315 /// Returns the range (of the same type as `parent`) *after* `child`.
316 fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
317 assert!(parent.range.contains_range(child.range));
318
319 let mut cloned = parent.clone();
320 parent.range = TextRange::new(parent.range.start(), child.range.start());
321 cloned.range = TextRange::new(child.range.end(), cloned.range.end());
322
323 cloned
324 }
325
326 /// Remove the `HighlightRange` of `parent` that's currently covered by `child`.
327 fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) {
328 assert!(
329 parent.range.start() <= child.range.start()
330 && parent.range.end() >= child.range.start()
331 && child.range.end() > parent.range.end()
332 );
333
334 parent.range = TextRange::new(parent.range.start(), child.range.start());
335 }
336
337 /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
338 /// can only modify the last range currently on the stack.
339 /// Can be used to do injections that span multiple ranges, like the
340 /// doctest injection below.
341 /// If `overwrite_parent` is non-optional, the highlighting of the parent range
342 /// is overwritten with the argument.
343 ///
344 /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
345 /// latter is computationally more expensive.
346 fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
347 let mut children = self.stack.pop().unwrap();
348 let prev = self.stack.last_mut().unwrap();
349 children.sort_by_key(|range| range.range.start());
350 prev.sort_by_key(|range| range.range.start());
351
352 for child in children {
353 if let Some(idx) =
354 prev.iter().position(|parent| parent.range.contains_range(child.range))
355 {
356 if let Some(tag) = overwrite_parent {
357 prev[idx].highlight = tag;
358 }
359
360 let cloned = Self::intersect(&mut prev[idx], &child);
361 let insert_idx = if prev[idx].range.is_empty() {
362 prev.remove(idx);
363 idx
364 } else {
365 idx + 1
366 };
367 prev.insert(insert_idx, child);
368 if !cloned.range.is_empty() {
369 prev.insert(insert_idx + 1, cloned);
370 }
371 } else {
372 let maybe_idx =
373 prev.iter().position(|parent| parent.range.contains(child.range.start()));
374 match (overwrite_parent, maybe_idx) {
375 (Some(_), Some(idx)) => {
376 Self::intersect_partial(&mut prev[idx], &child);
377 let insert_idx = if prev[idx].range.is_empty() {
378 prev.remove(idx);
379 idx
380 } else {
381 idx + 1
382 };
383 prev.insert(insert_idx, child);
384 }
385 (_, None) => {
386 let idx = prev
387 .binary_search_by_key(&child.range.start(), |range| range.range.start())
388 .unwrap_or_else(|x| x);
389 prev.insert(idx, child);
390 }
391 _ => {
392 unreachable!("child range should be completely contained in parent range");
393 }
394 }
395 }
396 }
397 }
398
399 fn add(&mut self, range: HighlightedRange) {
400 self.stack
401 .last_mut()
402 .expect("during DFS traversal, the stack must not be empty")
403 .push(range)
404 }
405
406 fn flattened(mut self) -> Vec<HighlightedRange> {
407 assert_eq!(
408 self.stack.len(),
409 1,
410 "after DFS traversal, the stack should only contain a single element"
411 );
412 let mut res = self.stack.pop().unwrap();
413 res.sort_by_key(|range| range.range.start());
414 // Check that ranges are sorted and disjoint
415 assert!(res
416 .iter()
417 .zip(res.iter().skip(1))
418 .all(|(left, right)| left.range.end() <= right.range.start()));
419 res
420 }
421}
422
423fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
424 Some(match kind {
425 FormatSpecifier::Open
426 | FormatSpecifier::Close
427 | FormatSpecifier::Colon
428 | FormatSpecifier::Fill
429 | FormatSpecifier::Align
430 | FormatSpecifier::Sign
431 | FormatSpecifier::NumberSign
432 | FormatSpecifier::DollarSign
433 | FormatSpecifier::Dot
434 | FormatSpecifier::Asterisk
435 | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier,
436 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
437 FormatSpecifier::Identifier => HighlightTag::Local,
438 })
439}
440
441fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
442 let path = macro_call.path()?;
443 let name_ref = path.segment()?.name_ref()?;
444
445 let range_start = name_ref.syntax().text_range().start();
446 let mut range_end = name_ref.syntax().text_range().end();
447 for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
448 match sibling.kind() {
449 T![!] | IDENT => range_end = sibling.text_range().end(),
450 _ => (),
451 }
452 }
453
454 Some(TextRange::new(range_start, range_end))
455}
456
457fn highlight_element(
458 sema: &Semantics<RootDatabase>,
459 bindings_shadow_count: &mut FxHashMap<Name, u32>,
460 syntactic_name_ref_highlighting: bool,
461 element: SyntaxElement,
462) -> Option<(Highlight, Option<u64>)> {
463 let db = sema.db;
464 let mut binding_hash = None;
465 let highlight: Highlight = match element.kind() {
466 FN => {
467 bindings_shadow_count.clear();
468 return None;
469 }
470
471 // Highlight definitions depending on the "type" of the definition.
472 NAME => {
473 let name = element.into_node().and_then(ast::Name::cast).unwrap();
474 let name_kind = classify_name(sema, &name);
475
476 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
477 if let Some(name) = local.name(db) {
478 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
479 *shadow_count += 1;
480 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
481 }
482 };
483
484 match name_kind {
485 Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(),
486 Some(NameClass::Definition(def)) => {
487 highlight_def(db, def) | HighlightModifier::Definition
488 }
489 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
490 Some(NameClass::FieldShorthand { field, .. }) => {
491 let mut h = HighlightTag::Field.into();
492 if let Definition::Field(field) = field {
493 if let VariantDef::Union(_) = field.parent_def(db) {
494 h |= HighlightModifier::Unsafe;
495 }
496 }
497
498 h
499 }
500 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
501 }
502 }
503
504 // Highlight references like the definitions they resolve to
505 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
506 Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
507 }
508 NAME_REF => {
509 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
510 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
511 match classify_name_ref(sema, &name_ref) {
512 Some(name_kind) => match name_kind {
513 NameRefClass::ExternCrate(_) => HighlightTag::Module.into(),
514 NameRefClass::Definition(def) => {
515 if let Definition::Local(local) = &def {
516 if let Some(name) = local.name(db) {
517 let shadow_count =
518 bindings_shadow_count.entry(name.clone()).or_default();
519 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
520 }
521 };
522
523 let mut h = highlight_def(db, def);
524
525 if let Some(parent) = name_ref.syntax().parent() {
526 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
527 if let Definition::Field(field) = def {
528 if let VariantDef::Union(_) = field.parent_def(db) {
529 h |= HighlightModifier::Unsafe;
530 }
531 }
532 }
533 }
534
535 h
536 }
537 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
538 },
539 None if syntactic_name_ref_highlighting => {
540 highlight_name_ref_by_syntax(name_ref, sema)
541 }
542 None => HighlightTag::UnresolvedReference.into(),
543 }
544 })
545 }
546
547 // Simple token-based highlighting
548 COMMENT => {
549 let comment = element.into_token().and_then(ast::Comment::cast)?;
550 let h = HighlightTag::Comment;
551 match comment.kind().doc {
552 Some(_) => h | HighlightModifier::Documentation,
553 None => h.into(),
554 }
555 }
556 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
557 ATTR => HighlightTag::Attribute.into(),
558 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
559 BYTE => HighlightTag::ByteLiteral.into(),
560 CHAR => HighlightTag::CharLiteral.into(),
561 QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
562 LIFETIME => {
563 let h = Highlight::new(HighlightTag::Lifetime);
564 match element.parent().map(|it| it.kind()) {
565 Some(LIFETIME_PARAM) | Some(LABEL) => h | HighlightModifier::Definition,
566 _ => h,
567 }
568 }
569 p if p.is_punct() => match p {
570 T![&] => {
571 let h = HighlightTag::Operator.into();
572 let is_unsafe = element
573 .parent()
574 .and_then(ast::RefExpr::cast)
575 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
576 .unwrap_or(false);
577 if is_unsafe {
578 h | HighlightModifier::Unsafe
579 } else {
580 h
581 }
582 }
583 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(),
584 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
585 HighlightTag::Macro.into()
586 }
587 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
588 HighlightTag::Keyword.into()
589 }
590 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
591 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
592
593 let expr = prefix_expr.expr()?;
594 let ty = sema.type_of_expr(&expr)?;
595 if ty.is_raw_ptr() {
596 HighlightTag::Operator | HighlightModifier::Unsafe
597 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
598 HighlightTag::Operator.into()
599 } else {
600 HighlightTag::Punctuation.into()
601 }
602 }
603 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
604 HighlightTag::NumericLiteral.into()
605 }
606 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
607 HighlightTag::Operator.into()
608 }
609 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
610 HighlightTag::Operator.into()
611 }
612 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
613 HighlightTag::Operator.into()
614 }
615 _ if element.parent().and_then(ast::RangePat::cast).is_some() => {
616 HighlightTag::Operator.into()
617 }
618 _ if element.parent().and_then(ast::RestPat::cast).is_some() => {
619 HighlightTag::Operator.into()
620 }
621 _ if element.parent().and_then(ast::Attr::cast).is_some() => {
622 HighlightTag::Attribute.into()
623 }
624 _ => HighlightTag::Punctuation.into(),
625 },
626
627 k if k.is_keyword() => {
628 let h = Highlight::new(HighlightTag::Keyword);
629 match k {
630 T![break]
631 | T![continue]
632 | T![else]
633 | T![if]
634 | T![loop]
635 | T![match]
636 | T![return]
637 | T![while]
638 | T![in] => h | HighlightModifier::ControlFlow,
639 T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow,
640 T![unsafe] => h | HighlightModifier::Unsafe,
641 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
642 T![self] => {
643 let self_param_is_mut = element
644 .parent()
645 .and_then(ast::SelfParam::cast)
646 .and_then(|p| p.mut_token())
647 .is_some();
648 // closure to enforce lazyness
649 let self_path = || {
650 sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?)
651 };
652 if self_param_is_mut
653 || matches!(self_path(),
654 Some(hir::PathResolution::Local(local))
655 if local.is_self(db)
656 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
657 )
658 {
659 HighlightTag::SelfKeyword | HighlightModifier::Mutable
660 } else {
661 HighlightTag::SelfKeyword.into()
662 }
663 }
664 T![ref] => element
665 .parent()
666 .and_then(ast::IdentPat::cast)
667 .and_then(|ident_pat| {
668 if sema.is_unsafe_ident_pat(&ident_pat) {
669 Some(HighlightModifier::Unsafe)
670 } else {
671 None
672 }
673 })
674 .map(|modifier| h | modifier)
675 .unwrap_or(h),
676 _ => h,
677 }
678 }
679
680 _ => return None,
681 };
682
683 return Some((highlight, binding_hash));
684
685 fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
686 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
687 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
688
689 let mut hasher = DefaultHasher::new();
690 x.hash(&mut hasher);
691 hasher.finish()
692 }
693
694 hash((name, shadow_count))
695 }
696}
697
698fn is_child_of_impl(element: &SyntaxElement) -> bool {
699 match element.parent() {
700 Some(e) => e.kind() == IMPL,
701 _ => false,
702 }
703}
704
705fn highlight_func_by_name_ref(
706 sema: &Semantics<RootDatabase>,
707 name_ref: &ast::NameRef,
708) -> Option<Highlight> {
709 let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
710 highlight_method_call(sema, &method_call)
711}
712
713fn highlight_method_call(
714 sema: &Semantics<RootDatabase>,
715 method_call: &ast::MethodCallExpr,
716) -> Option<Highlight> {
717 let func = sema.resolve_method_call(&method_call)?;
718 let mut h = HighlightTag::Function.into();
719 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
720 h |= HighlightModifier::Unsafe;
721 }
722 if let Some(self_param) = func.self_param(sema.db) {
723 match self_param.access(sema.db) {
724 hir::Access::Shared => (),
725 hir::Access::Exclusive => h |= HighlightModifier::Mutable,
726 hir::Access::Owned => {
727 if let Some(receiver_ty) =
728 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
729 {
730 if !receiver_ty.is_copy(sema.db) {
731 h |= HighlightModifier::Consuming
732 }
733 }
734 }
735 }
736 }
737 Some(h)
738}
739
740fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
741 match def {
742 Definition::Macro(_) => HighlightTag::Macro,
743 Definition::Field(_) => HighlightTag::Field,
744 Definition::ModuleDef(def) => match def {
745 hir::ModuleDef::Module(_) => HighlightTag::Module,
746 hir::ModuleDef::Function(func) => {
747 let mut h = HighlightTag::Function.into();
748 if func.is_unsafe(db) {
749 h |= HighlightModifier::Unsafe;
750 }
751 return h;
752 }
753 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
754 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
755 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
756 hir::ModuleDef::EnumVariant(_) => HighlightTag::EnumVariant,
757 hir::ModuleDef::Const(_) => HighlightTag::Constant,
758 hir::ModuleDef::Trait(_) => HighlightTag::Trait,
759 hir::ModuleDef::TypeAlias(_) => HighlightTag::TypeAlias,
760 hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType,
761 hir::ModuleDef::Static(s) => {
762 let mut h = Highlight::new(HighlightTag::Static);
763 if s.is_mut(db) {
764 h |= HighlightModifier::Mutable;
765 h |= HighlightModifier::Unsafe;
766 }
767 return h;
768 }
769 },
770 Definition::SelfType(_) => HighlightTag::SelfType,
771 Definition::TypeParam(_) => HighlightTag::TypeParam,
772 Definition::Local(local) => {
773 let tag =
774 if local.is_param(db) { HighlightTag::ValueParam } else { HighlightTag::Local };
775 let mut h = Highlight::new(tag);
776 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
777 h |= HighlightModifier::Mutable;
778 }
779 return h;
780 }
781 }
782 .into()
783}
784
785fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
786 let default = HighlightTag::UnresolvedReference;
787
788 let parent = match name.syntax().parent() {
789 Some(it) => it,
790 _ => return default.into(),
791 };
792
793 let tag = match parent.kind() {
794 STRUCT => HighlightTag::Struct,
795 ENUM => HighlightTag::Enum,
796 UNION => HighlightTag::Union,
797 TRAIT => HighlightTag::Trait,
798 TYPE_ALIAS => HighlightTag::TypeAlias,
799 TYPE_PARAM => HighlightTag::TypeParam,
800 RECORD_FIELD => HighlightTag::Field,
801 MODULE => HighlightTag::Module,
802 FN => HighlightTag::Function,
803 CONST => HighlightTag::Constant,
804 STATIC => HighlightTag::Static,
805 VARIANT => HighlightTag::EnumVariant,
806 IDENT_PAT => HighlightTag::Local,
807 _ => default,
808 };
809
810 tag.into()
811}
812
813fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
814 let default = HighlightTag::UnresolvedReference;
815
816 let parent = match name.syntax().parent() {
817 Some(it) => it,
818 _ => return default.into(),
819 };
820
821 match parent.kind() {
822 METHOD_CALL_EXPR => {
823 return ast::MethodCallExpr::cast(parent)
824 .and_then(|method_call| highlight_method_call(sema, &method_call))
825 .unwrap_or_else(|| HighlightTag::Function.into());
826 }
827 FIELD_EXPR => {
828 let h = HighlightTag::Field;
829 let is_union = ast::FieldExpr::cast(parent)
830 .and_then(|field_expr| {
831 let field = sema.resolve_field(&field_expr)?;
832 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
833 true
834 } else {
835 false
836 })
837 })
838 .unwrap_or(false);
839 if is_union {
840 h | HighlightModifier::Unsafe
841 } else {
842 h.into()
843 }
844 }
845 PATH_SEGMENT => {
846 let path = match parent.parent().and_then(ast::Path::cast) {
847 Some(it) => it,
848 _ => return default.into(),
849 };
850 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
851 Some(it) => it,
852 _ => {
853 // within path, decide whether it is module or adt by checking for uppercase name
854 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
855 HighlightTag::Struct
856 } else {
857 HighlightTag::Module
858 }
859 .into();
860 }
861 };
862 let parent = match expr.syntax().parent() {
863 Some(it) => it,
864 None => return default.into(),
865 };
866
867 match parent.kind() {
868 CALL_EXPR => HighlightTag::Function.into(),
869 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
870 HighlightTag::Struct.into()
871 } else {
872 HighlightTag::Constant
873 }
874 .into(),
875 }
876 }
877 _ => default.into(),
878 }
879}
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
new file mode 100644
index 000000000..249368ff8
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -0,0 +1,97 @@
1//! Renders a bit of code as HTML.
2
3use base_db::SourceDatabase;
4use oorandom::Rand32;
5use syntax::{AstNode, TextRange, TextSize};
6
7use crate::{syntax_highlighting::highlight, FileId, RootDatabase};
8
9pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
10 let parse = db.parse(file_id);
11
12 fn rainbowify(seed: u64) -> String {
13 let mut rng = Rand32::new(seed);
14 format!(
15 "hsl({h},{s}%,{l}%)",
16 h = rng.rand_range(0..361),
17 s = rng.rand_range(42..99),
18 l = rng.rand_range(40..91),
19 )
20 }
21
22 let ranges = highlight(db, file_id, None, false);
23 let text = parse.tree().syntax().to_string();
24 let mut prev_pos = TextSize::from(0);
25 let mut buf = String::new();
26 buf.push_str(&STYLE);
27 buf.push_str("<pre><code>");
28 for range in &ranges {
29 if range.range.start() > prev_pos {
30 let curr = &text[TextRange::new(prev_pos, range.range.start())];
31 let text = html_escape(curr);
32 buf.push_str(&text);
33 }
34 let curr = &text[TextRange::new(range.range.start(), range.range.end())];
35
36 let class = range.highlight.to_string().replace('.', " ");
37 let color = match (rainbow, range.binding_hash) {
38 (true, Some(hash)) => {
39 format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash))
40 }
41 _ => "".into(),
42 };
43 buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr)));
44
45 prev_pos = range.range.end();
46 }
47 // Add the remaining (non-highlighted) text
48 let curr = &text[TextRange::new(prev_pos, TextSize::of(&text))];
49 let text = html_escape(curr);
50 buf.push_str(&text);
51 buf.push_str("</code></pre>");
52 buf
53}
54
55//FIXME: like, real html escaping
56fn html_escape(text: &str) -> String {
57 text.replace("<", "&lt;").replace(">", "&gt;")
58}
59
60const STYLE: &str = "
61<style>
62body { margin: 0; }
63pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
64
65.lifetime { color: #DFAF8F; font-style: italic; }
66.comment { color: #7F9F7F; }
67.documentation { color: #629755; }
68.injected { opacity: 0.65 ; }
69.struct, .enum { color: #7CB8BB; }
70.enum_variant { color: #BDE0F3; }
71.string_literal { color: #CC9393; }
72.field { color: #94BFF3; }
73.function { color: #93E0E3; }
74.function.unsafe { color: #BC8383; }
75.operator.unsafe { color: #BC8383; }
76.parameter { color: #94BFF3; }
77.text { color: #DCDCCC; }
78.type { color: #7CB8BB; }
79.builtin_type { color: #8CD0D3; }
80.type_param { color: #DFAF8F; }
81.attribute { color: #94BFF3; }
82.numeric_literal { color: #BFEBBF; }
83.bool_literal { color: #BFE6EB; }
84.macro { color: #94BFF3; }
85.module { color: #AFD8AF; }
86.value_param { color: #DCDCCC; }
87.variable { color: #DCDCCC; }
88.format_specifier { color: #CC696B; }
89.mutable { text-decoration: underline; }
90.escape_sequence { color: #94BFF3; }
91.keyword { color: #F0DFAF; font-weight: bold; }
92.keyword.unsafe { color: #BC8383; font-weight: bold; }
93.control { font-style: italic; }
94
95.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
96</style>
97";
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
new file mode 100644
index 000000000..43f4e6fea
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -0,0 +1,187 @@
1//! Syntax highlighting injections such as highlighting of documentation tests.
2
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics;
7use itertools::Itertools;
8use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
9
10use crate::{
11 call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag,
12 HighlightedRange, RootDatabase,
13};
14
15use super::HighlightedRangeStack;
16
17pub(super) fn highlight_injection(
18 acc: &mut HighlightedRangeStack,
19 sema: &Semantics<RootDatabase>,
20 literal: ast::RawString,
21 expanded: SyntaxToken,
22) -> Option<()> {
23 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
24 if !active_parameter.name.starts_with("ra_fixture") {
25 return None;
26 }
27 let value = literal.value()?;
28 let (analysis, tmp_file_id) = Analysis::from_single_file(value.into_owned());
29
30 if let Some(range) = literal.open_quote_text_range() {
31 acc.add(HighlightedRange {
32 range,
33 highlight: HighlightTag::StringLiteral.into(),
34 binding_hash: None,
35 })
36 }
37
38 for mut h in analysis.highlight(tmp_file_id).unwrap() {
39 if let Some(r) = literal.map_range_up(h.range) {
40 h.range = r;
41 acc.add(h)
42 }
43 }
44
45 if let Some(range) = literal.close_quote_text_range() {
46 acc.add(HighlightedRange {
47 range,
48 highlight: HighlightTag::StringLiteral.into(),
49 binding_hash: None,
50 })
51 }
52
53 Some(())
54}
55
56/// Mapping from extracted documentation code to original code
57type RangesMap = BTreeMap<TextSize, TextSize>;
58
59const RUSTDOC_FENCE: &'static str = "```";
60const RUSTDOC_FENCE_TOKENS: &[&'static str] =
61 &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
62
63/// Extracts Rust code from documentation comments as well as a mapping from
64/// the extracted source code back to the original source ranges.
65/// Lastly, a vector of new comment highlight ranges (spanning only the
66/// comment prefix) is returned which is used in the syntax highlighting
67/// injection to replace the previous (line-spanning) comment ranges.
68pub(super) fn extract_doc_comments(
69 node: &SyntaxNode,
70) -> Option<(String, RangesMap, Vec<HighlightedRange>)> {
71 // wrap the doctest into function body to get correct syntax highlighting
72 let prefix = "fn doctest() {\n";
73 let suffix = "}\n";
74 // Mapping from extracted documentation code to original code
75 let mut range_mapping: RangesMap = BTreeMap::new();
76 let mut line_start = TextSize::try_from(prefix.len()).unwrap();
77 let mut is_codeblock = false;
78 let mut is_doctest = false;
79 // Replace the original, line-spanning comment ranges by new, only comment-prefix
80 // spanning comment ranges.
81 let mut new_comments = Vec::new();
82 let doctest = node
83 .children_with_tokens()
84 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
85 .filter(|comment| comment.kind().doc.is_some())
86 .filter(|comment| {
87 if let Some(idx) = comment.text().find(RUSTDOC_FENCE) {
88 is_codeblock = !is_codeblock;
89 // Check whether code is rust by inspecting fence guards
90 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..];
91 let is_rust =
92 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
93 is_doctest = is_codeblock && is_rust;
94 false
95 } else {
96 is_doctest
97 }
98 })
99 .map(|comment| {
100 let prefix_len = comment.prefix().len();
101 let line: &str = comment.text().as_str();
102 let range = comment.syntax().text_range();
103
104 // whitespace after comment is ignored
105 let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) {
106 prefix_len + ws.len_utf8()
107 } else {
108 prefix_len
109 };
110
111 // lines marked with `#` should be ignored in output, we skip the `#` char
112 let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') {
113 pos + ws.len_utf8()
114 } else {
115 pos
116 };
117
118 range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap());
119 new_comments.push(HighlightedRange {
120 range: TextRange::new(
121 range.start(),
122 range.start() + TextSize::try_from(pos).unwrap(),
123 ),
124 highlight: HighlightTag::Comment | HighlightModifier::Documentation,
125 binding_hash: None,
126 });
127 line_start += range.len() - TextSize::try_from(pos).unwrap();
128 line_start += TextSize::try_from('\n'.len_utf8()).unwrap();
129
130 line[pos..].to_owned()
131 })
132 .join("\n");
133
134 if doctest.is_empty() {
135 return None;
136 }
137
138 let doctest = format!("{}{}{}", prefix, doctest, suffix);
139 Some((doctest, range_mapping, new_comments))
140}
141
142/// Injection of syntax highlighting of doctests.
143pub(super) fn highlight_doc_comment(
144 text: String,
145 range_mapping: RangesMap,
146 new_comments: Vec<HighlightedRange>,
147 stack: &mut HighlightedRangeStack,
148) {
149 let (analysis, tmp_file_id) = Analysis::from_single_file(text);
150
151 stack.push();
152 for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
153 // Determine start offset and end offset in case of multi-line ranges
154 let mut start_offset = None;
155 let mut end_offset = None;
156 for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
157 // It's possible for orig_line_start - line_start to be negative. Add h.range.start()
158 // here and remove it from the end range after the loop below so that the values are
159 // always non-negative.
160 let offset = h.range.start() + orig_line_start - line_start;
161 if line_start <= &h.range.start() {
162 start_offset.get_or_insert(offset);
163 break;
164 } else {
165 end_offset.get_or_insert(offset);
166 }
167 }
168 if let Some(start_offset) = start_offset {
169 h.range = TextRange::new(
170 start_offset,
171 h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(),
172 );
173
174 h.highlight |= HighlightModifier::Injected;
175 stack.add(h);
176 }
177 }
178
179 // Inject the comment prefix highlight ranges
180 stack.push();
181 for comment in new_comments {
182 stack.add(comment);
183 }
184 stack.pop_and_inject(None);
185 stack
186 .pop_and_inject(Some(Highlight::from(HighlightTag::Generic) | HighlightModifier::Injected));
187}
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
new file mode 100644
index 000000000..c1b817f06
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -0,0 +1,206 @@
1//! Defines token tags we use for syntax highlighting.
2//! A tag is not unlike a CSS class.
3
4use std::{fmt, ops};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct Highlight {
8 pub tag: HighlightTag,
9 pub modifiers: HighlightModifiers,
10}
11
12#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct HighlightModifiers(u32);
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub enum HighlightTag {
17 Attribute,
18 BoolLiteral,
19 BuiltinType,
20 ByteLiteral,
21 CharLiteral,
22 Comment,
23 Constant,
24 Enum,
25 EnumVariant,
26 EscapeSequence,
27 Field,
28 Function,
29 Generic,
30 Keyword,
31 Lifetime,
32 Macro,
33 Module,
34 NumericLiteral,
35 Punctuation,
36 SelfKeyword,
37 SelfType,
38 Static,
39 StringLiteral,
40 Struct,
41 Trait,
42 TypeAlias,
43 TypeParam,
44 Union,
45 ValueParam,
46 Local,
47 UnresolvedReference,
48 FormatSpecifier,
49 Operator,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
53#[repr(u8)]
54pub enum HighlightModifier {
55 /// Used to differentiate individual elements within attributes.
56 Attribute = 0,
57 /// Used with keywords like `if` and `break`.
58 ControlFlow,
59 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
60 /// not.
61 Definition,
62 Documentation,
63 Injected,
64 Mutable,
65 Consuming,
66 Unsafe,
67}
68
69impl HighlightTag {
70 fn as_str(self) -> &'static str {
71 match self {
72 HighlightTag::Attribute => "attribute",
73 HighlightTag::BoolLiteral => "bool_literal",
74 HighlightTag::BuiltinType => "builtin_type",
75 HighlightTag::ByteLiteral => "byte_literal",
76 HighlightTag::CharLiteral => "char_literal",
77 HighlightTag::Comment => "comment",
78 HighlightTag::Constant => "constant",
79 HighlightTag::Enum => "enum",
80 HighlightTag::EnumVariant => "enum_variant",
81 HighlightTag::EscapeSequence => "escape_sequence",
82 HighlightTag::Field => "field",
83 HighlightTag::FormatSpecifier => "format_specifier",
84 HighlightTag::Function => "function",
85 HighlightTag::Generic => "generic",
86 HighlightTag::Keyword => "keyword",
87 HighlightTag::Lifetime => "lifetime",
88 HighlightTag::Punctuation => "punctuation",
89 HighlightTag::Macro => "macro",
90 HighlightTag::Module => "module",
91 HighlightTag::NumericLiteral => "numeric_literal",
92 HighlightTag::Operator => "operator",
93 HighlightTag::SelfKeyword => "self_keyword",
94 HighlightTag::SelfType => "self_type",
95 HighlightTag::Static => "static",
96 HighlightTag::StringLiteral => "string_literal",
97 HighlightTag::Struct => "struct",
98 HighlightTag::Trait => "trait",
99 HighlightTag::TypeAlias => "type_alias",
100 HighlightTag::TypeParam => "type_param",
101 HighlightTag::Union => "union",
102 HighlightTag::ValueParam => "value_param",
103 HighlightTag::Local => "variable",
104 HighlightTag::UnresolvedReference => "unresolved_reference",
105 }
106 }
107}
108
109impl fmt::Display for HighlightTag {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 fmt::Display::fmt(self.as_str(), f)
112 }
113}
114
115impl HighlightModifier {
116 const ALL: &'static [HighlightModifier] = &[
117 HighlightModifier::Attribute,
118 HighlightModifier::ControlFlow,
119 HighlightModifier::Definition,
120 HighlightModifier::Documentation,
121 HighlightModifier::Injected,
122 HighlightModifier::Mutable,
123 HighlightModifier::Consuming,
124 HighlightModifier::Unsafe,
125 ];
126
127 fn as_str(self) -> &'static str {
128 match self {
129 HighlightModifier::Attribute => "attribute",
130 HighlightModifier::ControlFlow => "control",
131 HighlightModifier::Definition => "declaration",
132 HighlightModifier::Documentation => "documentation",
133 HighlightModifier::Injected => "injected",
134 HighlightModifier::Mutable => "mutable",
135 HighlightModifier::Consuming => "consuming",
136 HighlightModifier::Unsafe => "unsafe",
137 }
138 }
139
140 fn mask(self) -> u32 {
141 1 << (self as u32)
142 }
143}
144
145impl fmt::Display for HighlightModifier {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 fmt::Display::fmt(self.as_str(), f)
148 }
149}
150
151impl fmt::Display for Highlight {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 write!(f, "{}", self.tag)?;
154 for modifier in self.modifiers.iter() {
155 write!(f, ".{}", modifier)?
156 }
157 Ok(())
158 }
159}
160
161impl From<HighlightTag> for Highlight {
162 fn from(tag: HighlightTag) -> Highlight {
163 Highlight::new(tag)
164 }
165}
166
167impl Highlight {
168 pub(crate) fn new(tag: HighlightTag) -> Highlight {
169 Highlight { tag, modifiers: HighlightModifiers::default() }
170 }
171}
172
173impl ops::BitOr<HighlightModifier> for HighlightTag {
174 type Output = Highlight;
175
176 fn bitor(self, rhs: HighlightModifier) -> Highlight {
177 Highlight::new(self) | rhs
178 }
179}
180
181impl ops::BitOrAssign<HighlightModifier> for HighlightModifiers {
182 fn bitor_assign(&mut self, rhs: HighlightModifier) {
183 self.0 |= rhs.mask();
184 }
185}
186
187impl ops::BitOrAssign<HighlightModifier> for Highlight {
188 fn bitor_assign(&mut self, rhs: HighlightModifier) {
189 self.modifiers |= rhs;
190 }
191}
192
193impl ops::BitOr<HighlightModifier> for Highlight {
194 type Output = Highlight;
195
196 fn bitor(mut self, rhs: HighlightModifier) -> Highlight {
197 self |= rhs;
198 self
199 }
200}
201
202impl HighlightModifiers {
203 pub fn iter(self) -> impl Iterator<Item = HighlightModifier> {
204 HighlightModifier::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask())
205 }
206}
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
new file mode 100644
index 000000000..1c3fea058
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -0,0 +1,484 @@
1use std::fs;
2
3use expect_test::{expect_file, ExpectFile};
4use test_utils::project_dir;
5
6use crate::{mock_analysis::single_file, FileRange, TextRange};
7
8#[test]
9fn test_highlighting() {
10 check_highlighting(
11 r#"
12use inner::{self as inner_mod};
13mod inner {}
14
15// Needed for function consuming vs normal
16pub mod marker {
17 #[lang = "copy"]
18 pub trait Copy {}
19}
20
21#[derive(Clone, Debug)]
22struct Foo {
23 pub x: i32,
24 pub y: i32,
25}
26
27trait Bar {
28 fn bar(&self) -> i32;
29}
30
31impl Bar for Foo {
32 fn bar(&self) -> i32 {
33 self.x
34 }
35}
36
37impl Foo {
38 fn baz(mut self) -> i32 {
39 self.x
40 }
41
42 fn qux(&mut self) {
43 self.x = 0;
44 }
45
46 fn quop(&self) -> i32 {
47 self.x
48 }
49}
50
51#[derive(Copy, Clone)]
52struct FooCopy {
53 x: u32,
54}
55
56impl FooCopy {
57 fn baz(self) -> u32 {
58 self.x
59 }
60
61 fn qux(&mut self) {
62 self.x = 0;
63 }
64
65 fn quop(&self) -> u32 {
66 self.x
67 }
68}
69
70static mut STATIC_MUT: i32 = 0;
71
72fn foo<'a, T>() -> T {
73 foo::<'a, i32>()
74}
75
76macro_rules! def_fn {
77 ($($tt:tt)*) => {$($tt)*}
78}
79
80def_fn! {
81 fn bar() -> u32 {
82 100
83 }
84}
85
86macro_rules! noop {
87 ($expr:expr) => {
88 $expr
89 }
90}
91
92// comment
93fn main() {
94 println!("Hello, {}!", 92);
95
96 let mut vec = Vec::new();
97 if true {
98 let x = 92;
99 vec.push(Foo { x, y: 1 });
100 }
101 unsafe {
102 vec.set_len(0);
103 STATIC_MUT = 1;
104 }
105
106 for e in vec {
107 // Do nothing
108 }
109
110 noop!(noop!(1));
111
112 let mut x = 42;
113 let y = &mut x;
114 let z = &y;
115
116 let Foo { x: z, y } = Foo { x: z, y };
117
118 y;
119
120 let mut foo = Foo { x, y: x };
121 foo.quop();
122 foo.qux();
123 foo.baz();
124
125 let mut copy = FooCopy { x };
126 copy.quop();
127 copy.qux();
128 copy.baz();
129}
130
131enum Option<T> {
132 Some(T),
133 None,
134}
135use Option::*;
136
137impl<T> Option<T> {
138 fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
139 match other {
140 None => unimplemented!(),
141 Nope => Nope,
142 }
143 }
144}
145"#
146 .trim(),
147 expect_file!["crates/ide/test_data/highlighting.html"],
148 false,
149 );
150}
151
152#[test]
153fn test_rainbow_highlighting() {
154 check_highlighting(
155 r#"
156fn main() {
157 let hello = "hello";
158 let x = hello.to_string();
159 let y = hello.to_string();
160
161 let x = "other color please!";
162 let y = x.to_string();
163}
164
165fn bar() {
166 let mut hello = "hello";
167}
168"#
169 .trim(),
170 expect_file!["crates/ide/test_data/rainbow_highlighting.html"],
171 true,
172 );
173}
174
175#[test]
176fn accidentally_quadratic() {
177 let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic");
178 let src = fs::read_to_string(file).unwrap();
179
180 let (analysis, file_id) = single_file(&src);
181
182 // let t = std::time::Instant::now();
183 let _ = analysis.highlight(file_id).unwrap();
184 // eprintln!("elapsed: {:?}", t.elapsed());
185}
186
187#[test]
188fn test_ranges() {
189 let (analysis, file_id) = single_file(
190 r#"
191#[derive(Clone, Debug)]
192struct Foo {
193 pub x: i32,
194 pub y: i32,
195}
196"#,
197 );
198
199 // The "x"
200 let highlights = &analysis
201 .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) })
202 .unwrap();
203
204 assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
205}
206
207#[test]
208fn test_flattening() {
209 check_highlighting(
210 r##"
211fn fixture(ra_fixture: &str) {}
212
213fn main() {
214 fixture(r#"
215 trait Foo {
216 fn foo() {
217 println!("2 + 2 = {}", 4);
218 }
219 }"#
220 );
221}"##
222 .trim(),
223 expect_file!["crates/ide/test_data/highlight_injection.html"],
224 false,
225 );
226}
227
228#[test]
229fn ranges_sorted() {
230 let (analysis, file_id) = single_file(
231 r#"
232#[foo(bar = "bar")]
233macro_rules! test {}
234}"#
235 .trim(),
236 );
237 let _ = analysis.highlight(file_id).unwrap();
238}
239
240#[test]
241fn test_string_highlighting() {
242 // The format string detection is based on macro-expansion,
243 // thus, we have to copy the macro definition from `std`
244 check_highlighting(
245 r#"
246macro_rules! println {
247 ($($arg:tt)*) => ({
248 $crate::io::_print($crate::format_args_nl!($($arg)*));
249 })
250}
251#[rustc_builtin_macro]
252macro_rules! format_args_nl {
253 ($fmt:expr) => {{ /* compiler built-in */ }};
254 ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
255}
256
257fn main() {
258 // from https://doc.rust-lang.org/std/fmt/index.html
259 println!("Hello"); // => "Hello"
260 println!("Hello, {}!", "world"); // => "Hello, world!"
261 println!("The number is {}", 1); // => "The number is 1"
262 println!("{:?}", (3, 4)); // => "(3, 4)"
263 println!("{value}", value=4); // => "4"
264 println!("{} {}", 1, 2); // => "1 2"
265 println!("{:04}", 42); // => "0042" with leading zerosV
266 println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
267 println!("{argument}", argument = "test"); // => "test"
268 println!("{name} {}", 1, name = 2); // => "2 1"
269 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
270 println!("{{{}}}", 2); // => "{2}"
271 println!("Hello {:5}!", "x");
272 println!("Hello {:1$}!", "x", 5);
273 println!("Hello {1:0$}!", 5, "x");
274 println!("Hello {:width$}!", "x", width = 5);
275 println!("Hello {:<5}!", "x");
276 println!("Hello {:-<5}!", "x");
277 println!("Hello {:^5}!", "x");
278 println!("Hello {:>5}!", "x");
279 println!("Hello {:+}!", 5);
280 println!("{:#x}!", 27);
281 println!("Hello {:05}!", 5);
282 println!("Hello {:05}!", -5);
283 println!("{:#010x}!", 27);
284 println!("Hello {0} is {1:.5}", "x", 0.01);
285 println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
286 println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
287 println!("Hello {} is {:.*}", "x", 5, 0.01);
288 println!("Hello {} is {2:.*}", "x", 5, 0.01);
289 println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
290 println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
291 println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
292 println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
293 println!("Hello {{}}");
294 println!("{{ Hello");
295
296 println!(r"Hello, {}!", "world");
297
298 // escape sequences
299 println!("Hello\nWorld");
300 println!("\u{48}\x65\x6C\x6C\x6F World");
301
302 println!("{\x41}", A = 92);
303 println!("{ничоси}", ничоси = 92);
304}"#
305 .trim(),
306 expect_file!["crates/ide/test_data/highlight_strings.html"],
307 false,
308 );
309}
310
311#[test]
312fn test_unsafe_highlighting() {
313 check_highlighting(
314 r#"
315unsafe fn unsafe_fn() {}
316
317union Union {
318 a: u32,
319 b: f32,
320}
321
322struct HasUnsafeFn;
323
324impl HasUnsafeFn {
325 unsafe fn unsafe_method(&self) {}
326}
327
328struct TypeForStaticMut {
329 a: u8
330}
331
332static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
333
334#[repr(packed)]
335struct Packed {
336 a: u16,
337}
338
339trait DoTheAutoref {
340 fn calls_autoref(&self);
341}
342
343impl DoTheAutoref for u16 {
344 fn calls_autoref(&self) {}
345}
346
347fn main() {
348 let x = &5 as *const _ as *const usize;
349 let u = Union { b: 0 };
350 unsafe {
351 // unsafe fn and method calls
352 unsafe_fn();
353 let b = u.b;
354 match u {
355 Union { b: 0 } => (),
356 Union { a } => (),
357 }
358 HasUnsafeFn.unsafe_method();
359
360 // unsafe deref
361 let y = *x;
362
363 // unsafe access to a static mut
364 let a = global_mut.a;
365
366 // unsafe ref of packed fields
367 let packed = Packed { a: 0 };
368 let a = &packed.a;
369 let ref a = packed.a;
370 let Packed { ref a } = packed;
371 let Packed { a: ref _a } = packed;
372
373 // unsafe auto ref of packed field
374 packed.a.calls_autoref();
375 }
376}
377"#
378 .trim(),
379 expect_file!["crates/ide/test_data/highlight_unsafe.html"],
380 false,
381 );
382}
383
384#[test]
385fn test_highlight_doctest() {
386 check_highlighting(
387 r#"
388/// ```
389/// let _ = "early doctests should not go boom";
390/// ```
391struct Foo {
392 bar: bool,
393}
394
395impl Foo {
396 pub const bar: bool = true;
397
398 /// Constructs a new `Foo`.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// # #![allow(unused_mut)]
404 /// let mut foo: Foo = Foo::new();
405 /// ```
406 pub const fn new() -> Foo {
407 Foo { bar: true }
408 }
409
410 /// `bar` method on `Foo`.
411 ///
412 /// # Examples
413 ///
414 /// ```
415 /// use x::y;
416 ///
417 /// let foo = Foo::new();
418 ///
419 /// // calls bar on foo
420 /// assert!(foo.bar());
421 ///
422 /// let bar = foo.bar || Foo::bar;
423 ///
424 /// /* multi-line
425 /// comment */
426 ///
427 /// let multi_line_string = "Foo
428 /// bar
429 /// ";
430 ///
431 /// ```
432 ///
433 /// ```rust,no_run
434 /// let foobar = Foo::new().bar();
435 /// ```
436 ///
437 /// ```sh
438 /// echo 1
439 /// ```
440 pub fn foo(&self) -> bool {
441 true
442 }
443}
444
445/// ```
446/// noop!(1);
447/// ```
448macro_rules! noop {
449 ($expr:expr) => {
450 $expr
451 }
452}
453"#
454 .trim(),
455 expect_file!["crates/ide/test_data/highlight_doctest.html"],
456 false,
457 );
458}
459
460#[test]
461fn test_extern_crate() {
462 check_highlighting(
463 r#"
464 //- /main.rs
465 extern crate std;
466 extern crate alloc as abc;
467 //- /std/lib.rs
468 pub struct S;
469 //- /alloc/lib.rs
470 pub struct A
471 "#,
472 expect_file!["crates/ide/test_data/highlight_extern_crate.html"],
473 false,
474 );
475}
476
477/// Highlights the code given by the `ra_fixture` argument, renders the
478/// result as HTML, and compares it with the HTML file given as `snapshot`.
479/// Note that the `snapshot` file is overwritten by the rendered HTML.
480fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
481 let (analysis, file_id) = single_file(ra_fixture);
482 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
483 expect.assert_eq(actual_html)
484}
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
new file mode 100644
index 000000000..f80044959
--- /dev/null
+++ b/crates/ide/src/syntax_tree.rs
@@ -0,0 +1,359 @@
1use base_db::{FileId, SourceDatabase};
2use ide_db::RootDatabase;
3use syntax::{
4 algo, AstNode, NodeOrToken, SourceFile,
5 SyntaxKind::{RAW_STRING, STRING},
6 SyntaxToken, TextRange, TextSize,
7};
8
9// Feature: Show Syntax Tree
10//
11// Shows the parse tree of the current file. It exists mostly for debugging
12// rust-analyzer itself.
13//
14// |===
15// | Editor | Action Name
16//
17// | VS Code | **Rust Analyzer: Show Syntax Tree**
18// |===
19pub(crate) fn syntax_tree(
20 db: &RootDatabase,
21 file_id: FileId,
22 text_range: Option<TextRange>,
23) -> String {
24 let parse = db.parse(file_id);
25 if let Some(text_range) = text_range {
26 let node = match algo::find_covering_element(parse.tree().syntax(), text_range) {
27 NodeOrToken::Node(node) => node,
28 NodeOrToken::Token(token) => {
29 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
30 return tree;
31 }
32 token.parent()
33 }
34 };
35
36 format!("{:#?}", node)
37 } else {
38 format!("{:#?}", parse.tree().syntax())
39 }
40}
41
42/// Attempts parsing the selected contents of a string literal
43/// as rust syntax and returns its syntax tree
44fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
45 // When the range is inside a string
46 // we'll attempt parsing it as rust syntax
47 // to provide the syntax tree of the contents of the string
48 match token.kind() {
49 STRING | RAW_STRING => syntax_tree_for_token(token, text_range),
50 _ => None,
51 }
52}
53
54fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
55 // Range of the full node
56 let node_range = node.text_range();
57 let text = node.text().to_string();
58
59 // We start at some point inside the node
60 // Either we have selected the whole string
61 // or our selection is inside it
62 let start = text_range.start() - node_range.start();
63
64 // how many characters we have selected
65 let len = text_range.len();
66
67 let node_len = node_range.len();
68
69 let start = start;
70
71 // We want to cap our length
72 let len = len.min(node_len);
73
74 // Ensure our slice is inside the actual string
75 let end =
76 if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
77
78 let text = &text[TextRange::new(start, end)];
79
80 // Remove possible extra string quotes from the start
81 // and the end of the string
82 let text = text
83 .trim_start_matches('r')
84 .trim_start_matches('#')
85 .trim_start_matches('"')
86 .trim_end_matches('#')
87 .trim_end_matches('"')
88 .trim()
89 // Remove custom markers
90 .replace("<|>", "");
91
92 let parsed = SourceFile::parse(&text);
93
94 // If the "file" parsed without errors,
95 // return its syntax
96 if parsed.errors().is_empty() {
97 return Some(format!("{:#?}", parsed.tree().syntax()));
98 }
99
100 None
101}
102
103#[cfg(test)]
104mod tests {
105 use test_utils::assert_eq_text;
106
107 use crate::mock_analysis::{analysis_and_range, single_file};
108
109 #[test]
110 fn test_syntax_tree_without_range() {
111 // Basic syntax
112 let (analysis, file_id) = single_file(r#"fn foo() {}"#);
113 let syn = analysis.syntax_tree(file_id, None).unwrap();
114
115 assert_eq_text!(
116 syn.trim(),
117 r#"
118[email protected]
119 [email protected]
120 [email protected] "fn"
121 [email protected] " "
122 [email protected]
123 [email protected] "foo"
124 [email protected]
125 [email protected] "("
126 [email protected] ")"
127 [email protected] " "
128 [email protected]
129 [email protected] "{"
130 [email protected] "}"
131"#
132 .trim()
133 );
134
135 let (analysis, file_id) = single_file(
136 r#"
137fn test() {
138 assert!("
139 fn foo() {
140 }
141 ", "");
142}"#
143 .trim(),
144 );
145 let syn = analysis.syntax_tree(file_id, None).unwrap();
146
147 assert_eq_text!(
148 syn.trim(),
149 r#"
150[email protected]
151 [email protected]
152 [email protected] "fn"
153 [email protected] " "
154 [email protected]
155 [email protected] "test"
156 [email protected]
157 [email protected] "("
158 [email protected] ")"
159 [email protected] " "
160 [email protected]
161 [email protected] "{"
162 [email protected] "\n "
163 [email protected]
164 [email protected]
165 [email protected]
166 [email protected]
167 [email protected]
168 [email protected] "assert"
169 [email protected] "!"
170 [email protected]
171 [email protected] "("
172 [email protected] "\"\n fn foo() {\n ..."
173 [email protected] ","
174 [email protected] " "
175 [email protected] "\"\""
176 [email protected] ")"
177 [email protected] ";"
178 [email protected] "\n"
179 [email protected] "}"
180"#
181 .trim()
182 );
183 }
184
185 #[test]
186 fn test_syntax_tree_with_range() {
187 let (analysis, range) = analysis_and_range(r#"<|>fn foo() {}<|>"#.trim());
188 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
189
190 assert_eq_text!(
191 syn.trim(),
192 r#"
193[email protected]
194 [email protected] "fn"
195 [email protected] " "
196 [email protected]
197 [email protected] "foo"
198 [email protected]
199 [email protected] "("
200 [email protected] ")"
201 [email protected] " "
202 [email protected]
203 [email protected] "{"
204 [email protected] "}"
205"#
206 .trim()
207 );
208
209 let (analysis, range) = analysis_and_range(
210 r#"fn test() {
211 <|>assert!("
212 fn foo() {
213 }
214 ", "");<|>
215}"#
216 .trim(),
217 );
218 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
219
220 assert_eq_text!(
221 syn.trim(),
222 r#"
223[email protected]
224 [email protected]
225 [email protected]
226 [email protected]
227 [email protected]
228 [email protected] "assert"
229 [email protected] "!"
230 [email protected]
231 [email protected] "("
232 [email protected] "\"\n fn foo() {\n ..."
233 [email protected] ","
234 [email protected] " "
235 [email protected] "\"\""
236 [email protected] ")"
237 [email protected] ";"
238"#
239 .trim()
240 );
241 }
242
243 #[test]
244 fn test_syntax_tree_inside_string() {
245 let (analysis, range) = analysis_and_range(
246 r#"fn test() {
247 assert!("
248<|>fn foo() {
249}<|>
250fn bar() {
251}
252 ", "");
253}"#
254 .trim(),
255 );
256 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
257 assert_eq_text!(
258 syn.trim(),
259 r#"
260[email protected]
261 [email protected]
262 [email protected] "fn"
263 [email protected] " "
264 [email protected]
265 [email protected] "foo"
266 [email protected]
267 [email protected] "("
268 [email protected] ")"
269 [email protected] " "
270 [email protected]
271 [email protected] "{"
272 [email protected] "\n"
273 [email protected] "}"
274"#
275 .trim()
276 );
277
278 // With a raw string
279 let (analysis, range) = analysis_and_range(
280 r###"fn test() {
281 assert!(r#"
282<|>fn foo() {
283}<|>
284fn bar() {
285}
286 "#, "");
287}"###
288 .trim(),
289 );
290 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
291 assert_eq_text!(
292 syn.trim(),
293 r#"
294[email protected]
295 [email protected]
296 [email protected] "fn"
297 [email protected] " "
298 [email protected]
299 [email protected] "foo"
300 [email protected]
301 [email protected] "("
302 [email protected] ")"
303 [email protected] " "
304 [email protected]
305 [email protected] "{"
306 [email protected] "\n"
307 [email protected] "}"
308"#
309 .trim()
310 );
311
312 // With a raw string
313 let (analysis, range) = analysis_and_range(
314 r###"fn test() {
315 assert!(r<|>#"
316fn foo() {
317}
318fn bar() {
319}"<|>#, "");
320}"###
321 .trim(),
322 );
323 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
324 assert_eq_text!(
325 syn.trim(),
326 r#"
327[email protected]
328 [email protected]
329 [email protected] "fn"
330 [email protected] " "
331 [email protected]
332 [email protected] "foo"
333 [email protected]
334 [email protected] "("
335 [email protected] ")"
336 [email protected] " "
337 [email protected]
338 [email protected] "{"
339 [email protected] "\n"
340 [email protected] "}"
341 [email protected] "\n"
342 [email protected]
343 [email protected] "fn"
344 [email protected] " "
345 [email protected]
346 [email protected] "bar"
347 [email protected]
348 [email protected] "("
349 [email protected] ")"
350 [email protected] " "
351 [email protected]
352 [email protected] "{"
353 [email protected] "\n"
354 [email protected] "}"
355"#
356 .trim()
357 );
358 }
359}
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
new file mode 100644
index 000000000..899ce5f26
--- /dev/null
+++ b/crates/ide/src/typing.rs
@@ -0,0 +1,364 @@
1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//! foo
6//! .bar()
7//! .baz()
8//! | // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
15
16mod on_enter;
17
18use base_db::{FilePosition, SourceDatabase};
19use ide_db::{source_change::SourceFileEdit, RootDatabase};
20use syntax::{
21 algo::find_node_at_offset,
22 ast::{self, edit::IndentLevel, AstToken},
23 AstNode, SourceFile,
24 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
25 TextRange, TextSize,
26};
27
28use text_edit::TextEdit;
29
30use crate::SourceChange;
31
32pub(crate) use on_enter::on_enter;
33
34pub(crate) const TRIGGER_CHARS: &str = ".=>";
35
36// Feature: On Typing Assists
37//
38// Some features trigger on typing certain characters:
39//
40// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
41// - typing `.` in a chain method call auto-indents
42pub(crate) fn on_char_typed(
43 db: &RootDatabase,
44 position: FilePosition,
45 char_typed: char,
46) -> Option<SourceChange> {
47 assert!(TRIGGER_CHARS.contains(char_typed));
48 let file = &db.parse(position.file_id).tree();
49 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
50 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
51 Some(SourceFileEdit { file_id: position.file_id, edit }.into())
52}
53
54fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
55 assert!(TRIGGER_CHARS.contains(char_typed));
56 match char_typed {
57 '.' => on_dot_typed(file, offset),
58 '=' => on_eq_typed(file, offset),
59 '>' => on_arrow_typed(file, offset),
60 _ => unreachable!(),
61 }
62}
63
64/// Returns an edit which should be applied after `=` was typed. Primarily,
65/// this works when adding `let =`.
66// FIXME: use a snippet completion instead of this hack here.
67fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
68 assert_eq!(file.syntax().text().char_at(offset), Some('='));
69 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
70 if let_stmt.semicolon_token().is_some() {
71 return None;
72 }
73 if let Some(expr) = let_stmt.initializer() {
74 let expr_range = expr.syntax().text_range();
75 if expr_range.contains(offset) && offset != expr_range.start() {
76 return None;
77 }
78 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
79 return None;
80 }
81 } else {
82 return None;
83 }
84 let offset = let_stmt.syntax().text_range().end();
85 Some(TextEdit::insert(offset, ";".to_string()))
86}
87
88/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
89fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
90 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
91 let whitespace =
92 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
93
94 let current_indent = {
95 let text = whitespace.text();
96 let newline = text.rfind('\n')?;
97 &text[newline + 1..]
98 };
99 let current_indent_len = TextSize::of(current_indent);
100
101 let parent = whitespace.syntax().parent();
102 // Make sure dot is a part of call chain
103 if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) {
104 return None;
105 }
106 let prev_indent = IndentLevel::from_node(&parent);
107 let target_indent = format!(" {}", prev_indent);
108 let target_indent_len = TextSize::of(&target_indent);
109 if current_indent_len == target_indent_len {
110 return None;
111 }
112
113 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
114}
115
116/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
117fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
118 let file_text = file.syntax().text();
119 assert_eq!(file_text.char_at(offset), Some('>'));
120 let after_arrow = offset + TextSize::of('>');
121 if file_text.char_at(after_arrow) != Some('{') {
122 return None;
123 }
124 if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
125 return None;
126 }
127
128 Some(TextEdit::insert(after_arrow, " ".to_string()))
129}
130
131#[cfg(test)]
132mod tests {
133 use test_utils::{assert_eq_text, extract_offset};
134
135 use super::*;
136
137 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
138 let (offset, before) = extract_offset(before);
139 let edit = TextEdit::insert(offset, char_typed.to_string());
140 let mut before = before.to_string();
141 edit.apply(&mut before);
142 let parse = SourceFile::parse(&before);
143 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
144 it.apply(&mut before);
145 before.to_string()
146 })
147 }
148
149 fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
150 let actual = do_type_char(char_typed, ra_fixture_before)
151 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
152
153 assert_eq_text!(ra_fixture_after, &actual);
154 }
155
156 fn type_char_noop(char_typed: char, before: &str) {
157 let file_change = do_type_char(char_typed, before);
158 assert!(file_change.is_none())
159 }
160
161 #[test]
162 fn test_on_eq_typed() {
163 // do_check(r"
164 // fn foo() {
165 // let foo =<|>
166 // }
167 // ", r"
168 // fn foo() {
169 // let foo =;
170 // }
171 // ");
172 type_char(
173 '=',
174 r"
175fn foo() {
176 let foo <|> 1 + 1
177}
178",
179 r"
180fn foo() {
181 let foo = 1 + 1;
182}
183",
184 );
185 // do_check(r"
186 // fn foo() {
187 // let foo =<|>
188 // let bar = 1;
189 // }
190 // ", r"
191 // fn foo() {
192 // let foo =;
193 // let bar = 1;
194 // }
195 // ");
196 }
197
198 #[test]
199 fn indents_new_chain_call() {
200 type_char(
201 '.',
202 r"
203 fn main() {
204 xs.foo()
205 <|>
206 }
207 ",
208 r"
209 fn main() {
210 xs.foo()
211 .
212 }
213 ",
214 );
215 type_char_noop(
216 '.',
217 r"
218 fn main() {
219 xs.foo()
220 <|>
221 }
222 ",
223 )
224 }
225
226 #[test]
227 fn indents_new_chain_call_with_semi() {
228 type_char(
229 '.',
230 r"
231 fn main() {
232 xs.foo()
233 <|>;
234 }
235 ",
236 r"
237 fn main() {
238 xs.foo()
239 .;
240 }
241 ",
242 );
243 type_char_noop(
244 '.',
245 r"
246 fn main() {
247 xs.foo()
248 <|>;
249 }
250 ",
251 )
252 }
253
254 #[test]
255 fn indents_new_chain_call_with_let() {
256 type_char(
257 '.',
258 r#"
259fn main() {
260 let _ = foo
261 <|>
262 bar()
263}
264"#,
265 r#"
266fn main() {
267 let _ = foo
268 .
269 bar()
270}
271"#,
272 );
273 }
274
275 #[test]
276 fn indents_continued_chain_call() {
277 type_char(
278 '.',
279 r"
280 fn main() {
281 xs.foo()
282 .first()
283 <|>
284 }
285 ",
286 r"
287 fn main() {
288 xs.foo()
289 .first()
290 .
291 }
292 ",
293 );
294 type_char_noop(
295 '.',
296 r"
297 fn main() {
298 xs.foo()
299 .first()
300 <|>
301 }
302 ",
303 );
304 }
305
306 #[test]
307 fn indents_middle_of_chain_call() {
308 type_char(
309 '.',
310 r"
311 fn source_impl() {
312 let var = enum_defvariant_list().unwrap()
313 <|>
314 .nth(92)
315 .unwrap();
316 }
317 ",
318 r"
319 fn source_impl() {
320 let var = enum_defvariant_list().unwrap()
321 .
322 .nth(92)
323 .unwrap();
324 }
325 ",
326 );
327 type_char_noop(
328 '.',
329 r"
330 fn source_impl() {
331 let var = enum_defvariant_list().unwrap()
332 <|>
333 .nth(92)
334 .unwrap();
335 }
336 ",
337 );
338 }
339
340 #[test]
341 fn dont_indent_freestanding_dot() {
342 type_char_noop(
343 '.',
344 r"
345 fn main() {
346 <|>
347 }
348 ",
349 );
350 type_char_noop(
351 '.',
352 r"
353 fn main() {
354 <|>
355 }
356 ",
357 );
358 }
359
360 #[test]
361 fn adds_space_after_return_type() {
362 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
363 }
364}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
new file mode 100644
index 000000000..f7d46146c
--- /dev/null
+++ b/crates/ide/src/typing/on_enter.rs
@@ -0,0 +1,256 @@
1//! Handles the `Enter` key press. At the momently, this only continues
2//! comments, but should handle indent some time in the future as well.
3
4use base_db::{FilePosition, SourceDatabase};
5use ide_db::RootDatabase;
6use syntax::{
7 ast::{self, AstToken},
8 AstNode, SmolStr, SourceFile,
9 SyntaxKind::*,
10 SyntaxToken, TextRange, TextSize, TokenAtOffset,
11};
12use test_utils::mark;
13use text_edit::TextEdit;
14
15// Feature: On Enter
16//
17// rust-analyzer can override kbd:[Enter] key to make it smarter:
18//
19// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
20// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
21//
22// This action needs to be assigned to shortcut explicitly.
23//
24// VS Code::
25//
26// Add the following to `keybindings.json`:
27// [source,json]
28// ----
29// {
30// "key": "Enter",
31// "command": "rust-analyzer.onEnter",
32// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust"
33// }
34// ----
35pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
36 let parse = db.parse(position.file_id);
37 let file = parse.tree();
38 let comment = file
39 .syntax()
40 .token_at_offset(position.offset)
41 .left_biased()
42 .and_then(ast::Comment::cast)?;
43
44 if comment.kind().shape.is_block() {
45 return None;
46 }
47
48 let prefix = comment.prefix();
49 let comment_range = comment.syntax().text_range();
50 if position.offset < comment_range.start() + TextSize::of(prefix) {
51 return None;
52 }
53
54 let mut remove_last_space = false;
55 // Continuing single-line non-doc comments (like this one :) ) is annoying
56 if prefix == "//" && comment_range.end() == position.offset {
57 if comment.text().ends_with(' ') {
58 mark::hit!(continues_end_of_line_comment_with_space);
59 remove_last_space = true;
60 } else if !followed_by_comment(&comment) {
61 return None;
62 }
63 }
64
65 let indent = node_indent(&file, comment.syntax())?;
66 let inserted = format!("\n{}{} $0", indent, prefix);
67 let delete = if remove_last_space {
68 TextRange::new(position.offset - TextSize::of(' '), position.offset)
69 } else {
70 TextRange::empty(position.offset)
71 };
72 let edit = TextEdit::replace(delete, inserted);
73 Some(edit)
74}
75
76fn followed_by_comment(comment: &ast::Comment) -> bool {
77 let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) {
78 Some(it) => it,
79 None => return false,
80 };
81 if ws.spans_multiple_lines() {
82 return false;
83 }
84 ws.syntax().next_token().and_then(ast::Comment::cast).is_some()
85}
86
87fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
88 let ws = match file.syntax().token_at_offset(token.text_range().start()) {
89 TokenAtOffset::Between(l, r) => {
90 assert!(r == *token);
91 l
92 }
93 TokenAtOffset::Single(n) => {
94 assert!(n == *token);
95 return Some("".into());
96 }
97 TokenAtOffset::None => unreachable!(),
98 };
99 if ws.kind() != WHITESPACE {
100 return None;
101 }
102 let text = ws.text();
103 let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0);
104 Some(text[pos..].into())
105}
106
107#[cfg(test)]
108mod tests {
109 use stdx::trim_indent;
110 use test_utils::{assert_eq_text, mark};
111
112 use crate::mock_analysis::analysis_and_position;
113
114 fn apply_on_enter(before: &str) -> Option<String> {
115 let (analysis, position) = analysis_and_position(&before);
116 let result = analysis.on_enter(position).unwrap()?;
117
118 let mut actual = analysis.file_text(position.file_id).unwrap().to_string();
119 result.apply(&mut actual);
120 Some(actual)
121 }
122
123 fn do_check(ra_fixture_before: &str, ra_fixture_after: &str) {
124 let ra_fixture_after = &trim_indent(ra_fixture_after);
125 let actual = apply_on_enter(ra_fixture_before).unwrap();
126 assert_eq_text!(ra_fixture_after, &actual);
127 }
128
129 fn do_check_noop(ra_fixture_text: &str) {
130 assert!(apply_on_enter(ra_fixture_text).is_none())
131 }
132
133 #[test]
134 fn continues_doc_comment() {
135 do_check(
136 r"
137/// Some docs<|>
138fn foo() {
139}
140",
141 r"
142/// Some docs
143/// $0
144fn foo() {
145}
146",
147 );
148
149 do_check(
150 r"
151impl S {
152 /// Some<|> docs.
153 fn foo() {}
154}
155",
156 r"
157impl S {
158 /// Some
159 /// $0 docs.
160 fn foo() {}
161}
162",
163 );
164
165 do_check(
166 r"
167///<|> Some docs
168fn foo() {
169}
170",
171 r"
172///
173/// $0 Some docs
174fn foo() {
175}
176",
177 );
178 }
179
180 #[test]
181 fn does_not_continue_before_doc_comment() {
182 do_check_noop(r"<|>//! docz");
183 }
184
185 #[test]
186 fn continues_code_comment_in_the_middle_of_line() {
187 do_check(
188 r"
189fn main() {
190 // Fix<|> me
191 let x = 1 + 1;
192}
193",
194 r"
195fn main() {
196 // Fix
197 // $0 me
198 let x = 1 + 1;
199}
200",
201 );
202 }
203
204 #[test]
205 fn continues_code_comment_in_the_middle_several_lines() {
206 do_check(
207 r"
208fn main() {
209 // Fix<|>
210 // me
211 let x = 1 + 1;
212}
213",
214 r"
215fn main() {
216 // Fix
217 // $0
218 // me
219 let x = 1 + 1;
220}
221",
222 );
223 }
224
225 #[test]
226 fn does_not_continue_end_of_line_comment() {
227 do_check_noop(
228 r"
229fn main() {
230 // Fix me<|>
231 let x = 1 + 1;
232}
233",
234 );
235 }
236
237 #[test]
238 fn continues_end_of_line_comment_with_space() {
239 mark::check!(continues_end_of_line_comment_with_space);
240 do_check(
241 r#"
242fn main() {
243 // Fix me <|>
244 let x = 1 + 1;
245}
246"#,
247 r#"
248fn main() {
249 // Fix me
250 // $0
251 let x = 1 + 1;
252}
253"#,
254 );
255 }
256}
diff --git a/crates/ide/test_data/highlight_doctest.html b/crates/ide/test_data/highlight_doctest.html
new file mode 100644
index 000000000..6322d404f
--- /dev/null
+++ b/crates/ide/test_data/highlight_doctest.html
@@ -0,0 +1,102 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="comment documentation">/// ```</span>
39<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="punctuation injected">_</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span><span class="punctuation injected">
40</span><span class="comment documentation">/// ```</span>
41<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
42 <span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span>
43<span class="punctuation">}</span>
44
45<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
46 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span>
47
48 <span class="comment documentation">/// Constructs a new `Foo`.</span>
49 <span class="comment documentation">///</span>
50 <span class="comment documentation">/// # Examples</span>
51 <span class="comment documentation">///</span>
52 <span class="comment documentation">/// ```</span>
53 <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute injected">#</span><span class="attribute injected">!</span><span class="attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation injected">(</span><span class="attribute injected">unused_mut</span><span class="punctuation injected">)</span><span class="attribute injected">]</span>
54 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="keyword injected">mut</span><span class="generic injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
55</span> <span class="comment documentation">/// ```</span>
56 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
57 <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span>
58 <span class="punctuation">}</span>
59
60 <span class="comment documentation">/// `bar` method on `Foo`.</span>
61 <span class="comment documentation">///</span>
62 <span class="comment documentation">/// # Examples</span>
63 <span class="comment documentation">///</span>
64 <span class="comment documentation">/// ```</span>
65 <span class="comment documentation">/// </span><span class="keyword injected">use</span><span class="generic injected"> </span><span class="module injected">x</span><span class="operator injected">::</span><span class="module injected">y</span><span class="punctuation injected">;</span>
66 <span class="comment documentation">///</span>
67 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
68 <span class="comment documentation">///</span>
69 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span>
70 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="punctuation injected">(</span><span class="generic injected">foo</span><span class="punctuation injected">.</span><span class="generic injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
71 <span class="comment documentation">///</span>
72 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">bar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="variable injected">foo</span><span class="punctuation injected">.</span><span class="field injected">bar</span><span class="generic injected"> </span><span class="operator injected">||</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span>
73 <span class="comment documentation">///</span>
74 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line
75 </span><span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
76 <span class="comment documentation">///</span>
77 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="string_literal injected">"Foo
78 </span><span class="comment documentation">/// </span><span class="string_literal injected"> bar
79 </span><span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span>
80 <span class="comment documentation">///</span>
81 <span class="comment documentation">/// ```</span>
82 <span class="comment documentation">///</span>
83 <span class="comment documentation">/// ```rust,no_run</span>
84 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="variable declaration injected">foobar</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
85</span> <span class="comment documentation">/// ```</span>
86 <span class="comment documentation">///</span>
87 <span class="comment documentation">/// ```sh</span>
88 <span class="comment documentation">/// echo 1</span>
89 <span class="comment documentation">/// ```</span>
90 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="punctuation">{</span>
91 <span class="bool_literal">true</span>
92 <span class="punctuation">}</span>
93<span class="punctuation">}</span>
94
95<span class="comment documentation">/// ```</span>
96<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
97</span><span class="comment documentation">/// ```</span>
98<span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span>
99 <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
100 <span class="punctuation">$</span>expr
101 <span class="punctuation">}</span>
102<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/highlight_extern_crate.html b/crates/ide/test_data/highlight_extern_crate.html
new file mode 100644
index 000000000..800d894c7
--- /dev/null
+++ b/crates/ide/test_data/highlight_extern_crate.html
@@ -0,0 +1,40 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">std</span><span class="punctuation">;</span>
39<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">alloc</span> <span class="keyword">as</span> <span class="module">abc</span><span class="punctuation">;</span>
40</code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/highlight_injection.html b/crates/ide/test_data/highlight_injection.html
new file mode 100644
index 000000000..18addd00d
--- /dev/null
+++ b/crates/ide/test_data/highlight_injection.html
@@ -0,0 +1,48 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="punctuation">(</span><span class="value_param declaration">ra_fixture</span><span class="punctuation">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
39
40<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
41 <span class="function">fixture</span><span class="punctuation">(</span><span class="string_literal">r#"</span>
42 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="punctuation">{</span>
43 <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
44 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span>
45 <span class="punctuation">}</span>
46 <span class="punctuation">}</span><span class="string_literal">"#</span>
47 <span class="punctuation">)</span><span class="punctuation">;</span>
48<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/highlight_strings.html b/crates/ide/test_data/highlight_strings.html
new file mode 100644
index 000000000..1b681b2c6
--- /dev/null
+++ b/crates/ide/test_data/highlight_strings.html
@@ -0,0 +1,96 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span>
39 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">{</span>
40 <span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
41 <span class="punctuation">}</span><span class="punctuation">)</span>
42<span class="punctuation">}</span>
43#[rustc_builtin_macro]
44<span class="macro">macro_rules!</span> <span class="macro declaration">format_args_nl</span> <span class="punctuation">{</span>
45 <span class="punctuation">(</span><span class="punctuation">$</span>fmt<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">{</span> <span class="comment">/* compiler built-in */</span> <span class="punctuation">}</span><span class="punctuation">}</span><span class="punctuation">;</span>
46 <span class="punctuation">(</span><span class="punctuation">$</span>fmt<span class="punctuation">:</span>expr<span class="punctuation">,</span> <span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>args<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">{</span> <span class="comment">/* compiler built-in */</span> <span class="punctuation">}</span><span class="punctuation">}</span><span class="punctuation">;</span>
47<span class="punctuation">}</span>
48
49<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
50 <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
51 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello"</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "Hello"</span>
52 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"world"</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "Hello, world!"</span>
53 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"The number is </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">1</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "The number is 1"</span>
54 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="punctuation">(</span><span class="numeric_literal">3</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "(3, 4)"</span>
55 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">value</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> value<span class="operator">=</span><span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "4"</span>
56 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">1</span><span class="punctuation">,</span> <span class="numeric_literal">2</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "1 2"</span>
57 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">42</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "0042" with leading zerosV</span>
58 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">1</span><span class="punctuation">,</span> <span class="numeric_literal">2</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "2 1 1 2"</span>
59 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> argument <span class="operator">=</span> <span class="string_literal">"test"</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "test"</span>
60 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">1</span><span class="punctuation">,</span> name <span class="operator">=</span> <span class="numeric_literal">2</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "2 1"</span>
61 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> a<span class="operator">=</span><span class="string_literal">"a"</span><span class="punctuation">,</span> b<span class="operator">=</span><span class="char_literal">'b'</span><span class="punctuation">,</span> c<span class="operator">=</span><span class="numeric_literal">3</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "a 3 b"</span>
62 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span><span class="punctuation">,</span> <span class="numeric_literal">2</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// =&gt; "{2}"</span>
63 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
64 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">)</span><span class="punctuation">;</span>
65 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
66 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">width</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> width <span class="operator">=</span> <span class="numeric_literal">5</span><span class="punctuation">)</span><span class="punctuation">;</span>
67 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&lt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
68 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">-</span><span class="format_specifier">&lt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
69 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
70 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">)</span><span class="punctuation">;</span>
71 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">)</span><span class="punctuation">;</span>
72 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="numeric_literal">27</span><span class="punctuation">)</span><span class="punctuation">;</span>
73 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">)</span><span class="punctuation">;</span>
74 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="punctuation">-</span><span class="numeric_literal">5</span><span class="punctuation">)</span><span class="punctuation">;</span>
75 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="numeric_literal">27</span><span class="punctuation">)</span><span class="punctuation">;</span>
76 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
77 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
78 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
79 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
80 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
81 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="variable">number</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="variable">prec</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> <span class="string_literal">"x"</span><span class="punctuation">,</span> prec <span class="operator">=</span> <span class="numeric_literal">5</span><span class="punctuation">,</span> number <span class="operator">=</span> <span class="numeric_literal">0.01</span><span class="punctuation">)</span><span class="punctuation">;</span>
82 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 fractional digits"</span><span class="punctuation">,</span> <span class="string_literal">"Hello"</span><span class="punctuation">,</span> <span class="numeric_literal">3</span><span class="punctuation">,</span> name<span class="operator">=</span><span class="numeric_literal">1234.56</span><span class="punctuation">)</span><span class="punctuation">;</span>
83 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 characters"</span><span class="punctuation">,</span> <span class="string_literal">"Hello"</span><span class="punctuation">,</span> <span class="numeric_literal">3</span><span class="punctuation">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="punctuation">)</span><span class="punctuation">;</span>
84 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">8</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 right-aligned characters"</span><span class="punctuation">,</span> <span class="string_literal">"Hello"</span><span class="punctuation">,</span> <span class="numeric_literal">3</span><span class="punctuation">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="punctuation">)</span><span class="punctuation">;</span>
85 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello {{}}"</span><span class="punctuation">)</span><span class="punctuation">;</span>
86 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"{{ Hello"</span><span class="punctuation">)</span><span class="punctuation">;</span>
87
88 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="punctuation">,</span> <span class="string_literal">"world"</span><span class="punctuation">)</span><span class="punctuation">;</span>
89
90 <span class="comment">// escape sequences</span>
91 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span><span class="punctuation">)</span><span class="punctuation">;</span>
92 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span><span class="punctuation">)</span><span class="punctuation">;</span>
93
94 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
95 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="punctuation">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
96<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/highlight_unsafe.html b/crates/ide/test_data/highlight_unsafe.html
new file mode 100644
index 000000000..552fea668
--- /dev/null
+++ b/crates/ide/test_data/highlight_unsafe.html
@@ -0,0 +1,99 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
39
40<span class="keyword">union</span> <span class="union declaration">Union</span> <span class="punctuation">{</span>
41 <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span>
42 <span class="field declaration">b</span><span class="punctuation">:</span> <span class="builtin_type">f32</span><span class="punctuation">,</span>
43<span class="punctuation">}</span>
44
45<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="punctuation">;</span>
46
47<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="punctuation">{</span>
48 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
49<span class="punctuation">}</span>
50
51<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="punctuation">{</span>
52 <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u8</span>
53<span class="punctuation">}</span>
54
55<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">global_mut</span><span class="punctuation">:</span> <span class="struct">TypeForStaticMut</span> <span class="operator">=</span> <span class="struct">TypeForStaticMut</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
56
57<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">repr</span><span class="punctuation">(</span><span class="attribute">packed</span><span class="punctuation">)</span><span class="attribute">]</span>
58<span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="punctuation">{</span>
59 <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u16</span><span class="punctuation">,</span>
60<span class="punctuation">}</span>
61
62<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="punctuation">{</span>
63 <span class="keyword">fn</span> <span class="function declaration">calls_autoref</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span><span class="punctuation">;</span>
64<span class="punctuation">}</span>
65
66<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="punctuation">{</span>
67 <span class="keyword">fn</span> <span class="function declaration">calls_autoref</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
68<span class="punctuation">}</span>
69
70<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
71 <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="punctuation">_</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="punctuation">;</span>
72 <span class="keyword">let</span> <span class="variable declaration">u</span> <span class="operator">=</span> <span class="union">Union</span> <span class="punctuation">{</span> <span class="field">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
73 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span>
74 <span class="comment">// unsafe fn and method calls</span>
75 <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
76 <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="punctuation">.</span><span class="field unsafe">b</span><span class="punctuation">;</span>
77 <span class="keyword control">match</span> <span class="variable">u</span> <span class="punctuation">{</span>
78 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
79 <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">a</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
80 <span class="punctuation">}</span>
81 <span class="struct">HasUnsafeFn</span><span class="punctuation">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
82
83 <span class="comment">// unsafe deref</span>
84 <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="variable">x</span><span class="punctuation">;</span>
85
86 <span class="comment">// unsafe access to a static mut</span>
87 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="static mutable unsafe">global_mut</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span>
88
89 <span class="comment">// unsafe ref of packed fields</span>
90 <span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
91 <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="operator unsafe">&</span><span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span>
92 <span class="keyword">let</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span>
93 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="keyword unsafe">ref</span> <span class="field">a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span>
94 <span class="keyword">let</span> <span class="struct">Packed</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="punctuation">;</span>
95
96 <span class="comment">// unsafe auto ref of packed field</span>
97 <span class="variable">packed</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">.</span><span class="function unsafe">calls_autoref</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
98 <span class="punctuation">}</span>
99<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/highlighting.html b/crates/ide/test_data/highlighting.html
new file mode 100644
index 000000000..d0df2e0ec
--- /dev/null
+++ b/crates/ide/test_data/highlighting.html
@@ -0,0 +1,170 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="punctuation">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="punctuation">}</span><span class="punctuation">;</span>
39<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="punctuation">{</span><span class="punctuation">}</span>
40
41<span class="comment">// Needed for function consuming vs normal</span>
42<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="punctuation">{</span>
43 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"copy"</span><span class="attribute">]</span>
44 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
45<span class="punctuation">}</span>
46
47<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Clone</span><span class="punctuation">,</span><span class="attribute"> Debug</span><span class="punctuation">)</span><span class="attribute">]</span>
48<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
49 <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span>
50 <span class="keyword">pub</span> <span class="field declaration">y</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span>
51<span class="punctuation">}</span>
52
53<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="punctuation">{</span>
54 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="punctuation">;</span>
55<span class="punctuation">}</span>
56
57<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
58 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
59 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
60 <span class="punctuation">}</span>
61<span class="punctuation">}</span>
62
63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
65 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
66 <span class="punctuation">}</span>
67
68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
69 <span class="self_keyword mutable">self</span><span class="punctuation">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span>
70 <span class="punctuation">}</span>
71
72 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
73 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
74 <span class="punctuation">}</span>
75<span class="punctuation">}</span>
76
77<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Copy</span><span class="punctuation">,</span><span class="attribute"> Clone</span><span class="punctuation">)</span><span class="attribute">]</span>
78<span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="punctuation">{</span>
79 <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span>
80<span class="punctuation">}</span>
81
82<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span>
83 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
84 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
85 <span class="punctuation">}</span>
86
87 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
88 <span class="self_keyword mutable">self</span><span class="punctuation">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span>
89 <span class="punctuation">}</span>
90
91 <span class="keyword">fn</span> <span class="function declaration">quop</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
92 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
93 <span class="punctuation">}</span>
94<span class="punctuation">}</span>
95
96<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="punctuation">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span>
97
98<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">&lt;</span><span class="lifetime declaration">'a</span><span class="punctuation">,</span> <span class="type_param declaration">T</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="type_param">T</span> <span class="punctuation">{</span>
99 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span>
100<span class="punctuation">}</span>
101
102<span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span>
103 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span>
104<span class="punctuation">}</span>
105
106<span class="macro">def_fn!</span> <span class="punctuation">{</span>
107 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-</span><span class="operator">&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
108 <span class="numeric_literal">100</span>
109 <span class="punctuation">}</span>
110<span class="punctuation">}</span>
111
112<span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span>
113 <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
114 <span class="punctuation">$</span>expr
115 <span class="punctuation">}</span>
116<span class="punctuation">}</span>
117
118<span class="comment">// comment</span>
119<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
120 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
121
122 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> <span class="operator">=</span> <span class="unresolved_reference">Vec</span><span class="operator">::</span><span class="unresolved_reference">new</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
123 <span class="keyword control">if</span> <span class="bool_literal">true</span> <span class="punctuation">{</span>
124 <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="numeric_literal">92</span><span class="punctuation">;</span>
125 <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">push</span><span class="punctuation">(</span><span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="numeric_literal">1</span> <span class="punctuation">}</span><span class="punctuation">)</span><span class="punctuation">;</span>
126 <span class="punctuation">}</span>
127 <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span>
128 <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">set_len</span><span class="punctuation">(</span><span class="numeric_literal">0</span><span class="punctuation">)</span><span class="punctuation">;</span>
129 <span class="static mutable unsafe">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="punctuation">;</span>
130 <span class="punctuation">}</span>
131
132 <span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> <span class="punctuation">{</span>
133 <span class="comment">// Do nothing</span>
134 <span class="punctuation">}</span>
135
136 <span class="macro">noop!</span><span class="punctuation">(</span><span class="macro">noop</span><span class="macro">!</span><span class="punctuation">(</span><span class="numeric_literal">1</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
137
138 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> <span class="operator">=</span> <span class="numeric_literal">42</span><span class="punctuation">;</span>
139 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> <span class="operator">=</span> <span class="operator">&</span><span class="keyword">mut</span> <span class="variable mutable">x</span><span class="punctuation">;</span>
140 <span class="keyword">let</span> <span class="variable declaration">z</span> <span class="operator">=</span> <span class="operator">&</span><span class="variable mutable">y</span><span class="punctuation">;</span>
141
142 <span class="keyword">let</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">:</span> <span class="variable declaration">z</span><span class="punctuation">,</span> <span class="field">y</span> <span class="punctuation">}</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">:</span> <span class="variable">z</span><span class="punctuation">,</span> <span class="field">y</span> <span class="punctuation">}</span><span class="punctuation">;</span>
143
144 <span class="variable">y</span><span class="punctuation">;</span>
145
146 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
147 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
148 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
149 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
150
151 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
152 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
153 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
154 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
155<span class="punctuation">}</span>
156
157<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
158 <span class="enum_variant declaration">Some</span><span class="punctuation">(</span><span class="type_param">T</span><span class="punctuation">)</span><span class="punctuation">,</span>
159 <span class="enum_variant declaration">None</span><span class="punctuation">,</span>
160<span class="punctuation">}</span>
161<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="punctuation">;</span>
162
163<span class="keyword">impl</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="enum">Option</span><span class="punctuation">&lt;</span><span class="type_param">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
164 <span class="keyword">fn</span> <span class="function declaration">and</span><span class="punctuation">&lt;</span><span class="type_param declaration">U</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">other</span><span class="punctuation">:</span> <span class="enum">Option</span><span class="punctuation">&lt;</span><span class="type_param">U</span><span class="punctuation">&gt;</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="punctuation">&lt;</span><span class="punctuation">(</span><span class="type_param">T</span><span class="punctuation">,</span> <span class="type_param">U</span><span class="punctuation">)</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
165 <span class="keyword control">match</span> <span class="value_param">other</span> <span class="punctuation">{</span>
166 <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="macro">unimplemented!</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
167 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="punctuation">,</span>
168 <span class="punctuation">}</span>
169 <span class="punctuation">}</span>
170<span class="punctuation">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/test_data/rainbow_highlighting.html b/crates/ide/test_data/rainbow_highlighting.html
new file mode 100644
index 000000000..401e87a73
--- /dev/null
+++ b/crates/ide/test_data/rainbow_highlighting.html
@@ -0,0 +1,49 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.documentation { color: #629755; }
9.injected { opacity: 0.65 ; }
10.struct, .enum { color: #7CB8BB; }
11.enum_variant { color: #BDE0F3; }
12.string_literal { color: #CC9393; }
13.field { color: #94BFF3; }
14.function { color: #93E0E3; }
15.function.unsafe { color: #BC8383; }
16.operator.unsafe { color: #BC8383; }
17.parameter { color: #94BFF3; }
18.text { color: #DCDCCC; }
19.type { color: #7CB8BB; }
20.builtin_type { color: #8CD0D3; }
21.type_param { color: #DFAF8F; }
22.attribute { color: #94BFF3; }
23.numeric_literal { color: #BFEBBF; }
24.bool_literal { color: #BFE6EB; }
25.macro { color: #94BFF3; }
26.module { color: #AFD8AF; }
27.value_param { color: #DCDCCC; }
28.variable { color: #DCDCCC; }
29.format_specifier { color: #CC696B; }
30.mutable { text-decoration: underline; }
31.escape_sequence { color: #94BFF3; }
32.keyword { color: #F0DFAF; font-weight: bold; }
33.keyword.unsafe { color: #BC8383; font-weight: bold; }
34.control { font-style: italic; }
35
36.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
37</style>
38<pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
39 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span>
40 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
41 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
42
43 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="punctuation">;</span>
44 <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
45<span class="punctuation">}</span>
46
47<span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
48 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span>
49<span class="punctuation">}</span></code></pre> \ No newline at end of file