aboutsummaryrefslogtreecommitdiff
path: root/crates/ssr/src/matching.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ssr/src/matching.rs')
-rw-r--r--crates/ssr/src/matching.rs816
1 files changed, 816 insertions, 0 deletions
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
new file mode 100644
index 000000000..948862a77
--- /dev/null
+++ b/crates/ssr/src/matching.rs
@@ -0,0 +1,816 @@
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
4use crate::{
5 parsing::{Constraint, NodeKind, Placeholder, Var},
6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
7 SsrMatches,
8};
9use base_db::FileRange;
10use hir::Semantics;
11use rustc_hash::FxHashMap;
12use std::{cell::Cell, iter::Peekable};
13use syntax::ast::{AstNode, AstToken};
14use syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
15use 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.
19macro_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.
41macro_rules! fail_match {
42 ($($args:tt)*) => {return Err(match_error!($($args)*))};
43}
44
45/// Information about a match that was found.
46#[derive(Debug)]
47pub 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/// Information about a placeholder bound in a match.
60#[derive(Debug)]
61pub(crate) struct PlaceholderMatch {
62 /// The node that the placeholder matched to. If set, then we'll search for further matches
63 /// within this node. It isn't set when we match tokens within a macro call's token tree.
64 pub(crate) node: Option<SyntaxNode>,
65 pub(crate) range: FileRange,
66 /// More matches, found within `node`.
67 pub(crate) inner_matches: SsrMatches,
68 /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
69 /// non-zero if the placeholder matched to the receiver of a method call.
70 pub(crate) autoderef_count: usize,
71 pub(crate) autoref_kind: ast::SelfParamKind,
72}
73
74#[derive(Debug)]
75pub(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)]
81pub(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.
90pub(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.
103struct 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.
113enum 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
121impl<'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_for_node(pattern) {
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 placeholder.ident.clone(),
187 PlaceholderMatch::new(Some(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 placeholder.ident.clone(),
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 // Check arguments.
540 let mut pattern_args = pattern_ufcs
541 .call_expr
542 .arg_list()
543 .ok_or_else(|| match_error!("Pattern function call has no args"))?
544 .args();
545 // If the function we're calling takes a self parameter, then we store additional
546 // information on the placeholder match about autoderef and autoref. This allows us to use
547 // the placeholder in a context where autoderef and autoref don't apply.
548 if code_resolved_function.self_param(self.sema.db).is_some() {
549 if let (Some(pattern_type), Some(expr)) =
550 (&pattern_ufcs.qualifier_type, &code.receiver())
551 {
552 let deref_count = self.check_expr_type(pattern_type, expr)?;
553 let pattern_receiver = pattern_args.next();
554 self.attempt_match_opt(phase, pattern_receiver.clone(), code.receiver())?;
555 if let Phase::Second(match_out) = phase {
556 if let Some(placeholder_value) = pattern_receiver
557 .and_then(|n| self.get_placeholder_for_node(n.syntax()))
558 .and_then(|placeholder| {
559 match_out.placeholder_values.get_mut(&placeholder.ident)
560 })
561 {
562 placeholder_value.autoderef_count = deref_count;
563 placeholder_value.autoref_kind = self
564 .sema
565 .resolve_method_call_as_callable(code)
566 .and_then(|callable| callable.receiver_param(self.sema.db))
567 .map(|self_param| self_param.kind())
568 .unwrap_or(ast::SelfParamKind::Owned);
569 }
570 }
571 }
572 } else {
573 self.attempt_match_opt(phase, pattern_args.next(), code.receiver())?;
574 }
575 let mut code_args =
576 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
577 loop {
578 match (pattern_args.next(), code_args.next()) {
579 (None, None) => return Ok(()),
580 (p, c) => self.attempt_match_opt(phase, p, c)?,
581 }
582 }
583 }
584
585 fn attempt_match_ufcs_to_ufcs(
586 &self,
587 phase: &mut Phase,
588 pattern_ufcs: &UfcsCallInfo,
589 code: &ast::CallExpr,
590 ) -> Result<(), MatchFailed> {
591 use ast::ArgListOwner;
592 // Check that the first argument is the expected type.
593 if let (Some(pattern_type), Some(expr)) = (
594 &pattern_ufcs.qualifier_type,
595 &code.arg_list().and_then(|code_args| code_args.args().next()),
596 ) {
597 self.check_expr_type(pattern_type, expr)?;
598 }
599 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
600 }
601
602 /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
603 /// times. Returns the number of times it needed to be dereferenced.
604 fn check_expr_type(
605 &self,
606 pattern_type: &hir::Type,
607 expr: &ast::Expr,
608 ) -> Result<usize, MatchFailed> {
609 use hir::HirDisplay;
610 let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| {
611 match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
612 })?;
613 // Temporary needed to make the borrow checker happy.
614 let res = code_type
615 .autoderef(self.sema.db)
616 .enumerate()
617 .find(|(_, deref_code_type)| pattern_type == deref_code_type)
618 .map(|(count, _)| count)
619 .ok_or_else(|| {
620 match_error!(
621 "Pattern type `{}` didn't match code type `{}`",
622 pattern_type.display(self.sema.db),
623 code_type.display(self.sema.db)
624 )
625 });
626 res
627 }
628
629 fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
630 self.get_placeholder(&SyntaxElement::Node(node.clone()))
631 }
632
633 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
634 only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
635 }
636}
637
638impl Match {
639 fn render_template_paths(
640 &mut self,
641 template: &ResolvedPattern,
642 sema: &Semantics<ide_db::RootDatabase>,
643 ) -> Result<(), MatchFailed> {
644 let module = sema
645 .scope(&self.matched_node)
646 .module()
647 .ok_or_else(|| match_error!("Matched node isn't in a module"))?;
648 for (path, resolved_path) in &template.resolved_paths {
649 if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
650 let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| {
651 match_error!("Failed to render template path `{}` at match location")
652 })?;
653 self.rendered_template_paths.insert(path.clone(), mod_path);
654 }
655 }
656 Ok(())
657 }
658}
659
660impl Phase<'_> {
661 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
662 loop {
663 let c = code_it.next();
664 if let Some(SyntaxElement::Token(t)) = &c {
665 self.record_ignored_comments(t);
666 if t.kind().is_trivia() {
667 continue;
668 }
669 }
670 return c;
671 }
672 }
673
674 fn record_ignored_comments(&mut self, token: &SyntaxToken) {
675 if token.kind() == SyntaxKind::COMMENT {
676 if let Phase::Second(match_out) = self {
677 if let Some(comment) = ast::Comment::cast(token.clone()) {
678 match_out.ignored_comments.push(comment);
679 }
680 }
681 }
682 }
683}
684
685fn is_closing_token(kind: SyntaxKind) -> bool {
686 kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
687}
688
689pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T
690where
691 F: Fn() -> T,
692{
693 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active));
694 let res = f();
695 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false));
696 res
697}
698
699// For performance reasons, we don't want to record the reason why every match fails, only the bit
700// of code that the user indicated they thought would match. We use a thread local to indicate when
701// we are trying to match that bit of code. This saves us having to pass a boolean into all the bits
702// of code that can make the decision to not match.
703thread_local! {
704 pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = Cell::new(false);
705}
706
707fn recording_match_fail_reasons() -> bool {
708 RECORDING_MATCH_FAIL_REASONS.with(|c| c.get())
709}
710
711impl PlaceholderMatch {
712 fn new(node: Option<&SyntaxNode>, range: FileRange) -> Self {
713 Self {
714 node: node.cloned(),
715 range,
716 inner_matches: SsrMatches::default(),
717 autoderef_count: 0,
718 autoref_kind: ast::SelfParamKind::Owned,
719 }
720 }
721
722 fn from_range(range: FileRange) -> Self {
723 Self::new(None, range)
724 }
725}
726
727impl NodeKind {
728 fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
729 let ok = match self {
730 Self::Literal => {
731 mark::hit!(literal_constraint);
732 ast::Literal::can_cast(node.kind())
733 }
734 };
735 if !ok {
736 fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
737 }
738 Ok(())
739 }
740}
741
742// If `node` contains nothing but an ident then return it, otherwise return None.
743fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
744 match element {
745 SyntaxElement::Token(t) => {
746 if t.kind() == SyntaxKind::IDENT {
747 return Some(t);
748 }
749 }
750 SyntaxElement::Node(n) => {
751 let mut children = n.children_with_tokens();
752 if let (Some(only_child), None) = (children.next(), children.next()) {
753 return only_ident(only_child);
754 }
755 }
756 }
757 None
758}
759
760struct PatternIterator {
761 iter: SyntaxElementChildren,
762}
763
764impl Iterator for PatternIterator {
765 type Item = SyntaxElement;
766
767 fn next(&mut self) -> Option<SyntaxElement> {
768 while let Some(element) = self.iter.next() {
769 if !element.kind().is_trivia() {
770 return Some(element);
771 }
772 }
773 None
774 }
775}
776
777impl PatternIterator {
778 fn new(parent: &SyntaxNode) -> Self {
779 Self { iter: parent.children_with_tokens() }
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use crate::{MatchFinder, SsrRule};
787
788 #[test]
789 fn parse_match_replace() {
790 let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
791 let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
792
793 let (db, position, selections) = crate::tests::single_file(input);
794 let mut match_finder = MatchFinder::in_context(&db, position, selections);
795 match_finder.add_rule(rule).unwrap();
796 let matches = match_finder.matches();
797 assert_eq!(matches.matches.len(), 1);
798 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
799 assert_eq!(matches.matches[0].placeholder_values.len(), 1);
800 assert_eq!(
801 matches.matches[0].placeholder_values[&Var("x".to_string())]
802 .node
803 .as_ref()
804 .unwrap()
805 .text(),
806 "1+2"
807 );
808
809 let edits = match_finder.edits();
810 assert_eq!(edits.len(), 1);
811 let edit = &edits[0];
812 let mut after = input.to_string();
813 edit.edit.apply(&mut after);
814 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
815 }
816}