Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 90 additions & 26 deletions crates/ra_ide/src/ssr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ra_db::{SourceDatabase, SourceDatabaseExt};
use ra_ide_db::symbol_index::SymbolsDatabase;
use ra_ide_db::RootDatabase;
use ra_syntax::ast::make::try_expr_from_text;
use ra_syntax::ast::{AstToken, Comment};
use ra_syntax::ast::{AstToken, Comment, RecordField, RecordLit};
use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
use ra_text_edit::{TextEdit, TextEditBuilder};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -186,47 +186,102 @@ fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrErr
}

fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
fn check_record_lit(
pattern: RecordLit,
code: RecordLit,
placeholders: &[Var],
match_: Match,
) -> Option<Match> {
let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;

let mut pattern_fields =
pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
let mut code_fields =
code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);

if pattern_fields.len() != code_fields.len() {
return None;
}

let by_name = |a: &RecordField, b: &RecordField| {
a.name_ref()
.map(|x| x.syntax().text().to_string())
.cmp(&b.name_ref().map(|x| x.syntax().text().to_string()))
};
pattern_fields.sort_by(by_name);
code_fields.sort_by(by_name);

pattern_fields.into_iter().zip(code_fields.into_iter()).fold(
Some(match_),
|accum, (a, b)| {
accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_))
},
)
}

fn check_opt_nodes(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to use this functions for function call vs method call check.

pattern: Option<impl AstNode>,
code: Option<impl AstNode>,
Copy link
Contributor

@edwin0cheng edwin0cheng Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to pass these as Option here? Seem like the all call sites of it could change to :

check_opt_nodes(pattern.path()?, code.path()?, placeholders, match_);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed (None,None) arm, added

placeholders: &[Var],
match_: Match,
) -> Option<Match> {
match (pattern, code) {
(Some(pattern), Some(code)) => check(
&SyntaxElement::from(pattern.syntax().clone()),
&SyntaxElement::from(code.syntax().clone()),
placeholders,
match_,
),
(None, None) => Some(match_),
_ => None,
}
}

fn check(
pattern: &SyntaxElement,
code: &SyntaxElement,
placeholders: &[Var],
mut match_: Match,
) -> Option<Match> {
match (pattern, code) {
(SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => {
match (&pattern, &code) {
(SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => {
if pattern.text() == code.text() {
Some(match_)
} else {
None
}
}
(SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => {
(SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => {
if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) {
match_.binding.insert(Var(pattern.text().to_string()), code.clone());
Some(match_)
} else {
let mut pattern_children = pattern
.children_with_tokens()
.filter(|element| !element.kind().is_trivia());
let mut code_children =
code.children_with_tokens().filter(|element| !element.kind().is_trivia());
let new_ignored_comments = code.children_with_tokens().filter_map(|element| {
element.as_token().and_then(|token| Comment::cast(token.clone()))
});
match_.ignored_comments.extend(new_ignored_comments);
let match_from_children = pattern_children
.by_ref()
.zip(code_children.by_ref())
.fold(Some(match_), |accum, (a, b)| {
accum.and_then(|match_| check(&a, &b, placeholders, match_))
});
match_from_children.and_then(|match_| {
if pattern_children.count() == 0 && code_children.count() == 0 {
Some(match_)
} else {
None
}
})
if let (Some(pattern), Some(code)) =
(RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone()))
{
check_record_lit(pattern, code, placeholders, match_)
} else {
let mut pattern_children = pattern
.children_with_tokens()
.filter(|element| !element.kind().is_trivia());
let mut code_children = code
.children_with_tokens()
.filter(|element| !element.kind().is_trivia());
let new_ignored_comments =
code.children_with_tokens().filter_map(|element| {
element.as_token().and_then(|token| Comment::cast(token.clone()))
});
match_.ignored_comments.extend(new_ignored_comments);
pattern_children
.by_ref()
.zip(code_children.by_ref())
.fold(Some(match_), |accum, (a, b)| {
accum.and_then(|match_| check(&a, &b, placeholders, match_))
})
.filter(|_| {
pattern_children.next().is_none() && code_children.next().is_none()
})
}
}
}
_ => None,
Expand Down Expand Up @@ -434,4 +489,13 @@ mod tests {
"fn main() { bar(5)/* using 5 */ }",
)
}

#[test]
fn ssr_struct_lit() {
assert_ssr_transform(
"foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)",
"fn main() { foo{b:2, a:1} }",
"fn main() { foo::new(1, 2) }",
)
}
}