Unnamed repository; edit this file 'description' to name the repository.
Merge #11714
11714: fix: Fix completions not always working in for-loop patterns r=Veykril a=Veykril Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11205 bors r+ Co-authored-by: Lukas Wirth <[email protected]>
bors[bot] 2022-03-16
parent 88ade42 · parent d5f8d91 · commit 61b1449
-rw-r--r--crates/ide_completion/src/context.rs6
-rw-r--r--crates/ide_completion/src/patterns.rs54
-rw-r--r--crates/ide_completion/src/tests/item.rs10
-rw-r--r--crates/ide_completion/src/tests/pattern.rs10
4 files changed, 63 insertions, 17 deletions
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index ba664a29da..1ad233494a 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -24,8 +24,8 @@ use text_edit::Indel;
use crate::{
patterns::{
- determine_location, determine_prev_sibling, for_is_prev2, is_in_loop_body, previous_token,
- ImmediateLocation, ImmediatePrevSibling,
+ determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
+ previous_token, ImmediateLocation, ImmediatePrevSibling,
},
CompletionConfig,
};
@@ -729,7 +729,7 @@ impl<'a> CompletionContext<'a> {
) {
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
let syntax_element = NodeOrToken::Token(fake_ident_token);
- if for_is_prev2(syntax_element.clone()) {
+ if is_in_token_of_for_loop(syntax_element.clone()) {
// for pat $0
// there is nothing to complete here except `in` keyword
// don't bother populating the context
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 8a53d6e4d4..4536e3e6ee 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -11,7 +11,7 @@ use syntax::{
ast::{self, HasArgList, HasLoopBody},
match_ast, AstNode, Direction, SyntaxElement,
SyntaxKind::*,
- SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+ SyntaxNode, SyntaxToken, TextRange, TextSize,
};
#[cfg(test)]
@@ -295,19 +295,37 @@ pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
element.into_token().and_then(previous_non_trivia_token)
}
-/// Check if the token previous to the previous one is `for`.
-/// For example, `for _ i$0` => true.
-pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
- element
- .into_token()
- .and_then(previous_non_trivia_token)
- .and_then(previous_non_trivia_token)
- .filter(|it| it.kind() == T![for])
- .is_some()
+pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
+ // oh my ...
+ (|| {
+ let syntax_token = element.into_token()?;
+ let range = syntax_token.text_range();
+ let for_expr = syntax_token.ancestors().find_map(ast::ForExpr::cast)?;
+
+ // check if the current token is the `in` token of a for loop
+ if let Some(token) = for_expr.in_token() {
+ return Some(syntax_token == token);
+ }
+ let pat = for_expr.pat()?;
+ if range.end() < pat.syntax().text_range().end() {
+ // if we are inside or before the pattern we can't be at the `in` token position
+ return None;
+ }
+ let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
+ Some(match next_sibl {
+ // the loop body is some node, if our token is at the start we are at the `in` position,
+ // otherwise we could be in a recovered expression, we don't wanna ruin completions there
+ syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
+ // the loop body consists of a single token, if we are this we are certainly at the `in` token position
+ syntax::NodeOrToken::Token(t) => t == syntax_token,
+ })
+ })()
+ .unwrap_or(false)
}
+
#[test]
fn test_for_is_prev2() {
- check_pattern_is_applicable(r"for i i$0", for_is_prev2);
+ check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
}
pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
@@ -329,7 +347,7 @@ pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
let mut token = token.prev_token();
- while let Some(inner) = token.clone() {
+ while let Some(inner) = token {
if !inner.kind().is_trivia() {
return Some(inner);
} else {
@@ -339,6 +357,18 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
None
}
+fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
+ let mut e = ele.next_sibling_or_token();
+ while let Some(inner) = e {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ e = inner.next_sibling_or_token();
+ }
+ }
+ None
+}
+
#[cfg(test)]
mod tests {
use syntax::algo::find_node_at_offset;
diff --git a/crates/ide_completion/src/tests/item.rs b/crates/ide_completion/src/tests/item.rs
index 1d5ddc092e..ed341b361a 100644
--- a/crates/ide_completion/src/tests/item.rs
+++ b/crates/ide_completion/src/tests/item.rs
@@ -76,8 +76,14 @@ fn after_target_name_in_impl() {
kw for
"#]],
);
- // FIXME: This should emit `kw where`
- check(r"impl Trait for Type $0", expect![[r#""#]]);
+ // FIXME: This should not emit `kw for`
+ check(
+ r"impl Trait for Type $0",
+ expect![[r#"
+ kw where
+ kw for
+ "#]],
+ );
}
#[test]
diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs
index 7767f24632..891c1346df 100644
--- a/crates/ide_completion/src/tests/pattern.rs
+++ b/crates/ide_completion/src/tests/pattern.rs
@@ -92,6 +92,16 @@ fn quux() {
"#,
expect![[r#""#]],
);
+ check_empty(
+ r#"
+fn foo() {
+ for &$0 in () {}
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
}
#[test]