diff options
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 106 |
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 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | parsing::{Constraint, NodeKind, ParsedRule, Placeholder}, | 5 | parsing::{Constraint, NodeKind, Placeholder}, |
6 | resolving::{ResolvedPattern, ResolvedRule}, | ||
6 | SsrMatches, | 7 | SsrMatches, |
7 | }; | 8 | }; |
8 | use hir::Semantics; | 9 | use 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. |
87 | pub(crate) fn get_match( | 90 | pub(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 | ||
118 | impl<'db, 'sema> Matcher<'db, 'sema> { | 121 | impl<'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 | ||
522 | impl 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 | |||
452 | impl Phase<'_> { | 544 | impl 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)"); |