Skip to content

Commit 69051d2

Browse files
SSR: Refactor matching code.
Mutable state is now stored in the enum Phase. MatchState, since it now has no mutable state is renamed Matcher. MatchInputs is merged into Matcher
1 parent 4a86798 commit 69051d2

File tree

1 file changed

+75
-80
lines changed

1 file changed

+75
-80
lines changed

crates/ra_ssr/src/matching.rs

Lines changed: 75 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -92,58 +92,52 @@ pub(crate) fn get_match(
9292
sema: &Semantics<ra_ide_db::RootDatabase>,
9393
) -> Result<Match, MatchFailed> {
9494
record_match_fails_reasons_scope(debug_active, || {
95-
MatchState::try_match(rule, code, restrict_range, sema)
95+
Matcher::try_match(rule, code, restrict_range, sema)
9696
})
9797
}
9898

99-
/// Inputs to matching. This cannot be part of `MatchState`, since we mutate `MatchState` and in at
100-
/// least one case need to hold a borrow of a placeholder from the input pattern while calling a
101-
/// mutable `MatchState` method.
102-
struct MatchInputs<'pattern> {
103-
ssr_pattern: &'pattern SsrPattern,
104-
}
105-
106-
/// State used while attempting to match our search pattern against a particular node of the AST.
107-
struct MatchState<'db, 'sema> {
99+
/// Checks if our search pattern matches a particular node of the AST.
100+
struct Matcher<'db, 'sema> {
108101
sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
109102
/// If any placeholders come from anywhere outside of this range, then the match will be
110103
/// rejected.
111104
restrict_range: Option<FileRange>,
112-
/// The match that we're building. We do two passes for a successful match. On the first pass,
113-
/// this is None so that we can avoid doing things like storing copies of what placeholders
114-
/// matched to. If that pass succeeds, then we do a second pass where we collect those details.
115-
/// This means that if we have a pattern like `$a.foo()` we won't do an insert into the
116-
/// placeholders map for every single method call in the codebase. Instead we'll discard all the
117-
/// method calls that aren't calls to `foo` on the first pass and only insert into the
118-
/// placeholders map on the second pass. Likewise for ignored comments.
119-
match_out: Option<Match>,
105+
rule: &'sema SsrRule,
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.
110+
enum 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),
120116
}
121117

122-
impl<'db, 'sema> MatchState<'db, 'sema> {
118+
impl<'db, 'sema> Matcher<'db, 'sema> {
123119
fn try_match(
124-
rule: &SsrRule,
120+
rule: &'sema SsrRule,
125121
code: &SyntaxNode,
126122
restrict_range: &Option<FileRange>,
127123
sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
128124
) -> Result<Match, MatchFailed> {
129-
let mut match_state =
130-
MatchState { sema, restrict_range: restrict_range.clone(), match_out: None };
131-
let match_inputs = MatchInputs { ssr_pattern: &rule.pattern };
125+
let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule };
132126
let pattern_tree = rule.pattern.tree_for_kind(code.kind())?;
133127
// First pass at matching, where we check that node types and idents match.
134-
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
128+
match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?;
135129
match_state.validate_range(&sema.original_range(code))?;
136-
match_state.match_out = Some(Match {
130+
let mut the_match = Match {
137131
range: sema.original_range(code),
138132
matched_node: code.clone(),
139133
placeholder_values: FxHashMap::default(),
140134
ignored_comments: Vec::new(),
141135
template: rule.template.clone(),
142-
});
136+
};
143137
// Second matching pass, where we record placeholder matches, ignored comments and maybe do
144138
// any other more expensive checks that we didn't want to do on the first pass.
145-
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
146-
Ok(match_state.match_out.unwrap())
139+
match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?;
140+
Ok(the_match)
147141
}
148142

149143
/// Checks that `range` is within the permitted range if any. This is applicable when we're
@@ -161,27 +155,22 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
161155
}
162156

