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