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 <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-03-15 17:58:21 +00:00 committed by GitHub
commit 61b1449387
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 17 deletions

View file

@ -24,8 +24,8 @@ use text_edit::Indel;
use crate::{ use crate::{
patterns::{ patterns::{
determine_location, determine_prev_sibling, for_is_prev2, is_in_loop_body, previous_token, determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
ImmediateLocation, ImmediatePrevSibling, previous_token, ImmediateLocation, ImmediatePrevSibling,
}, },
CompletionConfig, 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 fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
let syntax_element = NodeOrToken::Token(fake_ident_token); 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 // for pat $0
// there is nothing to complete here except `in` keyword // there is nothing to complete here except `in` keyword
// don't bother populating the context // don't bother populating the context

View file

@ -11,7 +11,7 @@ use syntax::{
ast::{self, HasArgList, HasLoopBody}, ast::{self, HasArgList, HasLoopBody},
match_ast, AstNode, Direction, SyntaxElement, match_ast, AstNode, Direction, SyntaxElement,
SyntaxKind::*, SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize, T, SyntaxNode, SyntaxToken, TextRange, TextSize,
}; };
#[cfg(test)] #[cfg(test)]
@ -295,19 +295,37 @@ pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
element.into_token().and_then(previous_non_trivia_token) element.into_token().and_then(previous_non_trivia_token)
} }
/// Check if the token previous to the previous one is `for`. pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
/// For example, `for _ i$0` => true. // oh my ...
pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool { (|| {
element let syntax_token = element.into_token()?;
.into_token() let range = syntax_token.text_range();
.and_then(previous_non_trivia_token) let for_expr = syntax_token.ancestors().find_map(ast::ForExpr::cast)?;
.and_then(previous_non_trivia_token)
.filter(|it| it.kind() == T![for]) // check if the current token is the `in` token of a for loop
.is_some() 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] #[test]
fn test_for_is_prev2() { 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 { 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> { fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
let mut token = token.prev_token(); let mut token = token.prev_token();
while let Some(inner) = token.clone() { while let Some(inner) = token {
if !inner.kind().is_trivia() { if !inner.kind().is_trivia() {
return Some(inner); return Some(inner);
} else { } else {
@ -339,6 +357,18 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
None 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)] #[cfg(test)]
mod tests { mod tests {
use syntax::algo::find_node_at_offset; use syntax::algo::find_node_at_offset;

View file

@ -76,8 +76,14 @@ fn after_target_name_in_impl() {
kw for kw for
"#]], "#]],
); );
// FIXME: This should emit `kw where` // FIXME: This should not emit `kw for`
check(r"impl Trait for Type $0", expect![[r#""#]]); check(
r"impl Trait for Type $0",
expect![[r#"
kw where
kw for
"#]],
);
} }
#[test] #[test]

View file

@ -92,6 +92,16 @@ fn quux() {
"#, "#,
expect![[r#""#]], expect![[r#""#]],
); );
check_empty(
r#"
fn foo() {
for &$0 in () {}
}
"#,
expect![[r#"
kw mut
"#]],
);
} }
#[test] #[test]