diff options
Diffstat (limited to 'crates/ide_completion/src/context.rs')
-rw-r--r-- | crates/ide_completion/src/context.rs | 495 |
1 files changed, 280 insertions, 215 deletions
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 787eb2fd3..7c46c815d 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -1,29 +1,34 @@ | |||
1 | //! See `CompletionContext` structure. | 1 | //! See `CompletionContext` structure. |
2 | 2 | ||
3 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | 3 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; |
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::{ |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | 5 | base_db::{FilePosition, SourceDatabase}, |
6 | call_info::ActiveParameter, | ||
7 | RootDatabase, | ||
8 | }; | ||
6 | use syntax::{ | 9 | use syntax::{ |
7 | algo::find_node_at_offset, | 10 | algo::find_node_at_offset, |
8 | ast::{self, NameOrNameRef, NameOwner}, | 11 | ast::{self, NameOrNameRef, NameOwner}, |
9 | match_ast, AstNode, NodeOrToken, | 12 | match_ast, AstNode, NodeOrToken, |
10 | SyntaxKind::*, | 13 | SyntaxKind::{self, *}, |
11 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 14 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, |
12 | }; | 15 | }; |
13 | |||
14 | use text_edit::Indel; | 16 | use text_edit::Indel; |
15 | 17 | ||
16 | use crate::{ | 18 | use crate::{ |
17 | patterns::{ | 19 | patterns::{ |
18 | fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent, | 20 | determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, |
19 | has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent, | 21 | is_in_loop_body, previous_token, ImmediateLocation, ImmediatePrevSibling, |
20 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
21 | has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm, | ||
22 | unsafe_is_prev, | ||
23 | }, | 22 | }, |
24 | CompletionConfig, | 23 | CompletionConfig, |
25 | }; | 24 | }; |
26 | 25 | ||
26 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
27 | pub(crate) enum PatternRefutability { | ||
28 | Refutable, | ||
29 | Irrefutable, | ||
30 | } | ||
31 | |||
27 | /// `CompletionContext` is created early during completion to figure out, where | 32 | /// `CompletionContext` is created early during completion to figure out, where |
28 | /// exactly is the cursor, syntax-wise. | 33 | /// exactly is the cursor, syntax-wise. |
29 | #[derive(Debug)] | 34 | #[derive(Debug)] |
@@ -41,28 +46,34 @@ pub(crate) struct CompletionContext<'a> { | |||
41 | pub(super) expected_name: Option<NameOrNameRef>, | 46 | pub(super) expected_name: Option<NameOrNameRef>, |
42 | pub(super) expected_type: Option<Type>, | 47 | pub(super) expected_type: Option<Type>, |
43 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 48 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
44 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | 49 | |
45 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
46 | pub(super) function_syntax: Option<ast::Fn>, | ||
47 | pub(super) use_item_syntax: Option<ast::Use>, | 50 | pub(super) use_item_syntax: Option<ast::Use>, |
48 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | 51 | |
49 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 52 | /// The parent function of the cursor position if it exists. |
50 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | 53 | pub(super) function_def: Option<ast::Fn>, |
54 | /// The parent impl of the cursor position if it exists. | ||
51 | pub(super) impl_def: Option<ast::Impl>, | 55 | pub(super) impl_def: Option<ast::Impl>, |
56 | |||
57 | // potentially set if we are completing a lifetime | ||
58 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | ||
59 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
52 | pub(super) lifetime_allowed: bool, | 60 | pub(super) lifetime_allowed: bool, |
61 | pub(super) is_label_ref: bool, | ||
62 | |||
63 | // potentially set if we are completing a name | ||
64 | pub(super) is_pat_or_const: Option<PatternRefutability>, | ||
65 | pub(super) is_param: bool, | ||
66 | |||
67 | pub(super) completion_location: Option<ImmediateLocation>, | ||
68 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, | ||
69 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
70 | |||
53 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 71 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
54 | pub(super) active_parameter: Option<ActiveParameter>, | 72 | pub(super) active_parameter: Option<ActiveParameter>, |
55 | pub(super) is_param: bool, | ||
56 | pub(super) is_label_ref: bool, | ||
57 | /// If a name-binding or reference to a const in a pattern. | ||
58 | /// Irrefutable patterns (like let) are excluded. | ||
59 | pub(super) is_pat_binding_or_const: bool, | ||
60 | pub(super) is_irrefutable_pat_binding: bool, | ||
61 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | 73 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. |
62 | pub(super) is_trivial_path: bool, | 74 | pub(super) is_trivial_path: bool, |
63 | /// If not a trivial path, the prefix (qualifier). | 75 | /// If not a trivial path, the prefix (qualifier). |
64 | pub(super) path_qual: Option<ast::Path>, | 76 | pub(super) path_qual: Option<ast::Path>, |
65 | pub(super) after_if: bool, | ||
66 | /// `true` if we are a statement or a last expr in the block. | 77 | /// `true` if we are a statement or a last expr in the block. |
67 | pub(super) can_be_stmt: bool, | 78 | pub(super) can_be_stmt: bool, |
68 | /// `true` if we expect an expression at the cursor position. | 79 | /// `true` if we expect an expression at the cursor position. |
@@ -80,26 +91,13 @@ pub(crate) struct CompletionContext<'a> { | |||
80 | pub(super) is_macro_call: bool, | 91 | pub(super) is_macro_call: bool, |
81 | pub(super) is_path_type: bool, | 92 | pub(super) is_path_type: bool, |
82 | pub(super) has_type_args: bool, | 93 | pub(super) has_type_args: bool, |
83 | pub(super) attribute_under_caret: Option<ast::Attr>, | 94 | pub(super) locals: Vec<(String, Local)>, |
84 | pub(super) mod_declaration_under_caret: Option<ast::Module>, | 95 | |
85 | pub(super) unsafe_is_prev: bool, | 96 | pub(super) previous_token: Option<SyntaxToken>, |
86 | pub(super) if_is_prev: bool, | ||
87 | pub(super) block_expr_parent: bool, | ||
88 | pub(super) bind_pat_parent: bool, | ||
89 | pub(super) ref_pat_parent: bool, | ||
90 | pub(super) in_loop_body: bool, | 97 | pub(super) in_loop_body: bool, |
91 | pub(super) has_trait_parent: bool, | ||
92 | pub(super) has_impl_parent: bool, | ||
93 | pub(super) inside_impl_trait_block: bool, | ||
94 | pub(super) has_field_list_parent: bool, | ||
95 | pub(super) trait_as_prev_sibling: bool, | ||
96 | pub(super) impl_as_prev_sibling: bool, | ||
97 | pub(super) is_match_arm: bool, | ||
98 | pub(super) has_item_list_or_source_file_parent: bool, | ||
99 | pub(super) for_is_prev2: bool, | ||
100 | pub(super) fn_is_prev: bool, | ||
101 | pub(super) incomplete_let: bool, | 98 | pub(super) incomplete_let: bool, |
102 | pub(super) locals: Vec<(String, Local)>, | 99 | |
100 | no_completion_required: bool, | ||
103 | } | 101 | } |
104 | 102 | ||
105 | impl<'a> CompletionContext<'a> { | 103 | impl<'a> CompletionContext<'a> { |
@@ -149,20 +147,15 @@ impl<'a> CompletionContext<'a> { | |||
149 | name_ref_syntax: None, | 147 | name_ref_syntax: None, |
150 | lifetime_syntax: None, | 148 | lifetime_syntax: None, |
151 | lifetime_param_syntax: None, | 149 | lifetime_param_syntax: None, |
152 | function_syntax: None, | 150 | function_def: None, |
153 | use_item_syntax: None, | 151 | use_item_syntax: None, |
154 | record_lit_syntax: None, | ||
155 | record_pat_syntax: None, | ||
156 | record_field_syntax: None, | ||
157 | impl_def: None, | 152 | impl_def: None, |
158 | active_parameter: ActiveParameter::at(db, position), | 153 | active_parameter: ActiveParameter::at(db, position), |
159 | is_label_ref: false, | 154 | is_label_ref: false, |
160 | is_param: false, | 155 | is_param: false, |
161 | is_pat_binding_or_const: false, | 156 | is_pat_or_const: None, |
162 | is_irrefutable_pat_binding: false, | ||
163 | is_trivial_path: false, | 157 | is_trivial_path: false, |
164 | path_qual: None, | 158 | path_qual: None, |
165 | after_if: false, | ||
166 | can_be_stmt: false, | 159 | can_be_stmt: false, |
167 | is_expr: false, | 160 | is_expr: false, |
168 | is_new_item: false, | 161 | is_new_item: false, |
@@ -173,69 +166,56 @@ impl<'a> CompletionContext<'a> { | |||
173 | is_macro_call: false, | 166 | is_macro_call: false, |
174 | is_path_type: false, | 167 | is_path_type: false, |
175 | has_type_args: false, | 168 | has_type_args: false, |
176 | attribute_under_caret: None, | 169 | previous_token: None, |
177 | mod_declaration_under_caret: None, | ||
178 | unsafe_is_prev: false, | ||
179 | if_is_prev: false, | ||
180 | block_expr_parent: false, | ||
181 | bind_pat_parent: false, | ||
182 | ref_pat_parent: false, | ||
183 | in_loop_body: false, | 170 | in_loop_body: false, |
184 | has_trait_parent: false, | 171 | completion_location: None, |
185 | has_impl_parent: false, | 172 | prev_sibling: None, |
186 | inside_impl_trait_block: false, | 173 | no_completion_required: false, |
187 | has_field_list_parent: false, | ||
188 | trait_as_prev_sibling: false, | ||
189 | impl_as_prev_sibling: false, | ||
190 | is_match_arm: false, | ||
191 | has_item_list_or_source_file_parent: false, | ||
192 | for_is_prev2: false, | ||
193 | fn_is_prev: false, | ||
194 | incomplete_let: false, | 174 | incomplete_let: false, |
175 | attribute_under_caret: None, | ||
195 | locals, | 176 | locals, |
196 | }; | 177 | }; |
197 | 178 | ||
198 | let mut original_file = original_file.syntax().clone(); | 179 | let mut original_file = original_file.syntax().clone(); |
199 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | 180 | let mut speculative_file = file_with_fake_ident.syntax().clone(); |
200 | let mut offset = position.offset; | 181 | let mut offset = position.offset; |
201 | let mut fake_ident_token = fake_ident_token; | 182 | let mut fake_ident_token = fake_ident_token; |
202 | 183 | ||
203 | // Are we inside a macro call? | 184 | // Are we inside a macro call? |
204 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | 185 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( |
205 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | 186 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), |
206 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | 187 | find_node_at_offset::<ast::MacroCall>(&speculative_file, offset), |
207 | ) { | 188 | ) { |
208 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) | 189 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) |
209 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) | 190 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) |
210 | { | 191 | { |
211 | break; | 192 | break; |
212 | } | 193 | } |
213 | let hypothetical_args = match macro_call_with_fake_ident.token_tree() { | 194 | let speculative_args = match macro_call_with_fake_ident.token_tree() { |
214 | Some(tt) => tt, | 195 | Some(tt) => tt, |
215 | None => break, | 196 | None => break, |
216 | }; | 197 | }; |
217 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | 198 | if let (Some(actual_expansion), Some(speculative_expansion)) = ( |
218 | ctx.sema.expand(&actual_macro_call), | 199 | ctx.sema.expand(&actual_macro_call), |
219 | ctx.sema.speculative_expand( | 200 | ctx.sema.speculative_expand( |
220 | &actual_macro_call, | 201 | &actual_macro_call, |
221 | &hypothetical_args, | 202 | &speculative_args, |
222 | fake_ident_token, | 203 | fake_ident_token, |
223 | ), | 204 | ), |
224 | ) { | 205 | ) { |
225 | let new_offset = hypothetical_expansion.1.text_range().start(); | 206 | let new_offset = speculative_expansion.1.text_range().start(); |
226 | if new_offset > actual_expansion.text_range().end() { | 207 | if new_offset > actual_expansion.text_range().end() { |
227 | break; | 208 | break; |
228 | } | 209 | } |
229 | original_file = actual_expansion; | 210 | original_file = actual_expansion; |
230 | hypothetical_file = hypothetical_expansion.0; | 211 | speculative_file = speculative_expansion.0; |
231 | fake_ident_token = hypothetical_expansion.1; | 212 | fake_ident_token = speculative_expansion.1; |
232 | offset = new_offset; | 213 | offset = new_offset; |
233 | } else { | 214 | } else { |
234 | break; | 215 | break; |
235 | } | 216 | } |
236 | } | 217 | } |
237 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | 218 | ctx.fill(&original_file, speculative_file, offset); |
238 | ctx.fill(&original_file, hypothetical_file, offset); | ||
239 | Some(ctx) | 219 | Some(ctx) |
240 | } | 220 | } |
241 | 221 | ||
@@ -245,7 +225,7 @@ impl<'a> CompletionContext<'a> { | |||
245 | /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names. | 225 | /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names. |
246 | /// - `for _ i$0` -- obviously, it'll be "in" keyword. | 226 | /// - `for _ i$0` -- obviously, it'll be "in" keyword. |
247 | pub(crate) fn no_completion_required(&self) -> bool { | 227 | pub(crate) fn no_completion_required(&self) -> bool { |
248 | (self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2 | 228 | self.no_completion_required |
249 | } | 229 | } |
250 | 230 | ||
251 | /// The range of the identifier that is being completed. | 231 | /// The range of the identifier that is being completed. |
@@ -264,33 +244,67 @@ impl<'a> CompletionContext<'a> { | |||
264 | } | 244 | } |
265 | } | 245 | } |
266 | 246 | ||
267 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | 247 | pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool { |
268 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | 248 | self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind) |
269 | let syntax_element = NodeOrToken::Token(fake_ident_token); | 249 | } |
270 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | 250 | |
271 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | 251 | pub(crate) fn expects_assoc_item(&self) -> bool { |
272 | self.if_is_prev = if_is_prev(syntax_element.clone()); | 252 | matches!( |
273 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | 253 | self.completion_location, |
274 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | 254 | Some(ImmediateLocation::Trait) | Some(ImmediateLocation::Impl) |
275 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | 255 | ) |
276 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | 256 | } |
277 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | 257 | |
278 | self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | 258 | pub(crate) fn expects_use_tree(&self) -> bool { |
279 | self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); | 259 | matches!(self.completion_location, Some(ImmediateLocation::Use)) |
280 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | 260 | } |
281 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | 261 | |
282 | self.is_match_arm = is_match_arm(syntax_element.clone()); | 262 | pub(crate) fn expects_non_trait_assoc_item(&self) -> bool { |
283 | self.has_item_list_or_source_file_parent = | 263 | matches!(self.completion_location, Some(ImmediateLocation::Impl)) |
284 | has_item_list_or_source_file_parent(syntax_element.clone()); | 264 | } |
285 | self.mod_declaration_under_caret = | 265 | |
286 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | 266 | pub(crate) fn expects_item(&self) -> bool { |
287 | .filter(|module| module.item_list().is_none()); | 267 | matches!(self.completion_location, Some(ImmediateLocation::ItemList)) |
288 | self.for_is_prev2 = for_is_prev2(syntax_element.clone()); | 268 | } |
289 | self.fn_is_prev = fn_is_prev(syntax_element.clone()); | 269 | |
290 | self.incomplete_let = | 270 | pub(crate) fn expects_expression(&self) -> bool { |
291 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | 271 | self.is_expr |
292 | it.syntax().text_range().end() == syntax_element.text_range().end() | 272 | } |
293 | }); | 273 | |
274 | pub(crate) fn has_block_expr_parent(&self) -> bool { | ||
275 | matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) | ||
276 | } | ||
277 | |||
278 | pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool { | ||
279 | matches!( | ||
280 | self.completion_location, | ||
281 | Some(ImmediateLocation::IdentPat) | Some(ImmediateLocation::RefExpr) | ||
282 | ) | ||
283 | } | ||
284 | |||
285 | pub(crate) fn expect_record_field(&self) -> bool { | ||
286 | matches!(self.completion_location, Some(ImmediateLocation::RecordField)) | ||
287 | } | ||
288 | |||
289 | pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { | ||
290 | matches!( | ||
291 | self.prev_sibling, | ||
292 | Some(ImmediatePrevSibling::ImplDefType) | Some(ImmediatePrevSibling::TraitDefName) | ||
293 | ) | ||
294 | } | ||
295 | |||
296 | pub(crate) fn after_if(&self) -> bool { | ||
297 | matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr)) | ||
298 | } | ||
299 | |||
300 | pub(crate) fn is_path_disallowed(&self) -> bool { | ||
301 | matches!( | ||
302 | self.completion_location, | ||
303 | Some(ImmediateLocation::Attribute(_)) | ||
304 | | Some(ImmediateLocation::ModDeclaration(_)) | ||
305 | | Some(ImmediateLocation::RecordPat(_)) | ||
306 | | Some(ImmediateLocation::RecordExpr(_)) | ||
307 | ) || self.attribute_under_caret.is_some() | ||
294 | } | 308 | } |
295 | 309 | ||
296 | fn fill_impl_def(&mut self) { | 310 | fn fill_impl_def(&mut self) { |
@@ -337,25 +351,24 @@ impl<'a> CompletionContext<'a> { | |||
337 | }, | 351 | }, |
338 | ast::RecordExprFieldList(_it) => { | 352 | ast::RecordExprFieldList(_it) => { |
339 | cov_mark::hit!(expected_type_struct_field_without_leading_char); | 353 | cov_mark::hit!(expected_type_struct_field_without_leading_char); |
340 | self.token.prev_sibling_or_token() | 354 | // wouldn't try {} be nice... |
341 | .and_then(|se| se.into_node()) | 355 | (|| { |
342 | .and_then(|node| ast::RecordExprField::cast(node)) | 356 | let expr_field = self.token.prev_sibling_or_token()? |
343 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) | 357 | .into_node() |
344 | .map(|(f, rf)|( | 358 | .and_then(|node| ast::RecordExprField::cast(node))?; |
345 | Some(f.0.ty(self.db)), | 359 | let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?; |
346 | rf.field_name().map(NameOrNameRef::NameRef), | 360 | Some(( |
361 | Some(ty), | ||
362 | expr_field.field_name().map(NameOrNameRef::NameRef), | ||
347 | )) | 363 | )) |
348 | .unwrap_or((None, None)) | 364 | })().unwrap_or((None, None)) |
349 | }, | 365 | }, |
350 | ast::RecordExprField(it) => { | 366 | ast::RecordExprField(it) => { |
351 | cov_mark::hit!(expected_type_struct_field_with_leading_char); | 367 | cov_mark::hit!(expected_type_struct_field_with_leading_char); |
352 | self.sema | 368 | ( |
353 | .resolve_record_field(&it) | 369 | it.expr().as_ref().and_then(|e| self.sema.type_of_expr(e)), |
354 | .map(|f|( | 370 | it.field_name().map(NameOrNameRef::NameRef), |
355 | Some(f.0.ty(self.db)), | 371 | ) |
356 | it.field_name().map(NameOrNameRef::NameRef), | ||
357 | )) | ||
358 | .unwrap_or((None, None)) | ||
359 | }, | 372 | }, |
360 | ast::MatchExpr(it) => { | 373 | ast::MatchExpr(it) => { |
361 | cov_mark::hit!(expected_type_match_arm_without_leading_char); | 374 | cov_mark::hit!(expected_type_match_arm_without_leading_char); |
@@ -382,6 +395,12 @@ impl<'a> CompletionContext<'a> { | |||
382 | let def = self.sema.to_def(&it); | 395 | let def = self.sema.to_def(&it); |
383 | (def.map(|def| def.ret_type(self.db)), None) | 396 | (def.map(|def| def.ret_type(self.db)), None) |
384 | }, | 397 | }, |
398 | ast::ClosureExpr(it) => { | ||
399 | let ty = self.sema.type_of_expr(&it.into()); | ||
400 | ty.and_then(|ty| ty.as_callable(self.db)) | ||
401 | .map(|c| (Some(c.return_type()), None)) | ||
402 | .unwrap_or((None, None)) | ||
403 | }, | ||
385 | ast::Stmt(_it) => (None, None), | 404 | ast::Stmt(_it) => (None, None), |
386 | _ => { | 405 | _ => { |
387 | match node.parent() { | 406 | match node.parent() { |
@@ -403,71 +422,43 @@ impl<'a> CompletionContext<'a> { | |||
403 | file_with_fake_ident: SyntaxNode, | 422 | file_with_fake_ident: SyntaxNode, |
404 | offset: TextSize, | 423 | offset: TextSize, |
405 | ) { | 424 | ) { |
425 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
426 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
427 | self.previous_token = previous_token(syntax_element.clone()); | ||
428 | self.attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast); | ||
429 | self.no_completion_required = { | ||
430 | let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
431 | let fn_is_prev = self.previous_token_is(T![fn]); | ||
432 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); | ||
433 | (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 | ||
434 | }; | ||
435 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
436 | |||
437 | self.incomplete_let = | ||
438 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | ||
439 | it.syntax().text_range().end() == syntax_element.text_range().end() | ||
440 | }); | ||
441 | |||
406 | let (expected_type, expected_name) = self.expected_type_and_name(); | 442 | let (expected_type, expected_name) = self.expected_type_and_name(); |
407 | self.expected_type = expected_type; | 443 | self.expected_type = expected_type; |
408 | self.expected_name = expected_name; | 444 | self.expected_name = expected_name; |
409 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
410 | |||
411 | if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset) | ||
412 | { | ||
413 | self.classify_lifetime(original_file, lifetime, offset); | ||
414 | } | ||
415 | |||
416 | // First, let's try to complete a reference to some declaration. | ||
417 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | ||
418 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
419 | // See RFC#1685. | ||
420 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
421 | self.is_param = true; | ||
422 | return; | ||
423 | } | ||
424 | // FIXME: remove this (V) duplication and make the check more precise | ||
425 | if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
426 | self.record_pat_syntax = | ||
427 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
428 | } | ||
429 | self.classify_name_ref(original_file, name_ref, offset); | ||
430 | } | ||
431 | |||
432 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
433 | // suggest declaration names, see `CompletionKind::Magic`. | ||
434 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { | ||
435 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { | ||
436 | self.is_pat_binding_or_const = true; | ||
437 | if bind_pat.at_token().is_some() | ||
438 | || bind_pat.ref_token().is_some() | ||
439 | || bind_pat.mut_token().is_some() | ||
440 | { | ||
441 | self.is_pat_binding_or_const = false; | ||
442 | } | ||
443 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | ||
444 | self.is_pat_binding_or_const = false; | ||
445 | } | ||
446 | if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| { | ||
447 | match_ast! { | ||
448 | match node { | ||
449 | ast::LetStmt(it) => Some(it.pat()), | ||
450 | ast::Param(it) => Some(it.pat()), | ||
451 | _ => None, | ||
452 | } | ||
453 | } | ||
454 | }) { | ||
455 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) { | ||
456 | self.is_pat_binding_or_const = false; | ||
457 | self.is_irrefutable_pat_binding = true; | ||
458 | } | ||
459 | } | ||
460 | 445 | ||
461 | self.fill_impl_def(); | 446 | let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) { |
447 | Some(it) => it, | ||
448 | None => return, | ||
449 | }; | ||
450 | self.completion_location = | ||
451 | determine_location(&self.sema, original_file, offset, &name_like); | ||
452 | self.prev_sibling = determine_prev_sibling(&name_like); | ||
453 | match name_like { | ||
454 | ast::NameLike::Lifetime(lifetime) => { | ||
455 | self.classify_lifetime(original_file, lifetime, offset); | ||
462 | } | 456 | } |
463 | if is_node::<ast::Param>(name.syntax()) { | 457 | ast::NameLike::NameRef(name_ref) => { |
464 | self.is_param = true; | 458 | self.classify_name_ref(original_file, name_ref); |
465 | return; | ||
466 | } | 459 | } |
467 | // FIXME: remove this (^) duplication and make the check more precise | 460 | ast::NameLike::Name(name) => { |
468 | if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | 461 | self.classify_name(name); |
469 | self.record_pat_syntax = | ||
470 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
471 | } | 462 | } |
472 | } | 463 | } |
473 | } | 464 | } |
@@ -501,22 +492,55 @@ impl<'a> CompletionContext<'a> { | |||
501 | } | 492 | } |
502 | } | 493 | } |
503 | 494 | ||
504 | fn classify_name_ref( | 495 | fn classify_name(&mut self, name: ast::Name) { |
505 | &mut self, | 496 | if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
506 | original_file: &SyntaxNode, | 497 | self.is_pat_or_const = Some(PatternRefutability::Refutable); |
507 | name_ref: ast::NameRef, | 498 | // if any of these is here our bind pat can't be a const pat anymore |
508 | offset: TextSize, | 499 | let complex_ident_pat = bind_pat.at_token().is_some() |
509 | ) { | 500 | || bind_pat.ref_token().is_some() |
510 | self.name_ref_syntax = | 501 | || bind_pat.mut_token().is_some(); |
511 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | 502 | if complex_ident_pat { |
512 | let name_range = name_ref.syntax().text_range(); | 503 | self.is_pat_or_const = None; |
513 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | 504 | } else { |
514 | self.record_lit_syntax = | 505 | let irrefutable_pat = bind_pat.syntax().ancestors().find_map(|node| { |
515 | self.sema.find_node_at_offset_with_macros(original_file, offset); | 506 | match_ast! { |
507 | match node { | ||
508 | ast::LetStmt(it) => Some(it.pat()), | ||
509 | ast::Param(it) => Some(it.pat()), | ||
510 | _ => None, | ||
511 | } | ||
512 | } | ||
513 | }); | ||
514 | if let Some(Some(pat)) = irrefutable_pat { | ||
515 | // This check is here since we could be inside a pattern in the initializer expression of the let statement. | ||
516 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) { | ||
517 | self.is_pat_or_const = Some(PatternRefutability::Irrefutable); | ||
518 | } | ||
519 | } | ||
520 | |||
521 | let is_name_in_field_pat = bind_pat | ||
522 | .syntax() | ||
523 | .parent() | ||
524 | .and_then(ast::RecordPatField::cast) | ||
525 | .map_or(false, |pat_field| pat_field.name_ref().is_none()); | ||
526 | if is_name_in_field_pat { | ||
527 | self.is_pat_or_const = None; | ||
528 | } | ||
529 | } | ||
530 | |||
531 | self.fill_impl_def(); | ||
516 | } | 532 | } |
517 | 533 | ||
534 | self.is_param |= is_node::<ast::Param>(name.syntax()); | ||
535 | } | ||
536 | |||
537 | fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { | ||
518 | self.fill_impl_def(); | 538 | self.fill_impl_def(); |
519 | 539 | ||
540 | self.name_ref_syntax = | ||
541 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | ||
542 | |||
543 | let name_range = name_ref.syntax().text_range(); | ||
520 | let top_node = name_ref | 544 | let top_node = name_ref |
521 | .syntax() | 545 | .syntax() |
522 | .ancestors() | 546 | .ancestors() |
@@ -524,31 +548,20 @@ impl<'a> CompletionContext<'a> { | |||
524 | .last() | 548 | .last() |
525 | .unwrap(); | 549 | .unwrap(); |
526 | 550 | ||
527 | match top_node.parent().map(|it| it.kind()) { | 551 | if matches!(top_node.parent().map(|it| it.kind()), Some(SOURCE_FILE) | Some(ITEM_LIST)) { |
528 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | 552 | self.is_new_item = true; |
529 | self.is_new_item = true; | 553 | return; |
530 | return; | ||
531 | } | ||
532 | _ => (), | ||
533 | } | 554 | } |
534 | 555 | ||
535 | self.use_item_syntax = | 556 | self.use_item_syntax = |
536 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); | 557 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); |
537 | 558 | ||
538 | self.function_syntax = self | 559 | self.function_def = self |
539 | .sema | 560 | .sema |
540 | .token_ancestors_with_macros(self.token.clone()) | 561 | .token_ancestors_with_macros(self.token.clone()) |
541 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 562 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
542 | .find_map(ast::Fn::cast); | 563 | .find_map(ast::Fn::cast); |
543 | 564 | ||
544 | self.record_field_syntax = self | ||
545 | .sema | ||
546 | .token_ancestors_with_macros(self.token.clone()) | ||
547 | .take_while(|it| { | ||
548 | it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR | ||
549 | }) | ||
550 | .find_map(ast::RecordExprField::cast); | ||
551 | |||
552 | let parent = match name_ref.syntax().parent() { | 565 | let parent = match name_ref.syntax().parent() { |
553 | Some(it) => it, | 566 | Some(it) => it, |
554 | None => return, | 567 | None => return, |
@@ -609,18 +622,8 @@ impl<'a> CompletionContext<'a> { | |||
609 | }) | 622 | }) |
610 | .unwrap_or(false); | 623 | .unwrap_or(false); |
611 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | 624 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); |
612 | |||
613 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
614 | if let Some(if_expr) = | ||
615 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
616 | { | ||
617 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
618 | { | ||
619 | self.after_if = true; | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | } | 625 | } |
626 | |||
624 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | 627 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { |
625 | // The receiver comes before the point of insertion of the fake | 628 | // The receiver comes before the point of insertion of the fake |
626 | // ident, so it should have the same range in the non-modified file | 629 | // ident, so it should have the same range in the non-modified file |
@@ -638,6 +641,7 @@ impl<'a> CompletionContext<'a> { | |||
638 | false | 641 | false |
639 | }; | 642 | }; |
640 | } | 643 | } |
644 | |||
641 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | 645 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { |
642 | // As above | 646 | // As above |
643 | self.dot_receiver = method_call_expr | 647 | self.dot_receiver = method_call_expr |
@@ -785,6 +789,19 @@ fn foo() { | |||
785 | } | 789 | } |
786 | 790 | ||
787 | #[test] | 791 | #[test] |
792 | fn expected_type_generic_struct_field() { | ||
793 | check_expected_type_and_name( | ||
794 | r#" | ||
795 | struct Foo<T> { a: T } | ||
796 | fn foo() -> Foo<u32> { | ||
797 | Foo { a: $0 } | ||
798 | } | ||
799 | "#, | ||
800 | expect![[r#"ty: u32, name: a"#]], | ||
801 | ) | ||
802 | } | ||
803 | |||
804 | #[test] | ||
788 | fn expected_type_struct_field_with_leading_char() { | 805 | fn expected_type_struct_field_with_leading_char() { |
789 | cov_mark::check!(expected_type_struct_field_with_leading_char); | 806 | cov_mark::check!(expected_type_struct_field_with_leading_char); |
790 | check_expected_type_and_name( | 807 | check_expected_type_and_name( |
@@ -895,4 +912,52 @@ fn foo() -> u32 { | |||
895 | expect![[r#"ty: u32, name: ?"#]], | 912 | expect![[r#"ty: u32, name: ?"#]], |
896 | ) | 913 | ) |
897 | } | 914 | } |
915 | |||
916 | #[test] | ||
917 | fn expected_type_closure_param_return() { | ||
918 | // FIXME: make this work with `|| $0` | ||
919 | check_expected_type_and_name( | ||
920 | r#" | ||
921 | fn foo() { | ||
922 | bar(|| a$0); | ||
923 | } | ||
924 | |||
925 | fn bar(f: impl FnOnce() -> u32) {} | ||
926 | #[lang = "fn_once"] | ||
927 | trait FnOnce { type Output; } | ||
928 | "#, | ||
929 | expect![[r#"ty: u32, name: ?"#]], | ||
930 | ); | ||
931 | } | ||
932 | |||
933 | #[test] | ||
934 | fn expected_type_generic_function() { | ||
935 | check_expected_type_and_name( | ||
936 | r#" | ||
937 | fn foo() { | ||
938 | bar::<u32>($0); | ||
939 | } | ||
940 | |||
941 | fn bar<T>(t: T) {} | ||
942 | "#, | ||
943 | expect![[r#"ty: u32, name: t"#]], | ||
944 | ); | ||
945 | } | ||
946 | |||
947 | #[test] | ||
948 | fn expected_type_generic_method() { | ||
949 | check_expected_type_and_name( | ||
950 | r#" | ||
951 | fn foo() { | ||
952 | S(1u32).bar($0); | ||
953 | } | ||
954 | |||
955 | struct S<T>(T); | ||
956 | impl<T> S<T> { | ||
957 | fn bar(self, t: T) {} | ||
958 | } | ||
959 | "#, | ||
960 | expect![[r#"ty: u32, name: t"#]], | ||
961 | ); | ||
962 | } | ||
898 | } | 963 | } |