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.
5 parsing::{Constraint, NodeKind, Placeholder, Var},
6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
10 use ide_db::base_db::FileRange;
11 use rustc_hash::FxHashMap;
12 use std::{cell::Cell, iter::Peekable};
13 use syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
15 ast::{AstNode, AstToken},
19 // Creates a match error. If we're currently attempting to match some code that we thought we were
20 // going to match, as indicated by the --debug-snippet flag, then populate the reason field.
21 macro_rules! match_error {
24 reason: if recording_match_fail_reasons() {
25 Some(format!("{}", $e))
31 ($fmt:expr, $($arg:tt)+) => {{
33 reason: if recording_match_fail_reasons() {
34 Some(format!($fmt, $($arg)+))
42 // Fails the current match attempt, recording the supplied reason if we're recording match fail reasons.
43 macro_rules! fail_match {
44 ($($args:tt)*) => {return Err(match_error!($($args)*))};
47 /// Information about a match that was found.
50 pub(crate) range: FileRange,
51 pub(crate) matched_node: SyntaxNode,
52 pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
53 pub(crate) ignored_comments: Vec<ast::Comment>,
54 pub(crate) rule_index: usize,
55 /// The depth of matched_node.
56 pub(crate) depth: usize,
57 // Each path in the template rendered for the module in which the match was found.
58 pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
61 /// Information about a placeholder bound in a match.
63 pub(crate) struct PlaceholderMatch {
64 pub(crate) range: FileRange,
65 /// More matches, found within `node`.
66 pub(crate) inner_matches: SsrMatches,
67 /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
68 /// non-zero if the placeholder matched to the receiver of a method call.
69 pub(crate) autoderef_count: usize,
70 pub(crate) autoref_kind: ast::SelfParamKind,
74 pub(crate) struct MatchFailureReason {
75 pub(crate) reason: String,
78 /// An "error" indicating that matching failed. Use the fail_match! macro to create and return this.
80 pub(crate) struct MatchFailed {
81 /// The reason why we failed to match. Only present when debug_active true in call to
83 pub(crate) reason: Option<String>,
86 /// Checks if `code` matches the search pattern found in `search_scope`, returning information about
87 /// the match, if it does. Since we only do matching in this module and searching is done by the
88 /// parent module, we don't populate nested matches.
89 pub(crate) fn get_match(
93 restrict_range: &Option<FileRange>,
94 sema: &Semantics<ide_db::RootDatabase>,
95 ) -> Result<Match, MatchFailed> {
96 record_match_fails_reasons_scope(debug_active, || {
97 Matcher::try_match(rule, code, restrict_range, sema)
101 /// Checks if our search pattern matches a particular node of the AST.
102 struct Matcher<'db, 'sema> {
103 sema: &'sema Semantics<'db, ide_db::RootDatabase>,
104 /// If any placeholders come from anywhere outside of this range, then the match will be
106 restrict_range: Option<FileRange>,
107 rule: &'sema ResolvedRule,
110 /// Which phase of matching we're currently performing. We do two phases because most attempted
111 /// matches will fail and it means we can defer more expensive checks to the second phase.
113 /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded.
115 /// On the second phase, we construct the `Match`. Things like what placeholders bind to is
117 Second(&'a mut Match),
120 impl<'db, 'sema> Matcher<'db, 'sema> {
124 restrict_range: &Option<FileRange>,
125 sema: &'sema Semantics<'db, ide_db::RootDatabase>,
126 ) -> Result<Match, MatchFailed> {
127 let match_state = Matcher { sema, restrict_range: *restrict_range, rule };
128 // First pass at matching, where we check that node types and idents match.
129 match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
130 match_state.validate_range(&sema.original_range(code))?;
131 let mut the_match = Match {
132 range: sema.original_range(code),
133 matched_node: code.clone(),
134 placeholder_values: FxHashMap::default(),
135 ignored_comments: Vec::new(),
136 rule_index: rule.index,
138 rendered_template_paths: FxHashMap::default(),
140 // Second matching pass, where we record placeholder matches, ignored comments and maybe do
141 // any other more expensive checks that we didn't want to do on the first pass.
142 match_state.attempt_match_node(
143 &mut Phase::Second(&mut the_match),
147 the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count();
148 if let Some(template) = &rule.template {
149 the_match.render_template_paths(template, sema)?;
154 /// Checks that `range` is within the permitted range if any. This is applicable when we're
155 /// processing a macro expansion and we want to fail the match if we're working with a node that
156 /// didn't originate from the token tree of the macro call.
157 fn validate_range(&self, range: &FileRange) -> Result<(), MatchFailed> {
158 if let Some(restrict_range) = &self.restrict_range {
159 if restrict_range.file_id != range.file_id
160 || !restrict_range.range.contains_range(range.range)
162 fail_match!("Node originated from a macro");
168 fn attempt_match_node(
171 pattern: &SyntaxNode,
173 ) -> Result<(), MatchFailed> {
174 // Handle placeholders.
175 if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
176 for constraint in &placeholder.constraints {
177 self.check_constraint(constraint, code)?;
179 if let Phase::Second(matches_out) = phase {
180 let original_range = self.sema.original_range(code);
181 // We validated the range for the node when we started the match, so the placeholder
182 // probably can't fail range validation, but just to be safe...
183 self.validate_range(&original_range)?;
184 matches_out.placeholder_values.insert(
185 placeholder.ident.clone(),
186 PlaceholderMatch::from_range(original_range),
191 // We allow a UFCS call to match a method call, provided they resolve to the same function.
192 if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) {
193 if let Some(code) = ast::MethodCallExpr::cast(code.clone()) {
194 return self.attempt_match_ufcs_to_method_call(phase, pattern_ufcs, &code);
196 if let Some(code) = ast::CallExpr::cast(code.clone()) {
197 return self.attempt_match_ufcs_to_ufcs(phase, pattern_ufcs, &code);
200 if pattern.kind() != code.kind() {
202 "Pattern had `{}` ({:?}), code had `{}` ({:?})",
209 // Some kinds of nodes have special handling. For everything else, we fall back to default
212 SyntaxKind::RECORD_EXPR_FIELD_LIST => {
213 self.attempt_match_record_field_list(phase, pattern, code)
215 SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
216 SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
217 _ => self.attempt_match_node_children(phase, pattern, code),
221 fn attempt_match_node_children(
224 pattern: &SyntaxNode,
226 ) -> Result<(), MatchFailed> {
227 self.attempt_match_sequences(
229 PatternIterator::new(pattern),
230 code.children_with_tokens(),
234 fn attempt_match_sequences(
237 pattern_it: PatternIterator,
238 mut code_it: SyntaxElementChildren,
239 ) -> Result<(), MatchFailed> {
240 let mut pattern_it = pattern_it.peekable();
242 match phase.next_non_trivial(&mut code_it) {
244 if let Some(p) = pattern_it.next() {
245 fail_match!("Part of the pattern was unmatched: {:?}", p);
249 Some(SyntaxElement::Token(c)) => {
250 self.attempt_match_token(phase, &mut pattern_it, &c)?;
252 Some(SyntaxElement::Node(c)) => match pattern_it.next() {
253 Some(SyntaxElement::Node(p)) => {
254 self.attempt_match_node(phase, &p, &c)?;
256 Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
257 None => fail_match!("Pattern reached end, code has {}", c.text()),
263 fn attempt_match_token(
266 pattern: &mut Peekable<PatternIterator>,
267 code: &syntax::SyntaxToken,
268 ) -> Result<(), MatchFailed> {
269 phase.record_ignored_comments(code);
270 // Ignore whitespace and comments.
271 if code.kind().is_trivia() {
274 if let Some(SyntaxElement::Token(p)) = pattern.peek() {
275 // If the code has a comma and the pattern is about to close something, then accept the
276 // comma without advancing the pattern. i.e. ignore trailing commas.
277 if code.kind() == SyntaxKind::COMMA && is_closing_token(p.kind()) {
280 // Conversely, if the pattern has a comma and the code doesn't, skip that part of the
281 // pattern and continue to match the code.
282 if p.kind() == SyntaxKind::COMMA && is_closing_token(code.kind()) {
286 // Consume an element from the pattern and make sure it matches.
287 match pattern.next() {
288 Some(SyntaxElement::Token(p)) => {
289 if p.kind() != code.kind() || p.text() != code.text() {
291 "Pattern wanted token '{}' ({:?}), but code had token '{}' ({:?})",
299 Some(SyntaxElement::Node(p)) => {
300 // Not sure if this is actually reachable.
302 "Pattern wanted {:?}, but code had token '{}' ({:?})",
309 fail_match!("Pattern exhausted, while code remains: `{}`", code.text());
317 constraint: &Constraint,
319 ) -> Result<(), MatchFailed> {
321 Constraint::Kind(kind) => {
324 Constraint::Not(sub) => {
325 if self.check_constraint(&*sub, code).is_ok() {
326 fail_match!("Constraint {:?} failed for '{}'", constraint, code.text());
333 /// Paths are matched based on whether they refer to the same thing, even if they're written
335 fn attempt_match_path(
338 pattern: &SyntaxNode,
340 ) -> Result<(), MatchFailed> {
341 if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
342 let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
343 let code_path = ast::Path::cast(code.clone()).unwrap();
344 if let (Some(pattern_segment), Some(code_segment)) =
345 (pattern_path.segment(), code_path.segment())
347 // Match everything within the segment except for the name-ref, which is handled
348 // separately via comparing what the path resolves to below.
349 self.attempt_match_opt(
351 pattern_segment.generic_arg_list(),
352 code_segment.generic_arg_list(),
354 self.attempt_match_opt(
356 pattern_segment.param_list(),
357 code_segment.param_list(),
360 if matches!(phase, Phase::Second(_)) {
361 let resolution = self
363 .resolve_path(&code_path)
364 .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
365 if pattern_resolved.resolution != resolution {
366 fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
370 return self.attempt_match_node_children(phase, pattern, code);
375 fn attempt_match_opt<T: AstNode>(
380 ) -> Result<(), MatchFailed> {
381 match (pattern, code) {
382 (Some(p), Some(c)) => self.attempt_match_node(phase, p.syntax(), c.syntax()),
383 (None, None) => Ok(()),
384 (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
386 fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
391 /// We want to allow the records to match in any order, so we have special matching logic for
393 fn attempt_match_record_field_list(
396 pattern: &SyntaxNode,
398 ) -> Result<(), MatchFailed> {
399 // Build a map keyed by field name.
400 let mut fields_by_name: FxHashMap<SmolStr, SyntaxNode> = FxHashMap::default();
401 for child in code.children() {
402 if let Some(record) = ast::RecordExprField::cast(child.clone()) {
403 if let Some(name) = record.field_name() {
404 fields_by_name.insert(name.text().into(), child.clone());
408 for p in pattern.children_with_tokens() {
409 if let SyntaxElement::Node(p) = p {
410 if let Some(name_element) = p.first_child_or_token() {
411 if self.get_placeholder(&name_element).is_some() {
412 // If the pattern is using placeholders for field names then order
413 // independence doesn't make sense. Fall back to regular ordered
415 return self.attempt_match_node_children(phase, pattern, code);
417 if let Some(ident) = only_ident(name_element) {
418 let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
420 "Placeholder has record field '{}', but code doesn't",
424 self.attempt_match_node(phase, &p, &code_record)?;
429 if let Some(unmatched_fields) = fields_by_name.keys().next() {
431 "{} field(s) of a record literal failed to match, starting with {}",
432 fields_by_name.len(),
439 /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
440 /// tree it can match a sequence of tokens. Note, that this code will only be used when the
441 /// pattern matches the macro invocation. For matches within the macro call, we'll already have
442 /// expanded the macro.
443 fn attempt_match_token_tree(
446 pattern: &SyntaxNode,
447 code: &syntax::SyntaxNode,
448 ) -> Result<(), MatchFailed> {
449 let mut pattern = PatternIterator::new(pattern).peekable();
450 let mut children = code.children_with_tokens();
451 while let Some(child) = children.next() {
452 if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) {
454 let next_pattern_token = pattern
456 .and_then(|p| match p {
457 SyntaxElement::Token(t) => Some(t.clone()),
458 SyntaxElement::Node(n) => n.first_token(),
460 .map(|p| p.text().to_string());
461 let first_matched_token = child.clone();
462 let mut last_matched_token = child;
463 // Read code tokens util we reach one equal to the next token from our pattern
464 // or we reach the end of the token tree.
465 for next in &mut children {
467 SyntaxElement::Token(t) => {
468 if Some(t.to_string()) == next_pattern_token {
473 SyntaxElement::Node(n) => {
474 if let Some(first_token) = n.first_token() {
475 if Some(first_token.text()) == next_pattern_token.as_deref() {
476 if let Some(SyntaxElement::Node(p)) = pattern.next() {
477 // We have a subtree that starts with the next token in our pattern.
478 self.attempt_match_token_tree(phase, &p, n)?;
485 last_matched_token = next;
487 if let Phase::Second(match_out) = phase {
488 match_out.placeholder_values.insert(
489 placeholder.ident.clone(),
490 PlaceholderMatch::from_range(FileRange {
491 file_id: self.sema.original_range(code).file_id,
492 range: first_matched_token
494 .cover(last_matched_token.text_range()),
500 // Match literal (non-placeholder) tokens.
502 SyntaxElement::Token(token) => {
503 self.attempt_match_token(phase, &mut pattern, &token)?;
505 SyntaxElement::Node(node) => match pattern.next() {
506 Some(SyntaxElement::Node(p)) => {
507 self.attempt_match_token_tree(phase, &p, &node)?;
509 Some(SyntaxElement::Token(p)) => fail_match!(
510 "Pattern has token '{}', code has subtree '{}'",
514 None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
518 if let Some(p) = pattern.next() {
519 fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
524 fn attempt_match_ufcs_to_method_call(
527 pattern_ufcs: &UfcsCallInfo,
528 code: &ast::MethodCallExpr,
529 ) -> Result<(), MatchFailed> {
531 let code_resolved_function = self
533 .resolve_method_call(code)
534 .ok_or_else(|| match_error!("Failed to resolve method call"))?;
535 if pattern_ufcs.function != code_resolved_function {
536 fail_match!("Method call resolved to a different function");
539 let mut pattern_args = pattern_ufcs
542 .ok_or_else(|| match_error!("Pattern function call has no args"))?
544 // If the function we're calling takes a self parameter, then we store additional
545 // information on the placeholder match about autoderef and autoref. This allows us to use
546 // the placeholder in a context where autoderef and autoref don't apply.
547 if code_resolved_function.self_param(self.sema.db).is_some() {
548 if let (Some(pattern_type), Some(expr)) =
549 (&pattern_ufcs.qualifier_type, &code.receiver())
551 let deref_count = self.check_expr_type(pattern_type, expr)?;
552 let pattern_receiver = pattern_args.next();
553 self.attempt_match_opt(phase, pattern_receiver.clone(), code.receiver())?;
554 if let Phase::Second(match_out) = phase {
555 if let Some(placeholder_value) = pattern_receiver
556 .and_then(|n| self.get_placeholder_for_node(n.syntax()))
557 .and_then(|placeholder| {
558 match_out.placeholder_values.get_mut(&placeholder.ident)
561 placeholder_value.autoderef_count = deref_count;
562 placeholder_value.autoref_kind = self
564 .resolve_method_call_as_callable(code)
565 .and_then(|callable| callable.receiver_param(self.sema.db))
566 .map(|self_param| self_param.kind())
567 .unwrap_or(ast::SelfParamKind::Owned);
572 self.attempt_match_opt(phase, pattern_args.next(), code.receiver())?;
575 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
577 match (pattern_args.next(), code_args.next()) {
578 (None, None) => return Ok(()),
579 (p, c) => self.attempt_match_opt(phase, p, c)?,
584 fn attempt_match_ufcs_to_ufcs(
587 pattern_ufcs: &UfcsCallInfo,
588 code: &ast::CallExpr,
589 ) -> Result<(), MatchFailed> {
591 // Check that the first argument is the expected type.
592 if let (Some(pattern_type), Some(expr)) = (
593 &pattern_ufcs.qualifier_type,
594 &code.arg_list().and_then(|code_args| code_args.args().next()),
596 self.check_expr_type(pattern_type, expr)?;
598 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
601 /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
602 /// times. Returns the number of times it needed to be dereferenced.
605 pattern_type: &hir::Type,
607 ) -> Result<usize, MatchFailed> {
613 match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
616 // Temporary needed to make the borrow checker happy.
618 .autoderef(self.sema.db)
620 .find(|(_, deref_code_type)| pattern_type == deref_code_type)
621 .map(|(count, _)| count)
624 "Pattern type `{}` didn't match code type `{}`",
625 pattern_type.display(self.sema.db),
626 code_type.display(self.sema.db)
632 fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
633 self.get_placeholder(&SyntaxElement::Node(node.clone()))
636 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
637 only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
642 fn render_template_paths(
644 template: &ResolvedPattern,
645 sema: &Semantics<ide_db::RootDatabase>,
646 ) -> Result<(), MatchFailed> {
648 .scope(&self.matched_node)
650 .ok_or_else(|| match_error!("Matched node isn't in a module"))?;
651 for (path, resolved_path) in &template.resolved_paths {
652 if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
653 let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| {
654 match_error!("Failed to render template path `{}` at match location")
656 self.rendered_template_paths.insert(path.clone(), mod_path);
664 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
666 let c = code_it.next();
667 if let Some(SyntaxElement::Token(t)) = &c {
668 self.record_ignored_comments(t);
669 if t.kind().is_trivia() {
677 fn record_ignored_comments(&mut self, token: &SyntaxToken) {
678 if token.kind() == SyntaxKind::COMMENT {
679 if let Phase::Second(match_out) = self {
680 if let Some(comment) = ast::Comment::cast(token.clone()) {
681 match_out.ignored_comments.push(comment);
688 fn is_closing_token(kind: SyntaxKind) -> bool {
689 kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
692 pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T
696 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active));
698 RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false));
702 // For performance reasons, we don't want to record the reason why every match fails, only the bit
703 // of code that the user indicated they thought would match. We use a thread local to indicate when
704 // we are trying to match that bit of code. This saves us having to pass a boolean into all the bits
705 // of code that can make the decision to not match.
707 pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = Cell::new(false);
710 fn recording_match_fail_reasons() -> bool {
711 RECORDING_MATCH_FAIL_REASONS.with(|c| c.get())
714 impl PlaceholderMatch {
715 fn from_range(range: FileRange) -> Self {
718 inner_matches: SsrMatches::default(),
720 autoref_kind: ast::SelfParamKind::Owned,
726 fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
727 let ok = match self {
729 cov_mark::hit!(literal_constraint);
730 ast::Literal::can_cast(node.kind())
734 fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
740 // If `node` contains nothing but an ident then return it, otherwise return None.
741 fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
743 SyntaxElement::Token(t) => {
744 if t.kind() == SyntaxKind::IDENT {
748 SyntaxElement::Node(n) => {
749 let mut children = n.children_with_tokens();
750 if let (Some(only_child), None) = (children.next(), children.next()) {
751 return only_ident(only_child);
758 struct PatternIterator {
759 iter: SyntaxElementChildren,
762 impl Iterator for PatternIterator {
763 type Item = SyntaxElement;
765 fn next(&mut self) -> Option<SyntaxElement> {
766 for element in &mut self.iter {
767 if !element.kind().is_trivia() {
768 return Some(element);
775 impl PatternIterator {
776 fn new(parent: &SyntaxNode) -> Self {
777 Self { iter: parent.children_with_tokens() }
783 use crate::{MatchFinder, SsrRule};
786 fn parse_match_replace() {
787 let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
788 let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
790 let (db, position, selections) = crate::tests::single_file(input);
791 let mut match_finder = MatchFinder::in_context(&db, position, selections);
792 match_finder.add_rule(rule).unwrap();
793 let matches = match_finder.matches();
794 assert_eq!(matches.matches.len(), 1);
795 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
796 assert_eq!(matches.matches[0].placeholder_values.len(), 1);
798 let edits = match_finder.edits();
799 assert_eq!(edits.len(), 1);
800 let edit = &edits[&position.file_id];
801 let mut after = input.to_string();
802 edit.apply(&mut after);
803 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");