diff options
Diffstat (limited to 'crates/ide/src/completion')
19 files changed, 6903 insertions, 0 deletions
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs new file mode 100644 index 000000000..0abfaebcb --- /dev/null +++ b/crates/ide/src/completion/complete_attribute.rs | |||
@@ -0,0 +1,653 @@ | |||
1 | //! Completion for attributes | ||
2 | //! | ||
3 | //! This module uses a bit of static metadata to provide completions | ||
4 | //! for built-in attributes. | ||
5 | |||
6 | use rustc_hash::FxHashSet; | ||
7 | use syntax::{ast, AstNode, SyntaxKind}; | ||
8 | |||
9 | use crate::completion::{ | ||
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 | let attribute = ctx.attribute_under_caret.as_ref()?; | ||
17 | match (attribute.path(), attribute.token_tree()) { | ||
18 | (Some(path), Some(token_tree)) if path.to_string() == "derive" => { | ||
19 | complete_derive(acc, ctx, token_tree) | ||
20 | } | ||
21 | (Some(path), Some(token_tree)) if path.to_string() == "feature" => { | ||
22 | complete_lint(acc, ctx, token_tree, FEATURES) | ||
23 | } | ||
24 | (Some(path), Some(token_tree)) | ||
25 | if ["allow", "warn", "deny", "forbid"] | ||
26 | .iter() | ||
27 | .any(|lint_level| lint_level == &path.to_string()) => | ||
28 | { | ||
29 | complete_lint(acc, ctx, token_tree, DEFAULT_LINT_COMPLETIONS) | ||
30 | } | ||
31 | (_, Some(_token_tree)) => {} | ||
32 | _ => complete_attribute_start(acc, ctx, attribute), | ||
33 | } | ||
34 | Some(()) | ||
35 | } | ||
36 | |||
37 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
38 | for attr_completion in ATTRIBUTES { | ||
39 | let mut item = CompletionItem::new( | ||
40 | CompletionKind::Attribute, | ||
41 | ctx.source_range(), | ||
42 | attr_completion.label, | ||
43 | ) | ||
44 | .kind(CompletionItemKind::Attribute); | ||
45 | |||
46 | if let Some(lookup) = attr_completion.lookup { | ||
47 | item = item.lookup_by(lookup); | ||
48 | } | ||
49 | |||
50 | match (attr_completion.snippet, ctx.config.snippet_cap) { | ||
51 | (Some(snippet), Some(cap)) => { | ||
52 | item = item.insert_snippet(cap, snippet); | ||
53 | } | ||
54 | _ => {} | ||
55 | } | ||
56 | |||
57 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { | ||
58 | acc.add(item); | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | struct AttrCompletion { | ||
64 | label: &'static str, | ||
65 | lookup: Option<&'static str>, | ||
66 | snippet: Option<&'static str>, | ||
67 | prefer_inner: bool, | ||
68 | } | ||
69 | |||
70 | impl AttrCompletion { | ||
71 | const fn prefer_inner(self) -> AttrCompletion { | ||
72 | AttrCompletion { prefer_inner: true, ..self } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | const fn attr( | ||
77 | label: &'static str, | ||
78 | lookup: Option<&'static str>, | ||
79 | snippet: Option<&'static str>, | ||
80 | ) -> AttrCompletion { | ||
81 | AttrCompletion { label, lookup, snippet, prefer_inner: false } | ||
82 | } | ||
83 | |||
84 | const ATTRIBUTES: &[AttrCompletion] = &[ | ||
85 | attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), | ||
86 | attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), | ||
87 | attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), | ||
88 | attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), | ||
89 | attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)), | ||
90 | attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), | ||
91 | attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), | ||
92 | attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), | ||
93 | attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), | ||
94 | // FIXME: resolve through macro resolution? | ||
95 | attr("global_allocator", None, None).prefer_inner(), | ||
96 | attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), | ||
97 | attr("inline(…)", Some("inline"), Some("inline(${0:lint})")), | ||
98 | attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)), | ||
99 | attr("link", None, None), | ||
100 | attr("macro_export", None, None), | ||
101 | attr("macro_use", None, None), | ||
102 | attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)), | ||
103 | attr("no_mangle", None, None), | ||
104 | attr("no_std", None, None).prefer_inner(), | ||
105 | attr("non_exhaustive", None, None), | ||
106 | attr("panic_handler", None, None).prefer_inner(), | ||
107 | attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")), | ||
108 | attr("proc_macro", None, None), | ||
109 | attr("proc_macro_attribute", None, None), | ||
110 | attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")), | ||
111 | attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}")) | ||
112 | .prefer_inner(), | ||
113 | attr("repr(…)", Some("repr"), Some("repr(${0:C})")), | ||
114 | attr( | ||
115 | "should_panic(…)", | ||
116 | Some("should_panic"), | ||
117 | Some(r#"should_panic(expected = "${0:reason}")"#), | ||
118 | ), | ||
119 | attr( | ||
120 | r#"target_feature = "…""#, | ||
121 | Some("target_feature"), | ||
122 | Some("target_feature = \"${0:feature}\""), | ||
123 | ), | ||
124 | attr("test", None, None), | ||
125 | attr("used", None, None), | ||
126 | attr("warn(…)", Some("warn"), Some("warn(${0:lint})")), | ||
127 | attr( | ||
128 | r#"windows_subsystem = "…""#, | ||
129 | Some("windows_subsystem"), | ||
130 | Some(r#"windows_subsystem = "${0:subsystem}""#), | ||
131 | ) | ||
132 | .prefer_inner(), | ||
133 | ]; | ||
134 | |||
135 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
136 | if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { | ||
137 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
138 | .into_iter() | ||
139 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
140 | { | ||
141 | let mut label = derive_completion.label.to_owned(); | ||
142 | for dependency in derive_completion | ||
143 | .dependencies | ||
144 | .into_iter() | ||
145 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
146 | { | ||
147 | label.push_str(", "); | ||
148 | label.push_str(dependency); | ||
149 | } | ||
150 | acc.add( | ||
151 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
152 | .kind(CompletionItemKind::Attribute), | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
157 | acc.add( | ||
158 | CompletionItem::new( | ||
159 | CompletionKind::Attribute, | ||
160 | ctx.source_range(), | ||
161 | custom_derive_name, | ||
162 | ) | ||
163 | .kind(CompletionItemKind::Attribute), | ||
164 | ); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | fn complete_lint( | ||
170 | acc: &mut Completions, | ||
171 | ctx: &CompletionContext, | ||
172 | derive_input: ast::TokenTree, | ||
173 | lints_completions: &[LintCompletion], | ||
174 | ) { | ||
175 | if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { | ||
176 | for lint_completion in lints_completions | ||
177 | .into_iter() | ||
178 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
179 | { | ||
180 | acc.add( | ||
181 | CompletionItem::new( | ||
182 | CompletionKind::Attribute, | ||
183 | ctx.source_range(), | ||
184 | lint_completion.label, | ||
185 | ) | ||
186 | .kind(CompletionItemKind::Attribute) | ||
187 | .detail(lint_completion.description), | ||
188 | ); | ||
189 | } | ||
190 | } | ||
191 | } | ||
192 | |||
193 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
194 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
195 | (Some(left_paren), Some(right_paren)) | ||
196 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
197 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
198 | { | ||
199 | let mut input_derives = FxHashSet::default(); | ||
200 | let mut current_derive = String::new(); | ||
201 | for token in derive_input | ||
202 | .syntax() | ||
203 | .children_with_tokens() | ||
204 | .filter_map(|token| token.into_token()) | ||
205 | .skip_while(|token| token != &left_paren) | ||
206 | .skip(1) | ||
207 | .take_while(|token| token != &right_paren) | ||
208 | { | ||
209 | if SyntaxKind::COMMA == token.kind() { | ||
210 | if !current_derive.is_empty() { | ||
211 | input_derives.insert(current_derive); | ||
212 | current_derive = String::new(); | ||
213 | } | ||
214 | } else { | ||
215 | current_derive.push_str(token.to_string().trim()); | ||
216 | } | ||
217 | } | ||
218 | |||
219 | if !current_derive.is_empty() { | ||
220 | input_derives.insert(current_derive); | ||
221 | } | ||
222 | Ok(input_derives) | ||
223 | } | ||
224 | _ => Err(()), | ||
225 | } | ||
226 | } | ||
227 | |||
228 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
229 | let mut result = FxHashSet::default(); | ||
230 | ctx.scope.process_all_names(&mut |name, scope_def| { | ||
231 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
232 | if mac.is_derive_macro() { | ||
233 | result.insert(name.to_string()); | ||
234 | } | ||
235 | } | ||
236 | }); | ||
237 | result | ||
238 | } | ||
239 | |||
240 | struct DeriveCompletion { | ||
241 | label: &'static str, | ||
242 | dependencies: &'static [&'static str], | ||
243 | } | ||
244 | |||
245 | /// Standard Rust derives and the information about their dependencies | ||
246 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
247 | #[rustfmt::skip] | ||
248 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
249 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
250 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
251 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
252 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
253 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
254 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
255 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
256 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
257 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
258 | ]; | ||
259 | |||
260 | pub(super) struct LintCompletion { | ||
261 | pub(super) label: &'static str, | ||
262 | pub(super) description: &'static str, | ||
263 | } | ||
264 | |||
265 | #[rustfmt::skip] | ||
266 | const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
267 | LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, | ||
268 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
269 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
270 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
271 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
272 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
273 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
274 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
275 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
276 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
277 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
278 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
279 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
280 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
281 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
282 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
283 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
284 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
285 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
286 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
287 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
288 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
289 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
290 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
291 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
292 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
293 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
294 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
295 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
296 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
297 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
298 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
299 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
300 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
301 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
302 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
303 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
304 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
305 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
306 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
307 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
308 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
309 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
310 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
311 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
312 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
313 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
314 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
315 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
316 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
317 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
318 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
319 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
320 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
321 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
322 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
323 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
324 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
325 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
326 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
327 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
328 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
329 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
330 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
331 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
332 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
333 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
334 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
335 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
336 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
337 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
338 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
339 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
340 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
341 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
342 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
343 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
344 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
345 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
346 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
347 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
348 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
349 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
350 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
351 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
352 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
353 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
354 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
355 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
356 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
357 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
358 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
359 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
360 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
361 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
362 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
363 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
364 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
365 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
366 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
367 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
368 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
369 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
370 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
371 | LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, | ||
372 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
373 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
374 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
375 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
376 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
377 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
378 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
379 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
380 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
381 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
382 | ]; | ||
383 | |||
384 | #[cfg(test)] | ||
385 | mod tests { | ||
386 | use expect_test::{expect, Expect}; | ||
387 | |||
388 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
389 | |||
390 | fn check(ra_fixture: &str, expect: Expect) { | ||
391 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); | ||
392 | expect.assert_eq(&actual); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn empty_derive_completion() { | ||
397 | check( | ||
398 | r#" | ||
399 | #[derive(<|>)] | ||
400 | struct Test {} | ||
401 | "#, | ||
402 | expect![[r#" | ||
403 | at Clone | ||
404 | at Copy, Clone | ||
405 | at Debug | ||
406 | at Default | ||
407 | at Eq, PartialEq | ||
408 | at Hash | ||
409 | at Ord, PartialOrd, Eq, PartialEq | ||
410 | at PartialEq | ||
411 | at PartialOrd, PartialEq | ||
412 | "#]], | ||
413 | ); | ||
414 | } | ||
415 | |||
416 | #[test] | ||
417 | fn empty_lint_completion() { | ||
418 | check( | ||
419 | r#"#[allow(<|>)]"#, | ||
420 | expect![[r#" | ||
421 | at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name | ||
422 | at ambiguous_associated_items ambiguous associated items | ||
423 | at anonymous_parameters detects anonymous parameters | ||
424 | at arithmetic_overflow arithmetic operation overflows | ||
425 | at array_into_iter detects calling `into_iter` on arrays | ||
426 | at asm_sub_register using only a subset of a register for inline asm inputs | ||
427 | at bare_trait_objects suggest using `dyn Trait` for trait objects | ||
428 | at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants | ||
429 | at box_pointers use of owned (Box type) heap memory | ||
430 | at cenum_impl_drop_cast a C-like enum implementing Drop is cast | ||
431 | at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types | ||
432 | at coherence_leak_check distinct impls distinguished only by the leak-check code | ||
433 | at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice | ||
434 | at confusable_idents detects visually confusable pairs between identifiers | ||
435 | at const_err constant evaluation detected erroneous expression | ||
436 | at dead_code detect unused, unexported items | ||
437 | at deprecated detects use of deprecated items | ||
438 | at deprecated_in_future detects use of items that will be deprecated in a future version | ||
439 | at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated | ||
440 | at ellipsis_inclusive_range_patterns `...` range patterns are deprecated | ||
441 | at explicit_outlives_requirements outlives requirements can be inferred | ||
442 | at exported_private_dependencies public interface leaks type from a private dependency | ||
443 | at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice | ||
444 | at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns | ||
445 | at improper_ctypes proper use of libc types in foreign modules | ||
446 | at improper_ctypes_definitions proper use of libc types in foreign item definitions | ||
447 | at incomplete_features incomplete features that may function improperly in some or all cases | ||
448 | at incomplete_include trailing content in included file | ||
449 | at indirect_structural_match pattern with const indirectly referencing non-structural-match type | ||
450 | at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]` | ||
451 | at intra_doc_link_resolution_failure failures in resolving intra-doc link targets | ||
452 | at invalid_codeblock_attributes codeblock attribute looks a lot like a known one | ||
453 | at invalid_type_param_default type parameter default erroneously allowed in invalid location | ||
454 | at invalid_value an invalid value is being created (such as a NULL reference) | ||
455 | at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements | ||
456 | at keyword_idents detects edition keywords being used as an identifier | ||
457 | at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters | ||
458 | at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths | ||
459 | at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system | ||
460 | at meta_variable_misuse possible meta-variable misuse at macro definition | ||
461 | at missing_copy_implementations detects potentially-forgotten implementations of `Copy` | ||
462 | at missing_crate_level_docs detects crates with no crate-level documentation | ||
463 | at missing_debug_implementations detects missing implementations of Debug | ||
464 | at missing_doc_code_examples detects publicly-exported items without code samples in their documentation | ||
465 | at missing_docs detects missing documentation for public members | ||
466 | at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns | ||
467 | at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used | ||
468 | at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows | ||
469 | at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior | ||
470 | at no_mangle_const_items const items will not have their symbols exported | ||
471 | at no_mangle_generic_items generic items must be mangled | ||
472 | at non_ascii_idents detects non-ASCII identifiers | ||
473 | at non_camel_case_types types, variants, traits and type parameters should have camel case names | ||
474 | at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern | ||
475 | at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names | ||
476 | at non_upper_case_globals static constants should have uppercase identifiers | ||
477 | at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order | ||
478 | at overflowing_literals literal out of range for its type | ||
479 | at overlapping_patterns detects overlapping patterns | ||
480 | at path_statements path statements with no effect | ||
481 | at patterns_in_fns_without_body patterns in functions without body were erroneously allowed | ||
482 | at private_doc_tests detects code samples in docs of private items not documented by rustdoc | ||
483 | at private_in_public detect private items in public interfaces not caught by the old implementation | ||
484 | at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules | ||
485 | at pub_use_of_private_extern_crate detect public re-exports of private extern crates | ||
486 | at redundant_semicolons detects unnecessary trailing semicolons | ||
487 | at renamed_and_removed_lints lints that have been renamed or removed | ||
488 | at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed | ||
489 | at single_use_lifetimes detects lifetime parameters that are only used once | ||
490 | at soft_unstable a feature gate that doesn't break dependent crates | ||
491 | at stable_features stable features found in `#[feature]` directive | ||
492 | at trivial_bounds these bounds don't depend on an type parameters | ||
493 | at trivial_casts detects trivial casts which could be removed | ||
494 | at trivial_numeric_casts detects trivial casts of numeric types which could be removed | ||
495 | at type_alias_bounds bounds in type aliases are not enforced | ||
496 | at tyvar_behind_raw_pointer raw pointer to an inference variable | ||
497 | at unaligned_references detects unaligned references to fields of packed structs | ||
498 | at uncommon_codepoints detects uncommon Unicode codepoints in identifiers | ||
499 | at unconditional_panic operation will cause a panic at runtime | ||
500 | at unconditional_recursion functions that cannot return without calling themselves | ||
501 | at unknown_crate_types unknown crate type found in `#[crate_type]` directive | ||
502 | at unknown_lints unrecognized lint attribute | ||
503 | at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]` | ||
504 | at unreachable_code detects unreachable code paths | ||
505 | at unreachable_patterns detects unreachable patterns | ||
506 | at unreachable_pub `pub` items not reachable from crate root | ||
507 | at unsafe_code usage of `unsafe` code | ||
508 | at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated | ||
509 | at unstable_features enabling unstable features (deprecated. do not use) | ||
510 | at unstable_name_collisions detects name collision with an existing but unstable method | ||
511 | at unused_allocation detects unnecessary allocations that can be eliminated | ||
512 | at unused_assignments detect assignments that will never be read | ||
513 | at unused_attributes detects attributes that were not used by the compiler | ||
514 | at unused_braces unnecessary braces around an expression | ||
515 | at unused_comparisons comparisons made useless by limits of the types involved | ||
516 | at unused_crate_dependencies crate dependencies that are never used | ||
517 | at unused_doc_comments detects doc comments that aren't used by rustdoc | ||
518 | at unused_extern_crates extern crates that are never used | ||
519 | at unused_features unused features found in crate-level `#[feature]` directives | ||
520 | at unused_import_braces unnecessary braces around an imported item | ||
521 | at unused_imports imports that are never used | ||
522 | at unused_labels detects labels that are never used | ||
523 | at unused_lifetimes detects lifetime parameters that are never used | ||
524 | at unused_macros detects macros that were not used | ||
525 | at unused_must_use unused result of a type flagged as `#[must_use]` | ||
526 | at unused_mut detect mut variables which don't need to be mutable | ||
527 | at unused_parens `if`, `match`, `while` and `return` do not need parentheses | ||
528 | at unused_qualifications detects unnecessarily qualified names | ||
529 | at unused_results unused result of an expression in a statement | ||
530 | at unused_unsafe unnecessary use of an `unsafe` block | ||
531 | at unused_variables detect variables which are not used in any way | ||
532 | at variant_size_differences detects enums with widely varying variant sizes | ||
533 | at warnings mass-change the level for lints which produce warnings | ||
534 | at where_clauses_object_safety checks the object safety of where clauses | ||
535 | at while_true suggest using `loop { }` instead of `while true { }` | ||
536 | "#]], | ||
537 | ) | ||
538 | } | ||
539 | |||
540 | #[test] | ||
541 | fn no_completion_for_incorrect_derive() { | ||
542 | check( | ||
543 | r#" | ||
544 | #[derive{<|>)] | ||
545 | struct Test {} | ||
546 | "#, | ||
547 | expect![[r#""#]], | ||
548 | ) | ||
549 | } | ||
550 | |||
551 | #[test] | ||
552 | fn derive_with_input_completion() { | ||
553 | check( | ||
554 | r#" | ||
555 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
556 | struct Test {} | ||
557 | "#, | ||
558 | expect![[r#" | ||
559 | at Clone | ||
560 | at Copy, Clone | ||
561 | at Debug | ||
562 | at Default | ||
563 | at Eq | ||
564 | at Hash | ||
565 | at Ord, PartialOrd, Eq | ||
566 | at PartialOrd | ||
567 | "#]], | ||
568 | ) | ||
569 | } | ||
570 | |||
571 | #[test] | ||
572 | fn test_attribute_completion() { | ||
573 | check( | ||
574 | r#"#[<|>]"#, | ||
575 | expect![[r#" | ||
576 | at allow(…) | ||
577 | at cfg(…) | ||
578 | at cfg_attr(…) | ||
579 | at deny(…) | ||
580 | at deprecated = "…" | ||
581 | at derive(…) | ||
582 | at doc = "…" | ||
583 | at forbid(…) | ||
584 | at ignore = "…" | ||
585 | at inline(…) | ||
586 | at link | ||
587 | at link_name = "…" | ||
588 | at macro_export | ||
589 | at macro_use | ||
590 | at must_use = "…" | ||
591 | at no_mangle | ||
592 | at non_exhaustive | ||
593 | at path = "…" | ||
594 | at proc_macro | ||
595 | at proc_macro_attribute | ||
596 | at proc_macro_derive(…) | ||
597 | at repr(…) | ||
598 | at should_panic(…) | ||
599 | at target_feature = "…" | ||
600 | at test | ||
601 | at used | ||
602 | at warn(…) | ||
603 | "#]], | ||
604 | ) | ||
605 | } | ||
606 | |||
607 | #[test] | ||
608 | fn test_attribute_completion_inside_nested_attr() { | ||
609 | check(r#"#[cfg(<|>)]"#, expect![[]]) | ||
610 | } | ||
611 | |||
612 | #[test] | ||
613 | fn test_inner_attribute_completion() { | ||
614 | check( | ||
615 | r"#![<|>]", | ||
616 | expect![[r#" | ||
617 | at allow(…) | ||
618 | at cfg(…) | ||
619 | at cfg_attr(…) | ||
620 | at deny(…) | ||
621 | at deprecated = "…" | ||
622 | at derive(…) | ||
623 | at doc = "…" | ||
624 | at feature(…) | ||
625 | at forbid(…) | ||
626 | at global_allocator | ||
627 | at ignore = "…" | ||
628 | at inline(…) | ||
629 | at link | ||
630 | at link_name = "…" | ||
631 | at macro_export | ||
632 | at macro_use | ||
633 | at must_use = "…" | ||
634 | at no_mangle | ||
635 | at no_std | ||
636 | at non_exhaustive | ||
637 | at panic_handler | ||
638 | at path = "…" | ||
639 | at proc_macro | ||
640 | at proc_macro_attribute | ||
641 | at proc_macro_derive(…) | ||
642 | at recursion_limit = … | ||
643 | at repr(…) | ||
644 | at should_panic(…) | ||
645 | at target_feature = "…" | ||
646 | at test | ||
647 | at used | ||
648 | at warn(…) | ||
649 | at windows_subsystem = "…" | ||
650 | "#]], | ||
651 | ); | ||
652 | } | ||
653 | } | ||
diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/ide/src/completion/complete_dot.rs new file mode 100644 index 000000000..0b9f1798a --- /dev/null +++ b/crates/ide/src/completion/complete_dot.rs | |||
@@ -0,0 +1,416 @@ | |||
1 | //! Completes references after dot (fields and method calls). | ||
2 | |||
3 | use hir::{HasVisibility, Type}; | ||
4 | use rustc_hash::FxHashSet; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::completion::{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::completion::{test_utils::completion_list, CompletionKind}; | ||
68 | |||
69 | fn check(ra_fixture: &str, expect: Expect) { | ||
70 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
71 | expect.assert_eq(&actual); | ||
72 | } | ||
73 | |||
74 | #[test] | ||
75 | fn test_struct_field_and_method_completion() { | ||
76 | check( | ||
77 | r#" | ||
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 | } | ||
diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/ide/src/completion/complete_fn_param.rs new file mode 100644 index 000000000..9efe25461 --- /dev/null +++ b/crates/ide/src/completion/complete_fn_param.rs | |||
@@ -0,0 +1,135 @@ | |||
1 | //! See `complete_fn_param`. | ||
2 | |||
3 | use rustc_hash::FxHashMap; | ||
4 | use syntax::{ | ||
5 | ast::{self, ModuleItemOwner}, | ||
6 | match_ast, AstNode, | ||
7 | }; | ||
8 | |||
9 | use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; | ||
10 | |||
11 | /// Complete repeated parameters, both name and type. For example, if all | ||
12 | /// functions in a file have a `spam: &mut Spam` parameter, a completion with | ||
13 | /// `spam: &mut Spam` insert text/label and `spam` lookup string will be | ||
14 | /// suggested. | ||
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::completion::{test_utils::completion_list, CompletionKind}; | ||
72 | |||
73 | fn check(ra_fixture: &str, expect: Expect) { | ||
74 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
75 | expect.assert_eq(&actual); | ||
76 | } | ||
77 | |||
78 | #[test] | ||
79 | fn test_param_completion_last_param() { | ||
80 | check( | ||
81 | r#" | ||
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/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs new file mode 100644 index 000000000..95e4ff1ac --- /dev/null +++ b/crates/ide/src/completion/complete_keyword.rs | |||
@@ -0,0 +1,527 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use syntax::{ast, SyntaxKind}; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::completion::{ | ||
7 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
8 | }; | ||
9 | |||
10 | pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
11 | // complete keyword "crate" in use stmt | ||
12 | let source_range = ctx.source_range(); | ||
13 | |||
14 | if ctx.use_item_syntax.is_some() { | ||
15 | if ctx.path_qual.is_none() { | ||
16 | CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") | ||
17 | .kind(CompletionItemKind::Keyword) | ||
18 | .insert_text("crate::") | ||
19 | .add_to(acc); | ||
20 | } | ||
21 | CompletionItem::new(CompletionKind::Keyword, source_range, "self") | ||
22 | .kind(CompletionItemKind::Keyword) | ||
23 | .add_to(acc); | ||
24 | CompletionItem::new(CompletionKind::Keyword, source_range, "super::") | ||
25 | .kind(CompletionItemKind::Keyword) | ||
26 | .insert_text("super::") | ||
27 | .add_to(acc); | ||
28 | } | ||
29 | |||
30 | // Suggest .await syntax for types that implement Future trait | ||
31 | if let Some(receiver) = &ctx.dot_receiver { | ||
32 | if let Some(ty) = ctx.sema.type_of_expr(receiver) { | ||
33 | if ty.impls_future(ctx.db) { | ||
34 | CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") | ||
35 | .kind(CompletionItemKind::Keyword) | ||
36 | .detail("expr.await") | ||
37 | .insert_text("await") | ||
38 | .add_to(acc); | ||
39 | } | ||
40 | }; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
45 | if ctx.token.kind() == SyntaxKind::COMMENT { | ||
46 | mark::hit!(no_keyword_completion_in_comments); | ||
47 | return; | ||
48 | } | ||
49 | |||
50 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; | ||
51 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { | ||
52 | add_keyword(ctx, acc, "where", "where "); | ||
53 | return; | ||
54 | } | ||
55 | if ctx.unsafe_is_prev { | ||
56 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
57 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
58 | } | ||
59 | |||
60 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
61 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
62 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
63 | } | ||
64 | |||
65 | return; | ||
66 | } | ||
67 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
68 | { | ||
69 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
70 | } | ||
71 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
72 | add_keyword(ctx, acc, "use", "use "); | ||
73 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
74 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
75 | } | ||
76 | |||
77 | if ctx.has_item_list_or_source_file_parent { | ||
78 | add_keyword(ctx, acc, "enum", "enum $0 {}"); | ||
79 | add_keyword(ctx, acc, "struct", "struct $0"); | ||
80 | add_keyword(ctx, acc, "union", "union $0 {}"); | ||
81 | } | ||
82 | |||
83 | if ctx.is_expr { | ||
84 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
85 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
86 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
87 | add_keyword(ctx, acc, "if", "if "); | ||
88 | add_keyword(ctx, acc, "if let", "if let "); | ||
89 | } | ||
90 | |||
91 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
92 | add_keyword(ctx, acc, "let", "let "); | ||
93 | } | ||
94 | |||
95 | if ctx.after_if { | ||
96 | add_keyword(ctx, acc, "else", "else {$0}"); | ||
97 | add_keyword(ctx, acc, "else if", "else if $0 {}"); | ||
98 | } | ||
99 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
100 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
101 | } | ||
102 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
103 | add_keyword(ctx, acc, "mut", "mut "); | ||
104 | } | ||
105 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
106 | { | ||
107 | add_keyword(ctx, acc, "const", "const "); | ||
108 | add_keyword(ctx, acc, "type", "type "); | ||
109 | } | ||
110 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
111 | add_keyword(ctx, acc, "static", "static "); | ||
112 | }; | ||
113 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
114 | add_keyword(ctx, acc, "extern", "extern "); | ||
115 | } | ||
116 | if ctx.has_item_list_or_source_file_parent | ||
117 | || has_trait_or_impl_parent | ||
118 | || ctx.block_expr_parent | ||
119 | || ctx.is_match_arm | ||
120 | { | ||
121 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
122 | } | ||
123 | if ctx.in_loop_body { | ||
124 | if ctx.can_be_stmt { | ||
125 | add_keyword(ctx, acc, "continue", "continue;"); | ||
126 | add_keyword(ctx, acc, "break", "break;"); | ||
127 | } else { | ||
128 | add_keyword(ctx, acc, "continue", "continue"); | ||
129 | add_keyword(ctx, acc, "break", "break"); | ||
130 | } | ||
131 | } | ||
132 | if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent { | ||
133 | add_keyword(ctx, acc, "pub", "pub ") | ||
134 | } | ||
135 | |||
136 | if !ctx.is_trivial_path { | ||
137 | return; | ||
138 | } | ||
139 | let fn_def = match &ctx.function_syntax { | ||
140 | Some(it) => it, | ||
141 | None => return, | ||
142 | }; | ||
143 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | ||
144 | } | ||
145 | |||
146 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | ||
147 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | ||
148 | .kind(CompletionItemKind::Keyword); | ||
149 | |||
150 | match ctx.config.snippet_cap { | ||
151 | Some(cap) => res.insert_snippet(cap, snippet), | ||
152 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
153 | } | ||
154 | .build() | ||
155 | } | ||
156 | |||
157 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
158 | acc.add(keyword(ctx, kw, snippet)); | ||
159 | } | ||
160 | |||
161 | fn complete_return( | ||
162 | ctx: &CompletionContext, | ||
163 | fn_def: &ast::Fn, | ||
164 | can_be_stmt: bool, | ||
165 | ) -> Option<CompletionItem> { | ||
166 | let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { | ||
167 | (true, true) => "return $0;", | ||
168 | (true, false) => "return;", | ||
169 | (false, true) => "return $0", | ||
170 | (false, false) => "return", | ||
171 | }; | ||
172 | Some(keyword(ctx, "return", snip)) | ||
173 | } | ||
174 | |||
175 | #[cfg(test)] | ||
176 | mod tests { | ||
177 | use expect_test::{expect, Expect}; | ||
178 | |||
179 | use crate::completion::{ | ||
180 | test_utils::{check_edit, completion_list}, | ||
181 | CompletionKind, | ||
182 | }; | ||
183 | use test_utils::mark; | ||
184 | |||
185 | fn check(ra_fixture: &str, expect: Expect) { | ||
186 | let actual = completion_list(ra_fixture, CompletionKind::Keyword); | ||
187 | expect.assert_eq(&actual) | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn test_keywords_in_use_stmt() { | ||
192 | check( | ||
193 | r"use <|>", | ||
194 | expect![[r#" | ||
195 | kw crate:: | ||
196 | kw self | ||
197 | kw super:: | ||
198 | "#]], | ||
199 | ); | ||
200 | |||
201 | check( | ||
202 | r"use a::<|>", | ||
203 | expect![[r#" | ||
204 | kw self | ||
205 | kw super:: | ||
206 | "#]], | ||
207 | ); | ||
208 | |||
209 | check( | ||
210 | r"use a::{b, <|>}", | ||
211 | expect![[r#" | ||
212 | kw self | ||
213 | kw super:: | ||
214 | "#]], | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn test_keywords_at_source_file_level() { | ||
220 | check( | ||
221 | r"m<|>", | ||
222 | expect![[r#" | ||
223 | kw const | ||
224 | kw enum | ||
225 | kw extern | ||
226 | kw fn | ||
227 | kw impl | ||
228 | kw mod | ||
229 | kw pub | ||
230 | kw static | ||
231 | kw struct | ||
232 | kw trait | ||
233 | kw type | ||
234 | kw union | ||
235 | kw unsafe | ||
236 | kw use | ||
237 | "#]], | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_keywords_in_function() { | ||
243 | check( | ||
244 | r"fn quux() { <|> }", | ||
245 | expect![[r#" | ||
246 | kw const | ||
247 | kw extern | ||
248 | kw fn | ||
249 | kw if | ||
250 | kw if let | ||
251 | kw impl | ||
252 | kw let | ||
253 | kw loop | ||
254 | kw match | ||
255 | kw mod | ||
256 | kw return | ||
257 | kw static | ||
258 | kw trait | ||
259 | kw type | ||
260 | kw unsafe | ||
261 | kw use | ||
262 | kw while | ||
263 | "#]], | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn test_keywords_inside_block() { | ||
269 | check( | ||
270 | r"fn quux() { if true { <|> } }", | ||
271 | expect![[r#" | ||
272 | kw const | ||
273 | kw extern | ||
274 | kw fn | ||
275 | kw if | ||
276 | kw if let | ||
277 | kw impl | ||
278 | kw let | ||
279 | kw loop | ||
280 | kw match | ||
281 | kw mod | ||
282 | kw return | ||
283 | kw static | ||
284 | kw trait | ||
285 | kw type | ||
286 | kw unsafe | ||
287 | kw use | ||
288 | kw while | ||
289 | "#]], | ||
290 | ); | ||
291 | } | ||
292 | |||
293 | #[test] | ||
294 | fn test_keywords_after_if() { | ||
295 | check( | ||
296 | r#"fn quux() { if true { () } <|> }"#, | ||
297 | expect![[r#" | ||
298 | kw const | ||
299 | kw else | ||
300 | kw else if | ||
301 | kw extern | ||
302 | kw fn | ||
303 | kw if | ||
304 | kw if let | ||
305 | kw impl | ||
306 | kw let | ||
307 | kw loop | ||
308 | kw match | ||
309 | kw mod | ||
310 | kw return | ||
311 | kw static | ||
312 | kw trait | ||
313 | kw type | ||
314 | kw unsafe | ||
315 | kw use | ||
316 | kw while | ||
317 | "#]], | ||
318 | ); | ||
319 | check_edit( | ||
320 | "else", | ||
321 | r#"fn quux() { if true { () } <|> }"#, | ||
322 | r#"fn quux() { if true { () } else {$0} }"#, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_keywords_in_match_arm() { | ||
328 | check( | ||
329 | r#" | ||
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 type | ||
368 | kw unsafe | ||
369 | "#]], | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | #[test] | ||
374 | fn test_keywords_in_loop() { | ||
375 | check( | ||
376 | r"fn my() { loop { <|> } }", | ||
377 | expect![[r#" | ||
378 | kw break | ||
379 | kw const | ||
380 | kw continue | ||
381 | kw extern | ||
382 | kw fn | ||
383 | kw if | ||
384 | kw if let | ||
385 | kw impl | ||
386 | kw let | ||
387 | kw loop | ||
388 | kw match | ||
389 | kw mod | ||
390 | kw return | ||
391 | kw static | ||
392 | kw trait | ||
393 | kw type | ||
394 | kw unsafe | ||
395 | kw use | ||
396 | kw while | ||
397 | "#]], | ||
398 | ); | ||
399 | } | ||
400 | |||
401 | #[test] | ||
402 | fn test_keywords_after_unsafe_in_item_list() { | ||
403 | check( | ||
404 | r"unsafe <|>", | ||
405 | expect![[r#" | ||
406 | kw fn | ||
407 | kw impl | ||
408 | kw trait | ||
409 | "#]], | ||
410 | ); | ||
411 | } | ||
412 | |||
413 | #[test] | ||
414 | fn test_keywords_after_unsafe_in_block_expr() { | ||
415 | check( | ||
416 | r"fn my_fn() { unsafe <|> }", | ||
417 | expect![[r#" | ||
418 | kw fn | ||
419 | kw impl | ||
420 | kw trait | ||
421 | "#]], | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | #[test] | ||
426 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
427 | check( | ||
428 | r"fn my_fn(&<|>) {}", | ||
429 | expect![[r#" | ||
430 | kw mut | ||
431 | "#]], | ||
432 | ); | ||
433 | check( | ||
434 | r"fn my_fn(<|>) {}", | ||
435 | expect![[r#" | ||
436 | kw mut | ||
437 | "#]], | ||
438 | ); | ||
439 | check( | ||
440 | r"fn my_fn() { let &<|> }", | ||
441 | expect![[r#" | ||
442 | kw mut | ||
443 | "#]], | ||
444 | ); | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn test_where_keyword() { | ||
449 | check( | ||
450 | r"trait A <|>", | ||
451 | expect![[r#" | ||
452 | kw where | ||
453 | "#]], | ||
454 | ); | ||
455 | check( | ||
456 | r"impl A <|>", | ||
457 | expect![[r#" | ||
458 | kw where | ||
459 | "#]], | ||
460 | ); | ||
461 | } | ||
462 | |||
463 | #[test] | ||
464 | fn no_keyword_completion_in_comments() { | ||
465 | mark::check!(no_keyword_completion_in_comments); | ||
466 | check( | ||
467 | r#" | ||
468 | fn test() { | ||
469 | let x = 2; // A comment<|> | ||
470 | } | ||
471 | "#, | ||
472 | expect![[""]], | ||
473 | ); | ||
474 | check( | ||
475 | r#" | ||
476 | /* | ||
477 | Some multi-line comment<|> | ||
478 | */ | ||
479 | "#, | ||
480 | expect![[""]], | ||
481 | ); | ||
482 | check( | ||
483 | r#" | ||
484 | /// Some doc comment | ||
485 | /// let test<|> = 1 | ||
486 | "#, | ||
487 | expect![[""]], | ||
488 | ); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn test_completion_await_impls_future() { | ||
493 | check( | ||
494 | r#" | ||
495 | //- /main.rs | ||
496 | use std::future::*; | ||
497 | struct A {} | ||
498 | impl Future for A {} | ||
499 | fn foo(a: A) { a.<|> } | ||
500 | |||
501 | //- /std/lib.rs | ||
502 | pub mod future { | ||
503 | #[lang = "future_trait"] | ||
504 | pub trait Future {} | ||
505 | } | ||
506 | "#, | ||
507 | expect![[r#" | ||
508 | kw await expr.await | ||
509 | "#]], | ||
510 | ) | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn after_let() { | ||
515 | check( | ||
516 | r#"fn main() { let _ = <|> }"#, | ||
517 | expect![[r#" | ||
518 | kw if | ||
519 | kw if let | ||
520 | kw loop | ||
521 | kw match | ||
522 | kw return | ||
523 | kw while | ||
524 | "#]], | ||
525 | ) | ||
526 | } | ||
527 | } | ||
diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/ide/src/completion/complete_macro_in_item_position.rs new file mode 100644 index 000000000..fc8625d8e --- /dev/null +++ b/crates/ide/src/completion/complete_macro_in_item_position.rs | |||
@@ -0,0 +1,41 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{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::completion::{test_utils::completion_list, CompletionKind}; | ||
21 | |||
22 | fn check(ra_fixture: &str, expect: Expect) { | ||
23 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
24 | expect.assert_eq(&actual) | ||
25 | } | ||
26 | |||
27 | #[test] | ||
28 | fn completes_macros_as_item() { | ||
29 | check( | ||
30 | r#" | ||
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/ide/src/completion/complete_pattern.rs b/crates/ide/src/completion/complete_pattern.rs new file mode 100644 index 000000000..5a13574d4 --- /dev/null +++ b/crates/ide/src/completion/complete_pattern.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{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::completion::{test_utils::completion_list, CompletionKind}; | ||
39 | |||
40 | fn check(ra_fixture: &str, expect: Expect) { | ||
41 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
42 | expect.assert_eq(&actual) | ||
43 | } | ||
44 | |||
45 | #[test] | ||
46 | fn completes_enum_variants_and_modules() { | ||
47 | check( | ||
48 | r#" | ||
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/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs new file mode 100644 index 000000000..84c4e129d --- /dev/null +++ b/crates/ide/src/completion/complete_postfix.rs | |||
@@ -0,0 +1,378 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | use assists::utils::TryEnum; | ||
3 | use syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | TextRange, TextSize, | ||
6 | }; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{ | ||
10 | completion::{ | ||
11 | completion_config::SnippetCap, | ||
12 | completion_context::CompletionContext, | ||
13 | completion_item::{Builder, CompletionKind, Completions}, | ||
14 | }, | ||
15 | CompletionItem, CompletionItemKind, | ||
16 | }; | ||
17 | |||
18 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | ||
19 | if !ctx.config.enable_postfix_completions { | ||
20 | return; | ||
21 | } | ||
22 | |||
23 | let dot_receiver = match &ctx.dot_receiver { | ||
24 | Some(it) => it, | ||
25 | None => return, | ||
26 | }; | ||
27 | |||
28 | let receiver_text = | ||
29 | get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
30 | |||
31 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { | ||
32 | Some(it) => it, | ||
33 | None => return, | ||
34 | }; | ||
35 | |||
36 | let cap = match ctx.config.snippet_cap { | ||
37 | Some(it) => it, | ||
38 | None => return, | ||
39 | }; | ||
40 | let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); | ||
41 | if let Some(try_enum) = &try_enum { | ||
42 | match try_enum { | ||
43 | TryEnum::Result => { | ||
44 | postfix_snippet( | ||
45 | ctx, | ||
46 | cap, | ||
47 | &dot_receiver, | ||
48 | "ifl", | ||
49 | "if let Ok {}", | ||
50 | &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
51 | ) | ||
52 | .add_to(acc); | ||
53 | |||
54 | postfix_snippet( | ||
55 | ctx, | ||
56 | cap, | ||
57 | &dot_receiver, | ||
58 | "while", | ||
59 | "while let Ok {}", | ||
60 | &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
61 | ) | ||
62 | .add_to(acc); | ||
63 | } | ||
64 | TryEnum::Option => { | ||
65 | postfix_snippet( | ||
66 | ctx, | ||
67 | cap, | ||
68 | &dot_receiver, | ||
69 | "ifl", | ||
70 | "if let Some {}", | ||
71 | &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
72 | ) | ||
73 | .add_to(acc); | ||
74 | |||
75 | postfix_snippet( | ||
76 | ctx, | ||
77 | cap, | ||
78 | &dot_receiver, | ||
79 | "while", | ||
80 | "while let Some {}", | ||
81 | &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
82 | ) | ||
83 | .add_to(acc); | ||
84 | } | ||
85 | } | ||
86 | } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { | ||
87 | postfix_snippet( | ||
88 | ctx, | ||
89 | cap, | ||
90 | &dot_receiver, | ||
91 | "if", | ||
92 | "if expr {}", | ||
93 | &format!("if {} {{\n $0\n}}", receiver_text), | ||
94 | ) | ||
95 | .add_to(acc); | ||
96 | postfix_snippet( | ||
97 | ctx, | ||
98 | cap, | ||
99 | &dot_receiver, | ||
100 | "while", | ||
101 | "while expr {}", | ||
102 | &format!("while {} {{\n $0\n}}", receiver_text), | ||
103 | ) | ||
104 | .add_to(acc); | ||
105 | postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) | ||
106 | .add_to(acc); | ||
107 | } | ||
108 | |||
109 | postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)) | ||
110 | .add_to(acc); | ||
111 | postfix_snippet( | ||
112 | ctx, | ||
113 | cap, | ||
114 | &dot_receiver, | ||
115 | "refm", | ||
116 | "&mut expr", | ||
117 | &format!("&mut {}", receiver_text), | ||
118 | ) | ||
119 | .add_to(acc); | ||
120 | |||
121 | // The rest of the postfix completions create an expression that moves an argument, | ||
122 | // so it's better to consider references now to avoid breaking the compilation | ||
123 | let dot_receiver = include_references(dot_receiver); | ||
124 | let receiver_text = | ||
125 | get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
126 | |||
127 | match try_enum { | ||
128 | Some(try_enum) => match try_enum { | ||
129 | TryEnum::Result => { | ||
130 | postfix_snippet( | ||
131 | ctx, | ||
132 | cap, | ||
133 | &dot_receiver, | ||
134 | "match", | ||
135 | "match expr {}", | ||
136 | &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text), | ||
137 | ) | ||
138 | .add_to(acc); | ||
139 | } | ||
140 | TryEnum::Option => { | ||
141 | postfix_snippet( | ||
142 | ctx, | ||
143 | cap, | ||
144 | &dot_receiver, | ||
145 | "match", | ||
146 | "match expr {}", | ||
147 | &format!( | ||
148 | "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}", | ||
149 | receiver_text | ||
150 | ), | ||
151 | ) | ||
152 | .add_to(acc); | ||
153 | } | ||
154 | }, | ||
155 | None => { | ||
156 | postfix_snippet( | ||
157 | ctx, | ||
158 | cap, | ||
159 | &dot_receiver, | ||
160 | "match", | ||
161 | "match expr {}", | ||
162 | &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text), | ||
163 | ) | ||
164 | .add_to(acc); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | postfix_snippet( | ||
169 | ctx, | ||
170 | cap, | ||
171 | &dot_receiver, | ||
172 | "box", | ||
173 | "Box::new(expr)", | ||
174 | &format!("Box::new({})", receiver_text), | ||
175 | ) | ||
176 | .add_to(acc); | ||
177 | |||
178 | postfix_snippet( | ||
179 | ctx, | ||
180 | cap, | ||
181 | &dot_receiver, | ||
182 | "dbg", | ||
183 | "dbg!(expr)", | ||
184 | &format!("dbg!({})", receiver_text), | ||
185 | ) | ||
186 | .add_to(acc); | ||
187 | |||
188 | postfix_snippet( | ||
189 | ctx, | ||
190 | cap, | ||
191 | &dot_receiver, | ||
192 | "call", | ||
193 | "function(expr)", | ||
194 | &format!("${{1}}({})", receiver_text), | ||
195 | ) | ||
196 | .add_to(acc); | ||
197 | } | ||
198 | |||
199 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { | ||
200 | if receiver_is_ambiguous_float_literal { | ||
201 | let text = receiver.syntax().text(); | ||
202 | let without_dot = ..text.len() - TextSize::of('.'); | ||
203 | text.slice(without_dot).to_string() | ||
204 | } else { | ||
205 | receiver.to_string() | ||
206 | } | ||
207 | } | ||
208 | |||
209 | fn include_references(initial_element: &ast::Expr) -> ast::Expr { | ||
210 | let mut resulting_element = initial_element.clone(); | ||
211 | while let Some(parent_ref_element) = | ||
212 | resulting_element.syntax().parent().and_then(ast::RefExpr::cast) | ||
213 | { | ||
214 | resulting_element = ast::Expr::from(parent_ref_element); | ||
215 | } | ||
216 | resulting_element | ||
217 | } | ||
218 | |||
219 | fn postfix_snippet( | ||
220 | ctx: &CompletionContext, | ||
221 | cap: SnippetCap, | ||
222 | receiver: &ast::Expr, | ||
223 | label: &str, | ||
224 | detail: &str, | ||
225 | snippet: &str, | ||
226 | ) -> Builder { | ||
227 | let edit = { | ||
228 | let receiver_syntax = receiver.syntax(); | ||
229 | let receiver_range = ctx.sema.original_range(receiver_syntax).range; | ||
230 | let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); | ||
231 | TextEdit::replace(delete_range, snippet.to_string()) | ||
232 | }; | ||
233 | CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) | ||
234 | .detail(detail) | ||
235 | .kind(CompletionItemKind::Snippet) | ||
236 | .snippet_edit(cap, edit) | ||
237 | } | ||
238 | |||
239 | #[cfg(test)] | ||
240 | mod tests { | ||
241 | use expect_test::{expect, Expect}; | ||
242 | |||
243 | use crate::completion::{ | ||
244 | test_utils::{check_edit, completion_list}, | ||
245 | CompletionKind, | ||
246 | }; | ||
247 | |||
248 | fn check(ra_fixture: &str, expect: Expect) { | ||
249 | let actual = completion_list(ra_fixture, CompletionKind::Postfix); | ||
250 | expect.assert_eq(&actual) | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn postfix_completion_works_for_trivial_path_expression() { | ||
255 | check( | ||
256 | r#" | ||
257 | fn main() { | ||
258 | let bar = true; | ||
259 | bar.<|> | ||
260 | } | ||
261 | "#, | ||
262 | expect![[r#" | ||
263 | sn box Box::new(expr) | ||
264 | sn call function(expr) | ||
265 | sn dbg dbg!(expr) | ||
266 | sn if if expr {} | ||
267 | sn match match expr {} | ||
268 | sn not !expr | ||
269 | sn ref &expr | ||
270 | sn refm &mut expr | ||
271 | sn while while expr {} | ||
272 | "#]], | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
277 | fn postfix_type_filtering() { | ||
278 | check( | ||
279 | r#" | ||
280 | fn main() { | ||
281 | let bar: u8 = 12; | ||
282 | bar.<|> | ||
283 | } | ||
284 | "#, | ||
285 | expect![[r#" | ||
286 | sn box Box::new(expr) | ||
287 | sn call function(expr) | ||
288 | sn dbg dbg!(expr) | ||
289 | sn match match expr {} | ||
290 | sn ref &expr | ||
291 | sn refm &mut expr | ||
292 | "#]], | ||
293 | ) | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn option_iflet() { | ||
298 | check_edit( | ||
299 | "ifl", | ||
300 | r#" | ||
301 | enum Option<T> { Some(T), None } | ||
302 | |||
303 | fn main() { | ||
304 | let bar = Option::Some(true); | ||
305 | bar.<|> | ||
306 | } | ||
307 | "#, | ||
308 | r#" | ||
309 | enum Option<T> { Some(T), None } | ||
310 | |||
311 | fn main() { | ||
312 | let bar = Option::Some(true); | ||
313 | if let Some($1) = bar { | ||
314 | $0 | ||
315 | } | ||
316 | } | ||
317 | "#, | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn result_match() { | ||
323 | check_edit( | ||
324 | "match", | ||
325 | r#" | ||
326 | enum Result<T, E> { Ok(T), Err(E) } | ||
327 | |||
328 | fn main() { | ||
329 | let bar = Result::Ok(true); | ||
330 | bar.<|> | ||
331 | } | ||
332 | "#, | ||
333 | r#" | ||
334 | enum Result<T, E> { Ok(T), Err(E) } | ||
335 | |||
336 | fn main() { | ||
337 | let bar = Result::Ok(true); | ||
338 | match bar { | ||
339 | Ok(${1:_}) => {$2}, | ||
340 | Err(${3:_}) => {$0}, | ||
341 | } | ||
342 | } | ||
343 | "#, | ||
344 | ); | ||
345 | } | ||
346 | |||
347 | #[test] | ||
348 | fn postfix_completion_works_for_ambiguous_float_literal() { | ||
349 | check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#) | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn works_in_simple_macro() { | ||
354 | check_edit( | ||
355 | "dbg", | ||
356 | r#" | ||
357 | macro_rules! m { ($e:expr) => { $e } } | ||
358 | fn main() { | ||
359 | let bar: u8 = 12; | ||
360 | m!(bar.d<|>) | ||
361 | } | ||
362 | "#, | ||
363 | r#" | ||
364 | macro_rules! m { ($e:expr) => { $e } } | ||
365 | fn main() { | ||
366 | let bar: u8 = 12; | ||
367 | m!(dbg!(bar)) | ||
368 | } | ||
369 | "#, | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | #[test] | ||
374 | fn postfix_completion_for_references() { | ||
375 | check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#); | ||
376 | check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#); | ||
377 | } | ||
378 | } | ||
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs new file mode 100644 index 000000000..accb09f7e --- /dev/null +++ b/crates/ide/src/completion/complete_qualified_path.rs | |||
@@ -0,0 +1,733 @@ | |||
1 | //! Completion of paths, i.e. `some::prefix::<|>`. | ||
2 | |||
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::completion::{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() { | ||
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::completion::{ | ||
153 | test_utils::{check_edit, completion_list}, | ||
154 | CompletionKind, | ||
155 | }; | ||
156 | |||
157 | fn check(ra_fixture: &str, expect: Expect) { | ||
158 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
159 | expect.assert_eq(&actual); | ||
160 | } | ||
161 | |||
162 | fn check_builtin(ra_fixture: &str, expect: Expect) { | ||
163 | let actual = completion_list(ra_fixture, CompletionKind::BuiltinType); | ||
164 | expect.assert_eq(&actual); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn dont_complete_current_use() { | ||
169 | mark::check!(dont_complete_current_use); | ||
170 | check(r#"use self::foo<|>;"#, expect![[""]]); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn dont_complete_current_use_in_braces_with_glob() { | ||
175 | check( | ||
176 | r#" | ||
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 | ||
426 | use foo::<|>; | ||
427 | |||
428 | //- /foo/lib.rs | ||
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 | } | ||
diff --git a/crates/ide/src/completion/complete_record.rs b/crates/ide/src/completion/complete_record.rs new file mode 100644 index 000000000..ceb8d16c1 --- /dev/null +++ b/crates/ide/src/completion/complete_record.rs | |||
@@ -0,0 +1,226 @@ | |||
1 | //! Complete fields in record literals and patterns. | ||
2 | use crate::completion::{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::completion::{test_utils::completion_list, CompletionKind}; | ||
24 | |||
25 | fn check(ra_fixture: &str, expect: Expect) { | ||
26 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
27 | expect.assert_eq(&actual); | ||
28 | } | ||
29 | |||
30 | #[test] | ||
31 | fn test_record_pattern_field() { | ||
32 | check( | ||
33 | r#" | ||
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/ide/src/completion/complete_snippet.rs b/crates/ide/src/completion/complete_snippet.rs new file mode 100644 index 000000000..c3b03b199 --- /dev/null +++ b/crates/ide/src/completion/complete_snippet.rs | |||
@@ -0,0 +1,116 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{ | ||
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 | snippet(ctx, cap, "pub(crate)", "pub(crate) $0").add_to(acc); | ||
69 | } | ||
70 | |||
71 | #[cfg(test)] | ||
72 | mod tests { | ||
73 | use expect_test::{expect, Expect}; | ||
74 | |||
75 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
76 | |||
77 | fn check(ra_fixture: &str, expect: Expect) { | ||
78 | let actual = completion_list(ra_fixture, CompletionKind::Snippet); | ||
79 | expect.assert_eq(&actual) | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn completes_snippets_in_expressions() { | ||
84 | check( | ||
85 | r#"fn foo(x: i32) { <|> }"#, | ||
86 | expect![[r#" | ||
87 | sn pd | ||
88 | sn ppd | ||
89 | "#]], | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn should_not_complete_snippets_in_path() { | ||
95 | check(r#"fn foo(x: i32) { ::foo<|> }"#, expect![[""]]); | ||
96 | check(r#"fn foo(x: i32) { ::<|> }"#, expect![[""]]); | ||
97 | } | ||
98 | |||
99 | #[test] | ||
100 | fn completes_snippets_in_items() { | ||
101 | check( | ||
102 | r#" | ||
103 | #[cfg(test)] | ||
104 | mod tests { | ||
105 | <|> | ||
106 | } | ||
107 | "#, | ||
108 | expect![[r#" | ||
109 | sn macro_rules | ||
110 | sn pub(crate) | ||
111 | sn tfn (Test function) | ||
112 | sn tmod (Test module) | ||
113 | "#]], | ||
114 | ) | ||
115 | } | ||
116 | } | ||
diff --git a/crates/ide/src/completion/complete_trait_impl.rs b/crates/ide/src/completion/complete_trait_impl.rs new file mode 100644 index 000000000..1a2b1e8a5 --- /dev/null +++ b/crates/ide/src/completion/complete_trait_impl.rs | |||
@@ -0,0 +1,488 @@ | |||
1 | //! Completion for associated items in a trait implementation. | ||
2 | //! | ||
3 | //! This module adds the completion items related to implementing associated | ||
4 | //! items within a `impl Trait for Struct` block. The current context node | ||
5 | //! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node | ||
6 | //! and an direct child of an `IMPL`. | ||
7 | //! | ||
8 | //! # Examples | ||
9 | //! | ||
10 | //! Considering the following trait `impl`: | ||
11 | //! | ||
12 | //! ```ignore | ||
13 | //! trait SomeTrait { | ||
14 | //! fn foo(); | ||
15 | //! } | ||
16 | //! | ||
17 | //! impl SomeTrait for () { | ||
18 | //! fn f<|> | ||
19 | //! } | ||
20 | //! ``` | ||
21 | //! | ||
22 | //! may result in the completion of the following method: | ||
23 | //! | ||
24 | //! ```ignore | ||
25 | //! # trait SomeTrait { | ||
26 | //! # fn foo(); | ||
27 | //! # } | ||
28 | //! | ||
29 | //! impl SomeTrait for () { | ||
30 | //! fn foo() {}<|> | ||
31 | //! } | ||
32 | //! ``` | ||
33 | |||
34 | use assists::utils::get_missing_assoc_items; | ||
35 | use hir::{self, Docs, HasSource}; | ||
36 | use syntax::{ | ||
37 | ast::{self, edit, Impl}, | ||
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | ||
39 | }; | ||
40 | use text_edit::TextEdit; | ||
41 | |||
42 | use crate::{ | ||
43 | completion::{ | ||
44 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
45 | }, | ||
46 | display::function_declaration, | ||
47 | }; | ||
48 | |||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | ||
50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | ||
51 | match trigger.kind() { | ||
52 | SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) | ||
53 | .into_iter() | ||
54 | .for_each(|item| match item { | ||
55 | hir::AssocItem::Function(fn_item) => { | ||
56 | add_function_impl(&trigger, acc, ctx, fn_item) | ||
57 | } | ||
58 | hir::AssocItem::TypeAlias(type_item) => { | ||
59 | add_type_alias_impl(&trigger, acc, ctx, type_item) | ||
60 | } | ||
61 | hir::AssocItem::Const(const_item) => { | ||
62 | add_const_impl(&trigger, acc, ctx, const_item) | ||
63 | } | ||
64 | }), | ||
65 | |||
66 | SyntaxKind::FN => { | ||
67 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
68 | .into_iter() | ||
69 | .filter_map(|item| match item { | ||
70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | ||
71 | _ => None, | ||
72 | }) | ||
73 | { | ||
74 | add_function_impl(&trigger, acc, ctx, missing_fn); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | SyntaxKind::TYPE_ALIAS => { | ||
79 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
80 | .into_iter() | ||
81 | .filter_map(|item| match item { | ||
82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | ||
83 | _ => None, | ||
84 | }) | ||
85 | { | ||
86 | add_type_alias_impl(&trigger, acc, ctx, missing_fn); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | SyntaxKind::CONST => { | ||
91 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
92 | .into_iter() | ||
93 | .filter_map(|item| match item { | ||
94 | hir::AssocItem::Const(const_item) => Some(const_item), | ||
95 | _ => None, | ||
96 | }) | ||
97 | { | ||
98 | add_const_impl(&trigger, acc, ctx, missing_fn); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | _ => {} | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn completion_match(ctx: &CompletionContext) -> Option<(SyntaxNode, Impl)> { | ||
108 | let (trigger, impl_def_offset) = ctx.token.ancestors().find_map(|p| match p.kind() { | ||
109 | SyntaxKind::FN | SyntaxKind::TYPE_ALIAS | SyntaxKind::CONST | SyntaxKind::BLOCK_EXPR => { | ||
110 | Some((p, 2)) | ||
111 | } | ||
112 | SyntaxKind::NAME_REF => Some((p, 5)), | ||
113 | _ => None, | ||
114 | })?; | ||
115 | let impl_def = (0..impl_def_offset - 1) | ||
116 | .try_fold(trigger.parent()?, |t, _| t.parent()) | ||
117 | .and_then(ast::Impl::cast)?; | ||
118 | Some((trigger, impl_def)) | ||
119 | } | ||
120 | |||
121 | fn add_function_impl( | ||
122 | fn_def_node: &SyntaxNode, | ||
123 | acc: &mut Completions, | ||
124 | ctx: &CompletionContext, | ||
125 | func: hir::Function, | ||
126 | ) { | ||
127 | let fn_name = func.name(ctx.db).to_string(); | ||
128 | |||
129 | let label = if func.params(ctx.db).is_empty() { | ||
130 | format!("fn {}()", fn_name) | ||
131 | } else { | ||
132 | format!("fn {}(..)", fn_name) | ||
133 | }; | ||
134 | |||
135 | let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) | ||
136 | .lookup_by(fn_name) | ||
137 | .set_documentation(func.docs(ctx.db)); | ||
138 | |||
139 | let completion_kind = if func.self_param(ctx.db).is_some() { | ||
140 | CompletionItemKind::Method | ||
141 | } else { | ||
142 | CompletionItemKind::Function | ||
143 | }; | ||
144 | let range = TextRange::new(fn_def_node.text_range().start(), ctx.source_range().end()); | ||
145 | |||
146 | let function_decl = function_declaration(&func.source(ctx.db).value); | ||
147 | match ctx.config.snippet_cap { | ||
148 | Some(cap) => { | ||
149 | let snippet = format!("{} {{\n $0\n}}", function_decl); | ||
150 | builder.snippet_edit(cap, TextEdit::replace(range, snippet)) | ||
151 | } | ||
152 | None => { | ||
153 | let header = format!("{} {{", function_decl); | ||
154 | builder.text_edit(TextEdit::replace(range, header)) | ||
155 | } | ||
156 | } | ||
157 | .kind(completion_kind) | ||
158 | .add_to(acc); | ||
159 | } | ||
160 | |||
161 | fn add_type_alias_impl( | ||
162 | type_def_node: &SyntaxNode, | ||
163 | acc: &mut Completions, | ||
164 | ctx: &CompletionContext, | ||
165 | type_alias: hir::TypeAlias, | ||
166 | ) { | ||
167 | let alias_name = type_alias.name(ctx.db).to_string(); | ||
168 | |||
169 | let snippet = format!("type {} = ", alias_name); | ||
170 | |||
171 | let range = TextRange::new(type_def_node.text_range().start(), ctx.source_range().end()); | ||
172 | |||
173 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
174 | .text_edit(TextEdit::replace(range, snippet)) | ||
175 | .lookup_by(alias_name) | ||
176 | .kind(CompletionItemKind::TypeAlias) | ||
177 | .set_documentation(type_alias.docs(ctx.db)) | ||
178 | .add_to(acc); | ||
179 | } | ||
180 | |||
181 | fn add_const_impl( | ||
182 | const_def_node: &SyntaxNode, | ||
183 | acc: &mut Completions, | ||
184 | ctx: &CompletionContext, | ||
185 | const_: hir::Const, | ||
186 | ) { | ||
187 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | ||
188 | |||
189 | if let Some(const_name) = const_name { | ||
190 | let snippet = make_const_compl_syntax(&const_.source(ctx.db).value); | ||
191 | |||
192 | let range = TextRange::new(const_def_node.text_range().start(), ctx.source_range().end()); | ||
193 | |||
194 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
195 | .text_edit(TextEdit::replace(range, snippet)) | ||
196 | .lookup_by(const_name) | ||
197 | .kind(CompletionItemKind::Const) | ||
198 | .set_documentation(const_.docs(ctx.db)) | ||
199 | .add_to(acc); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | fn make_const_compl_syntax(const_: &ast::Const) -> String { | ||
204 | let const_ = edit::remove_attrs_and_docs(const_); | ||
205 | |||
206 | let const_start = const_.syntax().text_range().start(); | ||
207 | let const_end = const_.syntax().text_range().end(); | ||
208 | |||
209 | let start = | ||
210 | const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start()); | ||
211 | |||
212 | let end = const_ | ||
213 | .syntax() | ||
214 | .children_with_tokens() | ||
215 | .find(|s| s.kind() == T![;] || s.kind() == T![=]) | ||
216 | .map_or(const_end, |f| f.text_range().start()); | ||
217 | |||
218 | let len = end - start; | ||
219 | let range = TextRange::new(0.into(), len); | ||
220 | |||
221 | let syntax = const_.syntax().text().slice(range).to_string(); | ||
222 | |||
223 | format!("{} = ", syntax.trim_end()) | ||
224 | } | ||
225 | |||
226 | #[cfg(test)] | ||
227 | mod tests { | ||
228 | use expect_test::{expect, Expect}; | ||
229 | |||
230 | use crate::completion::{ | ||
231 | test_utils::{check_edit, completion_list}, | ||
232 | CompletionKind, | ||
233 | }; | ||
234 | |||
235 | fn check(ra_fixture: &str, expect: Expect) { | ||
236 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
237 | expect.assert_eq(&actual) | ||
238 | } | ||
239 | |||
240 | #[test] | ||
241 | fn name_ref_function_type_const() { | ||
242 | check( | ||
243 | r#" | ||
244 | trait Test { | ||
245 | type TestType; | ||
246 | const TEST_CONST: u16; | ||
247 | fn test(); | ||
248 | } | ||
249 | struct T; | ||
250 | |||
251 | impl Test for T { | ||
252 | t<|> | ||
253 | } | ||
254 | "#, | ||
255 | expect![[" | ||
256 | ct const TEST_CONST: u16 = \n\ | ||
257 | fn fn test() | ||
258 | ta type TestType = \n\ | ||
259 | "]], | ||
260 | ); | ||
261 | } | ||
262 | |||
263 | #[test] | ||
264 | fn no_nested_fn_completions() { | ||
265 | check( | ||
266 | r" | ||
267 | trait Test { | ||
268 | fn test(); | ||
269 | fn test2(); | ||
270 | } | ||
271 | struct T; | ||
272 | |||
273 | impl Test for T { | ||
274 | fn test() { | ||
275 | t<|> | ||
276 | } | ||
277 | } | ||
278 | ", | ||
279 | expect![[""]], | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn name_ref_single_function() { | ||
285 | check_edit( | ||
286 | "test", | ||
287 | r#" | ||
288 | trait Test { | ||
289 | fn test(); | ||
290 | } | ||
291 | struct T; | ||
292 | |||
293 | impl Test for T { | ||
294 | t<|> | ||
295 | } | ||
296 | "#, | ||
297 | r#" | ||
298 | trait Test { | ||
299 | fn test(); | ||
300 | } | ||
301 | struct T; | ||
302 | |||
303 | impl Test for T { | ||
304 | fn test() { | ||
305 | $0 | ||
306 | } | ||
307 | } | ||
308 | "#, | ||
309 | ); | ||
310 | } | ||
311 | |||
312 | #[test] | ||
313 | fn single_function() { | ||
314 | check_edit( | ||
315 | "test", | ||
316 | r#" | ||
317 | trait Test { | ||
318 | fn test(); | ||
319 | } | ||
320 | struct T; | ||
321 | |||
322 | impl Test for T { | ||
323 | fn t<|> | ||
324 | } | ||
325 | "#, | ||
326 | r#" | ||
327 | trait Test { | ||
328 | fn test(); | ||
329 | } | ||
330 | struct T; | ||
331 | |||
332 | impl Test for T { | ||
333 | fn test() { | ||
334 | $0 | ||
335 | } | ||
336 | } | ||
337 | "#, | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn hide_implemented_fn() { | ||
343 | check( | ||
344 | r#" | ||
345 | trait Test { | ||
346 | fn foo(); | ||
347 | fn foo_bar(); | ||
348 | } | ||
349 | struct T; | ||
350 | |||
351 | impl Test for T { | ||
352 | fn foo() {} | ||
353 | fn f<|> | ||
354 | } | ||
355 | "#, | ||
356 | expect![[r#" | ||
357 | fn fn foo_bar() | ||
358 | "#]], | ||
359 | ); | ||
360 | } | ||
361 | |||
362 | #[test] | ||
363 | fn generic_fn() { | ||
364 | check_edit( | ||
365 | "foo", | ||
366 | r#" | ||
367 | trait Test { | ||
368 | fn foo<T>(); | ||
369 | } | ||
370 | struct T; | ||
371 | |||
372 | impl Test for T { | ||
373 | fn f<|> | ||
374 | } | ||
375 | "#, | ||
376 | r#" | ||
377 | trait Test { | ||
378 | fn foo<T>(); | ||
379 | } | ||
380 | struct T; | ||
381 | |||
382 | impl Test for T { | ||
383 | fn foo<T>() { | ||
384 | $0 | ||
385 | } | ||
386 | } | ||
387 | "#, | ||
388 | ); | ||
389 | check_edit( | ||
390 | "foo", | ||
391 | r#" | ||
392 | trait Test { | ||
393 | fn foo<T>() where T: Into<String>; | ||
394 | } | ||
395 | struct T; | ||
396 | |||
397 | impl Test for T { | ||
398 | fn f<|> | ||
399 | } | ||
400 | "#, | ||
401 | r#" | ||
402 | trait Test { | ||
403 | fn foo<T>() where T: Into<String>; | ||
404 | } | ||
405 | struct T; | ||
406 | |||
407 | impl Test for T { | ||
408 | fn foo<T>() | ||
409 | where T: Into<String> { | ||
410 | $0 | ||
411 | } | ||
412 | } | ||
413 | "#, | ||
414 | ); | ||
415 | } | ||
416 | |||
417 | #[test] | ||
418 | fn associated_type() { | ||
419 | check_edit( | ||
420 | "SomeType", | ||
421 | r#" | ||
422 | trait Test { | ||
423 | type SomeType; | ||
424 | } | ||
425 | |||
426 | impl Test for () { | ||
427 | type S<|> | ||
428 | } | ||
429 | "#, | ||
430 | " | ||
431 | trait Test { | ||
432 | type SomeType; | ||
433 | } | ||
434 | |||
435 | impl Test for () { | ||
436 | type SomeType = \n\ | ||
437 | } | ||
438 | ", | ||
439 | ); | ||
440 | } | ||
441 | |||
442 | #[test] | ||
443 | fn associated_const() { | ||
444 | check_edit( | ||
445 | "SOME_CONST", | ||
446 | r#" | ||
447 | trait Test { | ||
448 | const SOME_CONST: u16; | ||
449 | } | ||
450 | |||
451 | impl Test for () { | ||
452 | const S<|> | ||
453 | } | ||
454 | "#, | ||
455 | " | ||
456 | trait Test { | ||
457 | const SOME_CONST: u16; | ||
458 | } | ||
459 | |||
460 | impl Test for () { | ||
461 | const SOME_CONST: u16 = \n\ | ||
462 | } | ||
463 | ", | ||
464 | ); | ||
465 | |||
466 | check_edit( | ||
467 | "SOME_CONST", | ||
468 | r#" | ||
469 | trait Test { | ||
470 | const SOME_CONST: u16 = 92; | ||
471 | } | ||
472 | |||
473 | impl Test for () { | ||
474 | const S<|> | ||
475 | } | ||
476 | "#, | ||
477 | " | ||
478 | trait Test { | ||
479 | const SOME_CONST: u16 = 92; | ||
480 | } | ||
481 | |||
482 | impl Test for () { | ||
483 | const SOME_CONST: u16 = \n\ | ||
484 | } | ||
485 | ", | ||
486 | ); | ||
487 | } | ||
488 | } | ||
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs new file mode 100644 index 000000000..1f1b682a7 --- /dev/null +++ b/crates/ide/src/completion/complete_unqualified_path.rs | |||
@@ -0,0 +1,658 @@ | |||
1 | //! Completion of names from the current scope, e.g. locals and imported items. | ||
2 | |||
3 | use hir::{Adt, ModuleDef, ScopeDef, Type}; | ||
4 | use syntax::AstNode; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::completion::{CompletionContext, Completions}; | ||
8 | |||
9 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | ||
10 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { | ||
11 | return; | ||
12 | } | ||
13 | if ctx.record_lit_syntax.is_some() | ||
14 | || ctx.record_pat_syntax.is_some() | ||
15 | || ctx.attribute_under_caret.is_some() | ||
16 | { | ||
17 | return; | ||
18 | } | ||
19 | |||
20 | if let Some(ty) = &ctx.expected_type { | ||
21 | complete_enum_variants(acc, ctx, ty); | ||
22 | } | ||
23 | |||
24 | if ctx.is_pat_binding_or_const { | ||
25 | return; | ||
26 | } | ||
27 | |||
28 | ctx.scope.process_all_names(&mut |name, res| { | ||
29 | if ctx.use_item_syntax.is_some() { | ||
30 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { | ||
31 | if name_ref.syntax().text() == name.to_string().as_str() { | ||
32 | mark::hit!(self_fulfilling_completion); | ||
33 | return; | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | acc.add_resolution(ctx, name.to_string(), &res) | ||
38 | }); | ||
39 | } | ||
40 | |||
41 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { | ||
42 | if let Some(Adt::Enum(enum_data)) = ty.as_adt() { | ||
43 | let variants = enum_data.variants(ctx.db); | ||
44 | |||
45 | let module = if let Some(module) = ctx.scope.module() { | ||
46 | // Compute path from the completion site if available. | ||
47 | module | ||
48 | } else { | ||
49 | // Otherwise fall back to the enum's definition site. | ||
50 | enum_data.module(ctx.db) | ||
51 | }; | ||
52 | |||
53 | for variant in variants { | ||
54 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { | ||
55 | // Variants with trivial paths are already added by the existing completion logic, | ||
56 | // so we should avoid adding these twice | ||
57 | if path.segments.len() > 1 { | ||
58 | acc.add_qualified_enum_variant(ctx, variant, path); | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | |||
65 | #[cfg(test)] | ||
66 | mod tests { | ||
67 | use expect_test::{expect, Expect}; | ||
68 | use test_utils::mark; | ||
69 | |||
70 | use crate::completion::{ | ||
71 | test_utils::{check_edit, completion_list}, | ||
72 | CompletionKind, | ||
73 | }; | ||
74 | |||
75 | fn check(ra_fixture: &str, expect: Expect) { | ||
76 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
77 | expect.assert_eq(&actual) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn self_fulfilling_completion() { | ||
82 | mark::check!(self_fulfilling_completion); | ||
83 | check( | ||
84 | r#" | ||
85 | use foo<|> | ||
86 | use std::collections; | ||
87 | "#, | ||
88 | expect![[r#" | ||
89 | ?? collections | ||
90 | "#]], | ||
91 | ); | ||
92 | } | ||
93 | |||
94 | #[test] | ||
95 | fn bind_pat_and_path_ignore_at() { | ||
96 | check( | ||
97 | r#" | ||
98 | enum Enum { A, B } | ||
99 | fn quux(x: Option<Enum>) { | ||
100 | match x { | ||
101 | None => (), | ||
102 | Some(en<|> @ Enum::A) => (), | ||
103 | } | ||
104 | } | ||
105 | "#, | ||
106 | expect![[""]], | ||
107 | ); | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn bind_pat_and_path_ignore_ref() { | ||
112 | check( | ||
113 | r#" | ||
114 | enum Enum { A, B } | ||
115 | fn quux(x: Option<Enum>) { | ||
116 | match x { | ||
117 | None => (), | ||
118 | Some(ref en<|>) => (), | ||
119 | } | ||
120 | } | ||
121 | "#, | ||
122 | expect![[""]], | ||
123 | ); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn bind_pat_and_path() { | ||
128 | check( | ||
129 | r#" | ||
130 | enum Enum { A, B } | ||
131 | fn quux(x: Option<Enum>) { | ||
132 | match x { | ||
133 | None => (), | ||
134 | Some(En<|>) => (), | ||
135 | } | ||
136 | } | ||
137 | "#, | ||
138 | expect![[r#" | ||
139 | en Enum | ||
140 | "#]], | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn completes_bindings_from_let() { | ||
146 | check( | ||
147 | r#" | ||
148 | fn quux(x: i32) { | ||
149 | let y = 92; | ||
150 | 1 + <|>; | ||
151 | let z = (); | ||
152 | } | ||
153 | "#, | ||
154 | expect![[r#" | ||
155 | fn quux(…) fn quux(x: i32) | ||
156 | bn x i32 | ||
157 | bn y i32 | ||
158 | "#]], | ||
159 | ); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn completes_bindings_from_if_let() { | ||
164 | check( | ||
165 | r#" | ||
166 | fn quux() { | ||
167 | if let Some(x) = foo() { | ||
168 | let y = 92; | ||
169 | }; | ||
170 | if let Some(a) = bar() { | ||
171 | let b = 62; | ||
172 | 1 + <|> | ||
173 | } | ||
174 | } | ||
175 | "#, | ||
176 | expect![[r#" | ||
177 | bn a | ||
178 | bn b i32 | ||
179 | fn quux() fn quux() | ||
180 | "#]], | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn completes_bindings_from_for() { | ||
186 | check( | ||
187 | r#" | ||
188 | fn quux() { | ||
189 | for x in &[1, 2, 3] { <|> } | ||
190 | } | ||
191 | "#, | ||
192 | expect![[r#" | ||
193 | fn quux() fn quux() | ||
194 | bn x | ||
195 | "#]], | ||
196 | ); | ||
197 | } | ||
198 | |||
199 | #[test] | ||
200 | fn completes_if_prefix_is_keyword() { | ||
201 | mark::check!(completes_if_prefix_is_keyword); | ||
202 | check_edit( | ||
203 | "wherewolf", | ||
204 | r#" | ||
205 | fn main() { | ||
206 | let wherewolf = 92; | ||
207 | drop(where<|>) | ||
208 | } | ||
209 | "#, | ||
210 | r#" | ||
211 | fn main() { | ||
212 | let wherewolf = 92; | ||
213 | drop(wherewolf) | ||
214 | } | ||
215 | "#, | ||
216 | ) | ||
217 | } | ||
218 | |||
219 | #[test] | ||
220 | fn completes_generic_params() { | ||
221 | check( | ||
222 | r#"fn quux<T>() { <|> }"#, | ||
223 | expect![[r#" | ||
224 | tp T | ||
225 | fn quux() fn quux<T>() | ||
226 | "#]], | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn completes_generic_params_in_struct() { | ||
232 | check( | ||
233 | r#"struct S<T> { x: <|>}"#, | ||
234 | expect![[r#" | ||
235 | st S<…> | ||
236 | tp Self | ||
237 | tp T | ||
238 | "#]], | ||
239 | ); | ||
240 | } | ||
241 | |||
242 | #[test] | ||
243 | fn completes_self_in_enum() { | ||
244 | check( | ||
245 | r#"enum X { Y(<|>) }"#, | ||
246 | expect![[r#" | ||
247 | tp Self | ||
248 | en X | ||
249 | "#]], | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn completes_module_items() { | ||
255 | check( | ||
256 | r#" | ||
257 | struct S; | ||
258 | enum E {} | ||
259 | fn quux() { <|> } | ||
260 | "#, | ||
261 | expect![[r#" | ||
262 | en E | ||
263 | st S | ||
264 | fn quux() fn quux() | ||
265 | "#]], | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn completes_extern_prelude() { | ||
271 | check( | ||
272 | r#" | ||
273 | //- /lib.rs | ||
274 | use <|>; | ||
275 | |||
276 | //- /other_crate/lib.rs | ||
277 | // nothing here | ||
278 | "#, | ||
279 | expect![[r#" | ||
280 | md other_crate | ||
281 | "#]], | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | #[test] | ||
286 | fn completes_module_items_in_nested_modules() { | ||
287 | check( | ||
288 | r#" | ||
289 | struct Foo; | ||
290 | mod m { | ||
291 | struct Bar; | ||
292 | fn quux() { <|> } | ||
293 | } | ||
294 | "#, | ||
295 | expect![[r#" | ||
296 | st Bar | ||
297 | fn quux() fn quux() | ||
298 | "#]], | ||
299 | ); | ||
300 | } | ||
301 | |||
302 | #[test] | ||
303 | fn completes_return_type() { | ||
304 | check( | ||
305 | r#" | ||
306 | struct Foo; | ||
307 | fn x() -> <|> | ||
308 | "#, | ||
309 | expect![[r#" | ||
310 | st Foo | ||
311 | fn x() fn x() | ||
312 | "#]], | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn dont_show_both_completions_for_shadowing() { | ||
318 | check( | ||
319 | r#" | ||
320 | fn foo() { | ||
321 | let bar = 92; | ||
322 | { | ||
323 | let bar = 62; | ||
324 | drop(<|>) | ||
325 | } | ||
326 | } | ||
327 | "#, | ||
328 | // FIXME: should be only one bar here | ||
329 | expect![[r#" | ||
330 | bn bar i32 | ||
331 | bn bar i32 | ||
332 | fn foo() fn foo() | ||
333 | "#]], | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | #[test] | ||
338 | fn completes_self_in_methods() { | ||
339 | check( | ||
340 | r#"impl S { fn foo(&self) { <|> } }"#, | ||
341 | expect![[r#" | ||
342 | tp Self | ||
343 | bn self &{unknown} | ||
344 | "#]], | ||
345 | ); | ||
346 | } | ||
347 | |||
348 | #[test] | ||
349 | fn completes_prelude() { | ||
350 | check( | ||
351 | r#" | ||
352 | //- /main.rs | ||
353 | fn foo() { let x: <|> } | ||
354 | |||
355 | //- /std/lib.rs | ||
356 | #[prelude_import] | ||
357 | use prelude::*; | ||
358 | |||
359 | mod prelude { struct Option; } | ||
360 | "#, | ||
361 | expect![[r#" | ||
362 | st Option | ||
363 | fn foo() fn foo() | ||
364 | md std | ||
365 | "#]], | ||
366 | ); | ||
367 | } | ||
368 | |||
369 | #[test] | ||
370 | fn completes_std_prelude_if_core_is_defined() { | ||
371 | check( | ||
372 | r#" | ||
373 | //- /main.rs | ||
374 | fn foo() { let x: <|> } | ||
375 | |||
376 | //- /core/lib.rs | ||
377 | #[prelude_import] | ||
378 | use prelude::*; | ||
379 | |||
380 | mod prelude { struct Option; } | ||
381 | |||
382 | //- /std/lib.rs | ||
383 | #[prelude_import] | ||
384 | use prelude::*; | ||
385 | |||
386 | mod prelude { struct String; } | ||
387 | "#, | ||
388 | expect![[r#" | ||
389 | st String | ||
390 | md core | ||
391 | fn foo() fn foo() | ||
392 | md std | ||
393 | "#]], | ||
394 | ); | ||
395 | } | ||
396 | |||
397 | #[test] | ||
398 | fn completes_macros_as_value() { | ||
399 | check( | ||
400 | r#" | ||
401 | macro_rules! foo { () => {} } | ||
402 | |||
403 | #[macro_use] | ||
404 | mod m1 { | ||
405 | macro_rules! bar { () => {} } | ||
406 | } | ||
407 | |||
408 | mod m2 { | ||
409 | macro_rules! nope { () => {} } | ||
410 | |||
411 | #[macro_export] | ||
412 | macro_rules! baz { () => {} } | ||
413 | } | ||
414 | |||
415 | fn main() { let v = <|> } | ||
416 | "#, | ||
417 | expect![[r##" | ||
418 | ma bar!(…) macro_rules! bar | ||
419 | ma baz!(…) #[macro_export] | ||
420 | macro_rules! baz | ||
421 | ma foo!(…) macro_rules! foo | ||
422 | md m1 | ||
423 | md m2 | ||
424 | fn main() fn main() | ||
425 | "##]], | ||
426 | ); | ||
427 | } | ||
428 | |||
429 | #[test] | ||
430 | fn completes_both_macro_and_value() { | ||
431 | check( | ||
432 | r#" | ||
433 | macro_rules! foo { () => {} } | ||
434 | fn foo() { <|> } | ||
435 | "#, | ||
436 | expect![[r#" | ||
437 | ma foo!(…) macro_rules! foo | ||
438 | fn foo() fn foo() | ||
439 | "#]], | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn completes_macros_as_type() { | ||
445 | check( | ||
446 | r#" | ||
447 | macro_rules! foo { () => {} } | ||
448 | fn main() { let x: <|> } | ||
449 | "#, | ||
450 | expect![[r#" | ||
451 | ma foo!(…) macro_rules! foo | ||
452 | fn main() fn main() | ||
453 | "#]], | ||
454 | ); | ||
455 | } | ||
456 | |||
457 | #[test] | ||
458 | fn completes_macros_as_stmt() { | ||
459 | check( | ||
460 | r#" | ||
461 | macro_rules! foo { () => {} } | ||
462 | fn main() { <|> } | ||
463 | "#, | ||
464 | expect![[r#" | ||
465 | ma foo!(…) macro_rules! foo | ||
466 | fn main() fn main() | ||
467 | "#]], | ||
468 | ); | ||
469 | } | ||
470 | |||
471 | #[test] | ||
472 | fn completes_local_item() { | ||
473 | check( | ||
474 | r#" | ||
475 | fn main() { | ||
476 | return f<|>; | ||
477 | fn frobnicate() {} | ||
478 | } | ||
479 | "#, | ||
480 | expect![[r#" | ||
481 | fn frobnicate() fn frobnicate() | ||
482 | fn main() fn main() | ||
483 | "#]], | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn completes_in_simple_macro_1() { | ||
489 | check( | ||
490 | r#" | ||
491 | macro_rules! m { ($e:expr) => { $e } } | ||
492 | fn quux(x: i32) { | ||
493 | let y = 92; | ||
494 | m!(<|>); | ||
495 | } | ||
496 | "#, | ||
497 | expect![[r#" | ||
498 | ma m!(…) macro_rules! m | ||
499 | fn quux(…) fn quux(x: i32) | ||
500 | bn x i32 | ||
501 | bn y i32 | ||
502 | "#]], | ||
503 | ); | ||
504 | } | ||
505 | |||
506 | #[test] | ||
507 | fn completes_in_simple_macro_2() { | ||
508 | check( | ||
509 | r" | ||
510 | macro_rules! m { ($e:expr) => { $e } } | ||
511 | fn quux(x: i32) { | ||
512 | let y = 92; | ||
513 | m!(x<|>); | ||
514 | } | ||
515 | ", | ||
516 | expect![[r#" | ||
517 | ma m!(…) macro_rules! m | ||
518 | fn quux(…) fn quux(x: i32) | ||
519 | bn x i32 | ||
520 | bn y i32 | ||
521 | "#]], | ||
522 | ); | ||
523 | } | ||
524 | |||
525 | #[test] | ||
526 | fn completes_in_simple_macro_without_closing_parens() { | ||
527 | check( | ||
528 | r#" | ||
529 | macro_rules! m { ($e:expr) => { $e } } | ||
530 | fn quux(x: i32) { | ||
531 | let y = 92; | ||
532 | m!(x<|> | ||
533 | } | ||
534 | "#, | ||
535 | expect![[r#" | ||
536 | ma m!(…) macro_rules! m | ||
537 | fn quux(…) fn quux(x: i32) | ||
538 | bn x i32 | ||
539 | bn y i32 | ||
540 | "#]], | ||
541 | ); | ||
542 | } | ||
543 | |||
544 | #[test] | ||
545 | fn completes_unresolved_uses() { | ||
546 | check( | ||
547 | r#" | ||
548 | use spam::Quux; | ||
549 | |||
550 | fn main() { <|> } | ||
551 | "#, | ||
552 | expect![[r#" | ||
553 | ?? Quux | ||
554 | fn main() fn main() | ||
555 | "#]], | ||
556 | ); | ||
557 | } | ||
558 | #[test] | ||
559 | fn completes_enum_variant_matcharm() { | ||
560 | check( | ||
561 | r#" | ||
562 | enum Foo { Bar, Baz, Quux } | ||
563 | |||
564 | fn main() { | ||
565 | let foo = Foo::Quux; | ||
566 | match foo { Qu<|> } | ||
567 | } | ||
568 | "#, | ||
569 | expect![[r#" | ||
570 | en Foo | ||
571 | ev Foo::Bar () | ||
572 | ev Foo::Baz () | ||
573 | ev Foo::Quux () | ||
574 | "#]], | ||
575 | ) | ||
576 | } | ||
577 | |||
578 | #[test] | ||
579 | fn completes_enum_variant_iflet() { | ||
580 | check( | ||
581 | r#" | ||
582 | enum Foo { Bar, Baz, Quux } | ||
583 | |||
584 | fn main() { | ||
585 | let foo = Foo::Quux; | ||
586 | if let Qu<|> = foo { } | ||
587 | } | ||
588 | "#, | ||
589 | expect![[r#" | ||
590 | en Foo | ||
591 | ev Foo::Bar () | ||
592 | ev Foo::Baz () | ||
593 | ev Foo::Quux () | ||
594 | "#]], | ||
595 | ) | ||
596 | } | ||
597 | |||
598 | #[test] | ||
599 | fn completes_enum_variant_basic_expr() { | ||
600 | check( | ||
601 | r#" | ||
602 | enum Foo { Bar, Baz, Quux } | ||
603 | fn main() { let foo: Foo = Q<|> } | ||
604 | "#, | ||
605 | expect![[r#" | ||
606 | en Foo | ||
607 | ev Foo::Bar () | ||
608 | ev Foo::Baz () | ||
609 | ev Foo::Quux () | ||
610 | fn main() fn main() | ||
611 | "#]], | ||
612 | ) | ||
613 | } | ||
614 | |||
615 | #[test] | ||
616 | fn completes_enum_variant_from_module() { | ||
617 | check( | ||
618 | r#" | ||
619 | mod m { pub enum E { V } } | ||
620 | fn f() -> m::E { V<|> } | ||
621 | "#, | ||
622 | expect![[r#" | ||
623 | fn f() fn f() -> m::E | ||
624 | md m | ||
625 | ev m::E::V () | ||
626 | "#]], | ||
627 | ) | ||
628 | } | ||
629 | |||
630 | #[test] | ||
631 | fn dont_complete_attr() { | ||
632 | check( | ||
633 | r#" | ||
634 | struct Foo; | ||
635 | #[<|>] | ||
636 | fn f() {} | ||
637 | "#, | ||
638 | expect![[""]], | ||
639 | ) | ||
640 | } | ||
641 | |||
642 | #[test] | ||
643 | fn completes_type_or_trait_in_impl_block() { | ||
644 | check( | ||
645 | r#" | ||
646 | trait MyTrait {} | ||
647 | struct MyStruct {} | ||
648 | |||
649 | impl My<|> | ||
650 | "#, | ||
651 | expect![[r#" | ||
652 | st MyStruct | ||
653 | tt MyTrait | ||
654 | tp Self | ||
655 | "#]], | ||
656 | ) | ||
657 | } | ||
658 | } | ||
diff --git a/crates/ide/src/completion/completion_config.rs b/crates/ide/src/completion/completion_config.rs new file mode 100644 index 000000000..71b49ace8 --- /dev/null +++ b/crates/ide/src/completion/completion_config.rs | |||
@@ -0,0 +1,35 @@ | |||
1 | //! Settings for tweaking completion. | ||
2 | //! | ||
3 | //! The fun thing here is `SnippetCap` -- this type can only be created in this | ||
4 | //! module, and we use to statically check that we only produce snippet | ||
5 | //! completions if we are allowed to. | ||
6 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
8 | pub struct CompletionConfig { | ||
9 | pub enable_postfix_completions: bool, | ||
10 | pub add_call_parenthesis: bool, | ||
11 | pub add_call_argument_snippets: bool, | ||
12 | pub snippet_cap: Option<SnippetCap>, | ||
13 | } | ||
14 | |||
15 | impl CompletionConfig { | ||
16 | pub fn allow_snippets(&mut self, yes: bool) { | ||
17 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | ||
18 | } | ||
19 | } | ||
20 | |||
21 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
22 | pub struct SnippetCap { | ||
23 | _private: (), | ||
24 | } | ||
25 | |||
26 | impl Default for CompletionConfig { | ||
27 | fn default() -> Self { | ||
28 | CompletionConfig { | ||
29 | enable_postfix_completions: true, | ||
30 | add_call_parenthesis: true, | ||
31 | add_call_argument_snippets: true, | ||
32 | snippet_cap: Some(SnippetCap { _private: () }), | ||
33 | } | ||
34 | } | ||
35 | } | ||
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs new file mode 100644 index 000000000..5adac7ebc --- /dev/null +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -0,0 +1,486 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use base_db::SourceDatabase; | ||
4 | use hir::{Semantics, SemanticsScope, Type}; | ||
5 | use ide_db::RootDatabase; | ||
6 | use syntax::{ | ||
7 | algo::{find_covering_element, find_node_at_offset}, | ||
8 | ast, match_ast, AstNode, NodeOrToken, | ||
9 | SyntaxKind::*, | ||
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
11 | }; | ||
12 | use test_utils::mark; | ||
13 | use text_edit::Indel; | ||
14 | |||
15 | use crate::{ | ||
16 | call_info::ActiveParameter, | ||
17 | completion::{ | ||
18 | patterns::{ | ||
19 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
20 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
21 | has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, | ||
22 | }, | ||
23 | CompletionConfig, | ||
24 | }, | ||
25 | FilePosition, | ||
26 | }; | ||
27 | |||
28 | /// `CompletionContext` is created early during completion to figure out, where | ||
29 | /// exactly is the cursor, syntax-wise. | ||
30 | #[derive(Debug)] | ||
31 | pub(crate) struct CompletionContext<'a> { | ||
32 | pub(super) sema: Semantics<'a, RootDatabase>, | ||
33 | pub(super) scope: SemanticsScope<'a>, | ||
34 | pub(super) db: &'a RootDatabase, | ||
35 | pub(super) config: &'a CompletionConfig, | ||
36 | pub(super) position: FilePosition, | ||
37 | /// The token before the cursor, in the original file. | ||
38 | pub(super) original_token: SyntaxToken, | ||
39 | /// The token before the cursor, in the macro-expanded file. | ||
40 | pub(super) token: SyntaxToken, | ||
41 | pub(super) krate: Option<hir::Crate>, | ||
42 | pub(super) expected_type: Option<Type>, | ||
43 | pub(super) name_ref_syntax: Option<ast::NameRef>, | ||
44 | pub(super) function_syntax: Option<ast::Fn>, | ||
45 | pub(super) use_item_syntax: Option<ast::Use>, | ||
46 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | ||
47 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | ||
48 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | ||
49 | pub(super) impl_def: Option<ast::Impl>, | ||
50 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | ||
51 | pub(super) active_parameter: Option<ActiveParameter>, | ||
52 | pub(super) is_param: bool, | ||
53 | /// If a name-binding or reference to a const in a pattern. | ||
54 | /// Irrefutable patterns (like let) are excluded. | ||
55 | pub(super) is_pat_binding_or_const: bool, | ||
56 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | ||
57 | pub(super) is_trivial_path: bool, | ||
58 | /// If not a trivial path, the prefix (qualifier). | ||
59 | pub(super) path_qual: Option<ast::Path>, | ||
60 | pub(super) after_if: bool, | ||
61 | /// `true` if we are a statement or a last expr in the block. | ||
62 | pub(super) can_be_stmt: bool, | ||
63 | /// `true` if we expect an expression at the cursor position. | ||
64 | pub(super) is_expr: bool, | ||
65 | /// Something is typed at the "top" level, in module or impl/trait. | ||
66 | pub(super) is_new_item: bool, | ||
67 | /// The receiver if this is a field or method access, i.e. writing something.<|> | ||
68 | pub(super) dot_receiver: Option<ast::Expr>, | ||
69 | pub(super) dot_receiver_is_ambiguous_float_literal: bool, | ||
70 | /// If this is a call (method or function) in particular, i.e. the () are already there. | ||
71 | pub(super) is_call: bool, | ||
72 | /// Like `is_call`, but for tuple patterns. | ||
73 | pub(super) is_pattern_call: bool, | ||
74 | /// If this is a macro call, i.e. the () are already there. | ||
75 | pub(super) is_macro_call: bool, | ||
76 | pub(super) is_path_type: bool, | ||
77 | pub(super) has_type_args: bool, | ||
78 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
79 | pub(super) unsafe_is_prev: bool, | ||
80 | pub(super) if_is_prev: bool, | ||
81 | pub(super) block_expr_parent: bool, | ||
82 | pub(super) bind_pat_parent: bool, | ||
83 | pub(super) ref_pat_parent: bool, | ||
84 | pub(super) in_loop_body: bool, | ||
85 | pub(super) has_trait_parent: bool, | ||
86 | pub(super) has_impl_parent: bool, | ||
87 | pub(super) trait_as_prev_sibling: bool, | ||
88 | pub(super) impl_as_prev_sibling: bool, | ||
89 | pub(super) is_match_arm: bool, | ||
90 | pub(super) has_item_list_or_source_file_parent: bool, | ||
91 | } | ||
92 | |||
93 | impl<'a> CompletionContext<'a> { | ||
94 | pub(super) fn new( | ||
95 | db: &'a RootDatabase, | ||
96 | position: FilePosition, | ||
97 | config: &'a CompletionConfig, | ||
98 | ) -> Option<CompletionContext<'a>> { | ||
99 | let sema = Semantics::new(db); | ||
100 | |||
101 | let original_file = sema.parse(position.file_id); | ||
102 | |||
103 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
104 | // to determine context, though the original_file will be used for | ||
105 | // actual completion. | ||
106 | let file_with_fake_ident = { | ||
107 | let parse = db.parse(position.file_id); | ||
108 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); | ||
109 | parse.reparse(&edit).tree() | ||
110 | }; | ||
111 | let fake_ident_token = | ||
112 | file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); | ||
113 | |||
114 | let krate = sema.to_module_def(position.file_id).map(|m| m.krate()); | ||
115 | let original_token = | ||
116 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
117 | let token = sema.descend_into_macros(original_token.clone()); | ||
118 | let scope = sema.scope_at_offset(&token.parent(), position.offset); | ||
119 | let mut ctx = CompletionContext { | ||
120 | sema, | ||
121 | scope, | ||
122 | db, | ||
123 | config, | ||
124 | original_token, | ||
125 | token, | ||
126 | position, | ||
127 | krate, | ||
128 | expected_type: None, | ||
129 | name_ref_syntax: None, | ||
130 | function_syntax: None, | ||
131 | use_item_syntax: None, | ||
132 | record_lit_syntax: None, | ||
133 | record_pat_syntax: None, | ||
134 | record_field_syntax: None, | ||
135 | impl_def: None, | ||
136 | active_parameter: ActiveParameter::at(db, position), | ||
137 | is_param: false, | ||
138 | is_pat_binding_or_const: false, | ||
139 | is_trivial_path: false, | ||
140 | path_qual: None, | ||
141 | after_if: false, | ||
142 | can_be_stmt: false, | ||
143 | is_expr: false, | ||
144 | is_new_item: false, | ||
145 | dot_receiver: None, | ||
146 | is_call: false, | ||
147 | is_pattern_call: false, | ||
148 | is_macro_call: false, | ||
149 | is_path_type: false, | ||
150 | has_type_args: false, | ||
151 | dot_receiver_is_ambiguous_float_literal: false, | ||
152 | attribute_under_caret: None, | ||
153 | unsafe_is_prev: false, | ||
154 | in_loop_body: false, | ||
155 | ref_pat_parent: false, | ||
156 | bind_pat_parent: false, | ||
157 | block_expr_parent: false, | ||
158 | has_trait_parent: false, | ||
159 | has_impl_parent: false, | ||
160 | trait_as_prev_sibling: false, | ||
161 | impl_as_prev_sibling: false, | ||
162 | if_is_prev: false, | ||
163 | is_match_arm: false, | ||
164 | has_item_list_or_source_file_parent: false, | ||
165 | }; | ||
166 | |||
167 | let mut original_file = original_file.syntax().clone(); | ||
168 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | ||
169 | let mut offset = position.offset; | ||
170 | let mut fake_ident_token = fake_ident_token; | ||
171 | |||
172 | // Are we inside a macro call? | ||
173 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | ||
174 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | ||
175 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | ||
176 | ) { | ||
177 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) | ||
178 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) | ||
179 | { | ||
180 | break; | ||
181 | } | ||
182 | let hypothetical_args = match macro_call_with_fake_ident.token_tree() { | ||
183 | Some(tt) => tt, | ||
184 | None => break, | ||
185 | }; | ||
186 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | ||
187 | ctx.sema.expand(&actual_macro_call), | ||
188 | ctx.sema.speculative_expand( | ||
189 | &actual_macro_call, | ||
190 | &hypothetical_args, | ||
191 | fake_ident_token, | ||
192 | ), | ||
193 | ) { | ||
194 | let new_offset = hypothetical_expansion.1.text_range().start(); | ||
195 | if new_offset > actual_expansion.text_range().end() { | ||
196 | break; | ||
197 | } | ||
198 | original_file = actual_expansion; | ||
199 | hypothetical_file = hypothetical_expansion.0; | ||
200 | fake_ident_token = hypothetical_expansion.1; | ||
201 | offset = new_offset; | ||
202 | } else { | ||
203 | break; | ||
204 | } | ||
205 | } | ||
206 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | ||
207 | ctx.fill(&original_file, hypothetical_file, offset); | ||
208 | Some(ctx) | ||
209 | } | ||
210 | |||
211 | // The range of the identifier that is being completed. | ||
212 | pub(crate) fn source_range(&self) -> TextRange { | ||
213 | // check kind of macro-expanded token, but use range of original token | ||
214 | if self.token.kind() == IDENT || self.token.kind().is_keyword() { | ||
215 | mark::hit!(completes_if_prefix_is_keyword); | ||
216 | self.original_token.text_range() | ||
217 | } else { | ||
218 | TextRange::empty(self.position.offset) | ||
219 | } | ||
220 | } | ||
221 | |||
222 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
223 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
224 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
225 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
226 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
227 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
228 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
229 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
230 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
231 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
232 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
233 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
234 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
235 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
236 | self.has_item_list_or_source_file_parent = | ||
237 | has_item_list_or_source_file_parent(syntax_element); | ||
238 | } | ||
239 | |||
240 | fn fill( | ||
241 | &mut self, | ||
242 | original_file: &SyntaxNode, | ||
243 | file_with_fake_ident: SyntaxNode, | ||
244 | offset: TextSize, | ||
245 | ) { | ||
246 | // FIXME: this is wrong in at least two cases: | ||
247 | // * when there's no token `foo(<|>)` | ||
248 | // * when there is a token, but it happens to have type of it's own | ||
249 | self.expected_type = self | ||
250 | .token | ||
251 | .ancestors() | ||
252 | .find_map(|node| { | ||
253 | let ty = match_ast! { | ||
254 | match node { | ||
255 | ast::Pat(it) => self.sema.type_of_pat(&it), | ||
256 | ast::Expr(it) => self.sema.type_of_expr(&it), | ||
257 | _ => return None, | ||
258 | } | ||
259 | }; | ||
260 | Some(ty) | ||
261 | }) | ||
262 | .flatten(); | ||
263 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
264 | |||
265 | // First, let's try to complete a reference to some declaration. | ||
266 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | ||
267 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
268 | // See RFC#1685. | ||
269 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
270 | self.is_param = true; | ||
271 | return; | ||
272 | } | ||
273 | // FIXME: remove this (V) duplication and make the check more precise | ||
274 | if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
275 | self.record_pat_syntax = | ||
276 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
277 | } | ||
278 | self.classify_name_ref(original_file, name_ref, offset); | ||
279 | } | ||
280 | |||
281 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
282 | // suggest declaration names, see `CompletionKind::Magic`. | ||
283 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { | ||
284 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { | ||
285 | self.is_pat_binding_or_const = true; | ||
286 | if bind_pat.at_token().is_some() | ||
287 | || bind_pat.ref_token().is_some() | ||
288 | || bind_pat.mut_token().is_some() | ||
289 | { | ||
290 | self.is_pat_binding_or_const = false; | ||
291 | } | ||
292 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | ||
293 | self.is_pat_binding_or_const = false; | ||
294 | } | ||
295 | if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { | ||
296 | if let Some(pat) = let_stmt.pat() { | ||
297 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) | ||
298 | { | ||
299 | self.is_pat_binding_or_const = false; | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | if is_node::<ast::Param>(name.syntax()) { | ||
305 | self.is_param = true; | ||
306 | return; | ||
307 | } | ||
308 | // FIXME: remove this (^) duplication and make the check more precise | ||
309 | if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
310 | self.record_pat_syntax = | ||
311 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | |||
316 | fn classify_name_ref( | ||
317 | &mut self, | ||
318 | original_file: &SyntaxNode, | ||
319 | name_ref: ast::NameRef, | ||
320 | offset: TextSize, | ||
321 | ) { | ||
322 | self.name_ref_syntax = | ||
323 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | ||
324 | let name_range = name_ref.syntax().text_range(); | ||
325 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
326 | self.record_lit_syntax = | ||
327 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
328 | } | ||
329 | |||
330 | self.impl_def = self | ||
331 | .sema | ||
332 | .ancestors_with_macros(self.token.parent()) | ||
333 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
334 | .find_map(ast::Impl::cast); | ||
335 | |||
336 | let top_node = name_ref | ||
337 | .syntax() | ||
338 | .ancestors() | ||
339 | .take_while(|it| it.text_range() == name_range) | ||
340 | .last() | ||
341 | .unwrap(); | ||
342 | |||
343 | match top_node.parent().map(|it| it.kind()) { | ||
344 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | ||
345 | self.is_new_item = true; | ||
346 | return; | ||
347 | } | ||
348 | _ => (), | ||
349 | } | ||
350 | |||
351 | self.use_item_syntax = | ||
352 | self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast); | ||
353 | |||
354 | self.function_syntax = self | ||
355 | .sema | ||
356 | .ancestors_with_macros(self.token.parent()) | ||
357 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
358 | .find_map(ast::Fn::cast); | ||
359 | |||
360 | self.record_field_syntax = self | ||
361 | .sema | ||
362 | .ancestors_with_macros(self.token.parent()) | ||
363 | .take_while(|it| { | ||
364 | it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR | ||
365 | }) | ||
366 | .find_map(ast::RecordExprField::cast); | ||
367 | |||
368 | let parent = match name_ref.syntax().parent() { | ||
369 | Some(it) => it, | ||
370 | None => return, | ||
371 | }; | ||
372 | |||
373 | if let Some(segment) = ast::PathSegment::cast(parent.clone()) { | ||
374 | let path = segment.parent_path(); | ||
375 | self.is_call = path | ||
376 | .syntax() | ||
377 | .parent() | ||
378 | .and_then(ast::PathExpr::cast) | ||
379 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | ||
380 | .is_some(); | ||
381 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | ||
382 | self.is_pattern_call = | ||
383 | path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some(); | ||
384 | |||
385 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | ||
386 | self.has_type_args = segment.generic_arg_list().is_some(); | ||
387 | |||
388 | if let Some(path) = path_or_use_tree_qualifier(&path) { | ||
389 | self.path_qual = path | ||
390 | .segment() | ||
391 | .and_then(|it| { | ||
392 | find_node_with_range::<ast::PathSegment>( | ||
393 | original_file, | ||
394 | it.syntax().text_range(), | ||
395 | ) | ||
396 | }) | ||
397 | .map(|it| it.parent_path()); | ||
398 | return; | ||
399 | } | ||
400 | |||
401 | if let Some(segment) = path.segment() { | ||
402 | if segment.coloncolon_token().is_some() { | ||
403 | return; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | self.is_trivial_path = true; | ||
408 | |||
409 | // Find either enclosing expr statement (thing with `;`) or a | ||
410 | // block. If block, check that we are the last expr. | ||
411 | self.can_be_stmt = name_ref | ||
412 | .syntax() | ||
413 | .ancestors() | ||
414 | .find_map(|node| { | ||
415 | if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { | ||
416 | return Some(stmt.syntax().text_range() == name_ref.syntax().text_range()); | ||
417 | } | ||
418 | if let Some(block) = ast::BlockExpr::cast(node) { | ||
419 | return Some( | ||
420 | block.expr().map(|e| e.syntax().text_range()) | ||
421 | == Some(name_ref.syntax().text_range()), | ||
422 | ); | ||
423 | } | ||
424 | None | ||
425 | }) | ||
426 | .unwrap_or(false); | ||
427 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | ||
428 | |||
429 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
430 | if let Some(if_expr) = | ||
431 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
432 | { | ||
433 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
434 | { | ||
435 | self.after_if = true; | ||
436 | } | ||
437 | } | ||
438 | } | ||
439 | } | ||
440 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | ||
441 | // The receiver comes before the point of insertion of the fake | ||
442 | // ident, so it should have the same range in the non-modified file | ||
443 | self.dot_receiver = field_expr | ||
444 | .expr() | ||
445 | .map(|e| e.syntax().text_range()) | ||
446 | .and_then(|r| find_node_with_range(original_file, r)); | ||
447 | self.dot_receiver_is_ambiguous_float_literal = | ||
448 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { | ||
449 | match l.kind() { | ||
450 | ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), | ||
451 | _ => false, | ||
452 | } | ||
453 | } else { | ||
454 | false | ||
455 | } | ||
456 | } | ||
457 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | ||
458 | // As above | ||
459 | self.dot_receiver = method_call_expr | ||
460 | .receiver() | ||
461 | .map(|e| e.syntax().text_range()) | ||
462 | .and_then(|r| find_node_with_range(original_file, r)); | ||
463 | self.is_call = true; | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | |||
468 | fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { | ||
469 | find_covering_element(syntax, range).ancestors().find_map(N::cast) | ||
470 | } | ||
471 | |||
472 | fn is_node<N: AstNode>(node: &SyntaxNode) -> bool { | ||
473 | match node.ancestors().find_map(N::cast) { | ||
474 | None => false, | ||
475 | Some(n) => n.syntax().text_range() == node.text_range(), | ||
476 | } | ||
477 | } | ||
478 | |||
479 | fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> { | ||
480 | if let Some(qual) = path.qualifier() { | ||
481 | return Some(qual); | ||
482 | } | ||
483 | let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; | ||
484 | let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; | ||
485 | use_tree.path() | ||
486 | } | ||
diff --git a/crates/ide/src/completion/completion_item.rs b/crates/ide/src/completion/completion_item.rs new file mode 100644 index 000000000..9377cdc57 --- /dev/null +++ b/crates/ide/src/completion/completion_item.rs | |||
@@ -0,0 +1,384 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::fmt; | ||
4 | |||
5 | use hir::Documentation; | ||
6 | use syntax::TextRange; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::completion::completion_config::SnippetCap; | ||
10 | |||
11 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | ||
12 | /// It is basically a POD with various properties. To construct a | ||
13 | /// `CompletionItem`, use `new` method and the `Builder` struct. | ||
14 | pub struct CompletionItem { | ||
15 | /// Used only internally in tests, to check only specific kind of | ||
16 | /// completion (postfix, keyword, reference, etc). | ||
17 | #[allow(unused)] | ||
18 | pub(crate) completion_kind: CompletionKind, | ||
19 | /// Label in the completion pop up which identifies completion. | ||
20 | label: String, | ||
21 | /// Range of identifier that is being completed. | ||
22 | /// | ||
23 | /// It should be used primarily for UI, but we also use this to convert | ||
24 | /// genetic TextEdit into LSP's completion edit (see conv.rs). | ||
25 | /// | ||
26 | /// `source_range` must contain the completion offset. `insert_text` should | ||
27 | /// start with what `source_range` points to, or VSCode will filter out the | ||
28 | /// completion silently. | ||
29 | source_range: TextRange, | ||
30 | /// What happens when user selects this item. | ||
31 | /// | ||
32 | /// Typically, replaces `source_range` with new identifier. | ||
33 | text_edit: TextEdit, | ||
34 | insert_text_format: InsertTextFormat, | ||
35 | |||
36 | /// What item (struct, function, etc) are we completing. | ||
37 | kind: Option<CompletionItemKind>, | ||
38 | |||
39 | /// Lookup is used to check if completion item indeed can complete current | ||
40 | /// ident. | ||
41 | /// | ||
42 | /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it | ||
43 | /// contains `bar` sub sequence), and `quux` will rejected. | ||
44 | lookup: Option<String>, | ||
45 | |||
46 | /// Additional info to show in the UI pop up. | ||
47 | detail: Option<String>, | ||
48 | documentation: Option<Documentation>, | ||
49 | |||
50 | /// Whether this item is marked as deprecated | ||
51 | deprecated: bool, | ||
52 | |||
53 | /// If completing a function call, ask the editor to show parameter popup | ||
54 | /// after completion. | ||
55 | trigger_call_info: bool, | ||
56 | |||
57 | /// Score is useful to pre select or display in better order completion items | ||
58 | score: Option<CompletionScore>, | ||
59 | } | ||
60 | |||
61 | // We use custom debug for CompletionItem to make snapshot tests more readable. | ||
62 | impl fmt::Debug for CompletionItem { | ||
63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
64 | let mut s = f.debug_struct("CompletionItem"); | ||
65 | s.field("label", &self.label()).field("source_range", &self.source_range()); | ||
66 | if self.text_edit().len() == 1 { | ||
67 | let atom = &self.text_edit().iter().next().unwrap(); | ||
68 | s.field("delete", &atom.delete); | ||
69 | s.field("insert", &atom.insert); | ||
70 | } else { | ||
71 | s.field("text_edit", &self.text_edit); | ||
72 | } | ||
73 | if let Some(kind) = self.kind().as_ref() { | ||
74 | s.field("kind", kind); | ||
75 | } | ||
76 | if self.lookup() != self.label() { | ||
77 | s.field("lookup", &self.lookup()); | ||
78 | } | ||
79 | if let Some(detail) = self.detail() { | ||
80 | s.field("detail", &detail); | ||
81 | } | ||
82 | if let Some(documentation) = self.documentation() { | ||
83 | s.field("documentation", &documentation); | ||
84 | } | ||
85 | if self.deprecated { | ||
86 | s.field("deprecated", &true); | ||
87 | } | ||
88 | if let Some(score) = &self.score { | ||
89 | s.field("score", score); | ||
90 | } | ||
91 | if self.trigger_call_info { | ||
92 | s.field("trigger_call_info", &true); | ||
93 | } | ||
94 | s.finish() | ||
95 | } | ||
96 | } | ||
97 | |||
98 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] | ||
99 | pub enum CompletionScore { | ||
100 | /// If only type match | ||
101 | TypeMatch, | ||
102 | /// If type and name match | ||
103 | TypeAndNameMatch, | ||
104 | } | ||
105 | |||
106 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
107 | pub enum CompletionItemKind { | ||
108 | Snippet, | ||
109 | Keyword, | ||
110 | Module, | ||
111 | Function, | ||
112 | BuiltinType, | ||
113 | Struct, | ||
114 | Enum, | ||
115 | EnumVariant, | ||
116 | Binding, | ||
117 | Field, | ||
118 | Static, | ||
119 | Const, | ||
120 | Trait, | ||
121 | TypeAlias, | ||
122 | Method, | ||
123 | TypeParam, | ||
124 | Macro, | ||
125 | Attribute, | ||
126 | UnresolvedReference, | ||
127 | } | ||
128 | |||
129 | impl CompletionItemKind { | ||
130 | #[cfg(test)] | ||
131 | pub(crate) fn tag(&self) -> &'static str { | ||
132 | match self { | ||
133 | CompletionItemKind::Attribute => "at", | ||
134 | CompletionItemKind::Binding => "bn", | ||
135 | CompletionItemKind::BuiltinType => "bt", | ||
136 | CompletionItemKind::Const => "ct", | ||
137 | CompletionItemKind::Enum => "en", | ||
138 | CompletionItemKind::EnumVariant => "ev", | ||
139 | CompletionItemKind::Field => "fd", | ||
140 | CompletionItemKind::Function => "fn", | ||
141 | CompletionItemKind::Keyword => "kw", | ||
142 | CompletionItemKind::Macro => "ma", | ||
143 | CompletionItemKind::Method => "me", | ||
144 | CompletionItemKind::Module => "md", | ||
145 | CompletionItemKind::Snippet => "sn", | ||
146 | CompletionItemKind::Static => "sc", | ||
147 | CompletionItemKind::Struct => "st", | ||
148 | CompletionItemKind::Trait => "tt", | ||
149 | CompletionItemKind::TypeAlias => "ta", | ||
150 | CompletionItemKind::TypeParam => "tp", | ||
151 | CompletionItemKind::UnresolvedReference => "??", | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
157 | pub(crate) enum CompletionKind { | ||
158 | /// Parser-based keyword completion. | ||
159 | Keyword, | ||
160 | /// Your usual "complete all valid identifiers". | ||
161 | Reference, | ||
162 | /// "Secret sauce" completions. | ||
163 | Magic, | ||
164 | Snippet, | ||
165 | Postfix, | ||
166 | BuiltinType, | ||
167 | Attribute, | ||
168 | } | ||
169 | |||
170 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
171 | pub enum InsertTextFormat { | ||
172 | PlainText, | ||
173 | Snippet, | ||
174 | } | ||
175 | |||
176 | impl CompletionItem { | ||
177 | pub(crate) fn new( | ||
178 | completion_kind: CompletionKind, | ||
179 | source_range: TextRange, | ||
180 | label: impl Into<String>, | ||
181 | ) -> Builder { | ||
182 | let label = label.into(); | ||
183 | Builder { | ||
184 | source_range, | ||
185 | completion_kind, | ||
186 | label, | ||
187 | insert_text: None, | ||
188 | insert_text_format: InsertTextFormat::PlainText, | ||
189 | detail: None, | ||
190 | documentation: None, | ||
191 | lookup: None, | ||
192 | kind: None, | ||
193 | text_edit: None, | ||
194 | deprecated: None, | ||
195 | trigger_call_info: None, | ||
196 | score: None, | ||
197 | } | ||
198 | } | ||
199 | /// What user sees in pop-up in the UI. | ||
200 | pub fn label(&self) -> &str { | ||
201 | &self.label | ||
202 | } | ||
203 | pub fn source_range(&self) -> TextRange { | ||
204 | self.source_range | ||
205 | } | ||
206 | |||
207 | pub fn insert_text_format(&self) -> InsertTextFormat { | ||
208 | self.insert_text_format | ||
209 | } | ||
210 | |||
211 | pub fn text_edit(&self) -> &TextEdit { | ||
212 | &self.text_edit | ||
213 | } | ||
214 | |||
215 | /// Short one-line additional information, like a type | ||
216 | pub fn detail(&self) -> Option<&str> { | ||
217 | self.detail.as_deref() | ||
218 | } | ||
219 | /// A doc-comment | ||
220 | pub fn documentation(&self) -> Option<Documentation> { | ||
221 | self.documentation.clone() | ||
222 | } | ||
223 | /// What string is used for filtering. | ||
224 | pub fn lookup(&self) -> &str { | ||
225 | self.lookup.as_deref().unwrap_or(&self.label) | ||
226 | } | ||
227 | |||
228 | pub fn kind(&self) -> Option<CompletionItemKind> { | ||
229 | self.kind | ||
230 | } | ||
231 | |||
232 | pub fn deprecated(&self) -> bool { | ||
233 | self.deprecated | ||
234 | } | ||
235 | |||
236 | pub fn score(&self) -> Option<CompletionScore> { | ||
237 | self.score | ||
238 | } | ||
239 | |||
240 | pub fn trigger_call_info(&self) -> bool { | ||
241 | self.trigger_call_info | ||
242 | } | ||
243 | } | ||
244 | |||
245 | /// A helper to make `CompletionItem`s. | ||
246 | #[must_use] | ||
247 | pub(crate) struct Builder { | ||
248 | source_range: TextRange, | ||
249 | completion_kind: CompletionKind, | ||
250 | label: String, | ||
251 | insert_text: Option<String>, | ||
252 | insert_text_format: InsertTextFormat, | ||
253 | detail: Option<String>, | ||
254 | documentation: Option<Documentation>, | ||
255 | lookup: Option<String>, | ||
256 | kind: Option<CompletionItemKind>, | ||
257 | text_edit: Option<TextEdit>, | ||
258 | deprecated: Option<bool>, | ||
259 | trigger_call_info: Option<bool>, | ||
260 | score: Option<CompletionScore>, | ||
261 | } | ||
262 | |||
263 | impl Builder { | ||
264 | pub(crate) fn add_to(self, acc: &mut Completions) { | ||
265 | acc.add(self.build()) | ||
266 | } | ||
267 | |||
268 | pub(crate) fn build(self) -> CompletionItem { | ||
269 | let label = self.label; | ||
270 | let text_edit = match self.text_edit { | ||
271 | Some(it) => it, | ||
272 | None => TextEdit::replace( | ||
273 | self.source_range, | ||
274 | self.insert_text.unwrap_or_else(|| label.clone()), | ||
275 | ), | ||
276 | }; | ||
277 | |||
278 | CompletionItem { | ||
279 | source_range: self.source_range, | ||
280 | label, | ||
281 | insert_text_format: self.insert_text_format, | ||
282 | text_edit, | ||
283 | detail: self.detail, | ||
284 | documentation: self.documentation, | ||
285 | lookup: self.lookup, | ||
286 | kind: self.kind, | ||
287 | completion_kind: self.completion_kind, | ||
288 | deprecated: self.deprecated.unwrap_or(false), | ||
289 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | ||
290 | score: self.score, | ||
291 | } | ||
292 | } | ||
293 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | ||
294 | self.lookup = Some(lookup.into()); | ||
295 | self | ||
296 | } | ||
297 | pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { | ||
298 | self.label = label.into(); | ||
299 | self | ||
300 | } | ||
301 | pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder { | ||
302 | self.insert_text = Some(insert_text.into()); | ||
303 | self | ||
304 | } | ||
305 | pub(crate) fn insert_snippet( | ||
306 | mut self, | ||
307 | _cap: SnippetCap, | ||
308 | snippet: impl Into<String>, | ||
309 | ) -> Builder { | ||
310 | self.insert_text_format = InsertTextFormat::Snippet; | ||
311 | self.insert_text(snippet) | ||
312 | } | ||
313 | pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { | ||
314 | self.kind = Some(kind); | ||
315 | self | ||
316 | } | ||
317 | pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { | ||
318 | self.text_edit = Some(edit); | ||
319 | self | ||
320 | } | ||
321 | pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { | ||
322 | self.insert_text_format = InsertTextFormat::Snippet; | ||
323 | self.text_edit(edit) | ||
324 | } | ||
325 | #[allow(unused)] | ||
326 | pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { | ||
327 | self.set_detail(Some(detail)) | ||
328 | } | ||
329 | pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder { | ||
330 | self.detail = detail.map(Into::into); | ||
331 | self | ||
332 | } | ||
333 | #[allow(unused)] | ||
334 | pub(crate) fn documentation(self, docs: Documentation) -> Builder { | ||
335 | self.set_documentation(Some(docs)) | ||
336 | } | ||
337 | pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { | ||
338 | self.documentation = docs.map(Into::into); | ||
339 | self | ||
340 | } | ||
341 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | ||
342 | self.deprecated = Some(deprecated); | ||
343 | self | ||
344 | } | ||
345 | pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { | ||
346 | self.score = Some(score); | ||
347 | self | ||
348 | } | ||
349 | pub(crate) fn trigger_call_info(mut self) -> Builder { | ||
350 | self.trigger_call_info = Some(true); | ||
351 | self | ||
352 | } | ||
353 | } | ||
354 | |||
355 | impl<'a> Into<CompletionItem> for Builder { | ||
356 | fn into(self) -> CompletionItem { | ||
357 | self.build() | ||
358 | } | ||
359 | } | ||
360 | |||
361 | /// Represents an in-progress set of completions being built. | ||
362 | #[derive(Debug, Default)] | ||
363 | pub(crate) struct Completions { | ||
364 | buf: Vec<CompletionItem>, | ||
365 | } | ||
366 | |||
367 | impl Completions { | ||
368 | pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) { | ||
369 | self.buf.push(item.into()) | ||
370 | } | ||
371 | pub(crate) fn add_all<I>(&mut self, items: I) | ||
372 | where | ||
373 | I: IntoIterator, | ||
374 | I::Item: Into<CompletionItem>, | ||
375 | { | ||
376 | items.into_iter().for_each(|item| self.add(item.into())) | ||
377 | } | ||
378 | } | ||
379 | |||
380 | impl Into<Vec<CompletionItem>> for Completions { | ||
381 | fn into(self) -> Vec<CompletionItem> { | ||
382 | self.buf | ||
383 | } | ||
384 | } | ||
diff --git a/crates/ide/src/completion/generated_features.rs b/crates/ide/src/completion/generated_features.rs new file mode 100644 index 000000000..24754a8cf --- /dev/null +++ b/crates/ide/src/completion/generated_features.rs | |||
@@ -0,0 +1,4 @@ | |||
1 | //! Generated file, do not edit by hand, see `xtask/src/codegen` | ||
2 | |||
3 | use crate::completion::complete_attribute::LintCompletion; | ||
4 | pub ( super ) const FEATURES : & [ LintCompletion ] = & [ LintCompletion { label : "doc_cfg" , description : "# `doc_cfg`\n\nThe tracking issue for this feature is: [#43781]\n\n------\n\nThe `doc_cfg` feature allows an API be documented as only available in some specific platforms.\nThis attribute has two effects:\n\n1. In the annotated item's documentation, there will be a message saying \"This is supported on\n (platform) only\".\n\n2. The item's doc-tests will only run on the specific platform.\n\nIn addition to allowing the use of the `#[doc(cfg)]` attribute, this feature enables the use of a\nspecial conditional compilation flag, `#[cfg(doc)]`, set whenever building documentation on your\ncrate.\n\nThis feature was introduced as part of PR [#43348] to allow the platform-specific parts of the\nstandard library be documented.\n\n```rust\n#![feature(doc_cfg)]\n\n#[cfg(any(windows, doc))]\n#[doc(cfg(windows))]\n/// The application's icon in the notification area (a.k.a. system tray).\n///\n/// # Examples\n///\n/// ```no_run\n/// extern crate my_awesome_ui_library;\n/// use my_awesome_ui_library::current_app;\n/// use my_awesome_ui_library::windows::notification;\n///\n/// let icon = current_app().get::<notification::Icon>();\n/// icon.show();\n/// icon.show_message(\"Hello\");\n/// ```\npub struct Icon {\n // ...\n}\n```\n\n[#43781]: https://github.com/rust-lang/rust/issues/43781\n[#43348]: https://github.com/rust-lang/rust/issues/43348\n" } , LintCompletion { label : "impl_trait_in_bindings" , description : "# `impl_trait_in_bindings`\n\nThe tracking issue for this feature is: [#63065]\n\n[#63065]: https://github.com/rust-lang/rust/issues/63065\n\n------------------------\n\nThe `impl_trait_in_bindings` feature gate lets you use `impl Trait` syntax in\n`let`, `static`, and `const` bindings.\n\nA simple example is:\n\n```rust\n#![feature(impl_trait_in_bindings)]\n\nuse std::fmt::Debug;\n\nfn main() {\n let a: impl Debug + Clone = 42;\n let b = a.clone();\n println!(\"{:?}\", b); // prints `42`\n}\n```\n\nNote however that because the types of `a` and `b` are opaque in the above\nexample, calling inherent methods or methods outside of the specified traits\n(e.g., `a.abs()` or `b.abs()`) is not allowed, and yields an error.\n" } , LintCompletion { label : "plugin" , description : "# `plugin`\n\nThe tracking issue for this feature is: [#29597]\n\n[#29597]: https://github.com/rust-lang/rust/issues/29597\n\n\nThis feature is part of \"compiler plugins.\" It will often be used with the\n[`plugin_registrar`] and `rustc_private` features.\n\n[`plugin_registrar`]: plugin-registrar.md\n\n------------------------\n\n`rustc` can load compiler plugins, which are user-provided libraries that\nextend the compiler's behavior with new lint checks, etc.\n\nA plugin is a dynamic library crate with a designated *registrar* function that\nregisters extensions with `rustc`. Other crates can load these extensions using\nthe crate attribute `#![plugin(...)]`. See the\n`rustc_driver::plugin` documentation for more about the\nmechanics of defining and loading a plugin.\n\nIn the vast majority of cases, a plugin should *only* be used through\n`#![plugin]` and not through an `extern crate` item. Linking a plugin would\npull in all of librustc_ast and librustc as dependencies of your crate. This is\ngenerally unwanted unless you are building another plugin.\n\nThe usual practice is to put compiler plugins in their own crate, separate from\nany `macro_rules!` macros or ordinary Rust code meant to be used by consumers\nof a library.\n\n# Lint plugins\n\nPlugins can extend [Rust's lint\ninfrastructure](../../reference/attributes/diagnostics.md#lint-check-attributes) with\nadditional checks for code style, safety, etc. Now let's write a plugin\n[`lint-plugin-test.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs)\nthat warns about any item named `lintme`.\n\n```rust,ignore\n#![feature(plugin_registrar)]\n#![feature(box_syntax, rustc_private)]\n\nextern crate rustc_ast;\n\n// Load rustc as a plugin to get macros\nextern crate rustc_driver;\n#[macro_use]\nextern crate rustc_lint;\n#[macro_use]\nextern crate rustc_session;\n\nuse rustc_driver::plugin::Registry;\nuse rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};\nuse rustc_ast::ast;\ndeclare_lint!(TEST_LINT, Warn, \"Warn about items named 'lintme'\");\n\ndeclare_lint_pass!(Pass => [TEST_LINT]);\n\nimpl EarlyLintPass for Pass {\n fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {\n if it.ident.name.as_str() == \"lintme\" {\n cx.lint(TEST_LINT, |lint| {\n lint.build(\"item is named 'lintme'\").set_span(it.span).emit()\n });\n }\n }\n}\n\n#[plugin_registrar]\npub fn plugin_registrar(reg: &mut Registry) {\n reg.lint_store.register_lints(&[&TEST_LINT]);\n reg.lint_store.register_early_pass(|| box Pass);\n}\n```\n\nThen code like\n\n```rust,ignore\n#![feature(plugin)]\n#![plugin(lint_plugin_test)]\n\nfn lintme() { }\n```\n\nwill produce a compiler warning:\n\n```txt\nfoo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default\nfoo.rs:4 fn lintme() { }\n ^~~~~~~~~~~~~~~\n```\n\nThe components of a lint plugin are:\n\n* one or more `declare_lint!` invocations, which define static `Lint` structs;\n\n* a struct holding any state needed by the lint pass (here, none);\n\n* a `LintPass`\n implementation defining how to check each syntax element. A single\n `LintPass` may call `span_lint` for several different `Lint`s, but should\n register them all through the `get_lints` method.\n\nLint passes are syntax traversals, but they run at a late stage of compilation\nwhere type information is available. `rustc`'s [built-in\nlints](https://github.com/rust-lang/rust/blob/master/src/librustc_session/lint/builtin.rs)\nmostly use the same infrastructure as lint plugins, and provide examples of how\nto access type information.\n\nLints defined by plugins are controlled by the usual [attributes and compiler\nflags](../../reference/attributes/diagnostics.md#lint-check-attributes), e.g.\n`#[allow(test_lint)]` or `-A test-lint`. These identifiers are derived from the\nfirst argument to `declare_lint!`, with appropriate case and punctuation\nconversion.\n\nYou can run `rustc -W help foo.rs` to see a list of lints known to `rustc`,\nincluding those provided by plugins loaded by `foo.rs`.\n" } , LintCompletion { label : "infer_static_outlives_requirements" , description : "# `infer_static_outlives_requirements`\n\nThe tracking issue for this feature is: [#54185]\n\n[#54185]: https://github.com/rust-lang/rust/issues/54185\n\n------------------------\nThe `infer_static_outlives_requirements` feature indicates that certain\n`'static` outlives requirements can be inferred by the compiler rather than\nstating them explicitly.\n\nNote: It is an accompanying feature to `infer_outlives_requirements`,\nwhich must be enabled to infer outlives requirements.\n\nFor example, currently generic struct definitions that contain\nreferences, require where-clauses of the form T: 'static. By using\nthis feature the outlives predicates will be inferred, although\nthey may still be written explicitly.\n\n```rust,ignore (pseudo-Rust)\nstruct Foo<U> where U: 'static { // <-- currently required\n bar: Bar<U>\n}\nstruct Bar<T: 'static> {\n x: T,\n}\n```\n\n\n## Examples:\n\n```rust,ignore (pseudo-Rust)\n#![feature(infer_outlives_requirements)]\n#![feature(infer_static_outlives_requirements)]\n\n#[rustc_outlives]\n// Implicitly infer U: 'static\nstruct Foo<U> {\n bar: Bar<U>\n}\nstruct Bar<T: 'static> {\n x: T,\n}\n```\n\n" } , LintCompletion { label : "doc_alias" , description : "# `doc_alias`\n\nThe tracking issue for this feature is: [#50146]\n\n[#50146]: https://github.com/rust-lang/rust/issues/50146\n\n------------------------\n\nYou can add alias(es) to an item when using the `rustdoc` search through the\n`doc(alias)` attribute. Example:\n\n```rust,no_run\n#![feature(doc_alias)]\n\n#[doc(alias = \"x\")]\n#[doc(alias = \"big\")]\npub struct BigX;\n```\n\nThen, when looking for it through the `rustdoc` search, if you enter \"x\" or\n\"big\", search will show the `BigX` struct first.\n\nNote that this feature is currently hidden behind the `feature(doc_alias)` gate.\n" } , LintCompletion { label : "optin_builtin_traits" , description : "# `optin_builtin_traits`\n\nThe tracking issue for this feature is [#13231] \n\n[#13231]: https://github.com/rust-lang/rust/issues/13231\n\n----\n\nThe `optin_builtin_traits` feature gate allows you to define auto traits.\n\nAuto traits, like [`Send`] or [`Sync`] in the standard library, are marker traits\nthat are automatically implemented for every type, unless the type, or a type it contains, \nhas explicitly opted out via a negative impl. (Negative impls are separately controlled\nby the `negative_impls` feature.)\n\n[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html\n[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html\n\n```rust,ignore\nimpl !Trait for Type\n```\n\nExample:\n\n```rust\n#![feature(negative_impls)]\n#![feature(optin_builtin_traits)]\n\nauto trait Valid {}\n\nstruct True;\nstruct False;\n\nimpl !Valid for False {}\n\nstruct MaybeValid<T>(T);\n\nfn must_be_valid<T: Valid>(_t: T) { }\n\nfn main() {\n // works\n must_be_valid( MaybeValid(True) );\n \n // compiler error - trait bound not satisfied\n // must_be_valid( MaybeValid(False) );\n}\n```\n\n## Automatic trait implementations\n\nWhen a type is declared as an `auto trait`, we will automatically\ncreate impls for every struct/enum/union, unless an explicit impl is\nprovided. These automatic impls contain a where clause for each field\nof the form `T: AutoTrait`, where `T` is the type of the field and\n`AutoTrait` is the auto trait in question. As an example, consider the\nstruct `List` and the auto trait `Send`:\n\n```rust\nstruct List<T> {\n data: T,\n next: Option<Box<List<T>>>,\n}\n```\n\nPresuming that there is no explicit impl of `Send` for `List`, the\ncompiler will supply an automatic impl of the form:\n\n```rust\nstruct List<T> {\n data: T,\n next: Option<Box<List<T>>>,\n}\n\nunsafe impl<T> Send for List<T>\nwhere\n T: Send, // from the field `data`\n Option<Box<List<T>>>: Send, // from the field `next`\n{ }\n```\n\nExplicit impls may be either positive or negative. They take the form:\n\n```rust,ignore\nimpl<...> AutoTrait for StructName<..> { }\nimpl<...> !AutoTrait for StructName<..> { }\n```\n\n## Coinduction: Auto traits permit cyclic matching\n\nUnlike ordinary trait matching, auto traits are **coinductive**. This\nmeans, in short, that cycles which occur in trait matching are\nconsidered ok. As an example, consider the recursive struct `List`\nintroduced in the previous section. In attempting to determine whether\n`List: Send`, we would wind up in a cycle: to apply the impl, we must\nshow that `Option<Box<List>>: Send`, which will in turn require\n`Box<List>: Send` and then finally `List: Send` again. Under ordinary\ntrait matching, this cycle would be an error, but for an auto trait it\nis considered a successful match.\n\n## Items\n\nAuto traits cannot have any trait items, such as methods or associated types. This ensures that we can generate default implementations.\n\n## Supertraits\n\nAuto traits cannot have supertraits. This is for soundness reasons, as the interaction of coinduction with implied bounds is difficult to reconcile.\n\n" } , LintCompletion { label : "const_in_array_repeat_expressions" , description : "# `const_in_array_repeat_expressions`\n\nThe tracking issue for this feature is: [#49147]\n\n[#49147]: https://github.com/rust-lang/rust/issues/49147\n\n------------------------\n\nRelaxes the rules for repeat expressions, `[x; N]` such that `x` may also be `const` (strictly\nspeaking rvalue promotable), in addition to `typeof(x): Copy`. The result of `[x; N]` where `x` is\n`const` is itself also `const`.\n" } , LintCompletion { label : "generators" , description : "# `generators`\n\nThe tracking issue for this feature is: [#43122]\n\n[#43122]: https://github.com/rust-lang/rust/issues/43122\n\n------------------------\n\nThe `generators` feature gate in Rust allows you to define generator or\ncoroutine literals. A generator is a \"resumable function\" that syntactically\nresembles a closure but compiles to much different semantics in the compiler\nitself. The primary feature of a generator is that it can be suspended during\nexecution to be resumed at a later date. Generators use the `yield` keyword to\n\"return\", and then the caller can `resume` a generator to resume execution just\nafter the `yield` keyword.\n\nGenerators are an extra-unstable feature in the compiler right now. Added in\n[RFC 2033] they're mostly intended right now as a information/constraint\ngathering phase. The intent is that experimentation can happen on the nightly\ncompiler before actual stabilization. A further RFC will be required to\nstabilize generators/coroutines and will likely contain at least a few small\ntweaks to the overall design.\n\n[RFC 2033]: https://github.com/rust-lang/rfcs/pull/2033\n\nA syntactical example of a generator is:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::{Generator, GeneratorState};\nuse std::pin::Pin;\n\nfn main() {\n let mut generator = || {\n yield 1;\n return \"foo\"\n };\n\n match Pin::new(&mut generator).resume(()) {\n GeneratorState::Yielded(1) => {}\n _ => panic!(\"unexpected value from resume\"),\n }\n match Pin::new(&mut generator).resume(()) {\n GeneratorState::Complete(\"foo\") => {}\n _ => panic!(\"unexpected value from resume\"),\n }\n}\n```\n\nGenerators are closure-like literals which can contain a `yield` statement. The\n`yield` statement takes an optional expression of a value to yield out of the\ngenerator. All generator literals implement the `Generator` trait in the\n`std::ops` module. The `Generator` trait has one main method, `resume`, which\nresumes execution of the generator at the previous suspension point.\n\nAn example of the control flow of generators is that the following example\nprints all numbers in order:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::Generator;\nuse std::pin::Pin;\n\nfn main() {\n let mut generator = || {\n println!(\"2\");\n yield;\n println!(\"4\");\n };\n\n println!(\"1\");\n Pin::new(&mut generator).resume(());\n println!(\"3\");\n Pin::new(&mut generator).resume(());\n println!(\"5\");\n}\n```\n\nAt this time the main intended use case of generators is an implementation\nprimitive for async/await syntax, but generators will likely be extended to\nergonomic implementations of iterators and other primitives in the future.\nFeedback on the design and usage is always appreciated!\n\n### The `Generator` trait\n\nThe `Generator` trait in `std::ops` currently looks like:\n\n```rust\n# #![feature(arbitrary_self_types, generator_trait)]\n# use std::ops::GeneratorState;\n# use std::pin::Pin;\n\npub trait Generator<R = ()> {\n type Yield;\n type Return;\n fn resume(self: Pin<&mut Self>, resume: R) -> GeneratorState<Self::Yield, Self::Return>;\n}\n```\n\nThe `Generator::Yield` type is the type of values that can be yielded with the\n`yield` statement. The `Generator::Return` type is the returned type of the\ngenerator. This is typically the last expression in a generator's definition or\nany value passed to `return` in a generator. The `resume` function is the entry\npoint for executing the `Generator` itself.\n\nThe return value of `resume`, `GeneratorState`, looks like:\n\n```rust\npub enum GeneratorState<Y, R> {\n Yielded(Y),\n Complete(R),\n}\n```\n\nThe `Yielded` variant indicates that the generator can later be resumed. This\ncorresponds to a `yield` point in a generator. The `Complete` variant indicates\nthat the generator is complete and cannot be resumed again. Calling `resume`\nafter a generator has returned `Complete` will likely result in a panic of the\nprogram.\n\n### Closure-like semantics\n\nThe closure-like syntax for generators alludes to the fact that they also have\nclosure-like semantics. Namely:\n\n* When created, a generator executes no code. A closure literal does not\n actually execute any of the closure's code on construction, and similarly a\n generator literal does not execute any code inside the generator when\n constructed.\n\n* Generators can capture outer variables by reference or by move, and this can\n be tweaked with the `move` keyword at the beginning of the closure. Like\n closures all generators will have an implicit environment which is inferred by\n the compiler. Outer variables can be moved into a generator for use as the\n generator progresses.\n\n* Generator literals produce a value with a unique type which implements the\n `std::ops::Generator` trait. This allows actual execution of the generator\n through the `Generator::resume` method as well as also naming it in return\n types and such.\n\n* Traits like `Send` and `Sync` are automatically implemented for a `Generator`\n depending on the captured variables of the environment. Unlike closures,\n generators also depend on variables live across suspension points. This means\n that although the ambient environment may be `Send` or `Sync`, the generator\n itself may not be due to internal variables live across `yield` points being\n not-`Send` or not-`Sync`. Note that generators do\n not implement traits like `Copy` or `Clone` automatically.\n\n* Whenever a generator is dropped it will drop all captured environment\n variables.\n\n### Generators as state machines\n\nIn the compiler, generators are currently compiled as state machines. Each\n`yield` expression will correspond to a different state that stores all live\nvariables over that suspension point. Resumption of a generator will dispatch on\nthe current state and then execute internally until a `yield` is reached, at\nwhich point all state is saved off in the generator and a value is returned.\n\nLet's take a look at an example to see what's going on here:\n\n```rust\n#![feature(generators, generator_trait)]\n\nuse std::ops::Generator;\nuse std::pin::Pin;\n\nfn main() {\n let ret = \"foo\";\n let mut generator = move || {\n yield 1;\n return ret\n };\n\n Pin::new(&mut generator).resume(());\n Pin::new(&mut generator).resume(());\n}\n```\n\nThis generator literal will compile down to something similar to:\n\n```rust\n#![feature(arbitrary_self_types, generators, generator_trait)]\n\nuse std::ops::{Generator, GeneratorState};\nuse std::pin::Pin;\n\nfn main() {\n let ret = \"foo\";\n let mut generator = {\n enum __Generator {\n Start(&'static str),\n Yield1(&'static str),\n Done,\n }\n\n impl Generator for __Generator {\n type Yield = i32;\n type Return = &'static str;\n\n fn resume(mut self: Pin<&mut Self>, resume: ()) -> GeneratorState<i32, &'static str> {\n use std::mem;\n match mem::replace(&mut *self, __Generator::Done) {\n __Generator::Start(s) => {\n *self = __Generator::Yield1(s);\n GeneratorState::Yielded(1)\n }\n\n __Generator::Yield1(s) => {\n *self = __Generator::Done;\n GeneratorState::Complete(s)\n }\n\n __Generator::Done => {\n panic!(\"generator resumed after completion\")\n }\n }\n }\n }\n\n __Generator::Start(ret)\n };\n\n Pin::new(&mut generator).resume(());\n Pin::new(&mut generator).resume(());\n}\n```\n\nNotably here we can see that the compiler is generating a fresh type,\n`__Generator` in this case. This type has a number of states (represented here\nas an `enum`) corresponding to each of the conceptual states of the generator.\nAt the beginning we're closing over our outer variable `foo` and then that\nvariable is also live over the `yield` point, so it's stored in both states.\n\nWhen the generator starts it'll immediately yield 1, but it saves off its state\njust before it does so indicating that it has reached the yield point. Upon\nresuming again we'll execute the `return ret` which returns the `Complete`\nstate.\n\nHere we can also note that the `Done` state, if resumed, panics immediately as\nit's invalid to resume a completed generator. It's also worth noting that this\nis just a rough desugaring, not a normative specification for what the compiler\ndoes.\n" } , LintCompletion { label : "unsized_tuple_coercion" , description : "# `unsized_tuple_coercion`\n\nThe tracking issue for this feature is: [#42877]\n\n[#42877]: https://github.com/rust-lang/rust/issues/42877\n\n------------------------\n\nThis is a part of [RFC0401]. According to the RFC, there should be an implementation like this:\n\n```rust,ignore\nimpl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}\n```\n\nThis implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:\n\n```rust\n#![feature(unsized_tuple_coercion)]\n\nfn main() {\n let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);\n let y : &([i32; 3], [i32]) = &x;\n assert_eq!(y.1[0], 4);\n}\n```\n\n[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md\n" } , LintCompletion { label : "cfg_version" , description : "# `cfg_version`\n\nThe tracking issue for this feature is: [#64796]\n\n[#64796]: https://github.com/rust-lang/rust/issues/64796\n\n------------------------\n\nThe `cfg_version` feature makes it possible to execute different code\ndepending on the compiler version.\n\n## Examples\n\n```rust\n#![feature(cfg_version)]\n\n#[cfg(version(\"1.42\"))]\nfn a() {\n // ...\n}\n\n#[cfg(not(version(\"1.42\")))]\nfn a() {\n // ...\n}\n\nfn b() {\n if cfg!(version(\"1.42\")) {\n // ...\n } else {\n // ...\n }\n}\n```\n" } , LintCompletion { label : "ffi_const" , description : "# `ffi_const`\n\nThe `#[ffi_const]` attribute applies clang's `const` attribute to foreign\nfunctions declarations.\n\nThat is, `#[ffi_const]` functions shall have |