diff options
author | Igor Aleksanov <[email protected]> | 2020-10-18 11:09:00 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-10-18 11:09:00 +0100 |
commit | 9e7c952bbddc2e6763c49f0511a295362e9893d6 (patch) | |
tree | 5b144eabe1eaf62aa1ec5b804ee6fff00f5f84e9 /crates/completion | |
parent | 2067a410f31810f6e1941a86cdea0247c3b7d6f4 (diff) |
Extract call_info and completion into separate crates
Diffstat (limited to 'crates/completion')
23 files changed, 8442 insertions, 0 deletions
diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml new file mode 100644 index 000000000..99087c73f --- /dev/null +++ b/crates/completion/Cargo.toml | |||
@@ -0,0 +1,32 @@ | |||
1 | [package] | ||
2 | name = "completion" | ||
3 | version = "0.0.0" | ||
4 | description = "TBD" | ||
5 | license = "MIT OR Apache-2.0" | ||
6 | authors = ["rust-analyzer developers"] | ||
7 | edition = "2018" | ||
8 | |||
9 | [lib] | ||
10 | doctest = false | ||
11 | |||
12 | [dependencies] | ||
13 | itertools = "0.9.0" | ||
14 | log = "0.4.8" | ||
15 | rustc-hash = "1.1.0" | ||
16 | |||
17 | syntax = { path = "../syntax", version = "0.0.0" } | ||
18 | text_edit = { path = "../text_edit", version = "0.0.0" } | ||
19 | base_db = { path = "../base_db", version = "0.0.0" } | ||
20 | ide_db = { path = "../ide_db", version = "0.0.0" } | ||
21 | profile = { path = "../profile", version = "0.0.0" } | ||
22 | test_utils = { path = "../test_utils", version = "0.0.0" } | ||
23 | assists = { path = "../assists", version = "0.0.0" } | ||
24 | call_info = { path = "../call_info", version = "0.0.0" } | ||
25 | |||
26 | # completions crate should depend only on the top-level `hir` package. if you need | ||
27 | # something from some `hir_xxx` subpackage, reexport the API via `hir`. | ||
28 | hir = { path = "../hir", version = "0.0.0" } | ||
29 | |||
30 | [dev-dependencies] | ||
31 | expect-test = "1.0" | ||
32 | stdx = { path = "../stdx", version = "0.0.0" } | ||
diff --git a/crates/completion/src/complete_attribute.rs b/crates/completion/src/complete_attribute.rs new file mode 100644 index 000000000..ea8ad256a --- /dev/null +++ b/crates/completion/src/complete_attribute.rs | |||
@@ -0,0 +1,657 @@ | |||
1 | //! Completion for attributes | ||
2 | //! | ||
3 | //! This module uses a bit of static metadata to provide completions | ||
4 | //! for built-in attributes. | ||
5 | |||
6 | use rustc_hash::FxHashSet; | ||
7 | use syntax::{ast, AstNode, SyntaxKind}; | ||
8 | |||
9 | use crate::{ | ||
10 | completion_context::CompletionContext, | ||
11 | completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, | ||
12 | generated_features::FEATURES, | ||
13 | }; | ||
14 | |||
15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
16 | if ctx.mod_declaration_under_caret.is_some() { | ||
17 | return None; | ||
18 | } | ||
19 | |||
20 | let attribute = ctx.attribute_under_caret.as_ref()?; | ||
21 | match (attribute.path(), attribute.token_tree()) { | ||
22 | (Some(path), Some(token_tree)) if path.to_string() == "derive" => { | ||
23 | complete_derive(acc, ctx, token_tree) | ||
24 | } | ||
25 | (Some(path), Some(token_tree)) if path.to_string() == "feature" => { | ||
26 | complete_lint(acc, ctx, token_tree, FEATURES) | ||
27 | } | ||
28 | (Some(path), Some(token_tree)) | ||
29 | if ["allow", "warn", "deny", "forbid"] | ||
30 | .iter() | ||
31 | .any(|lint_level| lint_level == &path.to_string()) => | ||
32 | { | ||
33 | complete_lint(acc, ctx, token_tree, DEFAULT_LINT_COMPLETIONS) | ||
34 | } | ||
35 | (_, Some(_token_tree)) => {} | ||
36 | _ => complete_attribute_start(acc, ctx, attribute), | ||
37 | } | ||
38 | Some(()) | ||
39 | } | ||
40 | |||
41 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
42 | for attr_completion in ATTRIBUTES { | ||
43 | let mut item = CompletionItem::new( | ||
44 | CompletionKind::Attribute, | ||
45 | ctx.source_range(), | ||
46 | attr_completion.label, | ||
47 | ) | ||
48 | .kind(CompletionItemKind::Attribute); | ||
49 | |||
50 | if let Some(lookup) = attr_completion.lookup { | ||
51 | item = item.lookup_by(lookup); | ||
52 | } | ||
53 | |||
54 | match (attr_completion.snippet, ctx.config.snippet_cap) { | ||
55 | (Some(snippet), Some(cap)) => { | ||
56 | item = item.insert_snippet(cap, snippet); | ||
57 | } | ||
58 | _ => {} | ||
59 | } | ||
60 | |||
61 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { | ||
62 | acc.add(item); | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | struct AttrCompletion { | ||
68 | label: &'static str, | ||
69 | lookup: Option<&'static str>, | ||
70 | snippet: Option<&'static str>, | ||
71 | prefer_inner: bool, | ||
72 | } | ||
73 | |||
74 | impl AttrCompletion { | ||
75 | const fn prefer_inner(self) -> AttrCompletion { | ||
76 | AttrCompletion { prefer_inner: true, ..self } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | const fn attr( | ||
81 | label: &'static str, | ||
82 | lookup: Option<&'static str>, | ||
83 | snippet: Option<&'static str>, | ||
84 | ) -> AttrCompletion { | ||
85 | AttrCompletion { label, lookup, snippet, prefer_inner: false } | ||
86 | } | ||
87 | |||
88 | const ATTRIBUTES: &[AttrCompletion] = &[ | ||
89 | attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), | ||
90 | attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), | ||
91 | attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), | ||
92 | attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), | ||
93 | attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)), | ||
94 | attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), | ||
95 | attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), | ||
96 | attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), | ||
97 | attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), | ||
98 | // FIXME: resolve through macro resolution? | ||
99 | attr("global_allocator", None, None).prefer_inner(), | ||
100 | attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), | ||
101 | attr("inline(…)", Some("inline"), Some("inline(${0:lint})")), | ||
102 | attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)), | ||
103 | attr("link", None, None), | ||
104 | attr("macro_export", None, None), | ||
105 | attr("macro_use", None, None), | ||
106 | attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)), | ||
107 | attr("no_mangle", None, None), | ||
108 | attr("no_std", None, None).prefer_inner(), | ||
109 | attr("non_exhaustive", None, None), | ||
110 | attr("panic_handler", None, None).prefer_inner(), | ||
111 | attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")), | ||
112 | attr("proc_macro", None, None), | ||
113 | attr("proc_macro_attribute", None, None), | ||
114 | attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")), | ||
115 | attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}")) | ||
116 | .prefer_inner(), | ||
117 | attr("repr(…)", Some("repr"), Some("repr(${0:C})")), | ||
118 | attr( | ||
119 | "should_panic(…)", | ||
120 | Some("should_panic"), | ||
121 | Some(r#"should_panic(expected = "${0:reason}")"#), | ||
122 | ), | ||
123 | attr( | ||
124 | r#"target_feature = "…""#, | ||
125 | Some("target_feature"), | ||
126 | Some("target_feature = \"${0:feature}\""), | ||
127 | ), | ||
128 | attr("test", None, None), | ||
129 | attr("used", None, None), | ||
130 | attr("warn(…)", Some("warn"), Some("warn(${0:lint})")), | ||
131 | attr( | ||
132 | r#"windows_subsystem = "…""#, | ||
133 | Some("windows_subsystem"), | ||
134 | Some(r#"windows_subsystem = "${0:subsystem}""#), | ||
135 | ) | ||
136 | .prefer_inner(), | ||
137 | ]; | ||
138 | |||
139 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
140 | if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { | ||
141 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
142 | .into_iter() | ||
143 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
144 | { | ||
145 | let mut label = derive_completion.label.to_owned(); | ||
146 | for dependency in derive_completion | ||
147 | .dependencies | ||
148 | .into_iter() | ||
149 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
150 | { | ||
151 | label.push_str(", "); | ||
152 | label.push_str(dependency); | ||
153 | } | ||
154 | acc.add( | ||
155 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
156 | .kind(CompletionItemKind::Attribute), | ||
157 | ); | ||
158 | } | ||
159 | |||
160 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
161 | acc.add( | ||
162 | CompletionItem::new( | ||
163 | CompletionKind::Attribute, | ||
164 | ctx.source_range(), | ||
165 | custom_derive_name, | ||
166 | ) | ||
167 | .kind(CompletionItemKind::Attribute), | ||
168 | ); | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | fn complete_lint( | ||
174 | acc: &mut Completions, | ||
175 | ctx: &CompletionContext, | ||
176 | derive_input: ast::TokenTree, | ||
177 | lints_completions: &[LintCompletion], | ||
178 | ) { | ||
179 | if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { | ||
180 | for lint_completion in lints_completions | ||
181 | .into_iter() | ||
182 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
183 | { | ||
184 | acc.add( | ||
185 | CompletionItem::new( | ||
186 | CompletionKind::Attribute, | ||
187 | ctx.source_range(), | ||
188 | lint_completion.label, | ||
189 | ) | ||
190 | .kind(CompletionItemKind::Attribute) | ||
191 | .detail(lint_completion.description), | ||
192 | ); | ||
193 | } | ||
194 | } | ||
195 | } | ||
196 | |||
197 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
198 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
199 | (Some(left_paren), Some(right_paren)) | ||
200 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
201 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
202 | { | ||
203 | let mut input_derives = FxHashSet::default(); | ||
204 | let mut current_derive = String::new(); | ||
205 | for token in derive_input | ||
206 | .syntax() | ||
207 | .children_with_tokens() | ||
208 | .filter_map(|token| token.into_token()) | ||
209 | .skip_while(|token| token != &left_paren) | ||
210 | .skip(1) | ||
211 | .take_while(|token| token != &right_paren) | ||
212 | { | ||
213 | if SyntaxKind::COMMA == token.kind() { | ||
214 | if !current_derive.is_empty() { | ||
215 | input_derives.insert(current_derive); | ||
216 | current_derive = String::new(); | ||
217 | } | ||
218 | } else { | ||
219 | current_derive.push_str(token.to_string().trim()); | ||
220 | } | ||
221 | } | ||
222 | |||
223 | if !current_derive.is_empty() { | ||
224 | input_derives.insert(current_derive); | ||
225 | } | ||
226 | Ok(input_derives) | ||
227 | } | ||
228 | _ => Err(()), | ||
229 | } | ||
230 | } | ||
231 | |||
232 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
233 | let mut result = FxHashSet::default(); | ||
234 | ctx.scope.process_all_names(&mut |name, scope_def| { | ||
235 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
236 | if mac.is_derive_macro() { | ||
237 | result.insert(name.to_string()); | ||
238 | } | ||
239 | } | ||
240 | }); | ||
241 | result | ||
242 | } | ||
243 | |||
244 | struct DeriveCompletion { | ||
245 | label: &'static str, | ||
246 | dependencies: &'static [&'static str], | ||
247 | } | ||
248 | |||
249 | /// Standard Rust derives and the information about their dependencies | ||
250 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
251 | #[rustfmt::skip] | ||
252 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
253 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
254 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
255 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
256 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
257 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
258 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
259 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
260 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
261 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
262 | ]; | ||
263 | |||
264 | pub(super) struct LintCompletion { | ||
265 | pub(super) label: &'static str, | ||
266 | pub(super) description: &'static str, | ||
267 | } | ||
268 | |||
269 | #[rustfmt::skip] | ||
270 | const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
271 | 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"# }, | ||
272 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
273 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
274 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
275 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
276 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
277 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
278 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
279 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
280 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
281 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
282 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
283 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
284 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
285 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
286 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
287 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
288 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
289 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
290 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
291 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
292 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
293 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
294 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
295 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
296 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
297 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
298 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
299 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
300 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
301 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
302 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
303 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
304 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
305 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
306 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
307 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
308 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
309 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
310 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
311 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
312 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
313 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
314 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
315 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
316 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
317 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
318 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
319 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
320 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
321 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
322 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
323 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
324 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
325 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
326 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
327 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
328 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
329 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
330 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
331 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
332 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
333 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
334 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
335 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
336 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
337 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
338 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
339 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
340 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
341 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
342 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
343 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
344 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
345 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
346 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
347 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
348 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
349 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
350 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
351 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
352 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
353 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
354 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
355 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
356 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
357 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
358 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
359 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
360 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
361 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
362 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
363 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
364 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
365 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
366 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
367 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
368 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
369 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
370 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
371 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
372 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
373 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
374 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
375 | 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"# }, | ||
376 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
377 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
378 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
379 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
380 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
381 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
382 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
383 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
384 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
385 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
386 | ]; | ||
387 | |||
388 | #[cfg(test)] | ||
389 | mod tests { | ||
390 | use expect_test::{expect, Expect}; | ||
391 | |||
392 | use crate::{test_utils::completion_list, CompletionKind}; | ||
393 | |||
394 | fn check(ra_fixture: &str, expect: Expect) { | ||
395 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); | ||
396 | expect.assert_eq(&actual); | ||
397 | } | ||
398 | |||
399 | #[test] | ||
400 | fn empty_derive_completion() { | ||
401 | check( | ||
402 | r#" | ||
403 | #[derive(<|>)] | ||
404 | struct Test {} | ||
405 | "#, | ||
406 | expect![[r#" | ||
407 | at Clone | ||
408 | at Copy, Clone | ||
409 | at Debug | ||
410 | at Default | ||
411 | at Eq, PartialEq | ||
412 | at Hash | ||
413 | at Ord, PartialOrd, Eq, PartialEq | ||
414 | at PartialEq | ||
415 | at PartialOrd, PartialEq | ||
416 | "#]], | ||
417 | ); | ||
418 | } | ||
419 | |||
420 | #[test] | ||
421 | fn empty_lint_completion() { | ||
422 | check( | ||
423 | r#"#[allow(<|>)]"#, | ||
424 | expect![[r#" | ||
425 | 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 | ||
426 | at ambiguous_associated_items ambiguous associated items | ||
427 | at anonymous_parameters detects anonymous parameters | ||
428 | at arithmetic_overflow arithmetic operation overflows | ||
429 | at array_into_iter detects calling `into_iter` on arrays | ||
430 | at asm_sub_register using only a subset of a register for inline asm inputs | ||
431 | at bare_trait_objects suggest using `dyn Trait` for trait objects | ||
432 | at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants | ||
433 | at box_pointers use of owned (Box type) heap memory | ||
434 | at cenum_impl_drop_cast a C-like enum implementing Drop is cast | ||
435 | at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types | ||
436 | at coherence_leak_check distinct impls distinguished only by the leak-check code | ||
437 | at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice | ||
438 | at confusable_idents detects visually confusable pairs between identifiers | ||
439 | at const_err constant evaluation detected erroneous expression | ||
440 | at dead_code detect unused, unexported items | ||
441 | at deprecated detects use of deprecated items | ||
442 | at deprecated_in_future detects use of items that will be deprecated in a future version | ||
443 | at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated | ||
444 | at ellipsis_inclusive_range_patterns `...` range patterns are deprecated | ||
445 | at explicit_outlives_requirements outlives requirements can be inferred | ||
446 | at exported_private_dependencies public interface leaks type from a private dependency | ||
447 | at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice | ||
448 | at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns | ||
449 | at improper_ctypes proper use of libc types in foreign modules | ||
450 | at improper_ctypes_definitions proper use of libc types in foreign item definitions | ||
451 | at incomplete_features incomplete features that may function improperly in some or all cases | ||
452 | at incomplete_include trailing content in included file | ||
453 | at indirect_structural_match pattern with const indirectly referencing non-structural-match type | ||
454 | at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]` | ||
455 | at intra_doc_link_resolution_failure failures in resolving intra-doc link targets | ||
456 | at invalid_codeblock_attributes codeblock attribute looks a lot like a known one | ||
457 | at invalid_type_param_default type parameter default erroneously allowed in invalid location | ||
458 | at invalid_value an invalid value is being created (such as a NULL reference) | ||
459 | at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements | ||
460 | at keyword_idents detects edition keywords being used as an identifier | ||
461 | at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters | ||
462 | 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 | ||
463 | at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system | ||
464 | at meta_variable_misuse possible meta-variable misuse at macro definition | ||
465 | at missing_copy_implementations detects potentially-forgotten implementations of `Copy` | ||
466 | at missing_crate_level_docs detects crates with no crate-level documentation | ||
467 | at missing_debug_implementations detects missing implementations of Debug | ||
468 | at missing_doc_code_examples detects publicly-exported items without code samples in their documentation | ||
469 | at missing_docs detects missing documentation for public members | ||
470 | at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns | ||
471 | at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used | ||
472 | at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows | ||
473 | at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior | ||
474 | at no_mangle_const_items const items will not have their symbols exported | ||
475 | at no_mangle_generic_items generic items must be mangled | ||
476 | at non_ascii_idents detects non-ASCII identifiers | ||
477 | at non_camel_case_types types, variants, traits and type parameters should have camel case names | ||
478 | at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern | ||
479 | at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names | ||
480 | at non_upper_case_globals static constants should have uppercase identifiers | ||
481 | at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order | ||
482 | at overflowing_literals literal out of range for its type | ||
483 | at overlapping_patterns detects overlapping patterns | ||
484 | at path_statements path statements with no effect | ||
485 | at patterns_in_fns_without_body patterns in functions without body were erroneously allowed | ||
486 | at private_doc_tests detects code samples in docs of private items not documented by rustdoc | ||
487 | at private_in_public detect private items in public interfaces not caught by the old implementation | ||
488 | at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules | ||
489 | at pub_use_of_private_extern_crate detect public re-exports of private extern crates | ||
490 | at redundant_semicolons detects unnecessary trailing semicolons | ||
491 | at renamed_and_removed_lints lints that have been renamed or removed | ||
492 | at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed | ||
493 | at single_use_lifetimes detects lifetime parameters that are only used once | ||
494 | at soft_unstable a feature gate that doesn't break dependent crates | ||
495 | at stable_features stable features found in `#[feature]` directive | ||
496 | at trivial_bounds these bounds don't depend on an type parameters | ||
497 | at trivial_casts detects trivial casts which could be removed | ||
498 | at trivial_numeric_casts detects trivial casts of numeric types which could be removed | ||
499 | at type_alias_bounds bounds in type aliases are not enforced | ||
500 | at tyvar_behind_raw_pointer raw pointer to an inference variable | ||
501 | at unaligned_references detects unaligned references to fields of packed structs | ||
502 | at uncommon_codepoints detects uncommon Unicode codepoints in identifiers | ||
503 | at unconditional_panic operation will cause a panic at runtime | ||
504 | at unconditional_recursion functions that cannot return without calling themselves | ||
505 | at unknown_crate_types unknown crate type found in `#[crate_type]` directive | ||
506 | at unknown_lints unrecognized lint attribute | ||
507 | at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]` | ||
508 | at unreachable_code detects unreachable code paths | ||
509 | at unreachable_patterns detects unreachable patterns | ||
510 | at unreachable_pub `pub` items not reachable from crate root | ||
511 | at unsafe_code usage of `unsafe` code | ||
512 | at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated | ||
513 | at unstable_features enabling unstable features (deprecated. do not use) | ||
514 | at unstable_name_collisions detects name collision with an existing but unstable method | ||
515 | at unused_allocation detects unnecessary allocations that can be eliminated | ||
516 | at unused_assignments detect assignments that will never be read | ||
517 | at unused_attributes detects attributes that were not used by the compiler | ||
518 | at unused_braces unnecessary braces around an expression | ||
519 | at unused_comparisons comparisons made useless by limits of the types involved | ||
520 | at unused_crate_dependencies crate dependencies that are never used | ||
521 | at unused_doc_comments detects doc comments that aren't used by rustdoc | ||
522 | at unused_extern_crates extern crates that are never used | ||
523 | at unused_features unused features found in crate-level `#[feature]` directives | ||
524 | at unused_import_braces unnecessary braces around an imported item | ||
525 | at unused_imports imports that are never used | ||
526 | at unused_labels detects labels that are never used | ||
527 | at unused_lifetimes detects lifetime parameters that are never used | ||
528 | at unused_macros detects macros that were not used | ||
529 | at unused_must_use unused result of a type flagged as `#[must_use]` | ||
530 | at unused_mut detect mut variables which don't need to be mutable | ||
531 | at unused_parens `if`, `match`, `while` and `return` do not need parentheses | ||
532 | at unused_qualifications detects unnecessarily qualified names | ||
533 | at unused_results unused result of an expression in a statement | ||
534 | at unused_unsafe unnecessary use of an `unsafe` block | ||
535 | at unused_variables detect variables which are not used in any way | ||
536 | at variant_size_differences detects enums with widely varying variant sizes | ||
537 | at warnings mass-change the level for lints which produce warnings | ||
538 | at where_clauses_object_safety checks the object safety of where clauses | ||
539 | at while_true suggest using `loop { }` instead of `while true { }` | ||
540 | "#]], | ||
541 | ) | ||
542 | } | ||
543 | |||
544 | #[test] | ||
545 | fn no_completion_for_incorrect_derive() { | ||
546 | check( | ||
547 | r#" | ||
548 | #[derive{<|>)] | ||
549 | struct Test {} | ||
550 | "#, | ||
551 | expect![[r#""#]], | ||
552 | ) | ||
553 | } | ||
554 | |||
555 | #[test] | ||
556 | fn derive_with_input_completion() { | ||
557 | check( | ||
558 | r#" | ||
559 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
560 | struct Test {} | ||
561 | "#, | ||
562 | expect![[r#" | ||
563 | at Clone | ||
564 | at Copy, Clone | ||
565 | at Debug | ||
566 | at Default | ||
567 | at Eq | ||
568 | at Hash | ||
569 | at Ord, PartialOrd, Eq | ||
570 | at PartialOrd | ||
571 | "#]], | ||
572 | ) | ||
573 | } | ||
574 | |||
575 | #[test] | ||
576 | fn test_attribute_completion() { | ||
577 | check( | ||
578 | r#"#[<|>]"#, | ||
579 | expect![[r#" | ||
580 | at allow(…) | ||
581 | at cfg(…) | ||
582 | at cfg_attr(…) | ||
583 | at deny(…) | ||
584 | at deprecated = "…" | ||
585 | at derive(…) | ||
586 | at doc = "…" | ||
587 | at forbid(…) | ||
588 | at ignore = "…" | ||
589 | at inline(…) | ||
590 | at link | ||
591 | at link_name = "…" | ||
592 | at macro_export | ||
593 | at macro_use | ||
594 | at must_use = "…" | ||
595 | at no_mangle | ||
596 | at non_exhaustive | ||
597 | at path = "…" | ||
598 | at proc_macro | ||
599 | at proc_macro_attribute | ||
600 | at proc_macro_derive(…) | ||
601 | at repr(…) | ||
602 | at should_panic(…) | ||
603 | at target_feature = "…" | ||
604 | at test | ||
605 | at used | ||
606 | at warn(…) | ||
607 | "#]], | ||
608 | ) | ||
609 | } | ||
610 | |||
611 | #[test] | ||
612 | fn test_attribute_completion_inside_nested_attr() { | ||
613 | check(r#"#[cfg(<|>)]"#, expect![[]]) | ||
614 | } | ||
615 | |||
616 | #[test] | ||
617 | fn test_inner_attribute_completion() { | ||
618 | check( | ||
619 | r"#![<|>]", | ||
620 | expect![[r#" | ||
621 | at allow(…) | ||
622 | at cfg(…) | ||
623 | at cfg_attr(…) | ||
624 | at deny(…) | ||
625 | at deprecated = "…" | ||
626 | at derive(…) | ||
627 | at doc = "…" | ||
628 | at feature(…) | ||
629 | at forbid(…) | ||
630 | at global_allocator | ||
631 | at ignore = "…" | ||
632 | at inline(…) | ||
633 | at link | ||
634 | at link_name = "…" | ||
635 | at macro_export | ||
636 | at macro_use | ||
637 | at must_use = "…" | ||
638 | at no_mangle | ||
639 | at no_std | ||
640 | at non_exhaustive | ||
641 | at panic_handler | ||
642 | at path = "…" | ||
643 | at proc_macro | ||
644 | at proc_macro_attribute | ||
645 | at proc_macro_derive(…) | ||
646 | at recursion_limit = … | ||
647 | at repr(…) | ||
648 | at should_panic(…) | ||
649 | at target_feature = "…" | ||
650 | at test | ||
651 | at used | ||
652 | at warn(…) | ||
653 | at windows_subsystem = "…" | ||
654 | "#]], | ||
655 | ); | ||
656 | } | ||
657 | } | ||
diff --git a/crates/completion/src/complete_dot.rs b/crates/completion/src/complete_dot.rs new file mode 100644 index 000000000..0eabb48ae --- /dev/null +++ b/crates/completion/src/complete_dot.rs | |||
@@ -0,0 +1,431 @@ | |||
1 | //! Completes references after dot (fields and method calls). | ||
2 | |||
3 | use hir::{HasVisibility, Type}; | ||
4 | use rustc_hash::FxHashSet; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::{completion_context::CompletionContext, completion_item::Completions}; | ||
8 | |||
9 | /// Complete dot accesses, i.e. fields or methods. | ||
10 | pub(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 | |||
29 | fn 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 | |||
46 | fn 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)] | ||
63 | mod tests { | ||
64 | use expect_test::{expect, Expect}; | ||
65 | use test_utils::mark; | ||
66 | |||
67 | use crate::{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#" | ||
78 | struct S { foo: u32 } | ||
79 | impl S { | ||
80 | fn bar(&self) {} | ||
81 | } | ||
82 | fn 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#" | ||
95 | struct S { the_field: (u32,) } | ||
96 | impl 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#" | ||
111 | struct A { the_field: (u32, i32) } | ||
112 | impl 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#" | ||
128 | struct A { the_field: u32 } | ||
129 | fn foo(a: A) { a.<|>() } | ||
130 | "#, | ||
131 | expect![[""]], | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn test_visibility_filtering() { | ||
137 | check( | ||
138 | r#" | ||
139 | mod 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 | } | ||
147 | fn 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#" | ||
158 | struct A {} | ||
159 | mod m { | ||
160 | impl super::A { | ||
161 | fn private_method(&self) {} | ||
162 | pub(super) fn the_method(&self) {} | ||
163 | } | ||
164 | } | ||
165 | fn 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#" | ||
177 | union U { field: u8, other: u16 } | ||
178 | fn 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#" | ||
191 | struct A<T> {} | ||
192 | impl A<u32> { | ||
193 | fn the_method(&self) {} | ||
194 | } | ||
195 | impl A<i32> { | ||
196 | fn the_other_method(&self) {} | ||
197 | } | ||
198 | fn 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#" | ||
210 | struct A {} | ||
211 | trait Trait { fn the_method(&self); } | ||
212 | impl Trait for A {} | ||
213 | fn 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" | ||
225 | struct A {} | ||
226 | trait Trait { fn the_method(&self); } | ||
227 | impl<T> Trait for T {} | ||
228 | fn 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" | ||
240 | struct A {} | ||
241 | mod m { | ||
242 | pub trait Trait { fn the_method(&self); } | ||
243 | } | ||
244 | use m::Trait; | ||
245 | impl Trait for A {} | ||
246 | fn 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#" | ||
258 | struct A {} | ||
259 | impl A { | ||
260 | fn the_method() {} | ||
261 | } | ||
262 | fn foo(a: A) { | ||
263 | a.<|> | ||
264 | } | ||
265 | "#, | ||
266 | expect![[""]], | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_tuple_field_completion() { | ||
272 | check( | ||
273 | r#" | ||
274 | fn 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#" | ||
290 | pub struct S; | ||
291 | impl S { pub fn blah(&self) {} } | ||
292 | |||
293 | struct T(S); | ||
294 | |||
295 | impl 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#" | ||
312 | struct A { the_field: u32 } | ||
313 | const 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#" | ||
327 | macro_rules! m { ($e:expr) => { $e } } | ||
328 | struct A { the_field: u32 } | ||
329 | fn 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#" | ||
344 | macro_rules! m { ($e:expr) => { $e } } | ||
345 | struct A { the_field: u32 } | ||
346 | fn 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#" | ||
360 | macro_rules! m { ($e:expr) => { $e } } | ||
361 | struct A { the_field: u32 } | ||
362 | fn 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#" | ||
376 | macro_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 | } | ||
387 | struct A { the_field: u32 } | ||
388 | fn 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#" | ||
402 | struct HashSet<T> {} | ||
403 | impl<T> HashSet<T> { | ||
404 | pub fn the_method(&self) {} | ||
405 | } | ||
406 | fn 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 | |||
417 | #[test] | ||
418 | fn completes_method_call_when_receiver_is_a_macro_call() { | ||
419 | check( | ||
420 | r#" | ||
421 | struct S; | ||
422 | impl S { fn foo(&self) {} } | ||
423 | macro_rules! make_s { () => { S }; } | ||
424 | fn main() { make_s!().f<|>; } | ||
425 | "#, | ||
426 | expect![[r#" | ||
427 | me foo() fn foo(&self) | ||
428 | "#]], | ||
429 | ) | ||
430 | } | ||
431 | } | ||
diff --git a/crates/completion/src/complete_fn_param.rs b/crates/completion/src/complete_fn_param.rs new file mode 100644 index 000000000..918996727 --- /dev/null +++ b/crates/completion/src/complete_fn_param.rs | |||
@@ -0,0 +1,135 @@ | |||
1 | //! See `complete_fn_param`. | ||
2 | |||
3 | use rustc_hash::FxHashMap; | ||
4 | use syntax::{ | ||
5 | ast::{self, ModuleItemOwner}, | ||
6 | match_ast, AstNode, | ||
7 | }; | ||
8 | |||
9 | use crate::{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. | ||
15 | pub(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)] | ||
68 | mod tests { | ||
69 | use expect_test::{expect, Expect}; | ||
70 | |||
71 | use crate::{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#" | ||
82 | fn foo(file_id: FileId) {} | ||
83 | fn bar(file_id: FileId) {} | ||
84 | fn 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#" | ||
96 | fn foo(file_id: FileId) {} | ||
97 | fn 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#" | ||
109 | pub(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#" | ||
126 | fn outer(text: String) { | ||
127 | fn inner(<|>) | ||
128 | } | ||
129 | "#, | ||
130 | expect![[r#" | ||
131 | bn text: String | ||
132 | "#]], | ||
133 | ) | ||
134 | } | ||
135 | } | ||
diff --git a/crates/completion/src/complete_keyword.rs b/crates/completion/src/complete_keyword.rs new file mode 100644 index 000000000..ace914f3f --- /dev/null +++ b/crates/completion/src/complete_keyword.rs | |||
@@ -0,0 +1,566 @@ | |||
1 | //! Completes keywords. | ||
2 | |||
3 | use syntax::{ast, SyntaxKind}; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; | ||
7 | |||
8 | pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
9 | // complete keyword "crate" in use stmt | ||
10 | let source_range = ctx.source_range(); | ||
11 | |||
12 | if ctx.use_item_syntax.is_some() { | ||
13 | if ctx.path_qual.is_none() { | ||
14 | CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") | ||
15 | .kind(CompletionItemKind::Keyword) | ||
16 | .insert_text("crate::") | ||
17 | .add_to(acc); | ||
18 | } | ||
19 | CompletionItem::new(CompletionKind::Keyword, source_range, "self") | ||
20 | .kind(CompletionItemKind::Keyword) | ||
21 | .add_to(acc); | ||
22 | CompletionItem::new(CompletionKind::Keyword, source_range, "super::") | ||
23 | .kind(CompletionItemKind::Keyword) | ||
24 | .insert_text("super::") | ||
25 | .add_to(acc); | ||
26 | } | ||
27 | |||
28 | // Suggest .await syntax for types that implement Future trait | ||
29 | if let Some(receiver) = &ctx.dot_receiver { | ||
30 | if let Some(ty) = ctx.sema.type_of_expr(receiver) { | ||
31 | if ty.impls_future(ctx.db) { | ||
32 | CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") | ||
33 | .kind(CompletionItemKind::Keyword) | ||
34 | .detail("expr.await") | ||
35 | .insert_text("await") | ||
36 | .add_to(acc); | ||
37 | } | ||
38 | }; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
43 | if ctx.token.kind() == SyntaxKind::COMMENT { | ||
44 | mark::hit!(no_keyword_completion_in_comments); | ||
45 | return; | ||
46 | } | ||
47 | |||
48 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; | ||
49 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { | ||
50 | add_keyword(ctx, acc, "where", "where "); | ||
51 | return; | ||
52 | } | ||
53 | if ctx.unsafe_is_prev { | ||
54 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
55 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
56 | } | ||
57 | |||
58 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
59 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
60 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
61 | } | ||
62 | |||
63 | return; | ||
64 | } | ||
65 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
66 | { | ||
67 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
68 | } | ||
69 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
70 | add_keyword(ctx, acc, "use", "use "); | ||
71 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
72 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
73 | } | ||
74 | |||
75 | if ctx.has_item_list_or_source_file_parent { | ||
76 | add_keyword(ctx, acc, "enum", "enum $0 {}"); | ||
77 | add_keyword(ctx, acc, "struct", "struct $0"); | ||
78 | add_keyword(ctx, acc, "union", "union $0 {}"); | ||
79 | } | ||
80 | |||
81 | if ctx.is_expr { | ||
82 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
83 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
84 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
85 | add_keyword(ctx, acc, "if", "if "); | ||
86 | add_keyword(ctx, acc, "if let", "if let "); | ||
87 | } | ||
88 | |||
89 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
90 | add_keyword(ctx, acc, "let", "let "); | ||
91 | } | ||
92 | |||
93 | if ctx.after_if { | ||
94 | add_keyword(ctx, acc, "else", "else {$0}"); | ||
95 | add_keyword(ctx, acc, "else if", "else if $0 {}"); | ||
96 | } | ||
97 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
98 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
99 | } | ||
100 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
101 | add_keyword(ctx, acc, "mut", "mut "); | ||
102 | } | ||
103 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
104 | { | ||
105 | add_keyword(ctx, acc, "const", "const "); | ||
106 | add_keyword(ctx, acc, "type", "type "); | ||
107 | } | ||
108 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
109 | add_keyword(ctx, acc, "static", "static "); | ||
110 | }; | ||
111 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
112 | add_keyword(ctx, acc, "extern", "extern "); | ||
113 | } | ||
114 | if ctx.has_item_list_or_source_file_parent | ||
115 | || has_trait_or_impl_parent | ||
116 | || ctx.block_expr_parent | ||
117 | || ctx.is_match_arm | ||
118 | { | ||
119 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
120 | } | ||
121 | if ctx.in_loop_body { | ||
122 | if ctx.can_be_stmt { | ||
123 | add_keyword(ctx, acc, "continue", "continue;"); | ||
124 | add_keyword(ctx, acc, "break", "break;"); | ||
125 | } else { | ||
126 | add_keyword(ctx, acc, "continue", "continue"); | ||
127 | add_keyword(ctx, acc, "break", "break"); | ||
128 | } | ||
129 | } | ||
130 | if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent | ctx.has_field_list_parent { | ||
131 | add_keyword(ctx, acc, "pub(crate)", "pub(crate) "); | ||
132 | add_keyword(ctx, acc, "pub", "pub "); | ||
133 | } | ||
134 | |||
135 | if !ctx.is_trivial_path { | ||
136 | return; | ||
137 | } | ||
138 | let fn_def = match &ctx.function_syntax { | ||
139 | Some(it) => it, | ||
140 | None => return, | ||
141 | }; | ||
142 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | ||
143 | } | ||
144 | |||
145 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | ||
146 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | ||
147 | .kind(CompletionItemKind::Keyword); | ||
148 | |||
149 | match ctx.config.snippet_cap { | ||
150 | Some(cap) => res.insert_snippet(cap, snippet), | ||
151 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
152 | } | ||
153 | .build() | ||
154 | } | ||
155 | |||
156 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
157 | acc.add(keyword(ctx, kw, snippet)); | ||
158 | } | ||
159 | |||
160 | fn complete_return( | ||
161 | ctx: &CompletionContext, | ||
162 | fn_def: &ast::Fn, | ||
163 | can_be_stmt: bool, | ||
164 | ) -> Option<CompletionItem> { | ||
165 | let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { | ||
166 | (true, true) => "return $0;", | ||
167 | (true, false) => "return;", | ||
168 | (false, true) => "return $0", | ||
169 | (false, false) => "return", | ||
170 | }; | ||
171 | Some(keyword(ctx, "return", snip)) | ||
172 | } | ||
173 | |||
174 | #[cfg(test)] | ||
175 | mod tests { | ||
176 | use expect_test::{expect, Expect}; | ||
177 | |||
178 | use crate::{ | ||
179 | test_utils::{check_edit, completion_list}, | ||
180 | CompletionKind, | ||
181 | }; | ||
182 | use test_utils::mark; | ||
183 | |||
184 | fn check(ra_fixture: &str, expect: Expect) { | ||
185 | let actual = completion_list(ra_fixture, CompletionKind::Keyword); | ||
186 | expect.assert_eq(&actual) | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn test_keywords_in_use_stmt() { | ||
191 | check( | ||
192 | r"use <|>", | ||
193 | expect![[r#" | ||
194 | kw crate:: | ||
195 | kw self | ||
196 | kw super:: | ||
197 | "#]], | ||
198 | ); | ||
199 | |||
200 | check( | ||
201 | r"use a::<|>", | ||
202 | expect![[r#" | ||
203 | kw self | ||
204 | kw super:: | ||
205 | "#]], | ||
206 | ); | ||
207 | |||
208 | check( | ||
209 | r"use a::{b, <|>}", | ||
210 | expect![[r#" | ||
211 | kw self | ||
212 | kw super:: | ||
213 | "#]], | ||
214 | ); | ||
215 | } | ||
216 | |||
217 | #[test] | ||
218 | fn test_keywords_at_source_file_level() { | ||
219 | check( | ||
220 | r"m<|>", | ||
221 | expect![[r#" | ||
222 | kw const | ||
223 | kw enum | ||
224 | kw extern | ||
225 | kw fn | ||
226 | kw impl | ||
227 | kw mod | ||
228 | kw pub | ||
229 | kw pub(crate) | ||
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#" | ||
330 | fn 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 pub(crate) | ||
368 | kw type | ||
369 | kw unsafe | ||
370 | "#]], | ||
371 | ); | ||
372 | } | ||
373 | |||
374 | #[test] | ||
375 | fn test_keywords_in_loop() { | ||
376 | check( | ||
377 | r"fn my() { loop { <|> } }", | ||
378 | expect![[r#" | ||
379 | kw break | ||
380 | kw const | ||
381 | kw continue | ||
382 | kw extern | ||
383 | kw fn | ||
384 | kw if | ||
385 | kw if let | ||
386 | kw impl | ||
387 | kw let | ||
388 | kw loop | ||
389 | kw match | ||
390 | kw mod | ||
391 | kw return | ||
392 | kw static | ||
393 | kw trait | ||
394 | kw type | ||
395 | kw unsafe | ||
396 | kw use | ||
397 | kw while | ||
398 | "#]], | ||
399 | ); | ||
400 | } | ||
401 | |||
402 | #[test] | ||
403 | fn test_keywords_after_unsafe_in_item_list() { | ||
404 | check( | ||
405 | r"unsafe <|>", | ||
406 | expect![[r#" | ||
407 | kw fn | ||
408 | kw impl | ||
409 | kw trait | ||
410 | "#]], | ||
411 | ); | ||
412 | } | ||
413 | |||
414 | #[test] | ||
415 | fn test_keywords_after_unsafe_in_block_expr() { | ||
416 | check( | ||
417 | r"fn my_fn() { unsafe <|> }", | ||
418 | expect![[r#" | ||
419 | kw fn | ||
420 | kw impl | ||
421 | kw trait | ||
422 | "#]], | ||
423 | ); | ||
424 | } | ||
425 | |||
426 | #[test] | ||
427 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
428 | check( | ||
429 | r"fn my_fn(&<|>) {}", | ||
430 | expect![[r#" | ||
431 | kw mut | ||
432 | "#]], | ||
433 | ); | ||
434 | check( | ||
435 | r"fn my_fn(<|>) {}", | ||
436 | expect![[r#" | ||
437 | kw mut | ||
438 | "#]], | ||
439 | ); | ||
440 | check( | ||
441 | r"fn my_fn() { let &<|> }", | ||
442 | expect![[r#" | ||
443 | kw mut | ||
444 | "#]], | ||
445 | ); | ||
446 | } | ||
447 | |||
448 | #[test] | ||
449 | fn test_where_keyword() { | ||
450 | check( | ||
451 | r"trait A <|>", | ||
452 | expect![[r#" | ||
453 | kw where | ||
454 | "#]], | ||
455 | ); | ||
456 | check( | ||
457 | r"impl A <|>", | ||
458 | expect![[r#" | ||
459 | kw where | ||
460 | "#]], | ||
461 | ); | ||
462 | } | ||
463 | |||
464 | #[test] | ||
465 | fn no_keyword_completion_in_comments() { | ||
466 | mark::check!(no_keyword_completion_in_comments); | ||
467 | check( | ||
468 | r#" | ||
469 | fn test() { | ||
470 | let x = 2; // A comment<|> | ||
471 | } | ||
472 | "#, | ||
473 | expect![[""]], | ||
474 | ); | ||
475 | check( | ||
476 | r#" | ||
477 | /* | ||
478 | Some multi-line comment<|> | ||
479 | */ | ||
480 | "#, | ||
481 | expect![[""]], | ||
482 | ); | ||
483 | check( | ||
484 | r#" | ||
485 | /// Some doc comment | ||
486 | /// let test<|> = 1 | ||
487 | "#, | ||
488 | expect![[""]], | ||
489 | ); | ||
490 | } | ||
491 | |||
492 | #[test] | ||
493 | fn test_completion_await_impls_future() { | ||
494 | check( | ||
495 | r#" | ||
496 | //- /main.rs crate:main deps:std | ||
497 | use std::future::*; | ||
498 | struct A {} | ||
499 | impl Future for A {} | ||
500 | fn foo(a: A) { a.<|> } | ||
501 | |||
502 | //- /std/lib.rs crate:std | ||
503 | pub mod future { | ||
504 | #[lang = "future_trait"] | ||
505 | pub trait Future {} | ||
506 | } | ||
507 | "#, | ||
508 | expect![[r#" | ||
509 | kw await expr.await | ||
510 | "#]], | ||
511 | ); | ||
512 | |||
513 | check( | ||
514 | r#" | ||
515 | //- /main.rs crate:main deps:std | ||
516 | use std::future::*; | ||
517 | fn foo() { | ||
518 | let a = async {}; | ||
519 | a.<|> | ||
520 | } | ||
521 | |||
522 | //- /std/lib.rs crate:std | ||
523 | pub mod future { | ||
524 | #[lang = "future_trait"] | ||
525 | pub trait Future { | ||
526 | type Output; | ||
527 | } | ||
528 | } | ||
529 | "#, | ||
530 | expect![[r#" | ||
531 | kw await expr.await | ||
532 | "#]], | ||
533 | ) | ||
534 | } | ||
535 | |||
536 | #[test] | ||
537 | fn after_let() { | ||
538 | check( | ||
539 | r#"fn main() { let _ = <|> }"#, | ||
540 | expect![[r#" | ||
541 | kw if | ||
542 | kw if let | ||
543 | kw loop | ||
544 | kw match | ||
545 | kw return | ||
546 | kw while | ||
547 | "#]], | ||
548 | ) | ||
549 | } | ||
550 | |||
551 | #[test] | ||
552 | fn before_field() { | ||
553 | check( | ||
554 | r#" | ||
555 | struct Foo { | ||
556 | <|> | ||
557 | pub f: i32, | ||
558 | } | ||
559 | "#, | ||
560 | expect![[r#" | ||
561 | kw pub | ||
562 | kw pub(crate) | ||
563 | "#]], | ||
564 | ) | ||
565 | } | ||
566 | } | ||
diff --git a/crates/completion/src/complete_macro_in_item_position.rs b/crates/completion/src/complete_macro_in_item_position.rs new file mode 100644 index 000000000..d1d8c23d2 --- /dev/null +++ b/crates/completion/src/complete_macro_in_item_position.rs | |||
@@ -0,0 +1,41 @@ | |||
1 | //! Completes macro invocations used in item position. | ||
2 | |||
3 | use crate::{CompletionContext, Completions}; | ||
4 | |||
5 | pub(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)] | ||
17 | mod tests { | ||
18 | use expect_test::{expect, Expect}; | ||
19 | |||
20 | use crate::{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#" | ||
31 | macro_rules! foo { () => {} } | ||
32 | fn foo() {} | ||
33 | |||
34 | <|> | ||
35 | "#, | ||
36 | expect![[r#" | ||
37 | ma foo!(…) macro_rules! foo | ||
38 | "#]], | ||
39 | ) | ||
40 | } | ||
41 | } | ||
diff --git a/crates/completion/src/complete_mod.rs b/crates/completion/src/complete_mod.rs new file mode 100644 index 000000000..35a57aba3 --- /dev/null +++ b/crates/completion/src/complete_mod.rs | |||
@@ -0,0 +1,324 @@ | |||
1 | //! Completes mod declarations. | ||
2 | |||
3 | use base_db::{SourceDatabaseExt, VfsPath}; | ||
4 | use hir::{Module, ModuleSource}; | ||
5 | use ide_db::RootDatabase; | ||
6 | use rustc_hash::FxHashSet; | ||
7 | |||
8 | use crate::{CompletionItem, CompletionItemKind}; | ||
9 | |||
10 | use super::{ | ||
11 | completion_context::CompletionContext, completion_item::CompletionKind, | ||
12 | completion_item::Completions, | ||
13 | }; | ||
14 | |||
15 | /// Complete mod declaration, i.e. `mod <|> ;` | ||
16 | pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
17 | let mod_under_caret = match &ctx.mod_declaration_under_caret { | ||
18 | Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None, | ||
19 | Some(mod_under_caret) => mod_under_caret, | ||
20 | None => return None, | ||
21 | }; | ||
22 | |||
23 | let _p = profile::span("completion::complete_mod"); | ||
24 | |||
25 | let current_module = ctx.scope.module()?; | ||
26 | |||
27 | let module_definition_file = | ||
28 | current_module.definition_source(ctx.db).file_id.original_file(ctx.db); | ||
29 | let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file)); | ||
30 | let directory_to_look_for_submodules = directory_to_look_for_submodules( | ||
31 | current_module, | ||
32 | ctx.db, | ||
33 | source_root.path_for_file(&module_definition_file)?, | ||
34 | )?; | ||
35 | |||
36 | let existing_mod_declarations = current_module | ||
37 | .children(ctx.db) | ||
38 | .filter_map(|module| Some(module.name(ctx.db)?.to_string())) | ||
39 | .collect::<FxHashSet<_>>(); | ||
40 | |||
41 | let module_declaration_file = | ||
42 | current_module.declaration_source(ctx.db).map(|module_declaration_source_file| { | ||
43 | module_declaration_source_file.file_id.original_file(ctx.db) | ||
44 | }); | ||
45 | |||
46 | source_root | ||
47 | .iter() | ||
48 | .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file) | ||
49 | .filter(|submodule_candidate_file| { | ||
50 | Some(submodule_candidate_file) != module_declaration_file.as_ref() | ||
51 | }) | ||
52 | .filter_map(|submodule_file| { | ||
53 | let submodule_path = source_root.path_for_file(&submodule_file)?; | ||
54 | let directory_with_submodule = submodule_path.parent()?; | ||
55 | match submodule_path.name_and_extension()? { | ||
56 | ("lib", Some("rs")) | ("main", Some("rs")) => None, | ||
57 | ("mod", Some("rs")) => { | ||
58 | if directory_with_submodule.parent()? == directory_to_look_for_submodules { | ||
59 | match directory_with_submodule.name_and_extension()? { | ||
60 | (directory_name, None) => Some(directory_name.to_owned()), | ||
61 | _ => None, | ||
62 | } | ||
63 | } else { | ||
64 | None | ||
65 | } | ||
66 | } | ||
67 | (file_name, Some("rs")) | ||
68 | if directory_with_submodule == directory_to_look_for_submodules => | ||
69 | { | ||
70 | Some(file_name.to_owned()) | ||
71 | } | ||
72 | _ => None, | ||
73 | } | ||
74 | }) | ||
75 | .filter(|name| !existing_mod_declarations.contains(name)) | ||
76 | .for_each(|submodule_name| { | ||
77 | let mut label = submodule_name; | ||
78 | if mod_under_caret.semicolon_token().is_none() { | ||
79 | label.push(';') | ||
80 | } | ||
81 | acc.add( | ||
82 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label) | ||
83 | .kind(CompletionItemKind::Module), | ||
84 | ) | ||
85 | }); | ||
86 | |||
87 | Some(()) | ||
88 | } | ||
89 | |||
90 | fn directory_to_look_for_submodules( | ||
91 | module: Module, | ||
92 | db: &RootDatabase, | ||
93 | module_file_path: &VfsPath, | ||
94 | ) -> Option<VfsPath> { | ||
95 | let directory_with_module_path = module_file_path.parent()?; | ||
96 | let base_directory = match module_file_path.name_and_extension()? { | ||
97 | ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => { | ||
98 | Some(directory_with_module_path) | ||
99 | } | ||
100 | (regular_rust_file_name, Some("rs")) => { | ||
101 | if matches!( | ||
102 | ( | ||
103 | directory_with_module_path | ||
104 | .parent() | ||
105 | .as_ref() | ||
106 | .and_then(|path| path.name_and_extension()), | ||
107 | directory_with_module_path.name_and_extension(), | ||
108 | ), | ||
109 | (Some(("src", None)), Some(("bin", None))) | ||
110 | ) { | ||
111 | // files in /src/bin/ can import each other directly | ||
112 | Some(directory_with_module_path) | ||
113 | } else { | ||
114 | directory_with_module_path.join(regular_rust_file_name) | ||
115 | } | ||
116 | } | ||
117 | _ => None, | ||
118 | }?; | ||
119 | |||
120 | let mut resulting_path = base_directory; | ||
121 | for module in module_chain_to_containing_module_file(module, db) { | ||
122 | if let Some(name) = module.name(db) { | ||
123 | resulting_path = resulting_path.join(&name.to_string())?; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | Some(resulting_path) | ||
128 | } | ||
129 | |||
130 | fn module_chain_to_containing_module_file( | ||
131 | current_module: Module, | ||
132 | db: &RootDatabase, | ||
133 | ) -> Vec<Module> { | ||
134 | let mut path = Vec::new(); | ||
135 | |||
136 | let mut current_module = Some(current_module); | ||
137 | while let Some(ModuleSource::Module(_)) = | ||
138 | current_module.map(|module| module.definition_source(db).value) | ||
139 | { | ||
140 | if let Some(module) = current_module { | ||
141 | path.insert(0, module); | ||
142 | current_module = module.parent(db); | ||
143 | } else { | ||
144 | current_module = None; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | path | ||
149 | } | ||
150 | |||
151 | #[cfg(test)] | ||
152 | mod tests { | ||
153 | use crate::{test_utils::completion_list, CompletionKind}; | ||
154 | use expect_test::{expect, Expect}; | ||
155 | |||
156 | fn check(ra_fixture: &str, expect: Expect) { | ||
157 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
158 | expect.assert_eq(&actual); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn lib_module_completion() { | ||
163 | check( | ||
164 | r#" | ||
165 | //- /lib.rs | ||
166 | mod <|> | ||
167 | //- /foo.rs | ||
168 | fn foo() {} | ||
169 | //- /foo/ignored_foo.rs | ||
170 | fn ignored_foo() {} | ||
171 | //- /bar/mod.rs | ||
172 | fn bar() {} | ||
173 | //- /bar/ignored_bar.rs | ||
174 | fn ignored_bar() {} | ||
175 | "#, | ||
176 | expect![[r#" | ||
177 | md bar; | ||
178 | md foo; | ||
179 | "#]], | ||
180 | ); | ||
181 | } | ||
182 | |||
183 | #[test] | ||
184 | fn no_module_completion_with_module_body() { | ||
185 | check( | ||
186 | r#" | ||
187 | //- /lib.rs | ||
188 | mod <|> { | ||
189 | |||
190 | } | ||
191 | //- /foo.rs | ||
192 | fn foo() {} | ||
193 | "#, | ||
194 | expect![[r#""#]], | ||
195 | ); | ||
196 | } | ||
197 | |||
198 | #[test] | ||
199 | fn main_module_completion() { | ||
200 | check( | ||
201 | r#" | ||
202 | //- /main.rs | ||
203 | mod <|> | ||
204 | //- /foo.rs | ||
205 | fn foo() {} | ||
206 | //- /foo/ignored_foo.rs | ||
207 | fn ignored_foo() {} | ||
208 | //- /bar/mod.rs | ||
209 | fn bar() {} | ||
210 | //- /bar/ignored_bar.rs | ||
211 | fn ignored_bar() {} | ||
212 | "#, | ||
213 | expect![[r#" | ||
214 | md bar; | ||
215 | md foo; | ||
216 | "#]], | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn main_test_module_completion() { | ||
222 | check( | ||
223 | r#" | ||
224 | //- /main.rs | ||
225 | mod tests { | ||
226 | mod <|>; | ||
227 | } | ||
228 | //- /tests/foo.rs | ||
229 | fn foo() {} | ||
230 | "#, | ||
231 | expect![[r#" | ||
232 | md foo | ||
233 | "#]], | ||
234 | ); | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn directly_nested_module_completion() { | ||
239 | check( | ||
240 | r#" | ||
241 | //- /lib.rs | ||
242 | mod foo; | ||
243 | //- /foo.rs | ||
244 | mod <|>; | ||
245 | //- /foo/bar.rs | ||
246 | fn bar() {} | ||
247 | //- /foo/bar/ignored_bar.rs | ||
248 | fn ignored_bar() {} | ||
249 | //- /foo/baz/mod.rs | ||
250 | fn baz() {} | ||
251 | //- /foo/moar/ignored_moar.rs | ||
252 | fn ignored_moar() {} | ||
253 | "#, | ||
254 | expect![[r#" | ||
255 | md bar | ||
256 | md baz | ||
257 | "#]], | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn nested_in_source_module_completion() { | ||
263 | check( | ||
264 | r#" | ||
265 | //- /lib.rs | ||
266 | mod foo; | ||
267 | //- /foo.rs | ||
268 | mod bar { | ||
269 | mod <|> | ||
270 | } | ||
271 | //- /foo/bar/baz.rs | ||
272 | fn baz() {} | ||
273 | "#, | ||
274 | expect![[r#" | ||
275 | md baz; | ||
276 | "#]], | ||
277 | ); | ||
278 | } | ||
279 | |||
280 | // FIXME binary modules are not supported in tests properly | ||
281 | // Binary modules are a bit special, they allow importing the modules from `/src/bin` | ||
282 | // and that's why are good to test two things: | ||
283 | // * no cycles are allowed in mod declarations | ||
284 | // * no modules from the parent directory are proposed | ||
285 | // Unfortunately, binary modules support is in cargo not rustc, | ||
286 | // hence the test does not work now | ||
287 | // | ||
288 | // #[test] | ||
289 | // fn regular_bin_module_completion() { | ||
290 | // check( | ||
291 | // r#" | ||
292 | // //- /src/bin.rs | ||
293 | // fn main() {} | ||
294 | // //- /src/bin/foo.rs | ||
295 | // mod <|> | ||
296 | // //- /src/bin/bar.rs | ||
297 | // fn bar() {} | ||
298 | // //- /src/bin/bar/bar_ignored.rs | ||
299 | // fn bar_ignored() {} | ||
300 | // "#, | ||
301 | // expect![[r#" | ||
302 | // md bar; | ||
303 | // "#]],foo | ||
304 | // ); | ||
305 | // } | ||
306 | |||
307 | #[test] | ||
308 | fn already_declared_bin_module_completion_omitted() { | ||
309 | check( | ||
310 | r#" | ||
311 | //- /src/bin.rs crate:main | ||
312 | fn main() {} | ||
313 | //- /src/bin/foo.rs | ||
314 | mod <|> | ||
315 | //- /src/bin/bar.rs | ||
316 | mod foo; | ||
317 | fn bar() {} | ||
318 | //- /src/bin/bar/bar_ignored.rs | ||
319 | fn bar_ignored() {} | ||
320 | "#, | ||
321 | expect![[r#""#]], | ||
322 | ); | ||
323 | } | ||
324 | } | ||
diff --git a/crates/completion/src/complete_pattern.rs b/crates/completion/src/complete_pattern.rs new file mode 100644 index 000000000..5606dcdd9 --- /dev/null +++ b/crates/completion/src/complete_pattern.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | //! Completes constats and paths in patterns. | ||
2 | |||
3 | use crate::{CompletionContext, Completions}; | ||
4 | |||
5 | /// Completes constats and paths in patterns. | ||
6 | pub(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)] | ||
35 | mod tests { | ||
36 | use expect_test::{expect, Expect}; | ||
37 | |||
38 | use crate::{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#" | ||
49 | enum E { X } | ||
50 | use self::E::X; | ||
51 | const Z: E = E::X; | ||
52 | mod m {} | ||
53 | |||
54 | static FOO: E = E::X; | ||
55 | struct Bar { f: u32 } | ||
56 | |||
57 | fn 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#" | ||
75 | macro_rules! m { ($e:expr) => { $e } } | ||
76 | enum E { X } | ||
77 | |||
78 | fn 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/completion/src/complete_postfix.rs b/crates/completion/src/complete_postfix.rs new file mode 100644 index 000000000..700573cf2 --- /dev/null +++ b/crates/completion/src/complete_postfix.rs | |||
@@ -0,0 +1,452 @@ | |||
1 | //! Postfix completions, like `Ok(10).ifl<|>` => `if let Ok() = Ok(10) { <|> }`. | ||
2 | |||
3 | mod format_like; | ||
4 | |||
5 | use assists::utils::TryEnum; | ||
6 | use syntax::{ | ||
7 | ast::{self, AstNode, AstToken}, | ||
8 | TextRange, TextSize, | ||
9 | }; | ||
10 | use text_edit::TextEdit; | ||
11 | |||
12 | use self::format_like::add_format_like_completions; | ||
13 | use crate::{ | ||
14 | completion_config::SnippetCap, | ||
15 | completion_context::CompletionContext, | ||
16 | completion_item::{Builder, CompletionKind, Completions}, | ||
17 | CompletionItem, CompletionItemKind, | ||
18 | }; | ||
19 | |||
20 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | ||
21 | if !ctx.config.enable_postfix_completions { | ||
22 | return; | ||
23 | } | ||
24 | |||
25 | let dot_receiver = match &ctx.dot_receiver { | ||
26 | Some(it) => it, | ||
27 | None => return, | ||
28 | }; | ||
29 | |||
30 | let receiver_text = | ||
31 | get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
32 | |||
33 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { | ||
34 | Some(it) => it, | ||
35 | None => return, | ||
36 | }; | ||
37 | |||
38 | let cap = match ctx.config.snippet_cap { | ||
39 | Some(it) => it, | ||
40 | None => return, | ||
41 | }; | ||
42 | let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); | ||
43 | if let Some(try_enum) = &try_enum { | ||
44 | match try_enum { | ||
45 | TryEnum::Result => { | ||
46 | postfix_snippet( | ||
47 | ctx, | ||
48 | cap, | ||
49 | &dot_receiver, | ||
50 | "ifl", | ||
51 | "if let Ok {}", | ||
52 | &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
53 | ) | ||
54 | .add_to(acc); | ||
55 | |||
56 | postfix_snippet( | ||
57 | ctx, | ||
58 | cap, | ||
59 | &dot_receiver, | ||
60 | "while", | ||
61 | "while let Ok {}", | ||
62 | &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
63 | ) | ||
64 | .add_to(acc); | ||
65 | } | ||
66 | TryEnum::Option => { | ||
67 | postfix_snippet( | ||
68 | ctx, | ||
69 | cap, | ||
70 | &dot_receiver, | ||
71 | "ifl", | ||
72 | "if let Some {}", | ||
73 | &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
74 | ) | ||
75 | .add_to(acc); | ||
76 | |||
77 | postfix_snippet( | ||
78 | ctx, | ||
79 | cap, | ||
80 | &dot_receiver, | ||
81 | "while", | ||
82 | "while let Some {}", | ||
83 | &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
84 | ) | ||
85 | .add_to(acc); | ||
86 | } | ||
87 | } | ||
88 | } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { | ||
89 | postfix_snippet( | ||
90 | ctx, | ||
91 | cap, | ||
92 | &dot_receiver, | ||
93 | "if", | ||
94 | "if expr {}", | ||
95 | &format!("if {} {{\n $0\n}}", receiver_text), | ||
96 | ) | ||
97 | .add_to(acc); | ||
98 | postfix_snippet( | ||
99 | ctx, | ||
100 | cap, | ||
101 | &dot_receiver, | ||
102 | "while", | ||
103 | "while expr {}", | ||
104 | &format!("while {} {{\n $0\n}}", receiver_text), | ||
105 | ) | ||
106 | .add_to(acc); | ||
107 | postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) | ||
108 | .add_to(acc); | ||
109 | } | ||
110 | |||
111 | postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)) | ||
112 | .add_to(acc); | ||
113 | postfix_snippet( | ||
114 | ctx, | ||
115 | cap, | ||
116 | &dot_receiver, | ||
117 | "refm", | ||
118 | "&mut expr", | ||
119 | &format!("&mut {}", receiver_text), | ||
120 | ) | ||
121 | .add_to(acc); | ||
122 | |||
123 | // The rest of the postfix completions create an expression that moves an argument, | ||
124 | // so it's better to consider references now to avoid breaking the compilation | ||
125 | let dot_receiver = include_references(dot_receiver); | ||
126 | let receiver_text = | ||
127 | get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
128 | |||
129 | match try_enum { | ||
130 | Some(try_enum) => match try_enum { | ||
131 | TryEnum::Result => { | ||
132 | postfix_snippet( | ||
133 | ctx, | ||
134 | cap, | ||
135 | &dot_receiver, | ||
136 | "match", | ||
137 | "match expr {}", | ||
138 | &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text), | ||
139 | ) | ||
140 | .add_to(acc); | ||
141 | } | ||
142 | TryEnum::Option => { | ||
143 | postfix_snippet( | ||
144 | ctx, | ||
145 | cap, | ||
146 | &dot_receiver, | ||
147 | "match", | ||
148 | "match expr {}", | ||
149 | &format!( | ||
150 | "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}", | ||
151 | receiver_text | ||
152 | ), | ||
153 | ) | ||
154 | .add_to(acc); | ||
155 | } | ||
156 | }, | ||
157 | None => { | ||
158 | postfix_snippet( | ||
159 | ctx, | ||
160 | cap, | ||
161 | &dot_receiver, | ||
162 | "match", | ||
163 | "match expr {}", | ||
164 | &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text), | ||
165 | ) | ||
166 | .add_to(acc); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | postfix_snippet( | ||
171 | ctx, | ||
172 | cap, | ||
173 | &dot_receiver, | ||
174 | "box", | ||
175 | "Box::new(expr)", | ||
176 | &format!("Box::new({})", receiver_text), | ||
177 | ) | ||
178 | .add_to(acc); | ||
179 | |||
180 | postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text)) | ||
181 | .add_to(acc); | ||
182 | |||
183 | postfix_snippet( | ||
184 | ctx, | ||
185 | cap, | ||
186 | &dot_receiver, | ||
187 | "dbg", | ||
188 | "dbg!(expr)", | ||
189 | &format!("dbg!({})", receiver_text), | ||
190 | ) | ||
191 | .add_to(acc); | ||
192 | |||
193 | postfix_snippet( | ||
194 | ctx, | ||
195 | cap, | ||
196 | &dot_receiver, | ||
197 | "dbgr", | ||
198 | "dbg!(&expr)", | ||
199 | &format!("dbg!(&{})", receiver_text), | ||
200 | ) | ||
201 | .add_to(acc); | ||
202 | |||
203 | postfix_snippet( | ||
204 | ctx, | ||
205 | cap, | ||
206 | &dot_receiver, | ||
207 | "call", | ||
208 | "function(expr)", | ||
209 | &format!("${{1}}({})", receiver_text), | ||
210 | ) | ||
211 | .add_to(acc); | ||
212 | |||
213 | if let ast::Expr::Literal(literal) = dot_receiver.clone() { | ||
214 | if let Some(literal_text) = ast::String::cast(literal.token()) { | ||
215 | add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text); | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { | ||
221 | if receiver_is_ambiguous_float_literal { | ||
222 | let text = receiver.syntax().text(); | ||
223 | let without_dot = ..text.len() - TextSize::of('.'); | ||
224 | text.slice(without_dot).to_string() | ||
225 | } else { | ||
226 | receiver.to_string() | ||
227 | } | ||
228 | } | ||
229 | |||
230 | fn include_references(initial_element: &ast::Expr) -> ast::Expr { | ||
231 | let mut resulting_element = initial_element.clone(); | ||
232 | while let Some(parent_ref_element) = | ||
233 | resulting_element.syntax().parent().and_then(ast::RefExpr::cast) | ||
234 | { | ||
235 | resulting_element = ast::Expr::from(parent_ref_element); | ||
236 | } | ||
237 | resulting_element | ||
238 | } | ||
239 | |||
240 | fn postfix_snippet( | ||
241 | ctx: &CompletionContext, | ||
242 | cap: SnippetCap, | ||
243 | receiver: &ast::Expr, | ||
244 | label: &str, | ||
245 | detail: &str, | ||
246 | snippet: &str, | ||
247 | ) -> Builder { | ||
248 | let edit = { | ||
249 | let receiver_syntax = receiver.syntax(); | ||
250 | let receiver_range = ctx.sema.original_range(receiver_syntax).range; | ||
251 | let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); | ||
252 | TextEdit::replace(delete_range, snippet.to_string()) | ||
253 | }; | ||
254 | CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) | ||
255 | .detail(detail) | ||
256 | .kind(CompletionItemKind::Snippet) | ||
257 | .snippet_edit(cap, edit) | ||
258 | } | ||
259 | |||
260 | #[cfg(test)] | ||
261 | mod tests { | ||
262 | use expect_test::{expect, Expect}; | ||
263 | |||
264 | use crate::{ | ||
265 | test_utils::{check_edit, completion_list}, | ||
266 | CompletionKind, | ||
267 | }; | ||
268 | |||
269 | fn check(ra_fixture: &str, expect: Expect) { | ||
270 | let actual = completion_list(ra_fixture, CompletionKind::Postfix); | ||
271 | expect.assert_eq(&actual) | ||
272 | } | ||
273 | |||
274 | #[test] | ||
275 | fn postfix_completion_works_for_trivial_path_expression() { | ||
276 | check( | ||
277 | r#" | ||
278 | fn main() { | ||
279 | let bar = true; | ||
280 | bar.<|> | ||
281 | } | ||
282 | "#, | ||
283 | expect![[r#" | ||
284 | sn box Box::new(expr) | ||
285 | sn call function(expr) | ||
286 | sn dbg dbg!(expr) | ||
287 | sn dbgr dbg!(&expr) | ||
288 | sn if if expr {} | ||
289 | sn match match expr {} | ||
290 | sn not !expr | ||
291 | sn ok Ok(expr) | ||
292 | sn ref &expr | ||
293 | sn refm &mut expr | ||
294 | sn while while expr {} | ||
295 | "#]], | ||
296 | ); | ||
297 | } | ||
298 | |||
299 | #[test] | ||
300 | fn postfix_type_filtering() { | ||
301 | check( | ||
302 | r#" | ||
303 | fn main() { | ||
304 | let bar: u8 = 12; | ||
305 | bar.<|> | ||
306 | } | ||
307 | "#, | ||
308 | expect![[r#" | ||
309 | sn box Box::new(expr) | ||
310 | sn call function(expr) | ||
311 | sn dbg dbg!(expr) | ||
312 | sn dbgr dbg!(&expr) | ||
313 | sn match match expr {} | ||
314 | sn ok Ok(expr) | ||
315 | sn ref &expr | ||
316 | sn refm &mut expr | ||
317 | "#]], | ||
318 | ) | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn option_iflet() { | ||
323 | check_edit( | ||
324 | "ifl", | ||
325 | r#" | ||
326 | enum Option<T> { Some(T), None } | ||
327 | |||
328 | fn main() { | ||
329 | let bar = Option::Some(true); | ||
330 | bar.<|> | ||
331 | } | ||
332 | "#, | ||
333 | r#" | ||
334 | enum Option<T> { Some(T), None } | ||
335 | |||
336 | fn main() { | ||
337 | let bar = Option::Some(true); | ||
338 | if let Some($1) = bar { | ||
339 | $0 | ||
340 | } | ||
341 | } | ||
342 | "#, | ||
343 | ); | ||
344 | } | ||
345 | |||
346 | #[test] | ||
347 | fn result_match() { | ||
348 | check_edit( | ||
349 | "match", | ||
350 | r#" | ||
351 | enum Result<T, E> { Ok(T), Err(E) } | ||
352 | |||
353 | fn main() { | ||
354 | let bar = Result::Ok(true); | ||
355 | bar.<|> | ||
356 | } | ||
357 | "#, | ||
358 | r#" | ||
359 | enum Result<T, E> { Ok(T), Err(E) } | ||
360 | |||
361 | fn main() { | ||
362 | let bar = Result::Ok(true); | ||
363 | match bar { | ||
364 | Ok(${1:_}) => {$2}, | ||
365 | Err(${3:_}) => {$0}, | ||
366 | } | ||
367 | } | ||
368 | "#, | ||
369 | ); | ||
370 | } | ||
371 | |||
372 | #[test] | ||
373 | fn postfix_completion_works_for_ambiguous_float_literal() { | ||
374 | check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#) | ||
375 | } | ||
376 | |||
377 | #[test] | ||
378 | fn works_in_simple_macro() { | ||
379 | check_edit( | ||
380 | "dbg", | ||
381 | r#" | ||
382 | macro_rules! m { ($e:expr) => { $e } } | ||
383 | fn main() { | ||
384 | let bar: u8 = 12; | ||
385 | m!(bar.d<|>) | ||
386 | } | ||
387 | "#, | ||
388 | r#" | ||
389 | macro_rules! m { ($e:expr) => { $e } } | ||
390 | fn main() { | ||
391 | let bar: u8 = 12; | ||
392 | m!(dbg!(bar)) | ||
393 | } | ||
394 | "#, | ||
395 | ); | ||
396 | } | ||
397 | |||
398 | #[test] | ||
399 | fn postfix_completion_for_references() { | ||
400 | check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#); | ||
401 | check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#); | ||
402 | } | ||
403 | |||
404 | #[test] | ||
405 | fn postfix_completion_for_format_like_strings() { | ||
406 | check_edit( | ||
407 | "fmt", | ||
408 | r#"fn main() { "{some_var:?}".<|> }"#, | ||
409 | r#"fn main() { format!("{:?}", some_var) }"#, | ||
410 | ); | ||
411 | check_edit( | ||
412 | "panic", | ||
413 | r#"fn main() { "Panic with {a}".<|> }"#, | ||
414 | r#"fn main() { panic!("Panic with {}", a) }"#, | ||
415 | ); | ||
416 | check_edit( | ||
417 | "println", | ||
418 | r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".<|> }"#, | ||
419 | r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#, | ||
420 | ); | ||
421 | check_edit( | ||
422 | "loge", | ||
423 | r#"fn main() { "{2+2}".<|> }"#, | ||
424 | r#"fn main() { log::error!("{}", 2+2) }"#, | ||
425 | ); | ||
426 | check_edit( | ||
427 | "logt", | ||
428 | r#"fn main() { "{2+2}".<|> }"#, | ||
429 | r#"fn main() { log::trace!("{}", 2+2) }"#, | ||
430 | ); | ||
431 | check_edit( | ||
432 | "logd", | ||
433 | r#"fn main() { "{2+2}".<|> }"#, | ||
434 | r#"fn main() { log::debug!("{}", 2+2) }"#, | ||
435 | ); | ||
436 | check_edit( | ||
437 | "logi", | ||
438 | r#"fn main() { "{2+2}".<|> }"#, | ||
439 | r#"fn main() { log::info!("{}", 2+2) }"#, | ||
440 | ); | ||
441 | check_edit( | ||
442 | "logw", | ||
443 | r#"fn main() { "{2+2}".<|> }"#, | ||
444 | r#"fn main() { log::warn!("{}", 2+2) }"#, | ||
445 | ); | ||
446 | check_edit( | ||
447 | "loge", | ||
448 | r#"fn main() { "{2+2}".<|> }"#, | ||
449 | r#"fn main() { log::error!("{}", 2+2) }"#, | ||
450 | ); | ||
451 | } | ||
452 | } | ||
diff --git a/crates/completion/src/complete_postfix/format_like.rs b/crates/completion/src/complete_postfix/format_like.rs new file mode 100644 index 000000000..205c384e2 --- /dev/null +++ b/crates/completion/src/complete_postfix/format_like.rs | |||
@@ -0,0 +1,279 @@ | |||
1 | // Feature: Format String Completion. | ||
2 | // | ||
3 | // `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`. | ||
4 | // | ||
5 | // The following postfix snippets are available: | ||
6 | // | ||
7 | // - `format` -> `format!(...)` | ||
8 | // - `panic` -> `panic!(...)` | ||
9 | // - `println` -> `println!(...)` | ||
10 | // - `log`: | ||
11 | // + `logd` -> `log::debug!(...)` | ||
12 | // + `logt` -> `log::trace!(...)` | ||
13 | // + `logi` -> `log::info!(...)` | ||
14 | // + `logw` -> `log::warn!(...)` | ||
15 | // + `loge` -> `log::error!(...)` | ||
16 | |||
17 | use crate::{ | ||
18 | complete_postfix::postfix_snippet, completion_config::SnippetCap, | ||
19 | completion_context::CompletionContext, completion_item::Completions, | ||
20 | }; | ||
21 | use syntax::ast::{self, AstToken}; | ||
22 | |||
23 | /// Mapping ("postfix completion item" => "macro to use") | ||
24 | static KINDS: &[(&str, &str)] = &[ | ||
25 | ("fmt", "format!"), | ||
26 | ("panic", "panic!"), | ||
27 | ("println", "println!"), | ||
28 | ("eprintln", "eprintln!"), | ||
29 | ("logd", "log::debug!"), | ||
30 | ("logt", "log::trace!"), | ||
31 | ("logi", "log::info!"), | ||
32 | ("logw", "log::warn!"), | ||
33 | ("loge", "log::error!"), | ||
34 | ]; | ||
35 | |||
36 | pub(super) fn add_format_like_completions( | ||
37 | acc: &mut Completions, | ||
38 | ctx: &CompletionContext, | ||
39 | dot_receiver: &ast::Expr, | ||
40 | cap: SnippetCap, | ||
41 | receiver_text: &ast::String, | ||
42 | ) { | ||
43 | let input = match string_literal_contents(receiver_text) { | ||
44 | // It's not a string literal, do not parse input. | ||
45 | Some(input) => input, | ||
46 | None => return, | ||
47 | }; | ||
48 | |||
49 | let mut parser = FormatStrParser::new(input); | ||
50 | |||
51 | if parser.parse().is_ok() { | ||
52 | for (label, macro_name) in KINDS { | ||
53 | let snippet = parser.into_suggestion(macro_name); | ||
54 | |||
55 | postfix_snippet(ctx, cap, &dot_receiver, label, macro_name, &snippet).add_to(acc); | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | /// Checks whether provided item is a string literal. | ||
61 | fn string_literal_contents(item: &ast::String) -> Option<String> { | ||
62 | let item = item.text(); | ||
63 | if item.len() >= 2 && item.starts_with("\"") && item.ends_with("\"") { | ||
64 | return Some(item[1..item.len() - 1].to_owned()); | ||
65 | } | ||
66 | |||
67 | None | ||
68 | } | ||
69 | |||
70 | /// Parser for a format-like string. It is more allowing in terms of string contents, | ||
71 | /// as we expect variable placeholders to be filled with expressions. | ||
72 | #[derive(Debug)] | ||
73 | pub struct FormatStrParser { | ||
74 | input: String, | ||
75 | output: String, | ||
76 | extracted_expressions: Vec<String>, | ||
77 | state: State, | ||
78 | parsed: bool, | ||
79 | } | ||
80 | |||
81 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
82 | enum State { | ||
83 | NotExpr, | ||
84 | MaybeExpr, | ||
85 | Expr, | ||
86 | MaybeIncorrect, | ||
87 | FormatOpts, | ||
88 | } | ||
89 | |||
90 | impl FormatStrParser { | ||
91 | pub fn new(input: String) -> Self { | ||
92 | Self { | ||
93 | input: input.into(), | ||
94 | output: String::new(), | ||
95 | extracted_expressions: Vec::new(), | ||
96 | state: State::NotExpr, | ||
97 | parsed: false, | ||
98 | } | ||
99 | } | ||
100 | |||
101 | pub fn parse(&mut self) -> Result<(), ()> { | ||
102 | let mut current_expr = String::new(); | ||
103 | |||
104 | let mut placeholder_id = 1; | ||
105 | |||
106 | // Count of open braces inside of an expression. | ||
107 | // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g. | ||
108 | // "{MyStruct { val_a: 0, val_b: 1 }}". | ||
109 | let mut inexpr_open_count = 0; | ||
110 | |||
111 | for chr in self.input.chars() { | ||
112 | match (self.state, chr) { | ||
113 | (State::NotExpr, '{') => { | ||
114 | self.output.push(chr); | ||
115 | self.state = State::MaybeExpr; | ||
116 | } | ||
117 | (State::NotExpr, '}') => { | ||
118 | self.output.push(chr); | ||
119 | self.state = State::MaybeIncorrect; | ||
120 | } | ||
121 | (State::NotExpr, _) => { | ||
122 | self.output.push(chr); | ||
123 | } | ||
124 | (State::MaybeIncorrect, '}') => { | ||
125 | // It's okay, we met "}}". | ||
126 | self.output.push(chr); | ||
127 | self.state = State::NotExpr; | ||
128 | } | ||
129 | (State::MaybeIncorrect, _) => { | ||
130 | // Error in the string. | ||
131 | return Err(()); | ||
132 | } | ||
133 | (State::MaybeExpr, '{') => { | ||
134 | self.output.push(chr); | ||
135 | self.state = State::NotExpr; | ||
136 | } | ||
137 | (State::MaybeExpr, '}') => { | ||
138 | // This is an empty sequence '{}'. Replace it with placeholder. | ||
139 | self.output.push(chr); | ||
140 | self.extracted_expressions.push(format!("${}", placeholder_id)); | ||
141 | placeholder_id += 1; | ||
142 | self.state = State::NotExpr; | ||
143 | } | ||
144 | (State::MaybeExpr, _) => { | ||
145 | current_expr.push(chr); | ||
146 | self.state = State::Expr; | ||
147 | } | ||
148 | (State::Expr, '}') => { | ||
149 | if inexpr_open_count == 0 { | ||
150 | self.output.push(chr); | ||
151 | self.extracted_expressions.push(current_expr.trim().into()); | ||
152 | current_expr = String::new(); | ||
153 | self.state = State::NotExpr; | ||
154 | } else { | ||
155 | // We're closing one brace met before inside of the expression. | ||
156 | current_expr.push(chr); | ||
157 | inexpr_open_count -= 1; | ||
158 | } | ||
159 | } | ||
160 | (State::Expr, ':') => { | ||
161 | if inexpr_open_count == 0 { | ||
162 | // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}" | ||
163 | self.output.push(chr); | ||
164 | self.extracted_expressions.push(current_expr.trim().into()); | ||
165 | current_expr = String::new(); | ||
166 | self.state = State::FormatOpts; | ||
167 | } else { | ||
168 | // We're inside of braced expression, assume that it's a struct field name/value delimeter. | ||
169 | current_expr.push(chr); | ||
170 | } | ||
171 | } | ||
172 | (State::Expr, '{') => { | ||
173 | current_expr.push(chr); | ||
174 | inexpr_open_count += 1; | ||
175 | } | ||
176 | (State::Expr, _) => { | ||
177 | current_expr.push(chr); | ||
178 | } | ||
179 | (State::FormatOpts, '}') => { | ||
180 | self.output.push(chr); | ||
181 | self.state = State::NotExpr; | ||
182 | } | ||
183 | (State::FormatOpts, _) => { | ||
184 | self.output.push(chr); | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | if self.state != State::NotExpr { | ||
190 | return Err(()); | ||
191 | } | ||
192 | |||
193 | self.parsed = true; | ||
194 | Ok(()) | ||
195 | } | ||
196 | |||
197 | pub fn into_suggestion(&self, macro_name: &str) -> String { | ||
198 | assert!(self.parsed, "Attempt to get a suggestion from not parsed expression"); | ||
199 | |||
200 | let expressions_as_string = self.extracted_expressions.join(", "); | ||
201 | format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string) | ||
202 | } | ||
203 | } | ||
204 | |||
205 | #[cfg(test)] | ||
206 | mod tests { | ||
207 | use super::*; | ||
208 | use expect_test::{expect, Expect}; | ||
209 | |||
210 | fn check(input: &str, expect: &Expect) { | ||
211 | let mut parser = FormatStrParser::new((*input).to_owned()); | ||
212 | let outcome_repr = if parser.parse().is_ok() { | ||
213 | // Parsing should be OK, expected repr is "string; expr_1, expr_2". | ||
214 | if parser.extracted_expressions.is_empty() { | ||
215 | parser.output | ||
216 | } else { | ||
217 | format!("{}; {}", parser.output, parser.extracted_expressions.join(", ")) | ||
218 | } | ||
219 | } else { | ||
220 | // Parsing should fail, expected repr is "-". | ||
221 | "-".to_owned() | ||
222 | }; | ||
223 | |||
224 | expect.assert_eq(&outcome_repr); | ||
225 | } | ||
226 | |||
227 | #[test] | ||
228 | fn format_str_parser() { | ||
229 | let test_vector = &[ | ||
230 | ("no expressions", expect![["no expressions"]]), | ||
231 | ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]), | ||
232 | ("{expr:?}", expect![["{:?}; expr"]]), | ||
233 | ("{malformed", expect![["-"]]), | ||
234 | ("malformed}", expect![["-"]]), | ||
235 | ("{{correct", expect![["{{correct"]]), | ||
236 | ("correct}}", expect![["correct}}"]]), | ||
237 | ("{correct}}}", expect![["{}}}; correct"]]), | ||
238 | ("{correct}}}}}", expect![["{}}}}}; correct"]]), | ||
239 | ("{incorrect}}", expect![["-"]]), | ||
240 | ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]), | ||
241 | ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]), | ||
242 | ( | ||
243 | "{SomeStruct { val_a: 0, val_b: 1 }}", | ||
244 | expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]], | ||
245 | ), | ||
246 | ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]), | ||
247 | ( | ||
248 | "{SomeStruct { val_a: 0, val_b: 1 }:?}", | ||
249 | expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]], | ||
250 | ), | ||
251 | ("{ 2 + 2 }", expect![["{}; 2 + 2"]]), | ||
252 | ]; | ||
253 | |||
254 | for (input, output) in test_vector { | ||
255 | check(input, output) | ||
256 | } | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn test_into_suggestion() { | ||
261 | let test_vector = &[ | ||
262 | ("println!", "{}", r#"println!("{}", $1)"#), | ||
263 | ("eprintln!", "{}", r#"eprintln!("{}", $1)"#), | ||
264 | ( | ||
265 | "log::info!", | ||
266 | "{} {expr} {} {2 + 2}", | ||
267 | r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#, | ||
268 | ), | ||
269 | ("format!", "{expr:?}", r#"format!("{:?}", expr)"#), | ||
270 | ]; | ||
271 | |||
272 | for (kind, input, output) in test_vector { | ||
273 | let mut parser = FormatStrParser::new((*input).to_owned()); | ||
274 | parser.parse().expect("Parsing must succeed"); | ||
275 | |||
276 | assert_eq!(&parser.into_suggestion(*kind), output); | ||
277 | } | ||
278 | } | ||
279 | } | ||
diff --git a/crates/completion/src/complete_qualified_path.rs b/crates/completion/src/complete_qualified_path.rs new file mode 100644 index 000000000..80b271fdf --- /dev/null +++ b/crates/completion/src/complete_qualified_path.rs | |||
@@ -0,0 +1,755 @@ | |||
1 | //! Completion of paths, i.e. `some::prefix::<|>`. | ||
2 | |||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | ||
4 | use rustc_hash::FxHashSet; | ||
5 | use syntax::AstNode; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{CompletionContext, Completions}; | ||
9 | |||
10 | pub(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() || ctx.mod_declaration_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)] | ||
148 | mod tests { | ||
149 | use expect_test::{expect, Expect}; | ||
150 | use test_utils::mark; | ||
151 | |||
152 | use crate::{ | ||
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#" | ||
177 | mod foo { pub struct S; } | ||
178 | use 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#" | ||
227 | use self::my::<|>; | ||
228 | |||
229 | mod my { pub struct Bar; } | ||
230 | fn my() {} | ||
231 | "#, | ||
232 | expect![[r#" | ||
233 | st Bar | ||
234 | "#]], | ||
235 | ); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn filters_visibility() { | ||
240 | check( | ||
241 | r#" | ||
242 | use self::my::<|>; | ||
243 | |||
244 | mod 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#" | ||
261 | use self::m::<|>; | ||
262 | |||
263 | mod 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 | ||
276 | mod foo; | ||
277 | struct Spam; | ||
278 | //- /foo.rs | ||
279 | use 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 | ||
293 | mod foo; | ||
294 | struct Spam; | ||
295 | //- /foo.rs | ||
296 | use 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 | ||
310 | mod foo; | ||
311 | pub mod bar { | ||
312 | pub mod baz { | ||
313 | pub struct Spam; | ||
314 | } | ||
315 | } | ||
316 | //- /foo.rs | ||
317 | use 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#" | ||
329 | enum E { Foo, Bar(i32) } | ||
330 | fn 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 | ||
344 | struct S; | ||
345 | |||
346 | impl S { | ||
347 | fn a() {} | ||
348 | fn b(&self) {} | ||
349 | const C: i32 = 42; | ||
350 | type T = i32; | ||
351 | } | ||
352 | |||
353 | fn 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#" | ||
368 | struct S; | ||
369 | |||
370 | mod 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 | |||
381 | fn 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#" | ||
395 | enum E {}; | ||
396 | impl E { fn m() { } } | ||
397 | |||
398 | fn 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#" | ||
410 | union U {}; | ||
411 | impl U { fn m() { } } | ||
412 | |||
413 | fn 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 crate:main deps:foo | ||
426 | use foo::<|>; | ||
427 | |||
428 | //- /foo/lib.rs crate:foo | ||
429 | pub 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#" | ||
441 | trait Trait { fn m(); } | ||
442 | |||
443 | fn 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#" | ||
455 | trait Trait { fn m(); } | ||
456 | |||
457 | struct S; | ||
458 | impl Trait for S {} | ||
459 | |||
460 | fn 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#" | ||
472 | trait Trait { fn m(); } | ||
473 | |||
474 | struct S; | ||
475 | impl Trait for S {} | ||
476 | |||
477 | fn 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#" | ||
489 | trait Super { | ||
490 | type Ty; | ||
491 | const CONST: u8; | ||
492 | fn func() {} | ||
493 | fn method(&self) {} | ||
494 | } | ||
495 | |||
496 | trait Sub: Super { | ||
497 | type SubTy; | ||
498 | const C2: (); | ||
499 | fn subfunc() {} | ||
500 | fn submethod(&self) {} | ||
501 | } | ||
502 | |||
503 | fn 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#" | ||
522 | trait Super { | ||
523 | type Ty; | ||
524 | const CONST: u8 = 0; | ||
525 | fn func() {} | ||
526 | fn method(&self) {} | ||
527 | } | ||
528 | |||
529 | trait Sub: Super { | ||
530 | type SubTy; | ||
531 | const C2: () = (); | ||
532 | fn subfunc() {} | ||
533 | fn submethod(&self) {} | ||
534 | } | ||
535 | |||
536 | struct Wrap<T>(T); | ||
537 | impl<T> Super for Wrap<T> {} | ||
538 | impl<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#" | ||
562 | struct S; | ||
563 | impl S { fn foo() {} } | ||
564 | type T = S; | ||
565 | impl T { fn bar() {} } | ||
566 | |||
567 | fn 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] | ||
581 | macro_rules! foo { () => {} } | ||
582 | |||
583 | fn 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#" | ||
597 | mod 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#" | ||
616 | fn foo() { self::m::<|> } | ||
617 | |||
618 | mod 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 | } | ||
623 | mod 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#" | ||
639 | fn foo() { self::m::<|> } | ||
640 | |||
641 | mod 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 | } | ||
646 | mod p { | ||
647 | fn wrong_fn() {} | ||
648 | const WRONG_CONST: u32 = 1; | ||
649 | struct WrongType {}; | ||
650 | } | ||
651 | "#, | ||
652 | r#" | ||
653 | fn foo() { self::m::RightType } | ||
654 | |||
655 | mod 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 | } | ||
660 | mod 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#" | ||
673 | macro_rules! m { ($e:expr) => { $e } } | ||
674 | fn main() { m!(self::f<|>); } | ||
675 | fn 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#" | ||
688 | fn foo() { self::m::<|> } | ||
689 | |||
690 | mod 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#" | ||
706 | struct RandomState; | ||
707 | struct HashMap<K, V, S = RandomState> {} | ||
708 | |||
709 | impl<K, V> HashMap<K, V, RandomState> { | ||
710 | pub fn new() -> HashMap<K, V, RandomState> { } | ||
711 | } | ||
712 | fn 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#" | ||
726 | mod foo { pub struct Foo; } | ||
727 | #[foo::<|>] | ||
728 | fn f() {} | ||
729 | "#, | ||
730 | expect![[""]], | ||
731 | ); | ||
732 | } | ||
733 | |||
734 | #[test] | ||
735 | fn completes_function() { | ||
736 | check( | ||
737 | r#" | ||
738 | fn foo( | ||
739 | a: i32, | ||
740 | b: i32 | ||
741 | ) { | ||
742 | |||
743 | } | ||
744 | |||
745 | fn main() { | ||
746 | fo<|> | ||
747 | } | ||
748 | "#, | ||
749 | expect![[r#" | ||
750 | fn foo(…) fn foo(a: i32, b: i32) | ||
751 | fn main() fn main() | ||
752 | "#]], | ||
753 | ); | ||
754 | } | ||
755 | } | ||
diff --git a/crates/completion/src/complete_record.rs b/crates/completion/src/complete_record.rs new file mode 100644 index 000000000..129ddc055 --- /dev/null +++ b/crates/completion/src/complete_record.rs | |||
@@ -0,0 +1,226 @@ | |||
1 | //! Complete fields in record literals and patterns. | ||
2 | use crate::{CompletionContext, Completions}; | ||
3 | |||
4 | pub(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)] | ||
20 | mod tests { | ||
21 | use expect_test::{expect, Expect}; | ||
22 | |||
23 | use crate::{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#" | ||
34 | struct S { foo: u32 } | ||
35 | |||
36 | fn 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#" | ||
52 | enum E { S { foo: u32, bar: () } } | ||
53 | |||
54 | fn 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" | ||
71 | macro_rules! m { ($e:expr) => { $e } } | ||
72 | struct S { foo: u32 } | ||
73 | |||
74 | fn 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#" | ||
90 | struct S { | ||
91 | foo1: u32, foo2: u32, | ||
92 | bar: u32, baz: u32, | ||
93 | } | ||
94 | |||
95 | fn 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#" | ||
114 | struct A { the_field: u32 } | ||
115 | fn 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#" | ||
129 | enum E { A { a: u32 } } | ||
130 | fn 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#" | ||
144 | struct A { a: u32 } | ||
145 | struct B { b: u32 } | ||
146 | |||
147 | fn 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#" | ||
161 | struct A<T> { a: T } | ||
162 | |||
163 | fn 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#" | ||
177 | macro_rules! m { ($e:expr) => { $e } } | ||
178 | struct A { the_field: u32 } | ||
179 | fn 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#" | ||
193 | struct S { | ||
194 | foo1: u32, foo2: u32, | ||
195 | bar: u32, baz: u32, | ||
196 | } | ||
197 | |||
198 | fn 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#" | ||
214 | struct S { foo1: u32, foo2: u32 } | ||
215 | |||
216 | fn 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/completion/src/complete_snippet.rs b/crates/completion/src/complete_snippet.rs new file mode 100644 index 000000000..06096722b --- /dev/null +++ b/crates/completion/src/complete_snippet.rs | |||
@@ -0,0 +1,114 @@ | |||
1 | //! This file provides snippet completions, like `pd` => `eprintln!(...)`. | ||
2 | |||
3 | use crate::{ | ||
4 | completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem, | ||
5 | CompletionItemKind, CompletionKind, Completions, | ||
6 | }; | ||
7 | |||
8 | fn 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 | |||
14 | pub(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 | |||
27 | pub(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)] | ||
42 | mod 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] | ||
60 | fn ${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 | } | ||
69 | |||
70 | #[cfg(test)] | ||
71 | mod tests { | ||
72 | use expect_test::{expect, Expect}; | ||
73 | |||
74 | use crate::{test_utils::completion_list, CompletionKind}; | ||
75 | |||
76 | fn check(ra_fixture: &str, expect: Expect) { | ||
77 | let actual = completion_list(ra_fixture, CompletionKind::Snippet); | ||
78 | expect.assert_eq(&actual) | ||
79 | } | ||
80 | |||
81 | #[test] | ||
82 | fn completes_snippets_in_expressions() { | ||
83 | check( | ||
84 | r#"fn foo(x: i32) { <|> }"#, | ||
85 | expect![[r#" | ||
86 | sn pd | ||
87 | sn ppd | ||
88 | "#]], | ||
89 | ); | ||
90 | } | ||
91 | |||
92 | #[test] | ||
93 | fn should_not_complete_snippets_in_path() { | ||
94 | check(r#"fn foo(x: i32) { ::foo<|> }"#, expect![[""]]); | ||
95 | check(r#"fn foo(x: i32) { ::<|> }"#, expect![[""]]); | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn completes_snippets_in_items() { | ||
100 | check( | ||
101 | r#" | ||
102 | #[cfg(test)] | ||
103 | mod tests { | ||
104 | <|> | ||
105 | } | ||
106 | "#, | ||
107 | expect![[r#" | ||
108 | sn macro_rules | ||
109 | sn tfn (Test function) | ||
110 | sn tmod (Test module) | ||
111 | "#]], | ||
112 | ) | ||
113 | } | ||
114 | } | ||
diff --git a/crates/completion/src/complete_trait_impl.rs b/crates/completion/src/complete_trait_impl.rs new file mode 100644 index 000000000..c06af99e2 --- /dev/null +++ b/crates/completion/src/complete_trait_impl.rs | |||
@@ -0,0 +1,736 @@ | |||
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 | |||
34 | use assists::utils::get_missing_assoc_items; | ||
35 | use hir::{self, HasAttrs, HasSource}; | ||
36 | use syntax::{ | ||
37 | ast::{self, edit, Impl}, | ||
38 | display::function_declaration, | ||
39 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | ||
40 | }; | ||
41 | use text_edit::TextEdit; | ||
42 | |||
43 | use crate::{ | ||
44 | CompletionContext, | ||
45 | CompletionItem, | ||
46 | CompletionItemKind, | ||
47 | CompletionKind, | ||
48 | Completions, | ||
49 | // display::function_declaration, | ||
50 | }; | ||
51 | |||
52 | #[derive(Debug, PartialEq, Eq)] | ||
53 | enum ImplCompletionKind { | ||
54 | All, | ||
55 | Fn, | ||
56 | TypeAlias, | ||
57 | Const, | ||
58 | } | ||
59 | |||
60 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | ||
61 | if let Some((kind, trigger, impl_def)) = completion_match(ctx) { | ||
62 | get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item { | ||
63 | hir::AssocItem::Function(fn_item) | ||
64 | if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn => | ||
65 | { | ||
66 | add_function_impl(&trigger, acc, ctx, fn_item) | ||
67 | } | ||
68 | hir::AssocItem::TypeAlias(type_item) | ||
69 | if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias => | ||
70 | { | ||
71 | add_type_alias_impl(&trigger, acc, ctx, type_item) | ||
72 | } | ||
73 | hir::AssocItem::Const(const_item) | ||
74 | if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const => | ||
75 | { | ||
76 | add_const_impl(&trigger, acc, ctx, const_item) | ||
77 | } | ||
78 | _ => {} | ||
79 | }); | ||
80 | } | ||
81 | } | ||