Skip to content

Commit f069a52

Browse files
committed
ssr/matching: Implement match ranges
1 parent a5ec55a commit f069a52

File tree

2 files changed

+57
-26
lines changed

2 files changed

+57
-26
lines changed

crates/ssr/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ impl SsrMatches {
327327

328328
impl Match {
329329
pub fn matched_text(&self) -> String {
330-
self.matched_node.text().to_string()
330+
let range_inside_match = TextRange::up_to(self.range.range.len());
331+
self.matched_node.text().slice(range_inside_match).to_string()
331332
}
332333
}
333334

crates/ssr/src/matching.rs

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ enum Phase<'a> {
122122
Second(&'a mut Match),
123123
}
124124

125+
#[derive(Debug)]
126+
enum MatchRange {
127+
Full,
128+
// FIXME: Should be an Err(), so that it can be threaded up?
129+
// Otherwise all code returning a MatchRange must check that on success and take it into account, even if it's only a "high level" thing for now
130+
// After all, a partial match can only be triggered if the pattern does not match _anymore_, but did up to a certain point (ie. pattern
131+
// exhausted but the current SyntaxNode still has a trailing semicolon)
132+
Partial(FileRange),
133+
}
134+
125135
impl<'db, 'sema> Matcher<'db, 'sema> {
126136
fn try_match(
127137
rule: &ResolvedRule,
@@ -131,10 +141,17 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
131141
) -> Result<Match, MatchFailed> {
132142
let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule };
133143
// First pass at matching, where we check that node types and idents match.
134-
match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
135-
match_state.validate_range(&sema.original_range(code))?;
144+
let matched_range =
145+
match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
146+
147+
let range = match matched_range {
148+
MatchRange::Full => sema.original_range(code),
149+
MatchRange::Partial(r) => r,
150+
};
151+
152+
match_state.validate_range(&range)?;
136153
let mut the_match = Match {
137-
range: sema.original_range(code),
154+
range,
138155
matched_node: code.clone(),
139156
placeholder_values: FxHashMap::default(),
140157
ignored_comments: Vec::new(),
@@ -175,14 +192,16 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
175192
phase: &mut Phase,
176193
pattern: &SyntaxNode,
177194
code: &SyntaxNode,
178-
) -> Result<(), MatchFailed> {
195+
) -> Result<MatchRange, MatchFailed> {
179196
// Handle placeholders.
180197
if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
181198
for constraint in &placeholder.constraints {
182199
self.check_constraint(constraint, code)?;
183200
}
184201
if let Phase::Second(matches_out) = phase {
185-
let original_range = self.sema.original_range(code);
202+
let mut original_range = self.sema.original_range(code);
203+
original_range.range =
204+
original_range.range.intersect(matches_out.range.range).unwrap();
186205
// We validated the range for the node when we started the match, so the placeholder
187206
// probably can't fail range validation, but just to be safe...
188207
self.validate_range(&original_range)?;
@@ -191,7 +210,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
191210
PlaceholderMatch::new(Some(code), original_range),
192211
);
193212
}
194-
return Ok(());
213+
// Range not relevant here
214+
return Ok(MatchRange::Full);
195215
}
196216
// We allow a UFCS call to match a method call, provided they resolve to the same function.
197217
if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) {
@@ -229,14 +249,21 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
229249
phase: &mut Phase,
230250
pattern: &SyntaxNode,
231251
code: &SyntaxNode,
232-
) -> Result<(), MatchFailed> {
252+
) -> Result<MatchRange, MatchFailed> {
233253
let pattern_it = PatternIterator::new(pattern);
234254
let code_it = code.children_with_tokens();
235255

256+
// WIP: Only allow missing semicolons on `let = ...(;)`
236257
match self.attempt_match_sequences(phase, pattern_it, code_it) {
237-
// For now this error is only surfaced when the pattern is empty
238-
Err(MatchFailed { missing_semicolon: true, .. }) /* if pattern_it.peek().is_none() */ => {
239-
Ok(())
258+
Err(MatchFailed { missing_semicolon: true, .. }) => {
259+
let mut original_range = self.sema.original_range(code);
260+
let text_range = original_range.range;
261+
original_range.range = syntax::TextRange::new(
262+
text_range.start(),
263+
text_range.end() - syntax::TextSize::from(1),
264+
);
265+
266+
Ok(MatchRange::Partial(original_range))
240267
}
241268
res => res,
242269
}
@@ -247,7 +274,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
247274
phase: &mut Phase,
248275
pattern: &SyntaxNode,
249276
code: &SyntaxNode,
250-
) -> Result<(), MatchFailed> {
277+
) -> Result<MatchRange, MatchFailed> {
251278
self.attempt_match_sequences(
252279
phase,
253280
PatternIterator::new(pattern),
@@ -260,15 +287,15 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
260287
phase: &mut Phase,
261288
pattern_it: PatternIterator,
262289
mut code_it: SyntaxElementChildren,
263-
) -> Result<(), MatchFailed> {
290+
) -> Result<MatchRange, MatchFailed> {
264291
let mut pattern_it = pattern_it.peekable();
265292
loop {
266293
match phase.next_non_trivial(&mut code_it) {
267294
None => {
268295
if let Some(p) = pattern_it.next() {
269296
fail_match!("Part of the pattern was unmatched: {:?}", p);
270297
}
271-
return Ok(());
298+
return Ok(MatchRange::Full);
272299
}
273300
Some(SyntaxElement::Token(c)) => {
274301
self.attempt_match_token(phase, &mut pattern_it, &c)?;
@@ -332,6 +359,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
332359
}
333360
None => {
334361
if code.kind() == SyntaxKind::SEMICOLON {
362+
// FIXME: MatchRange::Partial?
335363
return Err(MatchFailed { reason: None, missing_semicolon: true });
336364
}
337365
fail_match!("Pattern exhausted, while code remains: `{}`", code.text());
@@ -365,7 +393,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
365393
phase: &mut Phase,
366394
pattern: &SyntaxNode,
367395
code: &SyntaxNode,
368-
) -> Result<(), MatchFailed> {
396+
) -> Result<MatchRange, MatchFailed> {
369397
if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
370398
let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
371399
let code_path = ast::Path::cast(code.clone()).unwrap();
@@ -397,18 +425,18 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
397425
} else {
398426
return self.attempt_match_node_children(phase, pattern, code);
399427
}
400-
Ok(())
428+
Ok(MatchRange::Full)
401429
}
402430

403431
fn attempt_match_opt<T: AstNode>(
404432
&self,
405433
phase: &mut Phase,
406434
pattern: Option<T>,
407435
code: Option<T>,
408-
) -> Result<(), MatchFailed> {
436+
) -> Result<MatchRange, MatchFailed> {
409437
match (pattern, code) {
410438
(Some(p), Some(c)) => self.attempt_match_node(phase, &p.syntax(), &c.syntax()),
411-
(None, None) => Ok(()),
439+
(None, None) => Ok(MatchRange::Full),
412440
(Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
413441
(None, Some(c)) => {
414442
fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
@@ -423,7 +451,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
423451
phase: &mut Phase,
424452
pattern: &SyntaxNode,
425453
code: &SyntaxNode,
426-
) -> Result<(), MatchFailed> {
454+
) -> Result<MatchRange, MatchFailed> {
427455
// Build a map keyed by field name.
428456
let mut fields_by_name = FxHashMap::default();
429457
for child in code.children() {
@@ -461,7 +489,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
461489
unmatched_fields
462490
);
463491
}
464-
Ok(())
492+
Ok(MatchRange::Full)
465493
}
466494

467495
/// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
@@ -473,7 +501,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
473501
phase: &mut Phase,
474502
pattern: &SyntaxNode,
475503
code: &syntax::SyntaxNode,
476-
) -> Result<(), MatchFailed> {
504+
) -> Result<MatchRange, MatchFailed> {
477505
let mut pattern = PatternIterator::new(pattern).peekable();
478506
let mut children = code.children_with_tokens();
479507
while let Some(child) = children.next() {
@@ -548,15 +576,15 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
548576
if let Some(p) = pattern.next() {
549577
fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
550578
}
551-
Ok(())
579+
Ok(MatchRange::Full)
552580
}
553581

554582
fn attempt_match_ufcs_to_method_call(
555583
&self,
556584
phase: &mut Phase,
557585
pattern_ufcs: &UfcsCallInfo,
558586
code: &ast::MethodCallExpr,
559-
) -> Result<(), MatchFailed> {
587+
) -> Result<MatchRange, MatchFailed> {
560588
use ast::ArgListOwner;
561589
let code_resolved_function = self
562590
.sema
@@ -605,8 +633,10 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
605633
code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
606634
loop {
607635
match (pattern_args.next(), code_args.next()) {
608-
(None, None) => return Ok(()),
609-
(p, c) => self.attempt_match_opt(phase, p, c)?,
636+
(None, None) => return Ok(MatchRange::Full),
637+
(p, c) => {
638+
let _ = self.attempt_match_opt(phase, p, c)?;
639+
}
610640
}
611641
}
612642
}
@@ -616,7 +646,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
616646
phase: &mut Phase,
617647
pattern_ufcs: &UfcsCallInfo,
618648
code: &ast::CallExpr,
619-
) -> Result<(), MatchFailed> {
649+
) -> Result<MatchRange, MatchFailed> {
620650
use ast::ArgListOwner;
621651
// Check that the first argument is the expected type.
622652
if let (Some(pattern_type), Some(expr)) = (

0 commit comments

Comments
 (0)