summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-completion/src
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion/src')
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs163
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/config.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context.rs37
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs175
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/item.rs104
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests.rs46
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs252
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs51
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs114
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs89
17 files changed, 756 insertions, 338 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 9a060857e..889d90095 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -869,7 +869,7 @@ impl Test for T {{
};
// Enumerate some possible next siblings.
- for next_sibling in &[
+ for next_sibling in [
"",
"fn other_fn() {}", // `const $0 fn` -> `const fn`
"type OtherType = i32;",
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
index 1d03c8cc5..b9ab2afca 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
@@ -86,6 +86,7 @@ fn foo(a: A) { a.$0 }
sn match match expr {}
sn ref &expr
sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
);
@@ -110,6 +111,7 @@ fn foo() {
sn match match expr {}
sn ref &expr
sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
);
}
@@ -136,6 +138,7 @@ fn foo(a: A) { a.$0 }
sn match match expr {}
sn ref &expr
sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
);
}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
index f4f37d77d..c55bd9aaa 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
@@ -6,7 +6,7 @@ use hir::{Documentation, HasAttrs};
use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
use syntax::{
ast::{self, make, AstNode, AstToken},
- SyntaxKind::{EXPR_STMT, STMT_LIST},
+ SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
TextRange, TextSize,
};
use text_edit::TextEdit;
@@ -123,6 +123,22 @@ pub(crate) fn complete_postfix(
postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc);
postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc);
+ let mut unsafe_should_be_wrapped = true;
+ if dot_receiver.syntax().kind() == BLOCK_EXPR {
+ unsafe_should_be_wrapped = false;
+ if let Some(parent) = dot_receiver.syntax().parent() {
+ if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
+ unsafe_should_be_wrapped = true;
+ }
+ }
+ };
+ let unsafe_completion_string = if unsafe_should_be_wrapped {
+ format!("unsafe {{ {receiver_text} }}")
+ } else {
+ format!("unsafe {receiver_text}")
+ };
+ postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc);
+
// The rest of the postfix completions create an expression that moves an argument,
// so it's better to consider references now to avoid breaking the compilation
@@ -329,18 +345,19 @@ fn main() {
}
"#,
expect![[r#"
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn if if expr {}
- sn let let
- sn letm let mut
- sn match match expr {}
- sn not !expr
- sn ref &expr
- sn refm &mut expr
- sn while while expr {}
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
+ sn while while expr {}
"#]],
);
}
@@ -359,16 +376,17 @@ fn main() {
}
"#,
expect![[r#"
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn if if expr {}
- sn match match expr {}
- sn not !expr
- sn ref &expr
- sn refm &mut expr
- sn while while expr {}
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
+ sn while while expr {}
"#]],
);
}
@@ -383,15 +401,16 @@ fn main() {
}
"#,
expect![[r#"
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn let let
- sn letm let mut
- sn match match expr {}
- sn ref &expr
- sn refm &mut expr
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
)
}
@@ -406,18 +425,19 @@ fn main() {
}
"#,
expect![[r#"
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn if if expr {}
- sn let let
- sn letm let mut
- sn match match expr {}
- sn not !expr
- sn ref &expr
- sn refm &mut expr
- sn while while expr {}
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
+ sn while while expr {}
"#]],
);
}
@@ -518,6 +538,49 @@ fn main() {
}
#[test]
+ fn postfix_completion_for_unsafe() {
+ check_edit("unsafe", r#"fn main() { foo.$0 }"#, r#"fn main() { unsafe { foo } }"#);
+ check_edit("unsafe", r#"fn main() { { foo }.$0 }"#, r#"fn main() { unsafe { foo } }"#);
+ check_edit(
+ "unsafe",
+ r#"fn main() { if x { foo }.$0 }"#,
+ r#"fn main() { unsafe { if x { foo } } }"#,
+ );
+ check_edit(
+ "unsafe",
+ r#"fn main() { loop { foo }.$0 }"#,
+ r#"fn main() { unsafe { loop { foo } } }"#,
+ );
+ check_edit(
+ "unsafe",
+ r#"fn main() { if true {}.$0 }"#,
+ r#"fn main() { unsafe { if true {} } }"#,
+ );
+ check_edit(
+ "unsafe",
+ r#"fn main() { while true {}.$0 }"#,
+ r#"fn main() { unsafe { while true {} } }"#,
+ );
+ check_edit(
+ "unsafe",
+ r#"fn main() { for i in 0..10 {}.$0 }"#,
+ r#"fn main() { unsafe { for i in 0..10 {} } }"#,
+ );
+ check_edit(
+ "unsafe",
+ r#"fn main() { let x = if true {1} else {2}.$0 }"#,
+ r#"fn main() { let x = unsafe { if true {1} else {2} } }"#,
+ );
+
+ // completion will not be triggered
+ check_edit(
+ "unsafe",
+ r#"fn main() { let x = true else {panic!()}.$0}"#,
+ r#"fn main() { let x = true else {panic!()}.unsafe}"#,
+ );
+ }
+
+ #[test]
fn custom_postfix_completion() {
let config = CompletionConfig {
snippets: vec![Snippet::new(
@@ -684,4 +747,16 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn no_postfix_completions_in_if_block_that_has_an_else() {
+ check(
+ r#"
+fn test() {
+ if true {}.$0 else {}
+}
+"#,
+ expect![[r#""#]],
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
index a0f5e81b4..8f6a97e1e 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
@@ -19,6 +19,7 @@ pub struct CompletionConfig {
pub insert_use: InsertUseConfig,
pub prefer_no_std: bool,
pub snippets: Vec<Snippet>,
+ pub limit: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
index aa77f4495..ea54068b0 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -571,28 +571,25 @@ impl<'a> CompletionContext<'a> {
// try to skip completions on path with invalid colons
// this approach works in normal path and inside token tree
- match original_token.kind() {
- T![:] => {
- // return if no prev token before colon
- let prev_token = original_token.prev_token()?;
-
- // only has a single colon
- if prev_token.kind() != T![:] {
- return None;
- }
+ if original_token.kind() == T![:] {
+ // return if no prev token before colon
+ let prev_token = original_token.prev_token()?;
- // has 3 colon or 2 coloncolon in a row
- // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205
- // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751
- if prev_token
- .prev_token()
- .map(|t| t.kind() == T![:] || t.kind() == T![::])
- .unwrap_or(false)
- {
- return None;
- }
+ // only has a single colon
+ if prev_token.kind() != T![:] {
+ return None;
+ }
+
+ // has 3 colon or 2 coloncolon in a row
+ // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205
+ // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751
+ if prev_token
+ .prev_token()
+ .map(|t| t.kind() == T![:] || t.kind() == T![::])
+ .unwrap_or(false)
+ {
+ return None;
}
- _ => {}
}
let AnalysisResult {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
index e34824e22..db0045aef 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -29,6 +29,7 @@ pub(super) struct AnalysisResult {
pub(super) analysis: CompletionAnalysis,
pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>),
pub(super) qualifier_ctx: QualifierCtx,
+ /// the original token of the expanded file
pub(super) token: SyntaxToken,
pub(super) offset: TextSize,
}
@@ -48,7 +49,9 @@ pub(super) fn expand_and_analyze(
// make the offset point to the start of the original token, as that is what the
// intermediate offsets calculated in expansion always points to
let offset = offset - relative_offset;
- let expansion = expand(sema, original_file, speculative_file, offset, fake_ident_token);
+ let expansion =
+ expand(sema, original_file, speculative_file, offset, fake_ident_token, relative_offset);
+
// add the relative offset back, so that left_biased finds the proper token
let offset = expansion.offset + relative_offset;
let token = expansion.original_file.token_at_offset(offset).left_biased()?;
@@ -67,6 +70,7 @@ fn expand(
mut speculative_file: SyntaxNode,
mut offset: TextSize,
mut fake_ident_token: SyntaxToken,
+ relative_offset: TextSize,
) -> ExpansionResult {
let _p = profile::span("CompletionContext::expand");
let mut derive_ctx = None;
@@ -97,7 +101,7 @@ fn expand(
// successful expansions
(Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
let new_offset = fake_mapped_token.text_range().start();
- if new_offset > actual_expansion.text_range().end() {
+ if new_offset + relative_offset > actual_expansion.text_range().end() {
// offset outside of bounds from the original expansion,
// stop here to prevent problems from happening
break 'expansion;
@@ -176,7 +180,7 @@ fn expand(
// successful expansions
(Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
let new_offset = fake_mapped_token.text_range().start();
- if new_offset > actual_expansion.text_range().end() {
+ if new_offset + relative_offset > actual_expansion.text_range().end() {
// offset outside of bounds from the original expansion,
// stop here to prevent problems from happening
break 'expansion;
@@ -210,15 +214,6 @@ fn analyze(
let _p = profile::span("CompletionContext::analyze");
let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } =
expansion_result;
- let syntax_element = NodeOrToken::Token(fake_ident_token);
- 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
- // FIXME: the completion calculations should end up good enough
- // such that this special case becomes unnecessary
- return None;
- }
// Overwrite the path kind for derives
if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
@@ -246,37 +241,35 @@ fn analyze(
return None;
}
- let name_like = match find_node_at_offset(&speculative_file, offset) {
- Some(it) => it,
- None => {
- let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
- CompletionAnalysis::String {
- original,
- expanded: ast::String::cast(self_token.clone()),
+ let Some(name_like) = find_node_at_offset(&speculative_file, offset) else {
+ let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
+ CompletionAnalysis::String {
+ original,
+ expanded: ast::String::cast(self_token.clone()),
+ }
+ } else {
+ // Fix up trailing whitespace problem
+ // #[attr(foo = $0
+ let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?;
+ let p = token.parent()?;
+ if p.kind() == SyntaxKind::TOKEN_TREE
+ && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
+ {
+ let colon_prefix = previous_non_trivia_token(self_token.clone())
+ .map_or(false, |it| T![:] == it.kind());
+ CompletionAnalysis::UnexpandedAttrTT {
+ fake_attribute_under_caret: fake_ident_token
+ .parent_ancestors()
+ .find_map(ast::Attr::cast),
+ colon_prefix,
}
} else {
- // Fix up trailing whitespace problem
- // #[attr(foo = $0
- let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?;
- let p = token.parent()?;
- if p.kind() == SyntaxKind::TOKEN_TREE
- && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
- {
- let colon_prefix = previous_non_trivia_token(self_token.clone())
- .map_or(false, |it| T![:] == it.kind());
- CompletionAnalysis::UnexpandedAttrTT {
- fake_attribute_under_caret: syntax_element
- .ancestors()
- .find_map(ast::Attr::cast),
- colon_prefix,
- }
- } else {
- return None;
- }
- };
- return Some((analysis, (None, None), QualifierCtx::default()));
- }
+ return None;
+ }
+ };
+ return Some((analysis, (None, None), QualifierCtx::default()));
};
+
let expected = expected_type_and_name(sema, self_token, &name_like);
let mut qual_ctx = QualifierCtx::default();
let analysis = match name_like {
@@ -287,6 +280,22 @@ fn analyze(
let parent = name_ref.syntax().parent()?;
let (nameref_ctx, qualifier_ctx) =
classify_name_ref(sema, &original_file, name_ref, parent)?;
+
+ if let NameRefContext {
+ kind:
+ NameRefKind::Path(PathCompletionCtx { kind: PathKind::Expr { .. }, path, .. }, ..),
+ ..
+ } = &nameref_ctx
+ {
+ if is_in_token_of_for_loop(path) {
+ // for pat $0
+ // there is nothing to complete here except `in` keyword
+ // don't bother populating the context
+ // Ideally this special casing wouldn't be needed, but the parser recovers
+ return None;
+ }
+ }
+
qual_ctx = qualifier_ctx;
CompletionAnalysis::NameRef(nameref_ctx)
}
@@ -320,16 +329,14 @@ fn expected_type_and_name(
ast::FieldExpr(e) => e
.syntax()
.ancestors()
- .map_while(ast::FieldExpr::cast)
- .last()
- .map(|it| it.syntax().clone()),
+ .take_while(|it| ast::FieldExpr::can_cast(it.kind()))
+ .last(),
ast::PathSegment(e) => e
.syntax()
.ancestors()
.skip(1)
.take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
- .find_map(ast::PathExpr::cast)
- .map(|it| it.syntax().clone()),
+ .find(|it| ast::PathExpr::can_cast(it.kind())),
_ => None
}
};
@@ -602,6 +609,18 @@ fn classify_name_ref(
},
_ => false,
};
+
+ let reciever_is_part_of_indivisible_expression = match &receiver {
+ Some(ast::Expr::IfExpr(_)) => {
+ let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
+ next_token_kind == Some(SyntaxKind::ELSE_KW)
+ },
+ _ => false
+ };
+ if reciever_is_part_of_indivisible_expression {
+ return None;
+ }
+
let kind = NameRefKind::DotAccess(DotAccess {
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
@@ -653,8 +672,15 @@ fn classify_name_ref(
};
let after_if_expr = |node: SyntaxNode| {
let prev_expr = (|| {
+ let node = match node.parent().and_then(ast::ExprStmt::cast) {
+ Some(stmt) => stmt.syntax().clone(),
+ None => node,
+ };
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
- ast::ExprStmt::cast(prev_sibling)?.expr()
+
+ ast::ExprStmt::cast(prev_sibling.clone())
+ .and_then(|it| it.expr())
+ .or_else(|| ast::Expr::cast(prev_sibling))
})();
matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
};
@@ -672,10 +698,10 @@ fn classify_name_ref(
{
if let Some(item) = ast::Item::cast(n) {
let is_inbetween = match &item {
- ast::Item::Const(it) => it.body().is_none(),
+ ast::Item::Const(it) => it.body().is_none() && it.semicolon_token().is_none(),
ast::Item::Enum(it) => it.variant_list().is_none(),
ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
- ast::Item::Fn(it) => it.body().is_none(),
+ ast::Item::Fn(it) => it.body().is_none() && it.semicolon_token().is_none(),
ast::Item::Impl(it) => it.assoc_item_list().is_none(),
ast::Item::Module(it) => {
it.item_list().is_none() && it.semicolon_token().is_none()
@@ -685,7 +711,7 @@ fn classify_name_ref(
it.field_list().is_none() && it.semicolon_token().is_none()
}
ast::Item::Trait(it) => it.assoc_item_list().is_none(),
- ast::Item::TypeAlias(it) => it.ty().is_none(),
+ ast::Item::TypeAlias(it) => it.ty().is_none() && it.semicolon_token().is_none(),
ast::Item::Union(it) => it.record_field_list().is_none(),
_ => false,
};
@@ -1248,40 +1274,29 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
Some((use_tree.path()?, true))
}
-pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
+fn is_in_token_of_for_loop(path: &ast::Path) -> bool {
// oh my ...
(|| {
- let syntax_token = element.into_token()?;
- let range = syntax_token.text_range();
- let for_expr = syntax_token.parent_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 expr = path.syntax().parent().and_then(ast::PathExpr::cast)?;
+ let for_expr = expr.syntax().parent().and_then(ast::ForExpr::cast)?;
+ if for_expr.in_token().is_some() {
+ return Some(false);
}
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,
+ syntax::NodeOrToken::Node(n) => {
+ n.text_range().start() == path.syntax().text_range().start()
+ }
+ syntax::NodeOrToken::Token(t) => {
+ t.text_range().start() == path.syntax().text_range().start()
+ }
})
})()
.unwrap_or(false)
}
-#[test]
-fn test_for_is_prev2() {
- crate::tests::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 {
+fn is_in_loop_body(node: &SyntaxNode) -> bool {
node.ancestors()
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
.find_map(|it| {
@@ -1314,6 +1329,22 @@ fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken>
None
}
+fn next_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
+ let mut token = match e.into() {
+ SyntaxElement::Node(n) => n.last_token()?,
+ SyntaxElement::Token(t) => t,
+ }
+ .next_token();
+ while let Some(inner) = token {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ token = inner.next_token();
+ }
+ }
+ None
+}
+
fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
let mut e = ele.next_sibling_or_token();
while let Some(inner) = e {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
index 657eab5b1..2f65491d8 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
@@ -14,13 +14,14 @@ use crate::{
render::{render_path_resolution, RenderContext},
};
-/// `CompletionItem` describes a single completion variant in the editor pop-up.
-/// It is basically a POD with various properties. To construct a
-/// `CompletionItem`, use `new` method and the `Builder` struct.
+/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the
+/// editor pop-up. It is basically a POD with various properties. To construct a
+/// [`CompletionItem`], use [`Builder::new`] method and the [`Builder`] struct.
#[derive(Clone)]
+#[non_exhaustive]
pub struct CompletionItem {
/// Label in the completion pop up which identifies completion.
- label: SmolStr,
+ pub label: SmolStr,
/// Range of identifier that is being completed.
///
/// It should be used primarily for UI, but we also use this to convert
@@ -29,33 +30,33 @@ pub struct CompletionItem {
/// `source_range` must contain the completion offset. `text_edit` should
/// start with what `source_range` points to, or VSCode will filter out the
/// completion silently.
- source_range: TextRange,
+ pub source_range: TextRange,
/// What happens when user selects this item.
///
/// Typically, replaces `source_range` with new identifier.
- text_edit: TextEdit,
- is_snippet: bool,
+ pub text_edit: TextEdit,
+ pub is_snippet: bool,
/// What item (struct, function, etc) are we completing.
- kind: CompletionItemKind,
+ pub kind: CompletionItemKind,
/// Lookup is used to check if completion item indeed can complete current
/// ident.
///
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
/// contains `bar` sub sequence), and `quux` will rejected.
- lookup: Option<SmolStr>,
+ pub lookup: Option<SmolStr>,
/// Additional info to show in the UI pop up.
- detail: Option<String>,
- documentation: Option<Documentation>,
+ pub detail: Option<String>,
+ pub documentation: Option<Documentation>,
/// Whether this item is marked as deprecated
- deprecated: bool,
+ pub deprecated: bool,
/// If completing a function call, ask the editor to show parameter popup
/// after completion.
- trigger_call_info: bool,
+ pub trigger_call_info: bool,
/// We use this to sort completion. Relevance records facts like "do the
/// types align precisely?". We can't sort by relevances directly, they are
@@ -64,36 +65,39 @@ pub struct CompletionItem {
/// Note that Relevance ignores fuzzy match score. We compute Relevance for
/// all possible items, and then separately build an ordered completion list
/// based on relevance and fuzzy matching with the already typed identifier.
- relevance: CompletionRelevance,
+ pub relevance: CompletionRelevance,
/// Indicates that a reference or mutable reference to this variable is a
/// possible match.
- ref_match: Option<(Mutability, TextSize)>,
+ // FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
+ // until we have more splitting completions in which case we should think about
+ // generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
+ pub ref_match: Option<(Mutability, TextSize)>,
/// The import data to add to completion's edits.
- import_to_add: SmallVec<[LocatedImport; 1]>,
+ pub import_to_add: SmallVec<[LocatedImport; 1]>,
}
// We use custom debug for CompletionItem to make snapshot tests more readable.
impl fmt::Debug for CompletionItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("CompletionItem");
- s.field("label", &self.label()).field("source_range", &self.source_range());
- if self.text_edit().len() == 1 {
- let atom = &self.text_edit().iter().next().unwrap();
+ s.field("label", &self.label).field("source_range", &self.source_range);
+ if self.text_edit.len() == 1 {
+ let atom = &self.text_edit.iter().next().unwrap();
s.field("delete", &atom.delete);
s.field("insert", &atom.insert);
} else {
s.field("text_edit", &self.text_edit);
}
- s.field("kind", &self.kind());
- if self.lookup() != self.label() {
+ s.field("kind", &self.kind);
+ if self.lookup() != self.label {
s.field("lookup", &self.lookup());
}
- if let Some(detail) = self.detail() {
+ if let Some(detail) = &self.detail {
s.field("detail", &detail);
}
- if let Some(documentation) = self.documentation() {
+ if let Some(documentation) = &self.documentation {
s.field("documentation", &documentation);
}
if self.deprecated {
@@ -351,63 +355,25 @@ impl CompletionItem {
}
}
- /// What user sees in pop-up in the UI.
- pub fn label(&self) -> &str {
- &self.label
- }
- pub fn source_range(&self) -> TextRange {
- self.source_range
- }
-
- pub fn text_edit(&self) -> &TextEdit {
- &self.text_edit
- }
- /// Whether `text_edit` is a snippet (contains `$0` markers).
- pub fn is_snippet(&self) -> bool {
- self.is_snippet
- }
-
- /// Short one-line additional information, like a type
- pub fn detail(&self) -> Option<&str> {
- self.detail.as_deref()
- }
- /// A doc-comment
- pub fn documentation(&self) -> Option<Documentation> {
- self.documentation.clone()
- }
/// What string is used for filtering.
pub fn lookup(&self) -> &str {
self.lookup.as_deref().unwrap_or(&self.label)
}
- pub fn kind(&self) -> CompletionItemKind {
- self.kind
- }
-
- pub fn deprecated(&self) -> bool {
- self.deprecated
- }
-
- pub fn relevance(&self) -> CompletionRelevance {
- self.relevance
- }
-
- pub fn trigger_call_info(&self) -> bool {
- self.trigger_call_info
- }
-
- pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> {
+ pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
// Relevance of the ref match should be the same as the original
// match, but with exact type match set because self.ref_match
// is only set if there is an exact type match.
let mut relevance = self.relevance;
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
- self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
- }
-
- pub fn imports_to_add(&self) -> &[LocatedImport] {
- &self.import_to_add
+ self.ref_match.map(|(mutability, offset)| {
+ (
+ format!("&{}{}", mutability.as_keyword_for_ref(), self.label),
+ text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
+ relevance,
+ )
+ })
}
}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
index 4b48ec6bc..6fe781114 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -156,13 +156,15 @@ pub fn completions(
// prevent `(` from triggering unwanted completion noise
if trigger_character == Some('(') {
- if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis {
- if let NameRefKind::Path(
- path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
- ) = kind
- {
- completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
- }
+ if let CompletionAnalysis::NameRef(NameRefContext {
+ kind:
+ NameRefKind::Path(
+ path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
+ ),
+ ..
+ }) = analysis
+ {
+ completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
}
return Some(completions.into());
}
@@ -170,7 +172,7 @@ pub fn completions(
{
let acc = &mut completions;
- match &analysis {
+ match analysis {
CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
CompletionAnalysis::NameRef(name_ref_ctx) => {
completions::complete_name_ref(acc, ctx, name_ref_ctx)
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
index e48d1aecd..d99ad5f9f 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
@@ -503,18 +503,18 @@ mod tests {
#[track_caller]
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
- actual.retain(|it| kinds.contains(&it.kind()));
- actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
+ actual.retain(|it| kinds.contains(&it.kind));
+ actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
check_relevance_(actual, expect);
}
#[track_caller]
fn check_relevance(ra_fixture: &str, expect: Expect) {
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
- actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
- actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
- actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
- actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
+ actual.retain(|it| it.kind != CompletionItemKind::Snippet);
+ actual.retain(|it| it.kind != CompletionItemKind::Keyword);
+ actual.retain(|it| it.kind != CompletionItemKind::BuiltinType);
+ actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
check_relevance_(actual, expect);
}
@@ -525,12 +525,11 @@ mod tests {
.flat_map(|it| {
let mut items = vec![];
- let tag = it.kind().tag();
- let relevance = display_relevance(it.relevance());
- items.push(format!("{tag} {} {relevance}\n", it.label()));
+ let tag = it.kind.tag();
+ let relevance = display_relevance(it.relevance);
+ items.push(format!("{tag} {} {relevance}\n", it.label));
- if let Some((mutability, _offset, relevance)) = it.ref_match() {
- let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label());
+ if let Some((label, _indel, relevance)) = it.ref_match() {
let relevance = display_relevance(relevance);
items.push(format!("{tag} {label} {relevance}\n"));
@@ -587,6 +586,7 @@ fn main() { Foo::Fo$0 }
),
lookup: "Foo{}",
detail: "Foo { x: i32, y: i32 }",
+ trigger_call_info: true,
},
]
"#]],
@@ -614,6 +614,7 @@ fn main() { Foo::Fo$0 }
),
lookup: "Foo()",
detail: "Foo(i32, i32)",
+ trigger_call_info: true,
},
]
"#]],
@@ -679,6 +680,7 @@ fn main() { Foo::Fo$0 }
Variant,
),
detail: "Foo",
+ trigger_call_info: true,
},
]
"#]],
@@ -745,6 +747,7 @@ fn main() { let _: m::Spam = S$0 }
postfix_match: None,
is_definite: false,
},
+ trigger_call_info: true,
},
CompletionItem {
label: "m::Spam::Foo",
@@ -770,6 +773,7 @@ fn main() { let _: m::Spam = S$0 }
postfix_match: None,
is_definite: false,
},
+ trigger_call_info: true,
},
]
"#]],
@@ -942,6 +946,7 @@ use self::E::*;
documentation: Documentation(
"variant docs",
),
+ trigger_call_info: true,
},
CompletionItem {
label: "E",
@@ -1691,6 +1696,7 @@ fn main() {
sn while []
sn ref []
sn refm []
+ sn unsafe []
sn match []
sn box []
sn dbg []
@@ -1718,6 +1724,7 @@ fn main() {
me f() []
sn ref []
sn refm []
+ sn unsafe []
sn match []
sn box []
sn dbg []
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
index 64dab02f7..ed78fcd8e 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
@@ -113,7 +113,7 @@ fn render(
item.detail(rendered.detail);
match snippet_cap {
- Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal).trigger_call_info(),
None => item.insert_text(rendered.literal),
};
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
index 1b09ad173..6e0c53ec9 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
@@ -72,7 +72,7 @@ pub(crate) fn render_union_literal(
.set_relevance(ctx.completion_relevance());
match ctx.snippet_cap() {
- Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, literal).trigger_call_info(),
None => item.insert_text(literal),
};
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
index abe14e48e..1fe48b9e9 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
@@ -23,7 +23,7 @@ mod type_pos;
mod use_tree;
mod visibility;
-use hir::{db::DefDatabase, PrefixKind, Semantics};
+use hir::{db::DefDatabase, PrefixKind};
use ide_db::{
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
imports::insert_use::{ImportGranularity, InsertUseConfig},
@@ -31,7 +31,6 @@ use ide_db::{
};
use itertools::Itertools;
use stdx::{format_to, trim_indent};
-use syntax::{AstNode, NodeOrToken, SyntaxElement};
use test_utils::assert_eq_text;
use crate::{
@@ -75,6 +74,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
skip_glob_imports: true,
},
snippets: Vec::new(),
+ limit: None,
};
pub(crate) fn completion_list(ra_fixture: &str) -> String {
@@ -108,10 +108,10 @@ fn completion_list_with_config(
let items = get_all_items(config, ra_fixture, trigger_character);
let items = items
.into_iter()
- .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
- .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
- .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
- .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
+ .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32")
+ .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword)
+ .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet)
+ .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned)))
.collect();
render_completion_list(items)
}
@@ -138,8 +138,8 @@ pub(crate) fn do_completion_with_config(
) -> Vec<CompletionItem> {
get_all_items(config, code, None)
.into_iter()
- .filter(|c| c.kind() == kind)
- .sorted_by(|l, r| l.label().cmp(r.label()))
+ .filter(|c| c.kind == kind)
+ .sorted_by(|l, r| l.label.cmp(&r.label))
.collect()
}
@@ -148,18 +148,18 @@ fn render_completion_list(completions: Vec<CompletionItem>) -> String {
s.chars().count()
}
let label_width =
- completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
+ completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22);
completions
.into_iter()
.map(|it| {
- let tag = it.kind().tag();
- let var_name = format!("{tag} {}", it.label());
+ let tag = it.kind.tag();
+ let var_name = format!("{tag} {}", it.label);
let mut buf = var_name;
- if let Some(detail) = it.detail() {
- let width = label_width.saturating_sub(monospace_width(it.label()));
+ if let Some(detail) = it.detail {
+ let width = label_width.saturating_sub(monospace_width(&it.label));
format_to!(buf, "{:width$} {}", "", detail, width = width);
}
- if it.deprecated() {
+ if it.deprecated {
format_to!(buf, " DEPRECATED");
}
format_to!(buf, "\n");
@@ -191,13 +191,13 @@ pub(crate) fn check_edit_with_config(
.unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
let mut actual = db.file_text(position.file_id).to_string();
- let mut combined_edit = completion.text_edit().to_owned();
+ let mut combined_edit = completion.text_edit.clone();
resolve_completion_edits(
&db,
&config,
position,
- completion.imports_to_add().iter().filter_map(|import_edit| {
+ completion.import_to_add.iter().filter_map(|import_edit| {
let import_path = &import_edit.import_path;
let import_name = import_path.segments().last()?;
Some((import_path.to_string(), import_name.to_string()))
@@ -215,15 +215,6 @@ pub(crate) fn check_edit_with_config(
assert_eq_text!(&ra_fixture_after, &actual)
}
-pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
- let (db, pos) = position(code);
-
- let sema = Semantics::new(&db);
- let original_file = sema.parse(pos.file_id);
- let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
- assert!(check(NodeOrToken::Token(token)));
-}
-
pub(crate) fn get_all_items(
config: CompletionConfig,
code: &str,
@@ -234,7 +225,7 @@ pub(crate) fn get_all_items(
.map_or_else(Vec::default, Into::into);
// validate
res.iter().for_each(|it| {
- let sr = it.source_range();
+ let sr = it.source_range;
assert!(
sr.contains_inclusive(position.offset),
"source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
@@ -245,8 +236,9 @@ pub(crate) fn get_all_items(
}
#[test]
-fn test_no_completions_required() {
+fn test_no_completions_in_for_loop_in_kw_pos() {
assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
+ assert_eq!(completion_list(r#"fn foo() { for i in$0 }"#), String::new());
}
#[test]
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
index 043f552bd..c1c6a689e 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
@@ -745,3 +745,255 @@ fn return_value_no_block() {
r#"fn f() -> i32 { match () { () => return $0 } }"#,
);
}
+
+#[test]
+fn else_completion_after_if() {
+ check_empty(
+ r#"
+fn foo() { if foo {} $0 }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { if foo {} el$0 }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { bar(if foo {} $0) }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw crate::
+ kw else
+ kw else if
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { bar(if foo {} el$0) }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw crate::
+ kw else
+ kw else if
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { if foo {} $0 let x = 92; }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { if foo {} el$0 let x = 92; }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+ check_empty(
+ r#"
+fn foo() { if foo {} el$0 { let x = 92; } }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
index a63ef0068..0b485eb77 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
@@ -541,9 +541,9 @@ fn main() {
}
"#,
expect![[r#"
- fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED
- ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED
- "#]],
+ ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED
+ fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED
+ "#]],
);
}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
index b62b98888..9fc731bb1 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
@@ -215,6 +215,57 @@ fn in_trait_assoc_item_list() {
}
#[test]
+fn in_trait_assoc_fn_missing_body() {
+ check(
+ r#"trait Foo { fn function(); $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw self::
+ kw type
+ kw unsafe
+ "#]],
+ );
+}
+
+#[test]
+fn in_trait_assoc_const_missing_body() {
+ check(
+ r#"trait Foo { const CONST: (); $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw self::
+ kw type
+ kw unsafe
+ "#]],
+ );
+}
+
+#[test]
+fn in_trait_assoc_type_aliases_missing_ty() {
+ check(
+ r#"trait Foo { type Type; $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw self::
+ kw type
+ kw unsafe
+ "#]],
+ );
+}
+
+#[test]
fn in_trait_impl_assoc_item_list() {
check(
r#"
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
index 9eae6f849..92ea4d15b 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
@@ -24,16 +24,17 @@ fn main() {
}
"#,
expect![[r#"
- me foo() fn(&self)
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn let let
- sn letm let mut
- sn match match expr {}
- sn ref &expr
- sn refm &mut expr
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
)
}
@@ -54,16 +55,17 @@ fn main() {
}
"#,
expect![[r#"
- me foo() fn(&self)
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn let let
- sn letm let mut
- sn match match expr {}
- sn ref &expr
- sn refm &mut expr
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
)
}
@@ -86,16 +88,17 @@ impl Foo {
fn main() {}
"#,
expect![[r#"
- me foo() fn(&self)
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn let let
- sn letm let mut
- sn match match expr {}
- sn ref &expr
- sn refm &mut expr
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
)
}
@@ -118,16 +121,47 @@ impl Foo {
fn main() {}
"#,
expect![[r#"
- me foo() fn(&self)
- sn box Box::new(expr)
- sn call function(expr)
- sn dbg dbg!(expr)
- sn dbgr dbg!(&expr)
- sn let let
- sn letm let mut
- sn match match expr {}
- sn ref &expr
- sn refm &mut expr
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn unsafe unsafe {}
"#]],
)
}
+
+#[test]
+fn issue_13836_str() {
+ check(
+ r#"
+//- proc_macros: shorten
+fn main() {
+ let s = proc_macros::shorten!("text.$0");
+}
+"#,
+ expect![[r#""#]],
+ )
+}
+
+#[test]
+fn issue_13836_ident() {
+ check(
+ r#"
+//- proc_macros: shorten
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn main() {
+ let s = proc_macros::shorten!(S.fo$0);
+}
+"#,
+ expect![[r#""#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
index cad4af493..cb71c7b2b 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
@@ -2,13 +2,20 @@
use expect_test::{expect, Expect};
-use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character};
+use crate::tests::{
+ check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character,
+};
-fn check(ra_fixture: &str, expect: Expect) {
+fn check_no_kw(ra_fixture: &str, expect: Expect) {
let actual = completion_list_no_kw(ra_fixture);
expect.assert_eq(&actual)
}
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
pub(crate) fn check_with_trigger_character(
ra_fixture: &str,
trigger_character: Option<char>,
@@ -59,9 +66,9 @@ fn _alpha() {}
#[test]
fn completes_prelude() {
- check(
+ check_no_kw(
r#"
-//- /main.rs crate:main deps:std
+//- /main.rs edition:2018 crate:main deps:std
fn foo() { let x: $0 }
//- /std/lib.rs crate:std
@@ -81,9 +88,9 @@ pub mod prelude {
#[test]
fn completes_prelude_macros() {
- check(
+ check_no_kw(
r#"
-//- /main.rs crate:main deps:std
+//- /main.rs edition:2018 crate:main deps:std
fn f() {$0}
//- /std/lib.rs crate:std
@@ -110,21 +117,21 @@ mod macros {
#[test]
fn completes_std_prelude_if_core_is_defined() {
- check(
+ check_no_kw(
r#"
//- /main.rs crate:main deps:core,std
fn foo() { let x: $0 }
//- /core/lib.rs crate:core
pub mod prelude {
- pub mod rust_2018 {
+ pub mod rust_2021 {
pub struct Option;
}
}
//- /std/lib.rs crate:std deps:core
pub mod prelude {
- pub mod rust_2018 {
+ pub mod rust_2021 {
pub struct String;
}
}
@@ -140,7 +147,7 @@ pub mod prelude {
#[test]
fn respects_doc_hidden() {
- check(
+ check_no_kw(
r#"
//- /lib.rs crate:lib deps:std
fn f() {
@@ -168,7 +175,7 @@ pub mod prelude {
#[test]
fn respects_doc_hidden_in_assoc_item_list() {
- check(
+ check_no_kw(
r#"
//- /lib.rs crate:lib deps:std
struct S;
@@ -195,7 +202,7 @@ pub mod prelude {
#[test]
fn associated_item_visibility() {
- check(
+ check_no_kw(
r#"
//- /lib.rs crate:lib new_source_root:library
pub struct S;
@@ -222,7 +229,7 @@ fn foo() { let _ = lib::S::$0 }
#[test]
fn completes_union_associated_method() {
- check(
+ check_no_kw(
r#"
union U {};
impl U { fn m() { } }
@@ -237,7 +244,7 @@ fn foo() { let _ = U::$0 }
#[test]
fn completes_trait_associated_method_1() {
- check(
+ check_no_kw(
r#"
trait Trait { fn m(); }
@@ -251,7 +258,7 @@ fn foo() { let _ = Trait::$0 }
#[test]
fn completes_trait_associated_method_2() {
- check(
+ check_no_kw(
r#"
trait Trait { fn m(); }
@@ -268,7 +275,7 @@ fn foo() { let _ = S::$0 }
#[test]
fn completes_trait_associated_method_3() {
- check(
+ check_no_kw(
r#"
trait Trait { fn m(); }
@@ -285,7 +292,7 @@ fn foo() { let _ = <S as Trait>::$0 }
#[test]
fn completes_ty_param_assoc_ty() {
- check(
+ check_no_kw(
r#"
trait Super {
type Ty;
@@ -318,7 +325,7 @@ fn foo<T: Sub>() { T::$0 }
#[test]
fn completes_self_param_assoc_ty() {
- check(
+ check_no_kw(
r#"
trait Super {
type Ty;
@@ -358,7 +365,7 @@ impl<T> Sub for Wrap<T> {
#[test]
fn completes_type_alias() {
- check(
+ check_no_kw(
r#"
struct S;
impl S { fn foo() {} }
@@ -376,7 +383,7 @@ fn main() { T::$0; }
#[test]
fn completes_qualified_macros() {
- check(
+ check_no_kw(
r#"
#[macro_export]
macro_rules! foo { () => {} }
@@ -392,7 +399,7 @@ fn main() { let _ = crate::$0 }
#[test]
fn does_not_complete_non_fn_macros() {
- check(
+ check_no_kw(
r#"
mod m {
#[rustc_builtin_macro]
@@ -403,7 +410,7 @@ fn f() {m::$0}
"#,
expect![[r#""#]],
);
- check(
+ check_no_kw(
r#"
mod m {
#[rustc_builtin_macro]
@@ -418,7 +425,7 @@ fn f() {m::$0}
#[test]
fn completes_reexported_items_under_correct_name() {
- check(
+ check_no_kw(
r#"
fn foo() { self::m::$0 }
@@ -475,7 +482,7 @@ mod p {
#[test]
fn completes_in_simple_macro_call() {
- check(
+ check_no_kw(
r#"
macro_rules! m { ($e:expr) => { $e } }
fn main() { m!(self::f$0); }
@@ -490,7 +497,7 @@ fn foo() {}
#[test]
fn function_mod_share_name() {
- check(
+ check_no_kw(
r#"
fn foo() { self::m::$0 }
@@ -508,7 +515,7 @@ mod m {
#[test]
fn completes_hashmap_new() {
- check(
+ check_no_kw(
r#"
struct RandomState;
struct HashMap<K, V, S = RandomState> {}
@@ -529,7 +536,7 @@ fn foo() {
#[test]
fn completes_variant_through_self() {
cov_mark::check!(completes_variant_through_self);
- check(
+ check_no_kw(
r#"
enum Foo {
Bar,
@@ -552,7 +559,7 @@ impl Foo {
#[test]
fn completes_non_exhaustive_variant_within_the_defining_crate() {
- check(
+ check_no_kw(
r#"
enum Foo {
#[non_exhaustive]
@@ -570,7 +577,7 @@ fn foo(self) {
"#]],
);
- check(
+ check_no_kw(
r#"
//- /main.rs crate:main deps:e
fn foo(self) {
@@ -593,7 +600,7 @@ enum Foo {
#[test]
fn completes_primitive_assoc_const() {
cov_mark::check!(completes_primitive_assoc_const);
- check(
+ check_no_kw(
r#"
//- /lib.rs crate:lib deps:core
fn f() {
@@ -618,7 +625,7 @@ impl u8 {
#[test]
fn completes_variant_through_alias() {
cov_mark::check!(completes_variant_through_alias);
- check(
+ check_no_kw(
r#"
enum Foo {
Bar
@@ -636,7 +643,7 @@ fn main() {
#[test]
fn respects_doc_hidden2() {
- check(
+ check_no_kw(
r#"
//- /lib.rs crate:lib deps:dep
fn f() {
@@ -665,7 +672,7 @@ pub mod m {}
#[test]
fn type_anchor_empty() {
- check(
+ check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@@ -688,7 +695,7 @@ fn bar() -> Bar {
#[test]
fn type_anchor_type() {
- check(
+ check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@@ -715,7 +722,7 @@ fn bar() -> Bar {
#[test]
fn type_anchor_type_trait() {
- check(
+ check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@@ -741,7 +748,7 @@ fn bar() -> Bar {
#[test]
fn completes_fn_in_pub_trait_generated_by_macro() {
- check(
+ check_no_kw(
r#"
mod other_mod {
macro_rules! make_method {
@@ -775,7 +782,7 @@ fn main() {
#[test]
fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
- check(
+ check_no_kw(
r#"
mod other_mod {
macro_rules! make_method {
@@ -815,7 +822,7 @@ fn main() {
#[test]
fn completes_const_in_pub_trait_generated_by_macro() {
- check(
+ check_no_kw(
r#"
mod other_mod {
macro_rules! make_const {
@@ -847,7 +854,7 @@ fn main() {
#[test]
fn completes_locals_from_macros() {
- check(
+ check_no_kw(
r#"
macro_rules! x {
@@ -875,7 +882,7 @@ fn main() {
#[test]
fn regression_12644() {
- check(
+ check_no_kw(
r#"
macro_rules! __rust_force_expr {
($e:expr) => {
@@ -974,7 +981,7 @@ fn foo { crate:::$0 }
"#,
expect![""],
);
- check(
+ check_no_kw(
r#"
fn foo { crate::::$0 }
"#,