ide_ssr/
matching.rs

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    SsrMatches,
6    parsing::{Constraint, NodeKind, Placeholder, Var},
7    resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
8};
9use hir::{FileRange, ImportPathConfig, Semantics};
10use ide_db::{FxHashMap, base_db::RootQueryDb};
11use std::{cell::Cell, iter::Peekable};
12use syntax::{
13    SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken,
14    ast::{self, AstNode, AstToken, HasGenericArgs},
15};
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    pub(crate) range: FileRange,
63    /// More matches, found within `node`.
64    pub(crate) inner_matches: SsrMatches,
65    /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
66    /// non-zero if the placeholder matched to the receiver of a method call.
67    pub(crate) autoderef_count: usize,
68    pub(crate) autoref_kind: ast::SelfParamKind,
69}
70
71#[derive(Debug)]
72pub(crate) struct MatchFailureReason {
73    pub(crate) reason: String,
74}
75
76/// An "error" indicating that matching failed. Use the fail_match! macro to create and return this.
77#[derive(Clone)]
78pub(crate) struct MatchFailed {
79    /// The reason why we failed to match. Only present when debug_active true in call to
80    /// `get_match`.
81    pub(crate) reason: Option<String>,
82}
83
84/// Checks if `code` matches the search pattern found in `search_scope`, returning information about
85/// the match, if it does. Since we only do matching in this module and searching is done by the
86/// parent module, we don't populate nested matches.
87pub(crate) fn get_match<'db>(
88    debug_active: bool,
89    rule: &ResolvedRule<'db>,
90    code: &SyntaxNode,
91    restrict_range: &Option<FileRange>,
92    sema: &Semantics<'db, ide_db::RootDatabase>,
93) -> Result<Match, MatchFailed> {
94    record_match_fails_reasons_scope(debug_active, || {
95        Matcher::try_match(rule, code, restrict_range, sema)
96    })
97}
98
99/// Checks if our search pattern matches a particular node of the AST.
100struct Matcher<'db, 'sema> {
101    sema: &'sema Semantics<'db, ide_db::RootDatabase>,
102    /// If any placeholders come from anywhere outside of this range, then the match will be
103    /// rejected.
104    restrict_range: Option<FileRange>,
105    rule: &'sema ResolvedRule<'db>,
106}
107
108/// Which phase of matching we're currently performing. We do two phases because most attempted
109/// matches will fail and it means we can defer more expensive checks to the second phase.
110enum Phase<'a> {
111    /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded.
112    First,
113    /// On the second phase, we construct the `Match`. Things like what placeholders bind to is
114    /// recorded.
115    Second(&'a mut Match),
116}
117
118impl<'db, 'sema> Matcher<'db, 'sema> {
119    fn try_match(
120        rule: &ResolvedRule<'db>,
121        code: &SyntaxNode,
122        restrict_range: &Option<FileRange>,
123        sema: &'sema Semantics<'db, ide_db::RootDatabase>,
124    ) -> Result<Match, MatchFailed> {
125        let match_state = Matcher { sema, restrict_range: *restrict_range, rule };
126        // First pass at matching, where we check that node types and idents match.
127        match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
128        let file_range = sema
129            .original_range_opt(code)
130            .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?;
131        match_state.validate_range(&file_range)?;
132        let mut the_match = Match {
133            range: file_range,
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            && (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        Ok(())
166    }
167
168    fn attempt_match_node(
169        &self,
170        phase: &mut Phase<'_>,
171        pattern: &SyntaxNode,
172        code: &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)?;
178            }
179            if let Phase::Second(matches_out) = phase {
180                let original_range = self
181                    .sema
182                    .original_range_opt(code)
183                    .ok_or(MatchFailed { reason: Some("def site definition".to_owned()) })?;
184                // We validated the range for the node when we started the match, so the placeholder
185                // probably can't fail range validation, but just to be safe...
186                self.validate_range(&original_range)?;
187                matches_out.placeholder_values.insert(
188                    placeholder.ident.clone(),
189                    PlaceholderMatch::from_range(original_range),
190                );
191            }
192            return Ok(());
193        }
194        // We allow a UFCS call to match a method call, provided they resolve to the same function.
195        if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) {
196            if let Some(code) = ast::MethodCallExpr::cast(code.clone()) {
197                return self.attempt_match_ufcs_to_method_call(phase, pattern_ufcs, &code);
198            }
199            if let Some(code) = ast::CallExpr::cast(code.clone()) {
200                return self.attempt_match_ufcs_to_ufcs(phase, pattern_ufcs, &code);
201            }
202        }
203        if pattern.kind() != code.kind() {
204            fail_match!(
205                "Pattern had `{}` ({:?}), code had `{}` ({:?})",
206                pattern.text(),
207                pattern.kind(),
208                code.text(),
209                code.kind()
210            );
211        }
212        // Some kinds of nodes have special handling. For everything else, we fall back to default
213        // matching.
214        match code.kind() {
215            SyntaxKind::RECORD_EXPR_FIELD_LIST => {
216                self.attempt_match_record_field_list(phase, pattern, code)
217            }
218            SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
219            SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
220            _ => self.attempt_match_node_children(phase, pattern, code),
221        }
222    }
223
224    fn attempt_match_node_children(
225        &self,
226        phase: &mut Phase<'_>,
227        pattern: &SyntaxNode,
228        code: &SyntaxNode,
229    ) -> Result<(), MatchFailed> {
230        self.attempt_match_sequences(
231            phase,
232            PatternIterator::new(pattern),
233            code.children_with_tokens(),
234        )
235    }
236
237    fn attempt_match_sequences(
238        &self,
239        phase: &mut Phase<'_>,
240        pattern_it: PatternIterator,
241        mut code_it: SyntaxElementChildren,
242    ) -> Result<(), MatchFailed> {
243        let mut pattern_it = pattern_it.peekable();
244        loop {
245            match phase.next_non_trivial(&mut code_it) {
246                None => {
247                    if let Some(p) = pattern_it.next() {
248                        fail_match!("Part of the pattern was unmatched: {:?}", p);
249                    }
250                    return Ok(());
251                }
252                Some(SyntaxElement::Token(c)) => {
253                    self.attempt_match_token(phase, &mut pattern_it, &c)?;
254                }
255                Some(SyntaxElement::Node(c)) => match pattern_it.next() {
256                    Some(SyntaxElement::Node(p)) => {
257                        self.attempt_match_node(phase, &p, &c)?;
258                    }
259                    Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
260                    None => fail_match!("Pattern reached end, code has {}", c.text()),
261                },
262            }
263        }
264    }
265
266    fn attempt_match_token(
267        &self,
268        phase: &mut Phase<'_>,
269        pattern: &mut Peekable<PatternIterator>,
270        code: &syntax::SyntaxToken,
271    ) -> Result<(), MatchFailed> {
272        phase.record_ignored_comments(code);
273        // Ignore whitespace and comments.
274        if code.kind().is_trivia() {
275            return Ok(());
276        }
277        if let Some(SyntaxElement::Token(p)) = pattern.peek() {
278            // If the code has a comma and the pattern is about to close something, then accept the
279            // comma without advancing the pattern. i.e. ignore trailing commas.
280            if code.kind() == SyntaxKind::COMMA && is_closing_token(p.kind()) {
281                return Ok(());
282            }
283            // Conversely, if the pattern has a comma and the code doesn't, skip that part of the
284            // pattern and continue to match the code.
285            if p.kind() == SyntaxKind::COMMA && is_closing_token(code.kind()) {
286                pattern.next();
287            }
288        }
289        // Consume an element from the pattern and make sure it matches.
290        match pattern.next() {
291            Some(SyntaxElement::Token(p)) => {
292                if p.kind() != code.kind() || p.text() != code.text() {
293                    fail_match!(
294                        "Pattern wanted token '{}' ({:?}), but code had token '{}' ({:?})",
295                        p.text(),
296                        p.kind(),
297                        code.text(),
298                        code.kind()
299                    )
300                }
301            }
302            Some(SyntaxElement::Node(p)) => {
303                // Not sure if this is actually reachable.
304                fail_match!(
305                    "Pattern wanted {:?}, but code had token '{}' ({:?})",
306                    p,
307                    code.text(),
308                    code.kind()
309                );
310            }
311            None => {
312                fail_match!("Pattern exhausted, while code remains: `{}`", code.text());
313            }
314        }
315        Ok(())
316    }
317
318    #[allow(clippy::only_used_in_recursion)]
319    fn check_constraint(
320        &self,
321        constraint: &Constraint,
322        code: &SyntaxNode,
323    ) -> Result<(), MatchFailed> {
324        match constraint {
325            Constraint::Kind(kind) => {
326                kind.matches(code)?;
327            }
328            Constraint::Not(sub) => {
329                if self.check_constraint(sub, code).is_ok() {
330                    fail_match!("Constraint {:?} failed for '{}'", constraint, code.text());
331                }
332            }
333        }
334        Ok(())
335    }
336
337    /// Paths are matched based on whether they refer to the same thing, even if they're written
338    /// differently.
339    fn attempt_match_path(
340        &self,
341        phase: &mut Phase<'_>,
342        pattern: &SyntaxNode,
343        code: &SyntaxNode,
344    ) -> Result<(), MatchFailed> {
345        if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
346            let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
347            let code_path = ast::Path::cast(code.clone()).unwrap();
348            if let (Some(pattern_segment), Some(code_segment)) =
349                (pattern_path.segment(), code_path.segment())
350            {
351                // Match everything within the segment except for the name-ref, which is handled
352                // separately via comparing what the path resolves to below.
353                self.attempt_match_opt(
354                    phase,
355                    pattern_segment.generic_arg_list(),
356                    code_segment.generic_arg_list(),
357                )?;
358                self.attempt_match_opt(
359                    phase,
360                    pattern_segment.parenthesized_arg_list(),
361                    code_segment.parenthesized_arg_list(),
362                )?;
363            }
364            if matches!(phase, Phase::Second(_)) {
365                let resolution = self
366                    .sema
367                    .resolve_path(&code_path)
368                    .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
369                if pattern_resolved.resolution != resolution {
370                    fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
371                }
372            }
373        } else {
374            return self.attempt_match_node_children(phase, pattern, code);
375        }
376        Ok(())
377    }
378
379    fn attempt_match_opt<T: AstNode>(
380        &self,
381        phase: &mut Phase<'_>,
382        pattern: Option<T>,
383        code: Option<T>,
384    ) -> Result<(), MatchFailed> {
385        match (pattern, code) {
386            (Some(p), Some(c)) => self.attempt_match_node(phase, p.syntax(), c.syntax()),
387            (None, None) => Ok(()),
388            (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
389            (None, Some(c)) => {
390                fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
391            }
392        }
393    }
394
395    /// We want to allow the records to match in any order, so we have special matching logic for
396    /// them.
397    fn attempt_match_record_field_list(
398        &self,
399        phase: &mut Phase<'_>,
400        pattern: &SyntaxNode,
401        code: &SyntaxNode,
402    ) -> Result<(), MatchFailed> {
403        // Build a map keyed by field name.
404        let mut fields_by_name: FxHashMap<SmolStr, SyntaxNode> = FxHashMap::default();
405        for child in code.children() {
406            if let Some(record) = ast::RecordExprField::cast(child.clone())
407                && let Some(name) = record.field_name()
408            {
409                fields_by_name.insert(name.text().into(), child.clone());
410            }
411        }
412        for p in pattern.children_with_tokens() {
413            if let SyntaxElement::Node(p) = p
414                && let Some(name_element) = p.first_child_or_token()
415            {
416                if self.get_placeholder(&name_element).is_some() {
417                    // If the pattern is using placeholders for field names then order
418                    // independence doesn't make sense. Fall back to regular ordered
419                    // matching.
420                    return self.attempt_match_node_children(phase, pattern, code);
421                }
422                if let Some(ident) = only_ident(name_element) {
423                    let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
424                        match_error!("Placeholder has record field '{}', but code doesn't", ident)
425                    })?;
426                    self.attempt_match_node(phase, &p, &code_record)?;
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_owned());
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                for next in &mut children {
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                                && Some(first_token.text()) == next_pattern_token.as_deref()
477                                && let Some(SyntaxElement::Node(p)) = pattern.next()
478                            {
479                                // We have a subtree that starts with the next token in our pattern.
480                                self.attempt_match_token_tree(phase, &p, n)?;
481                                break;
482                            }
483                        }
484                    };
485                    last_matched_token = next;
486                }
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
492                                .sema
493                                .original_range_opt(code)
494                                .ok_or(MatchFailed {
495                                    reason: Some("def site definition".to_owned()),
496                                })?
497                                .file_id,
498                            range: first_matched_token
499                                .text_range()
500                                .cover(last_matched_token.text_range()),
501                        }),
502                    );
503                }
504                continue;
505            }
506            // Match literal (non-placeholder) tokens.
507            match child {
508                SyntaxElement::Token(token) => {
509                    self.attempt_match_token(phase, &mut pattern, &token)?;
510                }
511                SyntaxElement::Node(node) => match pattern.next() {
512                    Some(SyntaxElement::Node(p)) => {
513                        self.attempt_match_token_tree(phase, &p, &node)?;
514                    }
515                    Some(SyntaxElement::Token(p)) => fail_match!(
516                        "Pattern has token '{}', code has subtree '{}'",
517                        p.text(),
518                        node.text()
519                    ),
520                    None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
521                },
522            }
523        }
524        if let Some(p) = pattern.next() {
525            fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
526        }
527        Ok(())
528    }
529
530    fn attempt_match_ufcs_to_method_call(
531        &self,
532        phase: &mut Phase<'_>,
533        pattern_ufcs: &UfcsCallInfo<'db>,
534        code: &ast::MethodCallExpr,
535    ) -> Result<(), MatchFailed> {
536        use ast::HasArgList;
537        let code_resolved_function = self
538            .sema
539            .resolve_method_call(code)
540            .ok_or_else(|| match_error!("Failed to resolve method call"))?;
541        if pattern_ufcs.function != code_resolved_function {
542            fail_match!("Method call resolved to a different function");
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        // If the function we're calling takes a self parameter, then we store additional
551        // information on the placeholder match about autoderef and autoref. This allows us to use
552        // the placeholder in a context where autoderef and autoref don't apply.
553        if code_resolved_function.self_param(self.sema.db).is_some() {
554            if let (Some(pattern_type), Some(expr)) =
555                (&pattern_ufcs.qualifier_type, &code.receiver())
556            {
557                let deref_count = self.check_expr_type(pattern_type, expr)?;
558                let pattern_receiver = pattern_args.next();
559                self.attempt_match_opt(phase, pattern_receiver.clone(), code.receiver())?;
560                if let Phase::Second(match_out) = phase
561                    && let Some(placeholder_value) = pattern_receiver
562                        .and_then(|n| self.get_placeholder_for_node(n.syntax()))
563                        .and_then(|placeholder| {
564                            match_out.placeholder_values.get_mut(&placeholder.ident)
565                        })
566                {
567                    placeholder_value.autoderef_count = deref_count;
568                    placeholder_value.autoref_kind = self
569                        .sema
570                        .resolve_method_call_as_callable(code)
571                        .and_then(|callable| {
572                            let (self_param, _) = callable.receiver_param(self.sema.db)?;
573                            Some(self.sema.source(self_param)?.value.kind())
574                        })
575                        .unwrap_or(ast::SelfParamKind::Owned);
576                }
577            }
578        } else {
579            self.attempt_match_opt(phase, pattern_args.next(), code.receiver())?;
580        }
581        let mut code_args =
582            code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
583        loop {
584            match (pattern_args.next(), code_args.next()) {
585                (None, None) => return Ok(()),
586                (p, c) => self.attempt_match_opt(phase, p, c)?,
587            }
588        }
589    }
590
591    fn attempt_match_ufcs_to_ufcs(
592        &self,
593        phase: &mut Phase<'_>,
594        pattern_ufcs: &UfcsCallInfo<'db>,
595        code: &ast::CallExpr,
596    ) -> Result<(), MatchFailed> {
597        use ast::HasArgList;
598        // Check that the first argument is the expected type.
599        if let (Some(pattern_type), Some(expr)) = (
600            &pattern_ufcs.qualifier_type,
601            &code.arg_list().and_then(|code_args| code_args.args().next()),
602        ) {
603            self.check_expr_type(pattern_type, expr)?;
604        }
605        self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
606    }
607
608    /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
609    /// times. Returns the number of times it needed to be dereferenced.
610    fn check_expr_type(
611        &self,
612        pattern_type: &hir::Type<'db>,
613        expr: &ast::Expr,
614    ) -> Result<usize, MatchFailed> {
615        use hir::HirDisplay;
616        let code_type = self
617            .sema
618            .type_of_expr(expr)
619            .ok_or_else(|| {
620                match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
621            })?
622            .original;
623        let krate = self.sema.scope(expr.syntax()).map(|it| it.krate()).unwrap_or_else(|| {
624            hir::Crate::from(*self.sema.db.all_crates().last().expect("no crate graph present"))
625        });
626
627        code_type
628            .autoderef(self.sema.db)
629            .enumerate()
630            .find(|(_, deref_code_type)| pattern_type == deref_code_type)
631            .map(|(count, _)| count)
632            .ok_or_else(|| {
633                let display_target = krate.to_display_target(self.sema.db);
634                // Temporary needed to make the borrow checker happy.
635                match_error!(
636                    "Pattern type `{}` didn't match code type `{}`",
637                    pattern_type.display(self.sema.db, display_target),
638                    code_type.display(self.sema.db, display_target)
639                )
640            })
641    }
642
643    fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
644        self.get_placeholder(&SyntaxElement::Node(node.clone()))
645    }
646
647    fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
648        only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
649    }
650}
651
652impl Match {
653    fn render_template_paths<'db>(
654        &mut self,
655        template: &ResolvedPattern<'db>,
656        sema: &Semantics<'db, ide_db::RootDatabase>,
657    ) -> Result<(), MatchFailed> {
658        let module = sema
659            .scope(&self.matched_node)
660            .ok_or_else(|| match_error!("Matched node isn't in a module"))?
661            .module();
662        for (path, resolved_path) in &template.resolved_paths {
663            if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
664                let cfg = ImportPathConfig {
665                    prefer_no_std: false,
666                    prefer_prelude: true,
667                    prefer_absolute: false,
668                    allow_unstable: true,
669                };
670                let mod_path = module.find_path(sema.db, module_def, cfg).ok_or_else(|| {
671                    match_error!("Failed to render template path `{}` at match location")
672                })?;
673                self.rendered_template_paths.insert(path.clone(), mod_path);
674            }
675        }
676        Ok(())
677    }
678}
679
680impl Phase<'_> {
681    fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
682        loop {
683            let c = code_it.next();
684            if let Some(SyntaxElement::Token(t)) = &c {
685                self.record_ignored_comments(t);
686                if t.kind().is_trivia() {
687                    continue;
688                }
689            }
690            return c;
691        }
692    }
693
694    fn record_ignored_comments(&mut self, token: &SyntaxToken) {
695        if token.kind() == SyntaxKind::COMMENT
696            && let Phase::Second(match_out) = self
697            && let Some(comment) = ast::Comment::cast(token.clone())
698        {
699            match_out.ignored_comments.push(comment);
700        }
701    }
702}
703
704fn is_closing_token(kind: SyntaxKind) -> bool {
705    kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
706}
707
708pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T
709where
710    F: Fn() -> T,
711{
712    RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active));
713    let res = f();
714    RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false));
715    res
716}
717
718// For performance reasons, we don't want to record the reason why every match fails, only the bit
719// of code that the user indicated they thought would match. We use a thread local to indicate when
720// we are trying to match that bit of code. This saves us having to pass a boolean into all the bits
721// of code that can make the decision to not match.
722thread_local! {
723    pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = const { Cell::new(false) };
724}
725
726fn recording_match_fail_reasons() -> bool {
727    RECORDING_MATCH_FAIL_REASONS.with(|c| c.get())
728}
729
730impl PlaceholderMatch {
731    fn from_range(range: FileRange) -> Self {
732        Self {
733            range,
734            inner_matches: SsrMatches::default(),
735            autoderef_count: 0,
736            autoref_kind: ast::SelfParamKind::Owned,
737        }
738    }
739}
740
741impl NodeKind {
742    fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
743        let ok = match self {
744            Self::Literal => {
745                cov_mark::hit!(literal_constraint);
746                ast::Literal::can_cast(node.kind())
747            }
748        };
749        if !ok {
750            fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
751        }
752        Ok(())
753    }
754}
755
756// If `node` contains nothing but an ident then return it, otherwise return None.
757fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
758    match element {
759        SyntaxElement::Token(t) => {
760            if t.kind() == SyntaxKind::IDENT {
761                return Some(t);
762            }
763        }
764        SyntaxElement::Node(n) => {
765            let mut children = n.children_with_tokens();
766            if let (Some(only_child), None) = (children.next(), children.next()) {
767                return only_ident(only_child);
768            }
769        }
770    }
771    None
772}
773
774struct PatternIterator {
775    iter: SyntaxElementChildren,
776}
777
778impl Iterator for PatternIterator {
779    type Item = SyntaxElement;
780
781    fn next(&mut self) -> Option<SyntaxElement> {
782        self.iter.find(|element| !element.kind().is_trivia())
783    }
784}
785
786impl PatternIterator {
787    fn new(parent: &SyntaxNode) -> Self {
788        Self { iter: parent.children_with_tokens() }
789    }
790}
791
792#[cfg(test)]
793mod tests {
794    use crate::{MatchFinder, SsrRule};
795
796    #[test]
797    fn parse_match_replace() {
798        let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
799        let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
800
801        let (db, position, selections) = crate::tests::single_file(input);
802        let position = ide_db::FilePosition {
803            file_id: position.file_id.file_id(&db),
804            offset: position.offset,
805        };
806        let mut match_finder = MatchFinder::in_context(
807            &db,
808            position,
809            selections
810                .into_iter()
811                .map(|frange| ide_db::FileRange {
812                    file_id: frange.file_id.file_id(&db),
813                    range: frange.range,
814                })
815                .collect(),
816        )
817        .unwrap();
818        match_finder.add_rule(rule).unwrap();
819        let matches = match_finder.matches();
820        assert_eq!(matches.matches.len(), 1);
821        assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
822        assert_eq!(matches.matches[0].placeholder_values.len(), 1);
823
824        let edits = match_finder.edits();
825        assert_eq!(edits.len(), 1);
826        let edit = &edits[&position.file_id];
827        let mut after = input.to_owned();
828        edit.apply(&mut after);
829        assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
830    }
831}