diff options
Diffstat (limited to 'crates/ssr/src/matching.rs')
-rw-r--r-- | crates/ssr/src/matching.rs | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs new file mode 100644 index 000000000..ffc7202ae --- /dev/null +++ b/crates/ssr/src/matching.rs | |||
@@ -0,0 +1,777 @@ | |||
1 | //! This module is responsible for matching a search pattern against a node in the AST. In the | ||
2 | //! process of matching, placeholder values are recorded. | ||
3 | |||
4 | use crate::{ | ||
5 | parsing::{Constraint, NodeKind, Placeholder}, | ||
6 | resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, | ||
7 | SsrMatches, | ||
8 | }; | ||
9 | use base_db::FileRange; | ||
10 | use hir::Semantics; | ||
11 | use rustc_hash::FxHashMap; | ||
12 | use std::{cell::Cell, iter::Peekable}; | ||
13 | use syntax::ast::{AstNode, AstToken}; | ||
14 | use syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken}; | ||
15 | use test_utils::mark; | ||
16 | |||
17 | // Creates a match error. If we're currently attempting to match some code that we thought we were | ||
18 | // going to match, as indicated by the --debug-snippet flag, then populate the reason field. | ||
19 | macro_rules! match_error { | ||
20 | ($e:expr) => {{ | ||
21 | MatchFailed { | ||
22 | reason: if recording_match_fail_reasons() { | ||
23 | Some(format!("{}", $e)) | ||
24 | } else { | ||
25 | None | ||
26 | } | ||
27 | } | ||
28 | }}; | ||
29 | ($fmt:expr, $($arg:tt)+) => {{ | ||
30 | MatchFailed { | ||
31 | reason: if recording_match_fail_reasons() { | ||
32 | Some(format!($fmt, $($arg)+)) | ||
33 | } else { | ||
34 | None | ||
35 | } | ||
36 | } | ||
37 | }}; | ||
38 | } | ||
39 | |||
40 | // Fails the current match attempt, recording the supplied reason if we're recording match fail reasons. | ||
41 | macro_rules! fail_match { | ||
42 | ($($args:tt)*) => {return Err(match_error!($($args)*))}; | ||
43 | } | ||
44 | |||
45 | /// Information about a match that was found. | ||
46 | #[derive(Debug)] | ||
47 | pub struct Match { | ||
48 | pub(crate) range: FileRange, | ||
49 | pub(crate) matched_node: SyntaxNode, | ||
50 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, | ||
51 | pub(crate) ignored_comments: Vec<ast::Comment>, | ||
52 | pub(crate) rule_index: usize, | ||
53 | /// The depth of matched_node. | ||
54 | pub(crate) depth: usize, | ||
55 | // Each path in the template rendered for the module in which the match was found. | ||
56 | pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>, | ||
57 | } | ||
58 | |||
59 | /// Represents a `$var` in an SSR query. | ||
60 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
61 | pub(crate) struct Var(pub String); | ||
62 | |||
63 | /// Information about a placeholder bound in a match. | ||
64 | #[derive(Debug)] | ||
65 | pub(crate) struct PlaceholderMatch { | ||
66 | /// The node that the placeholder matched to. If set, then we'll search for further matches | ||
67 | /// within this node. It isn't set when we match tokens within a macro call's token tree. | ||
68 | pub(crate) node: Option<SyntaxNode>, | ||
69 | pub(crate) range: FileRange, | ||
70 | /// More matches, found within `node`. | ||
71 | pub(crate) inner_matches: SsrMatches, | ||
72 | } | ||
73 | |||
74 | #[derive(Debug)] | ||
75 | pub(crate) struct MatchFailureReason { | ||
76 | pub(crate) reason: String, | ||
77 | } | ||
78 | |||
79 | /// An "error" indicating that matching failed. Use the fail_match! macro to create and return this. | ||
80 | #[derive(Clone)] | ||
81 | pub(crate) struct MatchFailed { | ||
82 | /// The reason why we failed to match. Only present when debug_active true in call to | ||
83 | /// `get_match`. | ||
84 | pub(crate) reason: Option<String>, | ||
85 | } | ||
86 | |||
87 | /// Checks if `code` matches the search pattern found in `search_scope`, returning information about | ||
88 | /// the match, if it does. Since we only do matching in this module and searching is done by the | ||
89 | /// parent module, we don't populate nested matches. | ||
90 | pub(crate) fn get_match( | ||
91 | debug_active: bool, | ||
92 | rule: &ResolvedRule, | ||
93 | code: &SyntaxNode, | ||
94 | restrict_range: &Option<FileRange>, | ||
95 | sema: &Semantics<ide_db::RootDatabase>, | ||
96 | ) -> Result<Match, MatchFailed> { | ||
97 | record_match_fails_reasons_scope(debug_active, || { | ||
98 | Matcher::try_match(rule, code, restrict_range, sema) | ||
99 | }) | ||
100 | } | ||
101 | |||
102 | /// Checks if our search pattern matches a particular node of the AST. | ||
103 | struct Matcher<'db, 'sema> { | ||
104 | sema: &'sema Semantics<'db, ide_db::RootDatabase>, | ||
105 | /// If any placeholders come from anywhere outside of this range, then the match will be | ||
106 | /// rejected. | ||
107 | restrict_range: Option<FileRange>, | ||
108 | rule: &'sema ResolvedRule, | ||
109 | } | ||
110 | |||
111 | /// Which phase of matching we're currently performing. We do two phases because most attempted | ||
112 | /// matches will fail and it means we can defer more expensive checks to the second phase. | ||
113 | enum Phase<'a> { | ||
114 | /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded. | ||
115 | First, | ||
116 | /// On the second phase, we construct the `Match`. Things like what placeholders bind to is | ||
117 | /// recorded. | ||
118 | Second(&'a mut Match), | ||
119 | } | ||
120 | |||
121 | impl<'db, 'sema> Matcher<'db, 'sema> { | ||
122 | fn try_match( | ||
123 | rule: &ResolvedRule, | ||
124 | code: &SyntaxNode, | ||
125 | restrict_range: &Option<FileRange>, | ||
126 | sema: &'sema Semantics<'db, ide_db::RootDatabase>, | ||
127 | ) -> Result<Match, MatchFailed> { | ||
128 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; | ||
129 | // First pass at matching, where we check that node types and idents match. | ||
130 | match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?; | ||
131 | match_state.validate_range(&sema.original_range(code))?; | ||
132 | let mut the_match = Match { | ||
133 | range: sema.original_range(code), | ||
134 | matched_node: code.clone(), | ||
135 | placeholder_values: FxHashMap::default(), | ||
136 | ignored_comments: Vec::new(), | ||
137 | rule_index: rule.index, | ||
138 | depth: 0, | ||
139 | rendered_template_paths: FxHashMap::default(), | ||
140 | }; | ||
141 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do | ||
142 | // any other more expensive checks that we didn't want to do on the first pass. | ||
143 | match_state.attempt_match_node( | ||
144 | &mut Phase::Second(&mut the_match), | ||
145 | &rule.pattern.node, | ||
146 | code, | ||
147 | )?; | ||
148 | the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count(); | ||
149 | if let Some(template) = &rule.template { | ||
150 | the_match.render_template_paths(template, sema)?; | ||
151 | } | ||
152 | Ok(the_match) | ||
153 | } | ||
154 | |||
155 | /// Checks that `range` is within the permitted range if any. This is applicable when we're | ||
156 | /// processing a macro expansion and we want to fail the match if we're working with a node that | ||
157 | /// didn't originate from the token tree of the macro call. | ||
158 | fn validate_range(&self, range: &FileRange) -> Result<(), MatchFailed> { | ||
159 | if let Some(restrict_range) = &self.restrict_range { | ||
160 | if restrict_range.file_id != range.file_id | ||
161 | || !restrict_range.range.contains_range(range.range) | ||
162 | { | ||
163 | fail_match!("Node originated from a macro"); | ||
164 | } | ||
165 | } | ||
166 | Ok(()) | ||
167 | } | ||
168 | |||
169 | fn attempt_match_node( | ||
170 | &self, | ||
171 | phase: &mut Phase, | ||
172 | pattern: &SyntaxNode, | ||
173 | code: &SyntaxNode, | ||
174 | ) -> Result<(), MatchFailed> { | ||
175 | // Handle placeholders. | ||
176 | if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { | ||
177 | for constraint in &placeholder.constraints { | ||
178 | self.check_constraint(constraint, code)?; | ||
179 | } | ||
180 | if let Phase::Second(matches_out) = phase { | ||
181 | let original_range = self.sema.original_range(code); | ||
182 | // We validated the range for the node when we started the match, so the placeholder | ||
183 | // probably can't fail range validation, but just to be safe... | ||
184 | self.validate_range(&original_range)?; | ||
185 | matches_out.placeholder_values.insert( | ||
186 | Var(placeholder.ident.to_string()), | ||
187 | PlaceholderMatch::new(code, original_range), | ||
188 | ); | ||
189 | } | ||
190 | return Ok(()); | ||
191 | } | ||
192 | // We allow a UFCS call to match a method call, provided they resolve to the same function. | ||
193 | if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) { | ||
194 | if let Some(code) = ast::MethodCallExpr::cast(code.clone()) { | ||
195 | return self.attempt_match_ufcs_to_method_call(phase, pattern_ufcs, &code); | ||
196 | } | ||
197 | if let Some(code) = ast::CallExpr::cast(code.clone()) { | ||
198 | return self.attempt_match_ufcs_to_ufcs(phase, pattern_ufcs, &code); | ||
199 | } | ||
200 | } | ||
201 | if pattern.kind() != code.kind() { | ||
202 | fail_match!( | ||
203 | "Pattern had `{}` ({:?}), code had `{}` ({:?})", | ||
204 | pattern.text(), | ||
205 | pattern.kind(), | ||
206 | code.text(), | ||
207 | code.kind() | ||
208 | ); | ||
209 | } | ||
210 | // Some kinds of nodes have special handling. For everything else, we fall back to default | ||
211 | // matching. | ||
212 | match code.kind() { | ||
213 | SyntaxKind::RECORD_EXPR_FIELD_LIST => { | ||
214 | self.attempt_match_record_field_list(phase, pattern, code) | ||
215 | } | ||
216 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), | ||
217 | SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code), | ||
218 | _ => self.attempt_match_node_children(phase, pattern, code), | ||
219 | } | ||
220 | } | ||
221 | |||
222 | fn attempt_match_node_children( | ||
223 | &self, | ||
224 | phase: &mut Phase, | ||
225 | pattern: &SyntaxNode, | ||
226 | code: &SyntaxNode, | ||
227 | ) -> Result<(), MatchFailed> { | ||
228 | self.attempt_match_sequences( | ||
229 | phase, | ||
230 | PatternIterator::new(pattern), | ||
231 | code.children_with_tokens(), | ||
232 | ) | ||
233 | } | ||
234 | |||
235 | fn attempt_match_sequences( | ||
236 | &self, | ||
237 | phase: &mut Phase, | ||
238 | pattern_it: PatternIterator, | ||
239 | mut code_it: SyntaxElementChildren, | ||
240 | ) -> Result<(), MatchFailed> { | ||
241 | let mut pattern_it = pattern_it.peekable(); | ||
242 | loop { | ||
243 | match phase.next_non_trivial(&mut code_it) { | ||
244 | None => { | ||
245 | if let Some(p) = pattern_it.next() { | ||
246 | fail_match!("Part of the pattern was unmatched: {:?}", p); | ||
247 | } | ||
248 | return Ok(()); | ||
249 | } | ||
250 | Some(SyntaxElement::Token(c)) => { | ||
251 | self.attempt_match_token(phase, &mut pattern_it, &c)?; | ||
252 | } | ||
253 | Some(SyntaxElement::Node(c)) => match pattern_it.next() { | ||
254 | Some(SyntaxElement::Node(p)) => { | ||
255 | self.attempt_match_node(phase, &p, &c)?; | ||
256 | } | ||
257 | Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()), | ||
258 | None => fail_match!("Pattern reached end, code has {}", c.text()), | ||
259 | }, | ||
260 | } | ||
261 | } | ||
262 | } | ||
263 | |||
264 | fn attempt_match_token( | ||
265 | &self, | ||
266 | phase: &mut Phase, | ||
267 | pattern: &mut Peekable<PatternIterator>, | ||
268 | code: &syntax::SyntaxToken, | ||
269 | ) -> Result<(), MatchFailed> { | ||
270 | phase.record_ignored_comments(code); | ||
271 | // Ignore whitespace and comments. | ||
272 | if code.kind().is_trivia() { | ||
273 | return Ok(()); | ||
274 | } | ||
275 | if let Some(SyntaxElement::Token(p)) = pattern.peek() { | ||
276 | // If the code has a comma and the pattern is about to close something, then accept the | ||
277 | // comma without advancing the pattern. i.e. ignore trailing commas. | ||
278 | if code.kind() == SyntaxKind::COMMA && is_closing_token(p.kind()) { | ||
279 | return Ok(()); | ||
280 | } | ||
281 | // Conversely, if the pattern has a comma and the code doesn't, skip that part of the | ||
282 | // pattern and continue to match the code. | ||
283 | if p.kind() == SyntaxKind::COMMA && is_closing_token(code.kind()) { | ||
284 | pattern.next(); | ||
285 | } | ||
286 | } | ||
287 | // Consume an element from the pattern and make sure it matches. | ||
288 | match pattern.next() { | ||
289 | Some(SyntaxElement::Token(p)) => { | ||
290 | if p.kind() != code.kind() || p.text() != code.text() { | ||
291 | fail_match!( | ||
292 | "Pattern wanted token '{}' ({:?}), but code had token '{}' ({:?})", | ||
293 | p.text(), | ||
294 | p.kind(), | ||
295 | code.text(), | ||
296 | code.kind() | ||
297 | ) | ||
298 | } | ||
299 | } | ||
300 | Some(SyntaxElement::Node(p)) => { | ||
301 | // Not sure if this is actually reachable. | ||
302 | fail_match!( | ||
303 | "Pattern wanted {:?}, but code had token '{}' ({:?})", | ||
304 | p, | ||
305 | code.text(), | ||
306 | code.kind() | ||
307 | ); | ||
308 | } | ||
309 | None => { | ||
310 | fail_match!("Pattern exhausted, while code remains: `{}`", code.text()); | ||
311 | } | ||
312 | } | ||
313 | Ok(()) | ||
314 | } | ||
315 | |||
316 | fn check_constraint( | ||
317 | &self, | ||
318 | constraint: &Constraint, | ||
319 | code: &SyntaxNode, | ||
320 | ) -> Result<(), MatchFailed> { | ||
321 | match constraint { | ||
322 | Constraint::Kind(kind) => { | ||
323 | kind.matches(code)?; | ||
324 | } | ||
325 | Constraint::Not(sub) => { | ||
326 | if self.check_constraint(&*sub, code).is_ok() { | ||
327 | fail_match!("Constraint {:?} failed for '{}'", constraint, code.text()); | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | Ok(()) | ||
332 | } | ||
333 | |||
334 | /// Paths are matched based on whether they refer to the same thing, even if they're written | ||
335 | /// differently. | ||
336 | fn attempt_match_path( | ||
337 | &self, | ||
338 | phase: &mut Phase, | ||
339 | pattern: &SyntaxNode, | ||
340 | code: &SyntaxNode, | ||
341 | ) -> Result<(), MatchFailed> { | ||
342 | if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) { | ||
343 | let pattern_path = ast::Path::cast(pattern.clone()).unwrap(); | ||
344 | let code_path = ast::Path::cast(code.clone()).unwrap(); | ||
345 | if let (Some(pattern_segment), Some(code_segment)) = | ||
346 | (pattern_path.segment(), code_path.segment()) | ||
347 | { | ||
348 | // Match everything within the segment except for the name-ref, which is handled | ||
349 | // separately via comparing what the path resolves to below. | ||
350 | self.attempt_match_opt( | ||
351 | phase, | ||
352 | pattern_segment.generic_arg_list(), | ||
353 | code_segment.generic_arg_list(), | ||
354 | )?; | ||
355 | self.attempt_match_opt( | ||
356 | phase, | ||
357 | pattern_segment.param_list(), | ||
358 | code_segment.param_list(), | ||
359 | )?; | ||
360 | } | ||
361 | if matches!(phase, Phase::Second(_)) { | ||
362 | let resolution = self | ||
363 | .sema | ||
364 | .resolve_path(&code_path) | ||
365 | .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?; | ||
366 | if pattern_resolved.resolution != resolution { | ||
367 | fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text()); | ||
368 | } | ||
369 | } | ||
370 | } else { | ||
371 | return self.attempt_match_node_children(phase, pattern, code); | ||
372 | } | ||
373 | Ok(()) | ||
374 | } | ||
375 | |||
376 | fn attempt_match_opt<T: AstNode>( | ||
377 | &self, | ||
378 | phase: &mut Phase, | ||
379 | pattern: Option<T>, | ||
380 | code: Option<T>, | ||
381 | ) -> Result<(), MatchFailed> { | ||
382 | match (pattern, code) { | ||
383 | (Some(p), Some(c)) => self.attempt_match_node(phase, &p.syntax(), &c.syntax()), | ||
384 | (None, None) => Ok(()), | ||
385 | (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()), | ||
386 | (None, Some(c)) => { | ||
387 | fail_match!("Nothing in pattern to match code `{}`", c.syntax().text()) | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | /// We want to allow the records to match in any order, so we have special matching logic for | ||
393 | /// them. | ||
394 | fn attempt_match_record_field_list( | ||
395 | &self, | ||
396 | phase: &mut Phase, | ||
397 | pattern: &SyntaxNode, | ||
398 | code: &SyntaxNode, | ||
399 | ) -> Result<(), MatchFailed> { | ||
400 | // Build a map keyed by field name. | ||
401 | let mut fields_by_name = FxHashMap::default(); | ||
402 | for child in code.children() { | ||
403 | if let Some(record) = ast::RecordExprField::cast(child.clone()) { | ||
404 | if let Some(name) = record.field_name() { | ||
405 | fields_by_name.insert(name.text().clone(), child.clone()); | ||
406 | } | ||
407 | } | ||
408 | } | ||
409 | for p in pattern.children_with_tokens() { | ||
410 | if let SyntaxElement::Node(p) = p { | ||
411 | if let Some(name_element) = p.first_child_or_token() { | ||
412 | if self.get_placeholder(&name_element).is_some() { | ||
413 | // If the pattern is using placeholders for field names then order | ||
414 | // independence doesn't make sense. Fall back to regular ordered | ||
415 | // matching. | ||
416 | return self.attempt_match_node_children(phase, pattern, code); | ||
417 | } | ||
418 | if let Some(ident) = only_ident(name_element) { | ||
419 | let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| { | ||
420 | match_error!( | ||
421 | "Placeholder has record field '{}', but code doesn't", | ||
422 | ident | ||
423 | ) | ||
424 | })?; | ||
425 | self.attempt_match_node(phase, &p, &code_record)?; | ||
426 | } | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | if let Some(unmatched_fields) = fields_by_name.keys().next() { | ||
431 | fail_match!( | ||
432 | "{} field(s) of a record literal failed to match, starting with {}", | ||
433 | fields_by_name.len(), | ||
434 | unmatched_fields | ||
435 | ); | ||
436 | } | ||
437 | Ok(()) | ||
438 | } | ||
439 | |||
440 | /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token | ||
441 | /// tree it can match a sequence of tokens. Note, that this code will only be used when the | ||
442 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have | ||
443 | /// expanded the macro. | ||
444 | fn attempt_match_token_tree( | ||
445 | &self, | ||
446 | phase: &mut Phase, | ||
447 | pattern: &SyntaxNode, | ||
448 | code: &syntax::SyntaxNode, | ||
449 | ) -> Result<(), MatchFailed> { | ||
450 | let mut pattern = PatternIterator::new(pattern).peekable(); | ||
451 | let mut children = code.children_with_tokens(); | ||
452 | while let Some(child) = children.next() { | ||
453 | if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) { | ||
454 | pattern.next(); | ||
455 | let next_pattern_token = pattern | ||
456 | .peek() | ||
457 | .and_then(|p| match p { | ||
458 | SyntaxElement::Token(t) => Some(t.clone()), | ||
459 | SyntaxElement::Node(n) => n.first_token(), | ||
460 | }) | ||
461 | .map(|p| p.text().to_string()); | ||
462 | let first_matched_token = child.clone(); | ||
463 | let mut last_matched_token = child; | ||
464 | // Read code tokens util we reach one equal to the next token from our pattern | ||
465 | // or we reach the end of the token tree. | ||
466 | while let Some(next) = children.next() { | ||
467 | match &next { | ||
468 | SyntaxElement::Token(t) => { | ||
469 | if Some(t.to_string()) == next_pattern_token { | ||
470 | pattern.next(); | ||
471 | break; | ||
472 | } | ||
473 | } | ||
474 | SyntaxElement::Node(n) => { | ||
475 | if let Some(first_token) = n.first_token() { | ||
476 | if Some(first_token.to_string()) == next_pattern_token { | ||
477 | if let Some(SyntaxElement::Node(p)) = pattern.next() { | ||
478 | // We have a subtree that starts with the next token in our pattern. | ||
479 | self.attempt_match_token_tree(phase, &p, &n)?; | ||
480 | break; | ||
481 | } | ||
482 | } | ||
483 | } | ||
484 | } | ||
485 | }; | ||
486 | last_matched_token = next; | ||
487 | } | ||
488 | if let Phase::Second(match_out) = phase { | ||
489 | match_out.placeholder_values.insert( | ||
490 | Var(placeholder.ident.to_string()), | ||
491 | PlaceholderMatch::from_range(FileRange { | ||
492 | file_id: self.sema.original_range(code).file_id, | ||
493 | range: first_matched_token | ||
494 | .text_range() | ||
495 | .cover(last_matched_token.text_range()), | ||
496 | }), | ||
497 | ); | ||
498 | } | ||
499 | continue; | ||
500 | } | ||
501 | // Match literal (non-placeholder) tokens. | ||
502 | match child { | ||
503 | SyntaxElement::Token(token) => { | ||
504 | self.attempt_match_token(phase, &mut pattern, &token)?; | ||
505 | } | ||
506 | SyntaxElement::Node(node) => match pattern.next() { | ||
507 | Some(SyntaxElement::Node(p)) => { | ||
508 | self.attempt_match_token_tree(phase, &p, &node)?; | ||
509 | } | ||
510 | Some(SyntaxElement::Token(p)) => fail_match!( | ||
511 | "Pattern has token '{}', code has subtree '{}'", | ||
512 | p.text(), | ||
513 | node.text() | ||
514 | ), | ||
515 | None => fail_match!("Pattern has nothing, code has '{}'", node.text()), | ||
516 | }, | ||
517 | } | ||
518 | } | ||
519 | if let Some(p) = pattern.next() { | ||
520 | fail_match!("Reached end of token tree in code, but pattern still has {:?}", p); | ||
521 | } | ||
522 | Ok(()) | ||
523 | } | ||
524 | |||
525 | fn attempt_match_ufcs_to_method_call( | ||
526 | &self, | ||
527 | phase: &mut Phase, | ||
528 | pattern_ufcs: &UfcsCallInfo, | ||
529 | code: &ast::MethodCallExpr, | ||
530 | ) -> Result<(), MatchFailed> { | ||
531 | use ast::ArgListOwner; | ||
532 | let code_resolved_function = self | ||
533 | .sema | ||
534 | .resolve_method_call(code) | ||
535 | .ok_or_else(|| match_error!("Failed to resolve method call"))?; | ||
536 | if pattern_ufcs.function != code_resolved_function { | ||
537 | fail_match!("Method call resolved to a different function"); | ||
538 | } | ||
539 | if code_resolved_function.has_self_param(self.sema.db) { | ||
540 | if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) { | ||
541 | self.check_expr_type(pattern_type, expr)?; | ||
542 | } | ||
543 | } | ||
544 | // Check arguments. | ||
545 | let mut pattern_args = pattern_ufcs | ||
546 | .call_expr | ||
547 | .arg_list() | ||
548 | .ok_or_else(|| match_error!("Pattern function call has no args"))? | ||
549 | .args(); | ||
550 | self.attempt_match_opt(phase, pattern_args.next(), code.expr())?; | ||
551 | let mut code_args = | ||
552 | code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args(); | ||
553 | loop { | ||
554 | match (pattern_args.next(), code_args.next()) { | ||
555 | (None, None) => return Ok(()), | ||
556 | (p, c) => self.attempt_match_opt(phase, p, c)?, | ||
557 | } | ||
558 | } | ||
559 | } | ||
560 | |||
561 | fn attempt_match_ufcs_to_ufcs( | ||
562 | &self, | ||
563 | phase: &mut Phase, | ||
564 | pattern_ufcs: &UfcsCallInfo, | ||
565 | code: &ast::CallExpr, | ||
566 | ) -> Result<(), MatchFailed> { | ||
567 | use ast::ArgListOwner; | ||
568 | // Check that the first argument is the expected type. | ||
569 | if let (Some(pattern_type), Some(expr)) = ( | ||
570 | &pattern_ufcs.qualifier_type, | ||
571 | &code.arg_list().and_then(|code_args| code_args.args().next()), | ||
572 | ) { | ||
573 | self.check_expr_type(pattern_type, expr)?; | ||
574 | } | ||
575 | self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax()) | ||
576 | } | ||
577 | |||
578 | fn check_expr_type( | ||
579 | &self, | ||
580 | pattern_type: &hir::Type, | ||
581 | expr: &ast::Expr, | ||
582 | ) -> Result<(), MatchFailed> { | ||
583 | use hir::HirDisplay; | ||
584 | let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| { | ||
585 | match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) | ||
586 | })?; | ||
587 | if !code_type | ||
588 | .autoderef(self.sema.db) | ||
589 | .any(|deref_code_type| *pattern_type == deref_code_type) | ||
590 | { | ||
591 | fail_match!( | ||
592 | "Pattern type `{}` didn't match code type `{}`", | ||
593 | pattern_type.display(self.sema.db), | ||
594 | code_type.display(self.sema.db) | ||
595 | ); | ||
596 | } | ||
597 | Ok(()) | ||
598 | } | ||
599 | |||
600 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | ||
601 | only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident)) | ||
602 | } | ||
603 | } | ||
604 | |||
605 | impl Match { | ||
606 | fn render_template_paths( | ||
607 | &mut self, | ||
608 | template: &ResolvedPattern, | ||
609 | sema: &Semantics<ide_db::RootDatabase>, | ||
610 | ) -> Result<(), MatchFailed> { | ||
611 | let module = sema | ||
612 | .scope(&self.matched_node) | ||
613 | .module() | ||
614 | .ok_or_else(|| match_error!("Matched node isn't in a module"))?; | ||
615 | for (path, resolved_path) in &template.resolved_paths { | ||
616 | if let hir::PathResolution::Def(module_def) = resolved_path.resolution { | ||
617 | let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| { | ||
618 | match_error!("Failed to render template path `{}` at match location") | ||
619 | })?; | ||
620 | self.rendered_template_paths.insert(path.clone(), mod_path); | ||
621 | } | ||
622 | } | ||
623 | Ok(()) | ||
624 | } | ||
625 | } | ||
626 | |||
627 | impl Phase<'_> { | ||
628 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { | ||
629 | loop { | ||
630 | let c = code_it.next(); | ||
631 | if let Some(SyntaxElement::Token(t)) = &c { | ||
632 | self.record_ignored_comments(t); | ||
633 | if t.kind().is_trivia() { | ||
634 | continue; | ||
635 | } | ||
636 | } | ||
637 | return c; | ||
638 | } | ||
639 | } | ||
640 | |||
641 | fn record_ignored_comments(&mut self, token: &SyntaxToken) { | ||
642 | if token.kind() == SyntaxKind::COMMENT { | ||
643 | if let Phase::Second(match_out) = self { | ||
644 | if let Some(comment) = ast::Comment::cast(token.clone()) { | ||
645 | match_out.ignored_comments.push(comment); | ||
646 | } | ||
647 | } | ||
648 | } | ||
649 | } | ||
650 | } | ||
651 | |||
652 | fn is_closing_token(kind: SyntaxKind) -> bool { | ||
653 | kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK | ||
654 | } | ||
655 | |||
656 | pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T | ||
657 | where | ||
658 | F: Fn() -> T, | ||
659 | { | ||
660 | RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active)); | ||
661 | let res = f(); | ||
662 | RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false)); | ||
663 | res | ||
664 | } | ||
665 | |||
666 | // For performance reasons, we don't want to record the reason why every match fails, only the bit | ||
667 | // of code that the user indicated they thought would match. We use a thread local to indicate when | ||
668 | // we are trying to match that bit of code. This saves us having to pass a boolean into all the bits | ||
669 | // of code that can make the decision to not match. | ||
670 | thread_local! { | ||
671 | pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = Cell::new(false); | ||
672 | } | ||
673 | |||
674 | fn recording_match_fail_reasons() -> bool { | ||
675 | RECORDING_MATCH_FAIL_REASONS.with(|c| c.get()) | ||
676 | } | ||
677 | |||
678 | impl PlaceholderMatch { | ||
679 | fn new(node: &SyntaxNode, range: FileRange) -> Self { | ||
680 | Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() } | ||
681 | } | ||
682 | |||
683 | fn from_range(range: FileRange) -> Self { | ||
684 | Self { node: None, range, inner_matches: SsrMatches::default() } | ||
685 | } | ||
686 | } | ||
687 | |||
688 | impl NodeKind { | ||
689 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { | ||
690 | let ok = match self { | ||
691 | Self::Literal => { | ||
692 | mark::hit!(literal_constraint); | ||
693 | ast::Literal::can_cast(node.kind()) | ||
694 | } | ||
695 | }; | ||
696 | if !ok { | ||
697 | fail_match!("Code '{}' isn't of kind {:?}", node.text(), self); | ||
698 | } | ||
699 | Ok(()) | ||
700 | } | ||
701 | } | ||
702 | |||
703 | // If `node` contains nothing but an ident then return it, otherwise return None. | ||
704 | fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> { | ||
705 | match element { | ||
706 | SyntaxElement::Token(t) => { | ||
707 | if t.kind() == SyntaxKind::IDENT { | ||
708 | return Some(t); | ||
709 | } | ||
710 | } | ||
711 | SyntaxElement::Node(n) => { | ||
712 | let mut children = n.children_with_tokens(); | ||
713 | if let (Some(only_child), None) = (children.next(), children.next()) { | ||
714 | return only_ident(only_child); | ||
715 | } | ||
716 | } | ||
717 | } | ||
718 | None | ||
719 | } | ||
720 | |||
721 | struct PatternIterator { | ||
722 | iter: SyntaxElementChildren, | ||
723 | } | ||
724 | |||
725 | impl Iterator for PatternIterator { | ||
726 | type Item = SyntaxElement; | ||
727 | |||
728 | fn next(&mut self) -> Option<SyntaxElement> { | ||
729 | while let Some(element) = self.iter.next() { | ||
730 | if !element.kind().is_trivia() { | ||
731 | return Some(element); | ||
732 | } | ||
733 | } | ||
734 | None | ||
735 | } | ||
736 | } | ||
737 | |||
738 | impl PatternIterator { | ||
739 | fn new(parent: &SyntaxNode) -> Self { | ||
740 | Self { iter: parent.children_with_tokens() } | ||
741 | } | ||
742 | } | ||
743 | |||
744 | #[cfg(test)] | ||
745 | mod tests { | ||
746 | use super::*; | ||
747 | use crate::{MatchFinder, SsrRule}; | ||
748 | |||
749 | #[test] | ||
750 | fn parse_match_replace() { | ||
751 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); | ||
752 | let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }"; | ||
753 | |||
754 | let (db, position, selections) = crate::tests::single_file(input); | ||
755 | let mut match_finder = MatchFinder::in_context(&db, position, selections); | ||
756 | match_finder.add_rule(rule).unwrap(); | ||
757 | let matches = match_finder.matches(); | ||
758 | assert_eq!(matches.matches.len(), 1); | ||
759 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); | ||
760 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); | ||
761 | assert_eq!( | ||
762 | matches.matches[0].placeholder_values[&Var("x".to_string())] | ||
763 | .node | ||
764 | .as_ref() | ||
765 | .unwrap() | ||
766 | .text(), | ||
767 | "1+2" | ||
768 | ); | ||
769 | |||
770 | let edits = match_finder.edits(); | ||
771 | assert_eq!(edits.len(), 1); | ||
772 | let edit = &edits[0]; | ||
773 | let mut after = input.to_string(); | ||
774 | edit.edit.apply(&mut after); | ||
775 | assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }"); | ||
776 | } | ||
777 | } | ||