mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-02-13 12:43:38 +00:00
Auto merge of #12562 - Veykril:completion, r=Veykril
Split completion context module into definitions and analysis parts
This commit is contained in:
commit
a69b17be2e
5 changed files with 1513 additions and 1514 deletions
crates/ide-completion/src
File diff suppressed because it is too large
Load diff
1113
crates/ide-completion/src/context/analysis.rs
Normal file
1113
crates/ide-completion/src/context/analysis.rs
Normal file
File diff suppressed because it is too large
Load diff
393
crates/ide-completion/src/context/tests.rs
Normal file
393
crates/ide-completion/src/context/tests.rs
Normal file
|
@ -0,0 +1,393 @@
|
|||
use expect_test::{expect, Expect};
|
||||
use hir::HirDisplay;
|
||||
|
||||
use crate::{
|
||||
context::CompletionContext,
|
||||
tests::{position, TEST_CONFIG},
|
||||
};
|
||||
|
||||
fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
|
||||
let (db, pos) = position(ra_fixture);
|
||||
let config = TEST_CONFIG;
|
||||
let completion_context = CompletionContext::new(&db, pos, &config).unwrap();
|
||||
|
||||
let ty = completion_context
|
||||
.expected_type
|
||||
.map(|t| t.display_test(&db).to_string())
|
||||
.unwrap_or("?".to_owned());
|
||||
|
||||
let name =
|
||||
completion_context.expected_name.map_or_else(|| "?".to_owned(), |name| name.to_string());
|
||||
|
||||
expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_let_without_leading_char() {
|
||||
cov_mark::check!(expected_type_let_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
let x: u32 = $0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_let_with_leading_char() {
|
||||
cov_mark::check!(expected_type_let_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
let x: u32 = c$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_let_pat() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
let x$0 = 0u32;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0 = 0u32;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_fn_param() {
|
||||
cov_mark::check!(expected_type_fn_param);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar($0); }
|
||||
fn bar(x: u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(c$0); }
|
||||
fn bar(x: u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_fn_param_ref() {
|
||||
cov_mark::check!(expected_type_fn_param_ref);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(&$0); }
|
||||
fn bar(x: &u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(&mut $0); }
|
||||
fn bar(x: &mut u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(& c$0); }
|
||||
fn bar(x: &u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(&mut c$0); }
|
||||
fn bar(x: &mut u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() { bar(&c$0); }
|
||||
fn bar(x: &u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: x"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_struct_field_without_leading_char() {
|
||||
cov_mark::check!(expected_type_struct_field_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { a: u32 }
|
||||
fn foo() {
|
||||
Foo { a: $0 };
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: a"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_struct_field_followed_by_comma() {
|
||||
cov_mark::check!(expected_type_struct_field_followed_by_comma);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { a: u32 }
|
||||
fn foo() {
|
||||
Foo { a: $0, };
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: a"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_generic_struct_field() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo<T> { a: T }
|
||||
fn foo() -> Foo<u32> {
|
||||
Foo { a: $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: a"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_struct_field_with_leading_char() {
|
||||
cov_mark::check!(expected_type_struct_field_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { a: u32 }
|
||||
fn foo() {
|
||||
Foo { a: c$0 };
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: a"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_match_arm_without_leading_char() {
|
||||
cov_mark::check!(expected_type_match_arm_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
enum E { X }
|
||||
fn foo() {
|
||||
match E::X { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: E, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_match_arm_with_leading_char() {
|
||||
cov_mark::check!(expected_type_match_arm_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
enum E { X }
|
||||
fn foo() {
|
||||
match E::X { c$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: E, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_match_arm_body_without_leading_char() {
|
||||
cov_mark::check!(expected_type_match_arm_body_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo;
|
||||
enum E { X }
|
||||
fn foo() -> Foo {
|
||||
match E::X { E::X => $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_match_body_arm_with_leading_char() {
|
||||
cov_mark::check!(expected_type_match_arm_body_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo;
|
||||
enum E { X }
|
||||
fn foo() -> Foo {
|
||||
match E::X { E::X => c$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_if_let_without_leading_char() {
|
||||
cov_mark::check!(expected_type_if_let_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
|
||||
fn foo() {
|
||||
let f = Foo::Quux;
|
||||
if let $0 = f { }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_if_let_with_leading_char() {
|
||||
cov_mark::check!(expected_type_if_let_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
|
||||
fn foo() {
|
||||
let f = Foo::Quux;
|
||||
if let c$0 = f { }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_fn_ret_without_leading_char() {
|
||||
cov_mark::check!(expected_type_fn_ret_without_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() -> u32 {
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_fn_ret_with_leading_char() {
|
||||
cov_mark::check!(expected_type_fn_ret_with_leading_char);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() -> u32 {
|
||||
c$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_fn_ret_fn_ref_fully_typed() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() -> u32 {
|
||||
foo$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_closure_param_return() {
|
||||
// FIXME: make this work with `|| $0`
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
//- minicore: fn
|
||||
fn foo() {
|
||||
bar(|| a$0);
|
||||
}
|
||||
|
||||
fn bar(f: impl FnOnce() -> u32) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_generic_function() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
bar::<u32>($0);
|
||||
}
|
||||
|
||||
fn bar<T>(t: T) {}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: t"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_generic_method() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
fn foo() {
|
||||
S(1u32).bar($0);
|
||||
}
|
||||
|
||||
struct S<T>(T);
|
||||
impl<T> S<T> {
|
||||
fn bar(self, t: T) {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: t"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_functional_update() {
|
||||
cov_mark::check!(expected_type_struct_func_update);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { field: u32 }
|
||||
fn foo() {
|
||||
Foo {
|
||||
..$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_param_pat() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { field: u32 }
|
||||
fn foo(a$0: Foo) {}
|
||||
"#,
|
||||
expect![[r#"ty: Foo, name: ?"#]],
|
||||
);
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo { field: u32 }
|
||||
fn foo($0: Foo) {}
|
||||
"#,
|
||||
// FIXME make this work, currently fails due to pattern recovery eating the `:`
|
||||
expect![[r#"ty: ?, name: ?"#]],
|
||||
);
|
||||
}
|
|
@ -4,7 +4,6 @@ mod completions;
|
|||
mod config;
|
||||
mod context;
|
||||
mod item;
|
||||
mod patterns;
|
||||
mod render;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
//! Patterns telling us certain facts about current syntax element, they are used in completion context
|
||||
//!
|
||||
//! Most logic in this module first expands the token below the cursor to a maximum node that acts similar to the token itself.
|
||||
//! This means we for example expand a NameRef token to its outermost Path node, as semantically these act in the same location
|
||||
//! and the completions usually query for path specific things on the Path context instead. This simplifies some location handling.
|
||||
|
||||
use syntax::{
|
||||
ast::{self, HasLoopBody},
|
||||
match_ast, AstNode, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::tests::check_pattern_is_applicable;
|
||||
|
||||
pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
|
||||
element.into_token().and_then(previous_non_trivia_token)
|
||||
}
|
||||
|
||||
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.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 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"fn __() { for i i$0 }", is_in_token_of_for_loop);
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
|
||||
node.ancestors()
|
||||
.take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR)
|
||||
.find_map(|it| {
|
||||
let loop_body = match_ast! {
|
||||
match it {
|
||||
ast::ForExpr(it) => it.loop_body(),
|
||||
ast::WhileExpr(it) => it.loop_body(),
|
||||
ast::LoopExpr(it) => it.loop_body(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
loop_body.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
|
||||
let mut token = token.prev_token();
|
||||
while let Some(inner) = token {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
token = inner.prev_token();
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
Loading…
Add table
Reference in a new issue