163157
fn attempt_match_node(
164-
&mut self,
165-
match_inputs: &MatchInputs,
158+
&self,
159+
phase: &mut Phase,
166160
pattern: &SyntaxNode,
167161
code: &SyntaxNode,
168162
) -> Result<(), MatchFailed> {
169163
// Handle placeholders.
170-
if let Some(placeholder) =
171-
match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone()))
172-
{
164+
if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) {
173165
for constraint in &placeholder.constraints {
174166
self.check_constraint(constraint, code)?;
175167
}
176-
if self.match_out.is_none() {
177-
return Ok(());
178-
}
179-
let original_range = self.sema.original_range(code);
180-
// We validated the range for the node when we started the match, so the placeholder
181-
// probably can't fail range validation, but just to be safe...
182-
self.validate_range(&original_range)?;
183-
if let Some(match_out) = &mut self.match_out {
184-
match_out.placeholder_values.insert(
168+
if let Phase::Second(matches_out) = phase {
169+
let original_range = self.sema.original_range(code);
170+
// We validated the range for the node when we started the match, so the placeholder
171+
// probably can't fail range validation, but just to be safe...
172+
self.validate_range(&original_range)?;
173+
matches_out.placeholder_values.insert(
185174
Var(placeholder.ident.to_string()),
186175
PlaceholderMatch::new(code, original_range),
187176
);
@@ -190,53 +179,59 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
190179
}
191180
// Non-placeholders.
192181
if pattern.kind() != code.kind() {
193-
fail_match!("Pattern had a {:?}, code had {:?}", pattern.kind(), code.kind());
182+
fail_match!(
183+
"Pattern had a `{}` ({:?}), code had `{}` ({:?})",
184+
pattern.text(),
185+
pattern.kind(),
186+
code.text(),
187+
code.kind()
188+
);
194189
}
195190
// Some kinds of nodes have special handling. For everything else, we fall back to default
196191
// matching.
197192
match code.kind() {
198193
SyntaxKind::RECORD_FIELD_LIST => {
199-
self.attempt_match_record_field_list(match_inputs, pattern, code)
194+
self.attempt_match_record_field_list(phase, pattern, code)
200195
}
201-
SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code),
202-
_ => self.attempt_match_node_children(match_inputs, pattern, code),
196+
SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
197+
_ => self.attempt_match_node_children(phase, pattern, code),
203198
}
204199
}
205200

206201
fn attempt_match_node_children(
207-
&mut self,
208-
match_inputs: &MatchInputs,
202+
&self,
203+
phase: &mut Phase,
209204
pattern: &SyntaxNode,
210205
code: &SyntaxNode,
211206
) -> Result<(), MatchFailed> {
212207
self.attempt_match_sequences(
213-
match_inputs,
208+
phase,
214209
PatternIterator::new(pattern),
215210
code.children_with_tokens(),
216211
)
217212
}
218213

219214
fn attempt_match_sequences(
220-
&mut self,
221-
match_inputs: &MatchInputs,
215+
&self,
216+
phase: &mut Phase,
222217
pattern_it: PatternIterator,
223218
mut code_it: SyntaxElementChildren,
224219
) -> Result<(), MatchFailed> {
225220
let mut pattern_it = pattern_it.peekable();
226221
loop {
227-
match self.next_non_trivial(&mut code_it) {
222+
match phase.next_non_trivial(&mut code_it) {
228223
None => {
229224
if let Some(p) = pattern_it.next() {
230225
fail_match!("Part of the pattern was unmatched: {:?}", p);
231226
}
232227
return Ok(());
233228
}
234229
Some(SyntaxElement::Token(c)) => {
235-
self.attempt_match_token(&mut pattern_it, &c)?;
230+
self.attempt_match_token(phase, &mut pattern_it, &c)?;
236231
}
237232
Some(SyntaxElement::Node(c)) => match pattern_it.next() {
238233
Some(SyntaxElement::Node(p)) => {
239-
self.attempt_match_node(match_inputs, &p, &c)?;
234+
self.attempt_match_node(phase, &p, &c)?;
240235
}
241236
Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
242237
None => fail_match!("Pattern reached end, code has {}", c.text()),
@@ -246,11 +241,12 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
246241
}
247242

248243
fn attempt_match_token(
249-
&mut self,
244+
&self,
245+
phase: &mut Phase,
250246
pattern: &mut Peekable<PatternIterator>,
251247
code: &ra_syntax::SyntaxToken,
252248
) -> Result<(), MatchFailed> {
253-
self.record_ignored_comments(code);
249+
phase.record_ignored_comments(code);
254250
// Ignore whitespace and comments.
255251
if code.kind().is_trivia() {
256252
return Ok(());
@@ -317,8 +313,8 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
317313
/// We want to allow the records to match in any order, so we have special matching logic for
318314
/// them.
319315
fn attempt_match_record_field_list(
320-
&mut self,
321-
match_inputs: &MatchInputs,
316+
&self,
317+
phase: &mut Phase,
322318
pattern: &SyntaxNode,
323319
code: &SyntaxNode,
324320
) -> Result<(), MatchFailed> {
@@ -334,11 +330,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
334330
for p in pattern.children_with_tokens() {
335331
if let SyntaxElement::Node(p) = p {
336332
if let Some(name_element) = p.first_child_or_token() {
337-
if match_inputs.get_placeholder(&name_element).is_some() {
333+
if self.get_placeholder(&name_element).is_some() {
338334
// If the pattern is using placeholders for field names then order
339335
// independence doesn't make sense. Fall back to regular ordered
340336
// matching.
341-
return self.attempt_match_node_children(match_inputs, pattern, code);
337+
return self.attempt_match_node_children(phase, pattern, code);
342338
}
343339
if let Some(ident) = only_ident(name_element) {
344340
let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
@@ -347,7 +343,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
347343
ident
348344
)
349345
})?;
350-
self.attempt_match_node(match_inputs, &p, &code_record)?;
346+
self.attempt_match_node(phase, &p, &code_record)?;
351347
}
352348
}
353349
}
@@ -367,16 +363,15 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
367363
/// pattern matches the macro invocation. For matches within the macro call, we'll already have
368364
/// expanded the macro.
369365
fn attempt_match_token_tree(
370-
&mut self,
371-
match_inputs: &MatchInputs,
366+
&self,
367+
phase: &mut Phase,
372368
pattern: &SyntaxNode,
373369
code: &ra_syntax::SyntaxNode,
374370
) -> Result<(), MatchFailed> {
375371
let mut pattern = PatternIterator::new(pattern).peekable();
376372
let mut children = code.children_with_tokens();
377373
while let Some(child) = children.next() {
378-
if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p))
379-
{
374+
if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) {
380375
pattern.next();
381376
let next_pattern_token = pattern
382377
.peek()
@@ -402,7 +397,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
402397
if Some(first_token.to_string()) == next_pattern_token {
403398
if let Some(SyntaxElement::Node(p)) = pattern.next() {
404399
// We have a subtree that starts with the next token in our pattern.
405-
self.attempt_match_token_tree(match_inputs, &p, &n)?;
400+
self.attempt_match_token_tree(phase, &p, &n)?;
406401
break;
407402
}
408403
}
@@ -411,7 +406,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
411406
};
412407
last_matched_token = next;
413408
}
414-
if let Some(match_out) = &mut self.match_out {
409+
if let Phase::Second(match_out) = phase {
415410
match_out.placeholder_values.insert(
416411
Var(placeholder.ident.to_string()),
417412
PlaceholderMatch::from_range(FileRange {
@@ -427,11 +422,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
427422
// Match literal (non-placeholder) tokens.
428423
match child {
429424
SyntaxElement::Token(token) => {
430-
self.attempt_match_token(&mut pattern, &token)?;
425+
self.attempt_match_token(phase, &mut pattern, &token)?;
431426
}
432427
SyntaxElement::Node(node) => match pattern.next() {
433428
Some(SyntaxElement::Node(p)) => {
434-
self.attempt_match_token_tree(match_inputs, &p, &node)?;
429+
self.attempt_match_token_tree(phase, &p, &node)?;
435430
}
436431
Some(SyntaxElement::Token(p)) => fail_match!(
437432
"Pattern has token '{}', code has subtree '{}'",
@@ -448,6 +443,13 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
448443
Ok(())
449444
}
450445

446+
fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
447+
only_ident(element.clone())
448+
.and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text()))
449+
}
450+
}
451+
452+
impl Phase<'_> {
451453
fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
452454
loop {
453455
let c = code_it.next();
@@ -463,7 +465,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
463465

464466
fn record_ignored_comments(&mut self, token: &SyntaxToken) {
465467
if token.kind() == SyntaxKind::COMMENT {
466-
if let Some(match_out) = &mut self.match_out {
468+
if let Phase::Second(match_out) = self {
467469
if let Some(comment) = ast::Comment::cast(token.clone()) {
468470
match_out.ignored_comments.push(comment);
469471
}
@@ -472,13 +474,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
472474
}
473475
}
474476

475-
impl MatchInputs<'_> {
476-
fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
477-
only_ident(element.clone())
478-
.and_then(|ident| self.ssr_pattern.placeholders_by_stand_in.get(ident.text()))
479-
}
480-
}
481-
482477
fn is_closing_token(kind: SyntaxKind) -> bool {
483478
kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
484479
}
@@ -596,12 +591,12 @@ impl PatternIterator {
596591
#[cfg(test)]
597592
mod tests {
598593
use super::*;
599-
use crate::MatchFinder;
594+
use crate::{MatchFinder, SsrRule};
600595

601596
#[test]
602597
fn parse_match_replace() {
603598
let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
604-
let input = "fn main() { foo(1+2); }";
599+
let input = "fn foo() {} fn main() { foo(1+2); }";
605600

606601
use ra_db::fixture::WithFixture;
607602
let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input);
@@ -623,6 +618,6 @@ mod tests {
623618
let edit = crate::replacing::matches_to_edit(&matches, input);
624619
let mut after = input.to_string();
625620
edit.apply(&mut after);
626-
assert_eq!(after, "fn main() { bar(1+2); }");
621+
assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }");
627622
}
628623
}

0 commit comments

Comments
 (0)