diff options
Diffstat (limited to 'crates/completion/src/completion_context.rs')
-rw-r--r-- | crates/completion/src/completion_context.rs | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/crates/completion/src/completion_context.rs b/crates/completion/src/completion_context.rs new file mode 100644 index 000000000..e4f86d0e0 --- /dev/null +++ b/crates/completion/src/completion_context.rs | |||
@@ -0,0 +1,533 @@ | |||
1 | //! See `CompletionContext` structure. | ||
2 | |||
3 | use base_db::{FilePosition, SourceDatabase}; | ||
4 | use call_info::ActiveParameter; | ||
5 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | ||
6 | use ide_db::RootDatabase; | ||
7 | use syntax::{ | ||
8 | algo::{find_covering_element, find_node_at_offset}, | ||
9 | ast, match_ast, AstNode, NodeOrToken, | ||
10 | SyntaxKind::*, | ||
11 | SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
12 | }; | ||
13 | use test_utils::mark; | ||
14 | use text_edit::Indel; | ||
15 | |||
16 | use crate::{ | ||
17 | patterns::{ | ||
18 | fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent, | ||
19 | has_field_list_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, inside_impl_trait_block, is_in_loop_body, is_match_arm, | ||
22 | unsafe_is_prev, | ||
23 | }, | ||
24 | CompletionConfig, | ||
25 | }; | ||
26 | |||
27 | /// `CompletionContext` is created early during completion to figure out, where | ||
28 | /// exactly is the cursor, syntax-wise. | ||
29 | #[derive(Debug)] | ||
30 | pub(crate) struct CompletionContext<'a> { | ||
31 | pub(super) sema: Semantics<'a, RootDatabase>, | ||
32 | pub(super) scope: SemanticsScope<'a>, | ||
33 | pub(super) db: &'a RootDatabase, | ||
34 | pub(super) config: &'a CompletionConfig, | ||
35 | pub(super) position: FilePosition, | ||
36 | /// The token before the cursor, in the original file. | ||
37 | pub(super) original_token: SyntaxToken, | ||
38 | /// The token before the cursor, in the macro-expanded file. | ||
39 | pub(super) token: SyntaxToken, | ||
40 | pub(super) krate: Option<hir::Crate>, | ||
41 | pub(super) expected_type: Option<Type>, | ||
42 | pub(super) name_ref_syntax: Option<ast::NameRef>, | ||
43 | pub(super) function_syntax: Option<ast::Fn>, | ||
44 | pub(super) use_item_syntax: Option<ast::Use>, | ||
45 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | ||
46 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | ||
47 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | ||
48 | pub(super) impl_def: Option<ast::Impl>, | ||
49 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | ||
50 | pub(super) active_parameter: Option<ActiveParameter>, | ||
51 | pub(super) is_param: bool, | ||
52 | /// If a name-binding or reference to a const in a pattern. | ||
53 | /// Irrefutable patterns (like let) are excluded. | ||
54 | pub(super) is_pat_binding_or_const: bool, | ||
55 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | ||
56 | pub(super) is_trivial_path: bool, | ||
57 | /// If not a trivial path, the prefix (qualifier). | ||
58 | pub(super) path_qual: Option<ast::Path>, | ||
59 | pub(super) after_if: bool, | ||
60 | /// `true` if we are a statement or a last expr in the block. | ||
61 | pub(super) can_be_stmt: bool, | ||
62 | /// `true` if we expect an expression at the cursor position. | ||
63 | pub(super) is_expr: bool, | ||
64 | /// Something is typed at the "top" level, in module or impl/trait. | ||
65 | pub(super) is_new_item: bool, | ||
66 | /// The receiver if this is a field or method access, i.e. writing something.<|> | ||
67 | pub(super) dot_receiver: Option<ast::Expr>, | ||
68 | pub(super) dot_receiver_is_ambiguous_float_literal: bool, | ||
69 | /// If this is a call (method or function) in particular, i.e. the () are already there. | ||
70 | pub(super) is_call: bool, | ||
71 | /// Like `is_call`, but for tuple patterns. | ||
72 | pub(super) is_pattern_call: bool, | ||
73 | /// If this is a macro call, i.e. the () are already there. | ||
74 | pub(super) is_macro_call: bool, | ||
75 | pub(super) is_path_type: bool, | ||
76 | pub(super) has_type_args: bool, | ||
77 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
78 | pub(super) mod_declaration_under_caret: Option<ast::Module>, | ||
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) inside_impl_trait_block: bool, | ||
88 | pub(super) has_field_list_parent: bool, | ||
89 | pub(super) trait_as_prev_sibling: bool, | ||
90 | pub(super) impl_as_prev_sibling: bool, | ||
91 | pub(super) is_match_arm: bool, | ||
92 | pub(super) has_item_list_or_source_file_parent: bool, | ||
93 | pub(super) for_is_prev2: bool, | ||
94 | pub(super) fn_is_prev: bool, | ||
95 | pub(super) locals: Vec<(String, Local)>, | ||
96 | } | ||
97 | |||
98 | impl<'a> CompletionContext<'a> { | ||
99 | pub(super) fn new( | ||
100 | db: &'a RootDatabase, | ||
101 | position: FilePosition, | ||
102 | config: &'a CompletionConfig, | ||
103 | ) -> Option<CompletionContext<'a>> { | ||
104 | let sema = Semantics::new(db); | ||
105 | |||
106 | let original_file = sema.parse(position.file_id); | ||
107 | |||
108 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
109 | // to determine context, though the original_file will be used for | ||
110 | // actual completion. | ||
111 | let file_with_fake_ident = { | ||
112 | let parse = db.parse(position.file_id); | ||
113 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); | ||
114 | parse.reparse(&edit).tree() | ||
115 | }; | ||
116 | let fake_ident_token = | ||
117 | file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); | ||
118 | |||
119 | let krate = sema.to_module_def(position.file_id).map(|m| m.krate()); | ||
120 | let original_token = | ||
121 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
122 | let token = sema.descend_into_macros(original_token.clone()); | ||
123 | let scope = sema.scope_at_offset(&token.parent(), position.offset); | ||
124 | let mut locals = vec![]; | ||
125 | scope.process_all_names(&mut |name, scope| { | ||
126 | if let ScopeDef::Local(local) = scope { | ||
127 | locals.push((name.to_string(), local)); | ||
128 | } | ||
129 | }); | ||
130 | let mut ctx = CompletionContext { | ||
131 | sema, | ||
132 | scope, | ||
133 | db, | ||
134 | config, | ||
135 | original_token, | ||
136 | token, | ||
137 | position, | ||
138 | krate, | ||
139 | expected_type: None, | ||
140 | name_ref_syntax: None, | ||
141 | function_syntax: None, | ||
142 | use_item_syntax: None, | ||
143 | record_lit_syntax: None, | ||
144 | record_pat_syntax: None, | ||
145 | record_field_syntax: None, | ||
146 | impl_def: None, | ||
147 | active_parameter: ActiveParameter::at(db, position), | ||
148 | is_param: false, | ||
149 | is_pat_binding_or_const: false, | ||
150 | is_trivial_path: false, | ||
151 | path_qual: None, | ||
152 | after_if: false, | ||
153 | can_be_stmt: false, | ||
154 | is_expr: false, | ||
155 | is_new_item: false, | ||
156 | dot_receiver: None, | ||
157 | is_call: false, | ||
158 | is_pattern_call: false, | ||
159 | is_macro_call: false, | ||
160 | is_path_type: false, | ||
161 | has_type_args: false, | ||
162 | dot_receiver_is_ambiguous_float_literal: false, | ||
163 | attribute_under_caret: None, | ||
164 | mod_declaration_under_caret: None, | ||
165 | unsafe_is_prev: false, | ||
166 | in_loop_body: false, | ||
167 | ref_pat_parent: false, | ||
168 | bind_pat_parent: false, | ||
169 | block_expr_parent: false, | ||
170 | has_trait_parent: false, | ||
171 | has_impl_parent: false, | ||
172 | inside_impl_trait_block: false, | ||
173 | has_field_list_parent: false, | ||
174 | trait_as_prev_sibling: false, | ||
175 | impl_as_prev_sibling: false, | ||
176 | if_is_prev: false, | ||
177 | is_match_arm: false, | ||
178 | has_item_list_or_source_file_parent: false, | ||
179 | for_is_prev2: false, | ||
180 | fn_is_prev: false, | ||
181 | locals, | ||
182 | }; | ||
183 | |||
184 | let mut original_file = original_file.syntax().clone(); | ||
185 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | ||
186 | let mut offset = position.offset; | ||
187 | let mut fake_ident_token = fake_ident_token; | ||
188 | |||
189 | // Are we inside a macro call? | ||
190 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | ||
191 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | ||
192 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | ||
193 | ) { | ||
194 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) | ||
195 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) | ||
196 | { | ||
197 | break; | ||
198 | } | ||
199 | let hypothetical_args = match macro_call_with_fake_ident.token_tree() { | ||
200 | Some(tt) => tt, | ||
201 | None => break, | ||
202 | }; | ||
203 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | ||
204 | ctx.sema.expand(&actual_macro_call), | ||
205 | ctx.sema.speculative_expand( | ||
206 | &actual_macro_call, | ||
207 | &hypothetical_args, | ||
208 | fake_ident_token, | ||
209 | ), | ||
210 | ) { | ||
211 | let new_offset = hypothetical_expansion.1.text_range().start(); | ||
212 | if new_offset > actual_expansion.text_range().end() { | ||
213 | break; | ||
214 | } | ||
215 | original_file = actual_expansion; | ||
216 | hypothetical_file = hypothetical_expansion.0; | ||
217 | fake_ident_token = hypothetical_expansion.1; | ||
218 | offset = new_offset; | ||
219 | } else { | ||
220 | break; | ||
221 | } | ||
222 | } | ||
223 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | ||
224 | ctx.fill(&original_file, hypothetical_file, offset); | ||
225 | Some(ctx) | ||
226 | } | ||
227 | |||
228 | /// Checks whether completions in that particular case don't make much sense. | ||
229 | /// Examples: | ||
230 | /// - `fn <|>` -- we expect function name, it's unlikely that "hint" will be helpful. | ||
231 | /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names. | ||
232 | /// - `for _ i<|>` -- obviously, it'll be "in" keyword. | ||
233 | pub(crate) fn no_completion_required(&self) -> bool { | ||
234 | (self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2 | ||
235 | } | ||
236 | |||
237 | /// The range of the identifier that is being completed. | ||
238 | pub(crate) fn source_range(&self) -> TextRange { | ||
239 | // check kind of macro-expanded token, but use range of original token | ||
240 | let kind = self.token.kind(); | ||
241 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | ||
242 | mark::hit!(completes_if_prefix_is_keyword); | ||
243 | self.original_token.text_range() | ||
244 | } else { | ||
245 | TextRange::empty(self.position.offset) | ||
246 | } | ||
247 | } | ||
248 | |||
249 | pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> { | ||
250 | if let Some(record_field) = &self.record_field_syntax { | ||
251 | mark::hit!(record_field_type_match); | ||
252 | let (struct_field, _local) = self.sema.resolve_record_field(record_field)?; | ||
253 | Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db))) | ||
254 | } else if let Some(active_parameter) = &self.active_parameter { | ||
255 | mark::hit!(active_param_type_match); | ||
256 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
257 | } else { | ||
258 | None | ||
259 | } | ||
260 | } | ||
261 | |||
262 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
263 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
264 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
265 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
266 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
267 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
268 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
269 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
270 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
271 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
272 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
273 | self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
274 | self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); | ||
275 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
276 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
277 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
278 | self.has_item_list_or_source_file_parent = | ||
279 | has_item_list_or_source_file_parent(syntax_element.clone()); | ||
280 | self.mod_declaration_under_caret = | ||
281 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | ||
282 | .filter(|module| module.item_list().is_none()); | ||
283 | self.for_is_prev2 = for_is_prev2(syntax_element.clone()); | ||
284 | self.fn_is_prev = fn_is_prev(syntax_element.clone()); | ||
285 | } | ||
286 | |||
287 | fn fill( | ||
288 | &mut self, | ||
289 | original_file: &SyntaxNode, | ||
290 | file_with_fake_ident: SyntaxNode, | ||
291 | offset: TextSize, | ||
292 | ) { | ||
293 | // FIXME: this is wrong in at least two cases: | ||
294 | // * when there's no token `foo(<|>)` | ||
295 | // * when there is a token, but it happens to have type of it's own | ||
296 | self.expected_type = self | ||
297 | .token | ||
298 | .ancestors() | ||
299 | .find_map(|node| { | ||
300 | let ty = match_ast! { | ||
301 | match node { | ||
302 | ast::Pat(it) => self.sema.type_of_pat(&it), | ||
303 | ast::Expr(it) => self.sema.type_of_expr(&it), | ||
304 | _ => return None, | ||
305 | } | ||
306 | }; | ||
307 | Some(ty) | ||
308 | }) | ||
309 | .flatten(); | ||
310 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
311 | |||
312 | // First, let's try to complete a reference to some declaration. | ||
313 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | ||
314 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
315 | // See RFC#1685. | ||
316 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
317 | self.is_param = true; | ||
318 | return; | ||
319 | } | ||
320 | // FIXME: remove this (V) duplication and make the check more precise | ||
321 | if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
322 | self.record_pat_syntax = | ||
323 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
324 | } | ||
325 | self.classify_name_ref(original_file, name_ref, offset); | ||
326 | } | ||
327 | |||
328 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
329 | // suggest declaration names, see `CompletionKind::Magic`. | ||
330 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { | ||
331 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { | ||
332 | self.is_pat_binding_or_const = true; | ||
333 | if bind_pat.at_token().is_some() | ||
334 | || bind_pat.ref_token().is_some() | ||
335 | || bind_pat.mut_token().is_some() | ||
336 | { | ||
337 | self.is_pat_binding_or_const = false; | ||
338 | } | ||
339 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | ||
340 | self.is_pat_binding_or_const = false; | ||
341 | } | ||
342 | if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { | ||
343 | if let Some(pat) = let_stmt.pat() { | ||
344 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) | ||
345 | { | ||
346 | self.is_pat_binding_or_const = false; | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | if is_node::<ast::Param>(name.syntax()) { | ||
352 | self.is_param = true; | ||
353 | return; | ||
354 | } | ||
355 | // FIXME: remove this (^) duplication and make the check more precise | ||
356 | if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
357 | self.record_pat_syntax = | ||
358 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | |||
363 | fn classify_name_ref( | ||
364 | &mut self, | ||
365 | original_file: &SyntaxNode, | ||
366 | name_ref: ast::NameRef, | ||
367 | offset: TextSize, | ||
368 | ) { | ||
369 | self.name_ref_syntax = | ||
370 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | ||
371 | let name_range = name_ref.syntax().text_range(); | ||
372 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
373 | self.record_lit_syntax = | ||
374 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
375 | } | ||
376 | |||
377 | self.impl_def = self | ||
378 | .sema | ||
379 | .ancestors_with_macros(self.token.parent()) | ||
380 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
381 | .find_map(ast::Impl::cast); | ||
382 | |||
383 | let top_node = name_ref | ||
384 | .syntax() | ||
385 | .ancestors() | ||
386 | .take_while(|it| it.text_range() == name_range) | ||
387 | .last() | ||
388 | .unwrap(); | ||
389 | |||
390 | match top_node.parent().map(|it| it.kind()) { | ||
391 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | ||
392 | self.is_new_item = true; | ||
393 | return; | ||
394 | } | ||
395 | _ => (), | ||
396 | } | ||
397 | |||
398 | self.use_item_syntax = | ||
399 | self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast); | ||
400 | |||
401 | self.function_syntax = self | ||
402 | .sema | ||
403 | .ancestors_with_macros(self.token.parent()) | ||
404 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
405 | .find_map(ast::Fn::cast); | ||
406 | |||
407 | self.record_field_syntax = self | ||
408 | .sema | ||
409 | .ancestors_with_macros(self.token.parent()) | ||
410 | .take_while(|it| { | ||
411 | it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR | ||
412 | }) | ||
413 | .find_map(ast::RecordExprField::cast); | ||
414 | |||
415 | let parent = match name_ref.syntax().parent() { | ||
416 | Some(it) => it, | ||
417 | None => return, | ||
418 | }; | ||
419 | |||
420 | if let Some(segment) = ast::PathSegment::cast(parent.clone()) { | ||
421 | let path = segment.parent_path(); | ||
422 | self.is_call = path | ||
423 | .syntax() | ||
424 | .parent() | ||
425 | .and_then(ast::PathExpr::cast) | ||
426 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | ||
427 | .is_some(); | ||
428 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | ||
429 | self.is_pattern_call = | ||
430 | path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some(); | ||
431 | |||
432 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | ||
433 | self.has_type_args = segment.generic_arg_list().is_some(); | ||
434 | |||
435 | if let Some(path) = path_or_use_tree_qualifier(&path) { | ||
436 | self.path_qual = path | ||
437 | .segment() | ||
438 | .and_then(|it| { | ||
439 | find_node_with_range::<ast::PathSegment>( | ||
440 | original_file, | ||
441 | it.syntax().text_range(), | ||
442 | ) | ||
443 | }) | ||
444 | .map(|it| it.parent_path()); | ||
445 | return; | ||
446 | } | ||
447 | |||
448 | if let Some(segment) = path.segment() { | ||
449 | if segment.coloncolon_token().is_some() { | ||
450 | return; | ||
451 | } | ||
452 | } | ||
453 | |||
454 | self.is_trivial_path = true; | ||
455 | |||
456 | // Find either enclosing expr statement (thing with `;`) or a | ||
457 | // block. If block, check that we are the last expr. | ||
458 | self.can_be_stmt = name_ref | ||
459 | .syntax() | ||
460 | .ancestors() | ||
461 | .find_map(|node| { | ||
462 | if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { | ||
463 | return Some(stmt.syntax().text_range() == name_ref.syntax().text_range()); | ||
464 | } | ||
465 | if let Some(block) = ast::BlockExpr::cast(node) { | ||
466 | return Some( | ||
467 | block.expr().map(|e| e.syntax().text_range()) | ||
468 | == Some(name_ref.syntax().text_range()), | ||
469 | ); | ||
470 | } | ||
471 | None | ||
472 | }) | ||
473 | .unwrap_or(false); | ||
474 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | ||
475 | |||
476 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
477 | if let Some(if_expr) = | ||
478 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
479 | { | ||
480 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
481 | { | ||
482 | self.after_if = true; | ||
483 | } | ||
484 | } | ||
485 | } | ||
486 | } | ||
487 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | ||
488 | // The receiver comes before the point of insertion of the fake | ||
489 | // ident, so it should have the same range in the non-modified file | ||
490 | self.dot_receiver = field_expr | ||
491 | .expr() | ||
492 | .map(|e| e.syntax().text_range()) | ||
493 | .and_then(|r| find_node_with_range(original_file, r)); | ||
494 | self.dot_receiver_is_ambiguous_float_literal = | ||
495 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { | ||
496 | match l.kind() { | ||
497 | ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), | ||
498 | _ => false, | ||
499 | } | ||
500 | } else { | ||
501 | false | ||
502 | }; | ||
503 | } | ||
504 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | ||
505 | // As above | ||
506 | self.dot_receiver = method_call_expr | ||
507 | .receiver() | ||
508 | .map(|e| e.syntax().text_range()) | ||
509 | .and_then(|r| find_node_with_range(original_file, r)); | ||
510 | self.is_call = true; | ||
511 | } | ||
512 | } | ||
513 | } | ||
514 | |||
515 | fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { | ||
516 | find_covering_element(syntax, range).ancestors().find_map(N::cast) | ||
517 | } | ||
518 | |||
519 | fn is_node<N: AstNode>(node: &SyntaxNode) -> bool { | ||
520 | match node.ancestors().find_map(N::cast) { | ||
521 | None => false, | ||
522 | Some(n) => n.syntax().text_range() == node.text_range(), | ||
523 | } | ||
524 | } | ||
525 | |||
526 | fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> { | ||
527 | if let Some(qual) = path.qualifier() { | ||
528 | return Some(qual); | ||
529 | } | ||
530 | let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; | ||
531 | let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; | ||
532 | use_tree.path() | ||
533 | } | ||