aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/matching.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r--crates/ra_ssr/src/matching.rs106
1 files changed, 99 insertions, 7 deletions
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index a43d57c34..f3cc60c29 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -2,7 +2,8 @@
2//! process of matching, placeholder values are recorded. 2//! process of matching, placeholder values are recorded.
3 3
4use crate::{ 4use crate::{
5 parsing::{Constraint, NodeKind, ParsedRule, Placeholder}, 5 parsing::{Constraint, NodeKind, Placeholder},
6 resolving::{ResolvedPattern, ResolvedRule},
6 SsrMatches, 7 SsrMatches,
7}; 8};
8use hir::Semantics; 9use hir::Semantics;
@@ -51,6 +52,8 @@ pub struct Match {
51 pub(crate) rule_index: usize, 52 pub(crate) rule_index: usize,
52 /// The depth of matched_node. 53 /// The depth of matched_node.
53 pub(crate) depth: usize, 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>,
54} 57}
55 58
56/// Represents a `$var` in an SSR query. 59/// Represents a `$var` in an SSR query.
@@ -86,7 +89,7 @@ pub(crate) struct MatchFailed {
86/// parent module, we don't populate nested matches. 89/// parent module, we don't populate nested matches.
87pub(crate) fn get_match( 90pub(crate) fn get_match(
88 debug_active: bool, 91 debug_active: bool,
89 rule: &ParsedRule, 92 rule: &ResolvedRule,
90 code: &SyntaxNode, 93 code: &SyntaxNode,
91 restrict_range: &Option<FileRange>, 94 restrict_range: &Option<FileRange>,
92 sema: &Semantics<ra_ide_db::RootDatabase>, 95 sema: &Semantics<ra_ide_db::RootDatabase>,
@@ -102,7 +105,7 @@ struct Matcher<'db, 'sema> {
102 /// If any placeholders come from anywhere outside of this range, then the match will be 105 /// If any placeholders come from anywhere outside of this range, then the match will be
103 /// rejected. 106 /// rejected.
104 restrict_range: Option<FileRange>, 107 restrict_range: Option<FileRange>,
105 rule: &'sema ParsedRule, 108 rule: &'sema ResolvedRule,
106} 109}
107 110
108/// Which phase of matching we're currently performing. We do two phases because most attempted 111/// Which phase of matching we're currently performing. We do two phases because most attempted
@@ -117,14 +120,14 @@ enum Phase<'a> {
117 120
118impl<'db, 'sema> Matcher<'db, 'sema> { 121impl<'db, 'sema> Matcher<'db, 'sema> {
119 fn try_match( 122 fn try_match(
120 rule: &ParsedRule, 123 rule: &ResolvedRule,
121 code: &SyntaxNode, 124 code: &SyntaxNode,
122 restrict_range: &Option<FileRange>, 125 restrict_range: &Option<FileRange>,
123 sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, 126 sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
124 ) -> Result<Match, MatchFailed> { 127 ) -> Result<Match, MatchFailed> {
125 let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; 128 let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule };
126 // First pass at matching, where we check that node types and idents match. 129 // First pass at matching, where we check that node types and idents match.
127 match_state.attempt_match_node(&mut Phase::First, &rule.pattern, code)?; 130 match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
128 match_state.validate_range(&sema.original_range(code))?; 131 match_state.validate_range(&sema.original_range(code))?;
129 let mut the_match = Match { 132 let mut the_match = Match {
130 range: sema.original_range(code), 133 range: sema.original_range(code),
@@ -133,11 +136,19 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
133 ignored_comments: Vec::new(), 136 ignored_comments: Vec::new(),
134 rule_index: rule.index, 137 rule_index: rule.index,
135 depth: 0, 138 depth: 0,
139 rendered_template_paths: FxHashMap::default(),
136 }; 140 };
137 // Second matching pass, where we record placeholder matches, ignored comments and maybe do 141 // Second matching pass, where we record placeholder matches, ignored comments and maybe do
138 // any other more expensive checks that we didn't want to do on the first pass. 142 // any other more expensive checks that we didn't want to do on the first pass.
139 match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &rule.pattern, code)?; 143 match_state.attempt_match_node(
144 &mut Phase::Second(&mut the_match),
145 &rule.pattern.node,
146 code,
147 )?;
140 the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count(); 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 }
141 Ok(the_match) 152 Ok(the_match)
142 } 153 }
143 154
@@ -195,6 +206,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
195 self.attempt_match_record_field_list(phase, pattern, code) 206 self.attempt_match_record_field_list(phase, pattern, code)
196 } 207 }
197 SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), 208 SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
209 SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
198 _ => self.attempt_match_node_children(phase, pattern, code), 210 _ => self.attempt_match_node_children(phase, pattern, code),
199 } 211 }
200 } 212 }
@@ -311,6 +323,64 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
311 Ok(()) 323 Ok(())
312 } 324 }
313 325
326 /// Paths are matched based on whether they refer to the same thing, even if they're written
327 /// differently.
328 fn attempt_match_path(
329 &self,
330 phase: &mut Phase,
331 pattern: &SyntaxNode,
332 code: &SyntaxNode,
333 ) -> Result<(), MatchFailed> {
334 if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
335 let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
336 let code_path = ast::Path::cast(code.clone()).unwrap();
337 if let (Some(pattern_segment), Some(code_segment)) =
338 (pattern_path.segment(), code_path.segment())
339 {
340 // Match everything within the segment except for the name-ref, which is handled
341 // separately via comparing what the path resolves to below.
342 self.attempt_match_opt(
343 phase,
344 pattern_segment.type_arg_list(),
345 code_segment.type_arg_list(),
346 )?;
347 self.attempt_match_opt(
348 phase,
349 pattern_segment.param_list(),
350 code_segment.param_list(),
351 )?;
352 }
353 if matches!(phase, Phase::Second(_)) {
354 let resolution = self
355 .sema
356 .resolve_path(&code_path)
357 .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
358 if pattern_resolved.resolution != resolution {
359 fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
360 }
361 }
362 } else {
363 return self.attempt_match_node_children(phase, pattern, code);
364 }
365 Ok(())
366 }
367
368 fn attempt_match_opt<T: AstNode>(
369 &self,
370 phase: &mut Phase,
371 pattern: Option<T>,
372 code: Option<T>,
373 ) -> Result<(), MatchFailed> {
374 match (pattern, code) {
375 (Some(p), Some(c)) => self.attempt_match_node(phase, &p.syntax(), &c.syntax()),
376 (None, None) => Ok(()),
377 (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
378 (None, Some(c)) => {
379 fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
380 }
381 }
382 }
383
314 /// We want to allow the records to match in any order, so we have special matching logic for 384 /// We want to allow the records to match in any order, so we have special matching logic for
315 /// them. 385 /// them.
316 fn attempt_match_record_field_list( 386 fn attempt_match_record_field_list(
@@ -449,6 +519,28 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
449 } 519 }
450} 520}
451 521
522impl Match {
523 fn render_template_paths(
524 &mut self,
525 template: &ResolvedPattern,
526 sema: &Semantics<ra_ide_db::RootDatabase>,
527 ) -> Result<(), MatchFailed> {
528 let module = sema
529 .scope(&self.matched_node)
530 .module()
531 .ok_or_else(|| match_error!("Matched node isn't in a module"))?;
532 for (path, resolved_path) in &template.resolved_paths {
533 if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
534 let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| {
535 match_error!("Failed to render template path `{}` at match location")
536 })?;
537 self.rendered_template_paths.insert(path.clone(), mod_path);
538 }
539 }
540 Ok(())
541 }
542}
543
452impl Phase<'_> { 544impl Phase<'_> {
453 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { 545 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
454 loop { 546 loop {
@@ -578,7 +670,7 @@ mod tests {
578 670
579 let (db, position) = crate::tests::single_file(input); 671 let (db, position) = crate::tests::single_file(input);
580 let mut match_finder = MatchFinder::in_context(&db, position); 672 let mut match_finder = MatchFinder::in_context(&db, position);
581 match_finder.add_rule(rule); 673 match_finder.add_rule(rule).unwrap();
582 let matches = match_finder.matches(); 674 let matches = match_finder.matches();
583 assert_eq!(matches.matches.len(), 1); 675 assert_eq!(matches.matches.len(), 1);
584 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); 676 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");