summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-completion
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:11:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:13:23 +0000
commit20431706a863f92cb37dc512fef6e48d192aaf2c (patch)
tree2867f13f5fd5437ba628c67d7f87309ccadcd286 /src/tools/rust-analyzer/crates/ide-completion
parentReleasing progress-linux version 1.65.0+dfsg1-2~progress7.99u1. (diff)
downloadrustc-20431706a863f92cb37dc512fef6e48d192aaf2c.tar.xz
rustc-20431706a863f92cb37dc512fef6e48d192aaf2c.zip
Merging upstream version 1.66.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion')
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/Cargo.toml6
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs150
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs14
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs51
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs247
-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.rs45
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs1939
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs27
15 files changed, 1275 insertions, 1242 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
index 8c9d6b228..75835bce9 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
@@ -11,10 +11,10 @@ doctest = false
[dependencies]
cov-mark = "2.0.0-pre.1"
-itertools = "0.10.3"
+itertools = "0.10.5"
-once_cell = "1.12.0"
-smallvec = "1.9.0"
+once_cell = "1.15.0"
+smallvec = "1.10.0"
stdx = { path = "../stdx", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
index 55c3e2839..296dfc142 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
@@ -19,6 +19,7 @@ pub(crate) mod snippet;
pub(crate) mod r#type;
pub(crate) mod use_;
pub(crate) mod vis;
+pub(crate) mod env_vars;
use std::iter;
@@ -551,7 +552,11 @@ fn enum_variants_with_paths(
}
for variant in variants {
- if let Some(path) = ctx.module.find_use_path(ctx.db, hir::ModuleDef::from(variant)) {
+ if let Some(path) = ctx.module.find_use_path(
+ ctx.db,
+ hir::ModuleDef::from(variant),
+ ctx.config.prefer_no_std,
+ ) {
// Variants with trivial paths are already added by the existing completion logic,
// so we should avoid adding these twice
if path.segments().len() > 1 {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs
new file mode 100644
index 000000000..09e95e53d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs
@@ -0,0 +1,150 @@
+//! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html)
+use hir::Semantics;
+use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase};
+use syntax::ast::{self, IsString};
+
+use crate::{
+ completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
+};
+
+const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
+ ("CARGO","Path to the cargo binary performing the build"),
+ ("CARGO_MANIFEST_DIR","The directory containing the manifest of your package"),
+ ("CARGO_PKG_VERSION","The full version of your package"),
+ ("CARGO_PKG_VERSION_MAJOR","The major version of your package"),
+ ("CARGO_PKG_VERSION_MINOR","The minor version of your package"),
+ ("CARGO_PKG_VERSION_PATCH","The patch version of your package"),
+ ("CARGO_PKG_VERSION_PRE","The pre-release version of your package"),
+ ("CARGO_PKG_AUTHORS","Colon separated list of authors from the manifest of your package"),
+ ("CARGO_PKG_NAME","The name of your package"),
+ ("CARGO_PKG_DESCRIPTION","The description from the manifest of your package"),
+ ("CARGO_PKG_HOMEPAGE","The home page from the manifest of your package"),
+ ("CARGO_PKG_REPOSITORY","The repository from the manifest of your package"),
+ ("CARGO_PKG_LICENSE","The license from the manifest of your package"),
+ ("CARGO_PKG_LICENSE_FILE","The license file from the manifest of your package"),
+ ("CARGO_PKG_RUST_VERSION","The Rust version from the manifest of your package. Note that this is the minimum Rust version supported by the package, not the current Rust version"),
+ ("CARGO_CRATE_NAME","The name of the crate that is currently being compiled"),
+ ("CARGO_BIN_NAME","The name of the binary that is currently being compiled (if it is a binary). This name does not include any file extension, such as .exe"),
+ ("CARGO_PRIMARY_PACKAGE","This environment variable will be set if the package being built is primary. Primary packages are the ones the user selected on the command-line, either with -p flags or the defaults based on the current directory and the default workspace members. This environment variable will not be set when building dependencies. This is only set when compiling the package (not when running binaries or tests)"),
+ ("CARGO_TARGET_TMPDIR","Only set when building integration test or benchmark code. This is a path to a directory inside the target directory where integration tests or benchmarks are free to put any data needed by the tests/benches. Cargo initially creates this directory but doesn't manage its content in any way, this is the responsibility of the test code")
+];
+
+pub(crate) fn complete_cargo_env_vars(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ expanded: &ast::String,
+) -> Option<()> {
+ guard_env_macro(expanded, &ctx.sema)?;
+ let range = expanded.text_range_between_quotes()?;
+
+ CARGO_DEFINED_VARS.iter().for_each(|(var, detail)| {
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
+ item.detail(*detail);
+ item.add_to(acc);
+ });
+
+ Some(())
+}
+
+fn guard_env_macro(string: &ast::String, semantics: &Semantics<'_, RootDatabase>) -> Option<()> {
+ let call = macro_call_for_string_token(string)?;
+ let name = call.path()?.segment()?.name_ref()?;
+ let makro = semantics.resolve_macro_call(&call)?;
+ let db = semantics.db;
+
+ match name.text().as_str() {
+ "env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(macro_name: &str) {
+ check_edit(
+ "CARGO_BIN_NAME",
+ &format!(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! {} {{
+ ($var:literal) => {{ 0 }}
+ }}
+
+ fn main() {{
+ let foo = {}!("CAR$0");
+ }}
+ "#,
+ macro_name, macro_name
+ ),
+ &format!(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! {} {{
+ ($var:literal) => {{ 0 }}
+ }}
+
+ fn main() {{
+ let foo = {}!("CARGO_BIN_NAME");
+ }}
+ "#,
+ macro_name, macro_name
+ ),
+ );
+ }
+ #[test]
+ fn completes_env_variable_in_env() {
+ check("env")
+ }
+
+ #[test]
+ fn completes_env_variable_in_option_env() {
+ check("option_env");
+ }
+
+ #[test]
+ fn doesnt_complete_in_random_strings() {
+ let fixture = r#"
+ fn main() {
+ let foo = "CA$0";
+ }
+ "#;
+
+ let completions = completion_list(fixture);
+ assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
+ }
+
+ #[test]
+ fn doesnt_complete_in_random_macro() {
+ let fixture = r#"
+ macro_rules! bar {
+ ($($arg:tt)*) => { 0 }
+ }
+
+ fn main() {
+ let foo = bar!("CA$0");
+
+ }
+ "#;
+
+ let completions = completion_list(fixture);
+ assert!(completions.is_empty(), "Completions weren't empty: {}", completions);
+ }
+
+ #[test]
+ fn doesnt_complete_for_shadowed_macro() {
+ let fixture = r#"
+ macro_rules! env {
+ ($var:literal) => { 0 }
+ }
+
+ fn main() {
+ let foo = env!("CA$0");
+ }
+ "#;
+
+ let completions = completion_list(fixture);
+ assert!(completions.is_empty(), "Completions weren't empty: {}", completions)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
index 588b52cc1..3192b21cf 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
@@ -165,7 +165,11 @@ pub(crate) fn complete_expr_path(
hir::Adt::Struct(strukt) => {
let path = ctx
.module
- .find_use_path(ctx.db, hir::ModuleDef::from(strukt))
+ .find_use_path(
+ ctx.db,
+ hir::ModuleDef::from(strukt),
+ ctx.config.prefer_no_std,
+ )
.filter(|it| it.len() > 1);
acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
@@ -183,7 +187,11 @@ pub(crate) fn complete_expr_path(
hir::Adt::Union(un) => {
let path = ctx
.module
- .find_use_path(ctx.db, hir::ModuleDef::from(un))
+ .find_use_path(
+ ctx.db,
+ hir::ModuleDef::from(un),
+ ctx.config.prefer_no_std,
+ )
.filter(|it| it.len() > 1);
acc.add_union_literal(ctx, un, path, None);
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
index f04cc15d7..364969af9 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
@@ -262,7 +262,11 @@ fn import_on_the_fly(
acc.add_all(
import_assets
- .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .search_for_imports(
+ &ctx.sema,
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ )
.into_iter()
.filter(ns_filter)
.filter(|import| {
@@ -306,7 +310,11 @@ fn import_on_the_fly_pat_(
acc.add_all(
import_assets
- .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .search_for_imports(
+ &ctx.sema,
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ )
.into_iter()
.filter(ns_filter)
.filter(|import| {
@@ -344,7 +352,7 @@ fn import_on_the_fly_method(
let user_input_lowercased = potential_import_name.to_lowercase();
import_assets
- .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std)
.into_iter()
.filter(|import| {
!ctx.is_item_hidden(&import.item_to_import)
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 785db6fde..e82cbfdcb 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
@@ -38,7 +38,7 @@ use ide_db::{
};
use syntax::{
ast::{self, edit_in_place::AttrsOwnerEdit},
- AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
+ AstNode, SyntaxElement, SyntaxKind, TextRange, T,
};
use text_edit::TextEdit;
@@ -85,20 +85,36 @@ fn complete_trait_impl_name(
name: &Option<ast::Name>,
kind: ImplCompletionKind,
) -> Option<()> {
- let token = ctx.token.clone();
let item = match name {
Some(name) => name.syntax().parent(),
- None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
- .parent(),
+ None => {
+ let token = &ctx.token;
+ match token.kind() {
+ SyntaxKind::WHITESPACE => token.prev_token()?,
+ _ => token.clone(),
+ }
+ .parent()
+ }
}?;
- complete_trait_impl(
- acc,
- ctx,
- kind,
- replacement_range(ctx, &item),
- // item -> ASSOC_ITEM_LIST -> IMPL
- &ast::Impl::cast(item.parent()?.parent()?)?,
- );
+ let item = ctx.sema.original_syntax_node(&item)?;
+ // item -> ASSOC_ITEM_LIST -> IMPL
+ let impl_def = ast::Impl::cast(item.parent()?.parent()?)?;
+ let replacement_range = {
+ // ctx.sema.original_ast_node(item)?;
+ let first_child = item
+ .children_with_tokens()
+ .find(|child| {
+ !matches!(
+ child.kind(),
+ SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR
+ )
+ })
+ .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
+
+ TextRange::new(first_child.text_range().start(), ctx.source_range().end())
+ };
+
+ complete_trait_impl(acc, ctx, kind, replacement_range, &impl_def);
Some(())
}
@@ -341,17 +357,6 @@ fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
syntax.trim_end().to_owned()
}
-fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange {
- let first_child = item
- .children_with_tokens()
- .find(|child| {
- !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR)
- })
- .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
-
- TextRange::new(first_child.text_range().start(), ctx.source_range().end())
-}
-
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
index 71d2d9d43..58d5bf114 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
@@ -145,6 +145,7 @@ pub(crate) fn complete_pattern_path(
u.ty(ctx.db)
}
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(ty)) => ty.ty(ctx.db),
+ hir::PathResolution::Def(hir::ModuleDef::TypeAlias(ty)) => ty.ty(ctx.db),
_ => return,
};
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
index b273a4cb5..b43bdb9ab 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
@@ -16,8 +16,11 @@
//
// image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
-use ide_db::SnippetCap;
-use syntax::ast::{self, AstToken};
+use ide_db::{
+ syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders},
+ SnippetCap,
+};
+use syntax::{ast, AstToken};
use crate::{
completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
@@ -43,250 +46,24 @@ pub(crate) fn add_format_like_completions(
cap: SnippetCap,
receiver_text: &ast::String,
) {
- let input = match string_literal_contents(receiver_text) {
- // It's not a string literal, do not parse input.
- Some(input) => input,
- None => return,
- };
-
let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
Some(it) => it,
None => return,
};
- let mut parser = FormatStrParser::new(input);
- if parser.parse().is_ok() {
+ if let Ok((out, exprs)) = parse_format_exprs(receiver_text.text()) {
+ let exprs = with_placeholders(exprs);
for (label, macro_name) in KINDS {
- let snippet = parser.to_suggestion(macro_name);
+ let snippet = format!(r#"{}({}, {})"#, macro_name, out, exprs.join(", "));
postfix_snippet(label, macro_name, &snippet).add_to(acc);
}
}
}
-/// Checks whether provided item is a string literal.
-fn string_literal_contents(item: &ast::String) -> Option<String> {
- let item = item.text();
- if item.len() >= 2 && item.starts_with('\"') && item.ends_with('\"') {
- return Some(item[1..item.len() - 1].to_owned());
- }
-
- None
-}
-
-/// Parser for a format-like string. It is more allowing in terms of string contents,
-/// as we expect variable placeholders to be filled with expressions.
-#[derive(Debug)]
-pub(crate) struct FormatStrParser {
- input: String,
- output: String,
- extracted_expressions: Vec<String>,
- state: State,
- parsed: bool,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-enum State {
- NotExpr,
- MaybeExpr,
- Expr,
- MaybeIncorrect,
- FormatOpts,
-}
-
-impl FormatStrParser {
- pub(crate) fn new(input: String) -> Self {
- Self {
- input,
- output: String::new(),
- extracted_expressions: Vec::new(),
- state: State::NotExpr,
- parsed: false,
- }
- }
-
- pub(crate) fn parse(&mut self) -> Result<(), ()> {
- let mut current_expr = String::new();
-
- let mut placeholder_id = 1;
-
- // Count of open braces inside of an expression.
- // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
- // "{MyStruct { val_a: 0, val_b: 1 }}".
- let mut inexpr_open_count = 0;
-
- // We need to escape '\' and '$'. See the comments on `get_receiver_text()` for detail.
- let mut chars = self.input.chars().peekable();
- while let Some(chr) = chars.next() {
- match (self.state, chr) {
- (State::NotExpr, '{') => {
- self.output.push(chr);
- self.state = State::MaybeExpr;
- }
- (State::NotExpr, '}') => {
- self.output.push(chr);
- self.state = State::MaybeIncorrect;
- }
- (State::NotExpr, _) => {
- if matches!(chr, '\\' | '$') {
- self.output.push('\\');
- }
- self.output.push(chr);
- }
- (State::MaybeIncorrect, '}') => {
- // It's okay, we met "}}".
- self.output.push(chr);
- self.state = State::NotExpr;
- }
- (State::MaybeIncorrect, _) => {
- // Error in the string.
- return Err(());
- }
- (State::MaybeExpr, '{') => {
- self.output.push(chr);
- self.state = State::NotExpr;
- }
- (State::MaybeExpr, '}') => {
- // This is an empty sequence '{}'. Replace it with placeholder.
- self.output.push(chr);
- self.extracted_expressions.push(format!("${}", placeholder_id));
- placeholder_id += 1;
- self.state = State::NotExpr;
- }
- (State::MaybeExpr, _) => {
- if matches!(chr, '\\' | '$') {
- current_expr.push('\\');
- }
- current_expr.push(chr);
- self.state = State::Expr;
- }
- (State::Expr, '}') => {
- if inexpr_open_count == 0 {
- self.output.push(chr);
- self.extracted_expressions.push(current_expr.trim().into());
- current_expr = String::new();
- self.state = State::NotExpr;
- } else {
- // We're closing one brace met before inside of the expression.
- current_expr.push(chr);
- inexpr_open_count -= 1;
- }
- }
- (State::Expr, ':') if chars.peek().copied() == Some(':') => {
- // path separator
- current_expr.push_str("::");
- chars.next();
- }
- (State::Expr, ':') => {
- if inexpr_open_count == 0 {
- // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
- self.output.push(chr);
- self.extracted_expressions.push(current_expr.trim().into());
- current_expr = String::new();
- self.state = State::FormatOpts;
- } else {
- // We're inside of braced expression, assume that it's a struct field name/value delimiter.
- current_expr.push(chr);
- }
- }
- (State::Expr, '{') => {
- current_expr.push(chr);
- inexpr_open_count += 1;
- }
- (State::Expr, _) => {
- if matches!(chr, '\\' | '$') {
- current_expr.push('\\');
- }
- current_expr.push(chr);
- }
- (State::FormatOpts, '}') => {
- self.output.push(chr);
- self.state = State::NotExpr;
- }
- (State::FormatOpts, _) => {
- if matches!(chr, '\\' | '$') {
- self.output.push('\\');
- }
- self.output.push(chr);
- }
- }
- }
-
- if self.state != State::NotExpr {
- return Err(());
- }
-
- self.parsed = true;
- Ok(())
- }
-
- pub(crate) fn to_suggestion(&self, macro_name: &str) -> String {
- assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
-
- let expressions_as_string = self.extracted_expressions.join(", ");
- format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
- use expect_test::{expect, Expect};
-
- fn check(input: &str, expect: &Expect) {
- let mut parser = FormatStrParser::new((*input).to_owned());
- let outcome_repr = if parser.parse().is_ok() {
- // Parsing should be OK, expected repr is "string; expr_1, expr_2".
- if parser.extracted_expressions.is_empty() {
- parser.output
- } else {
- format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
- }
- } else {
- // Parsing should fail, expected repr is "-".
- "-".to_owned()
- };
-
- expect.assert_eq(&outcome_repr);
- }
-
- #[test]
- fn format_str_parser() {
- let test_vector = &[
- ("no expressions", expect![["no expressions"]]),
- (r"no expressions with \$0$1", expect![r"no expressions with \\\$0\$1"]),
- ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
- ("{expr:?}", expect![["{:?}; expr"]]),
- ("{expr:1$}", expect![[r"{:1\$}; expr"]]),
- ("{$0}", expect![[r"{}; \$0"]]),
- ("{malformed", expect![["-"]]),
- ("malformed}", expect![["-"]]),
- ("{{correct", expect![["{{correct"]]),
- ("correct}}", expect![["correct}}"]]),
- ("{correct}}}", expect![["{}}}; correct"]]),
- ("{correct}}}}}", expect![["{}}}}}; correct"]]),
- ("{incorrect}}", expect![["-"]]),
- ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
- ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
- (
- "{SomeStruct { val_a: 0, val_b: 1 }}",
- expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
- ),
- ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
- (
- "{SomeStruct { val_a: 0, val_b: 1 }:?}",
- expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
- ),
- ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
- ("{strsim::jaro_winkle(a)}", expect![["{}; strsim::jaro_winkle(a)"]]),
- ("{foo::bar::baz()}", expect![["{}; foo::bar::baz()"]]),
- ("{foo::bar():?}", expect![["{:?}; foo::bar()"]]),
- ];
-
- for (input, output) in test_vector {
- check(input, output)
- }
- }
#[test]
fn test_into_suggestion() {
@@ -302,10 +79,10 @@ mod tests {
];
for (kind, input, output) in test_vector {
- let mut parser = FormatStrParser::new((*input).to_owned());
- parser.parse().expect("Parsing must succeed");
-
- assert_eq!(&parser.to_suggestion(*kind), output);
+ let (parsed_string, exprs) = parse_format_exprs(input).unwrap();
+ let exprs = with_placeholders(exprs);
+ let snippet = format!(r#"{}("{}", {})"#, kind, parsed_string, exprs.join(", "));
+ assert_eq!(&snippet, output);
}
}
}
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 80d6af281..a0f5e81b4 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
@@ -17,6 +17,7 @@ pub struct CompletionConfig {
pub callable: Option<CallableSnippets>,
pub snippet_cap: Option<SnippetCap>,
pub insert_use: InsertUseConfig,
+ pub prefer_no_std: bool,
pub snippets: Vec<Snippet>,
}
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 a5e854b74..9850813a0 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -1,4 +1,4 @@
-//! See `CompletionContext` structure.
+//! See [`CompletionContext`] structure.
mod analysis;
#[cfg(test)]
@@ -23,7 +23,10 @@ use syntax::{
};
use text_edit::Indel;
-use crate::CompletionConfig;
+use crate::{
+ context::analysis::{expand_and_analyze, AnalysisResult},
+ CompletionConfig,
+};
const COMPLETION_MARKER: &str = "intellijRulezz";
@@ -561,15 +564,27 @@ impl<'a> CompletionContext<'a> {
let edit = Indel::insert(offset, COMPLETION_MARKER.to_string());
parse.reparse(&edit).tree()
};
- let fake_ident_token =
- file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
+ // always pick the token to the immediate left of the cursor, as that is what we are actually
+ // completing on
let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
- let token = sema.descend_into_macros_single(original_token.clone());
+
+ let AnalysisResult {
+ analysis,
+ expected: (expected_type, expected_name),
+ qualifier_ctx,
+ token,
+ offset,
+ } = expand_and_analyze(
+ &sema,
+ original_file.syntax().clone(),
+ file_with_fake_ident.syntax().clone(),
+ offset,
+ &original_token,
+ )?;
// adjust for macro input, this still fails if there is no token written yet
- let scope_offset = if original_token == token { offset } else { token.text_range().end() };
- let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?;
+ let scope = sema.scope_at_offset(&token.parent()?, offset)?;
let krate = scope.krate();
let module = scope.module();
@@ -583,7 +598,7 @@ impl<'a> CompletionContext<'a> {
let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count();
- let mut ctx = CompletionContext {
+ let ctx = CompletionContext {
sema,
scope,
db,
@@ -593,19 +608,13 @@ impl<'a> CompletionContext<'a> {
token,
krate,
module,
- expected_name: None,
- expected_type: None,
- qualifier_ctx: Default::default(),
+ expected_name,
+ expected_type,
+ qualifier_ctx,
locals,
depth_from_crate_root,
};
- let ident_ctx = ctx.expand_and_analyze(
- original_file.syntax().clone(),
- file_with_fake_ident.syntax().clone(),
- offset,
- fake_ident_token,
- )?;
- Some((ctx, ident_ctx))
+ Some((ctx, analysis))
}
}
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 01dd9a234..04111ec7e 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
@@ -11,1063 +11,1094 @@ use syntax::{
};
use crate::context::{
- AttrCtx, CompletionAnalysis, CompletionContext, DotAccess, DotAccessKind, ExprCtx,
- ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext,
- NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathKind, PatternContext,
- PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation,
- COMPLETION_MARKER,
+ AttrCtx, CompletionAnalysis, DotAccess, DotAccessKind, ExprCtx, ItemListKind, LifetimeContext,
+ LifetimeKind, NameContext, NameKind, NameRefContext, NameRefKind, ParamContext, ParamKind,
+ PathCompletionCtx, PathKind, PatternContext, PatternRefutability, Qualified, QualifierCtx,
+ TypeAscriptionTarget, TypeLocation, COMPLETION_MARKER,
};
-impl<'a> CompletionContext<'a> {
- /// Expand attributes and macro calls at the current cursor position for both the original file
- /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
- /// and speculative states stay in sync.
- pub(super) fn expand_and_analyze(
- &mut self,
- mut original_file: SyntaxNode,
- mut speculative_file: SyntaxNode,
- mut offset: TextSize,
- mut fake_ident_token: SyntaxToken,
- ) -> Option<CompletionAnalysis> {
- let _p = profile::span("CompletionContext::expand_and_fill");
- let mut derive_ctx = None;
-
- 'expansion: loop {
- let parent_item =
- |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
- let ancestor_items = iter::successors(
- Option::zip(
- find_node_at_offset::<ast::Item>(&original_file, offset),
- find_node_at_offset::<ast::Item>(&speculative_file, offset),
+struct ExpansionResult {
+ original_file: SyntaxNode,
+ speculative_file: SyntaxNode,
+ offset: TextSize,
+ fake_ident_token: SyntaxToken,
+ derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
+}
+
+pub(super) struct AnalysisResult {
+ pub(super) analysis: CompletionAnalysis,
+ pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>),
+ pub(super) qualifier_ctx: QualifierCtx,
+ pub(super) token: SyntaxToken,
+ pub(super) offset: TextSize,
+}
+
+pub(super) fn expand_and_analyze(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: SyntaxNode,
+ speculative_file: SyntaxNode,
+ offset: TextSize,
+ original_token: &SyntaxToken,
+) -> Option<AnalysisResult> {
+ // as we insert after the offset, right biased will *always* pick the identifier no matter
+ // if there is an ident already typed or not
+ let fake_ident_token = speculative_file.token_at_offset(offset).right_biased()?;
+ // the relative offset between the cursor and the *identifier* token we are completing on
+ let relative_offset = offset - fake_ident_token.text_range().start();
+ // 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);
+ // 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()?;
+
+ analyze(sema, expansion, original_token, &token).map(|(analysis, expected, qualifier_ctx)| {
+ AnalysisResult { analysis, expected, qualifier_ctx, token, offset }
+ })
+}
+
+/// Expand attributes and macro calls at the current cursor position for both the original file
+/// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
+/// and speculative states stay in sync.
+fn expand(
+ sema: &Semantics<'_, RootDatabase>,
+ mut original_file: SyntaxNode,
+ mut speculative_file: SyntaxNode,
+ mut offset: TextSize,
+ mut fake_ident_token: SyntaxToken,
+) -> ExpansionResult {
+ let _p = profile::span("CompletionContext::expand");
+ let mut derive_ctx = None;
+
+ 'expansion: loop {
+ let parent_item =
+ |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
+ let ancestor_items = iter::successors(
+ Option::zip(
+ find_node_at_offset::<ast::Item>(&original_file, offset),
+ find_node_at_offset::<ast::Item>(&speculative_file, offset),
+ ),
+ |(a, b)| parent_item(a).zip(parent_item(b)),
+ );
+
+ // first try to expand attributes as these are always the outermost macro calls
+ 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
+ match (
+ sema.expand_attr_macro(&actual_item),
+ sema.speculative_expand_attr_macro(
+ &actual_item,
+ &item_with_fake_ident,
+ fake_ident_token.clone(),
),
- |(a, b)| parent_item(a).zip(parent_item(b)),
- );
-
- // first try to expand attributes as these are always the outermost macro calls
- 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
- match (
- self.sema.expand_attr_macro(&actual_item),
- self.sema.speculative_expand_attr_macro(
- &actual_item,
- &item_with_fake_ident,
- fake_ident_token.clone(),
- ),
- ) {
- // maybe parent items have attributes, so continue walking the ancestors
- (None, None) => continue 'ancestors,
- // 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() {
- // offset outside of bounds from the original expansion,
- // stop here to prevent problems from happening
- break 'expansion;
- }
- original_file = actual_expansion;
- speculative_file = fake_expansion;
- fake_ident_token = fake_mapped_token;
- offset = new_offset;
- continue 'expansion;
+ ) {
+ // maybe parent items have attributes, so continue walking the ancestors
+ (None, None) => continue 'ancestors,
+ // 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() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
}
- // exactly one expansion failed, inconsistent state so stop expanding completely
- _ => break 'expansion,
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
}
+ // exactly one expansion failed, inconsistent state so stop expanding completely
+ _ => break 'expansion,
}
+ }
- // No attributes have been expanded, so look for macro_call! token trees or derive token trees
- let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
- Some(it) => it,
- None => break 'expansion,
- };
- let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
- Some(it) => it,
- None => break 'expansion,
- };
+ // No attributes have been expanded, so look for macro_call! token trees or derive token trees
+ let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
+ let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
- // Expand pseudo-derive expansion
- if let (Some(orig_attr), Some(spec_attr)) = (
- orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
- spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ // Expand pseudo-derive expansion
+ if let (Some(orig_attr), Some(spec_attr)) = (
+ orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ ) {
+ if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
+ sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
+ sema.speculative_expand_derive_as_pseudo_attr_macro(
+ &orig_attr,
+ &spec_attr,
+ fake_ident_token.clone(),
+ ),
) {
- if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
- self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
- self.sema.speculative_expand_derive_as_pseudo_attr_macro(
- &orig_attr,
- &spec_attr,
- fake_ident_token.clone(),
- ),
- ) {
- derive_ctx = Some((
- actual_expansion,
- fake_expansion,
- fake_mapped_token.text_range().start(),
- orig_attr,
- ));
- }
- // at this point we won't have any more successful expansions, so stop
+ derive_ctx = Some((
+ actual_expansion,
+ fake_expansion,
+ fake_mapped_token.text_range().start(),
+ orig_attr,
+ ));
+ }
+ // at this point we won't have any more successful expansions, so stop
+ break 'expansion;
+ }
+
+ // Expand fn-like macro calls
+ if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
+ orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ ) {
+ let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
+ let mac_call_path1 =
+ macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
+
+ // inconsistent state, stop expanding
+ if mac_call_path0 != mac_call_path1 {
break 'expansion;
}
+ let speculative_args = match macro_call_with_fake_ident.token_tree() {
+ Some(tt) => tt,
+ None => break 'expansion,
+ };
- // Expand fn-like macro calls
- if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
- orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
- spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ match (
+ sema.expand(&actual_macro_call),
+ sema.speculative_expand(
+ &actual_macro_call,
+ &speculative_args,
+ fake_ident_token.clone(),
+ ),
) {
- let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
- let mac_call_path1 =
- macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
-
- // inconsistent state, stop expanding
- if mac_call_path0 != mac_call_path1 {
- break 'expansion;
- }
- let speculative_args = match macro_call_with_fake_ident.token_tree() {
- Some(tt) => tt,
- None => break 'expansion,
- };
-
- match (
- self.sema.expand(&actual_macro_call),
- self.sema.speculative_expand(
- &actual_macro_call,
- &speculative_args,
- fake_ident_token.clone(),
- ),
- ) {
- // 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() {
- // offset outside of bounds from the original expansion,
- // stop here to prevent problems from happening
- break 'expansion;
- }
- original_file = actual_expansion;
- speculative_file = fake_expansion;
- fake_ident_token = fake_mapped_token;
- offset = new_offset;
- continue 'expansion;
+ // 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() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
}
- // at least on expansion failed, we won't have anything to expand from this point
- // onwards so break out
- _ => break 'expansion,
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
}
+ // at least on expansion failed, we won't have anything to expand from this point
+ // onwards so break out
+ _ => break 'expansion,
}
-
- // none of our states have changed so stop the loop
- break 'expansion;
}
- self.analyze(&original_file, speculative_file, offset, derive_ctx)
+ // none of our states have changed so stop the loop
+ break 'expansion;
}
+ ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
+}
- /// Calculate the expected type and name of the cursor position.
- fn expected_type_and_name(
- &self,
- name_like: &ast::NameLike,
- ) -> (Option<Type>, Option<NameOrNameRef>) {
- let mut node = match self.token.parent() {
- Some(it) => it,
- None => return (None, None),
- };
+/// Fill the completion context, this is what does semantic reasoning about the surrounding context
+/// of the completion location.
+fn analyze(
+ sema: &Semantics<'_, RootDatabase>,
+ expansion_result: ExpansionResult,
+ original_token: &SyntaxToken,
+ self_token: &SyntaxToken,
+) -> Option<(CompletionAnalysis, (Option<Type>, Option<ast::NameOrNameRef>), QualifierCtx)> {
+ 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;
+ }
- let strip_refs = |mut ty: Type| match name_like {
- ast::NameLike::NameRef(n) => {
- let p = match n.syntax().parent() {
- Some(it) => it,
- None => return ty,
- };
- let top_syn = match_ast! {
- match p {
- ast::FieldExpr(e) => e
- .syntax()
- .ancestors()
- .map_while(ast::FieldExpr::cast)
- .last()
- .map(|it| it.syntax().clone()),
- 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()),
- _ => None
- }
+ // Overwrite the path kind for derives
+ if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
+ if let Some(ast::NameLike::NameRef(name_ref)) =
+ find_node_at_offset(&file_with_fake_ident, offset)
+ {
+ let parent = name_ref.syntax().parent()?;
+ let (mut nameref_ctx, _) = classify_name_ref(&sema, &original_file, name_ref, parent)?;
+ if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind {
+ path_ctx.kind = PathKind::Derive {
+ existing_derives: sema
+ .resolve_derive_macro(&origin_attr)
+ .into_iter()
+ .flatten()
+ .flatten()
+ .collect(),
};
- let top_syn = match top_syn {
- Some(it) => it,
- None => return ty,
- };
- for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
- cov_mark::hit!(expected_type_fn_param_ref);
- ty = ty.strip_reference();
- }
- ty
}
- _ => ty,
- };
+ return Some((
+ CompletionAnalysis::NameRef(nameref_ctx),
+ (None, None),
+ QualifierCtx::default(),
+ ));
+ }
+ return None;
+ }
- loop {
- break match_ast! {
- match node {
- ast::LetStmt(it) => {
- cov_mark::hit!(expected_type_let_with_leading_char);
- cov_mark::hit!(expected_type_let_without_leading_char);
- let ty = it.pat()
- .and_then(|pat| self.sema.type_of_pat(&pat))
- .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)))
- .map(TypeInfo::original);
- let name = match it.pat() {
- Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
- Some(_) | None => None,
- };
-
- (ty, name)
- },
- ast::LetExpr(it) => {
- cov_mark::hit!(expected_type_if_let_without_leading_char);
- let ty = it.pat()
- .and_then(|pat| self.sema.type_of_pat(&pat))
- .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it)))
- .map(TypeInfo::original);
- (ty, None)
- },
- ast::ArgList(_) => {
- cov_mark::hit!(expected_type_fn_param);
- ActiveParameter::at_token(
- &self.sema,
- self.token.clone(),
- ).map(|ap| {
- let name = ap.ident().map(NameOrNameRef::Name);
-
- let ty = strip_refs(ap.ty);
- (Some(ty), name)
- })
- .unwrap_or((None, None))
- },
- ast::RecordExprFieldList(it) => {
- // wouldn't try {} be nice...
- (|| {
- if self.token.kind() == T![..]
- || self.token.prev_token().map(|t| t.kind()) == Some(T![..])
- {
- cov_mark::hit!(expected_type_struct_func_update);
- let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?;
- let ty = self.sema.type_of_expr(&record_expr.into())?;
- Some((
- Some(ty.original),
- None
- ))
- } else {
- cov_mark::hit!(expected_type_struct_field_without_leading_char);
- let expr_field = self.token.prev_sibling_or_token()?
- .into_node()
- .and_then(ast::RecordExprField::cast)?;
- let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?;
- Some((
- Some(ty),
- expr_field.field_name().map(NameOrNameRef::NameRef),
- ))
- }
- })().unwrap_or((None, None))
- },
- ast::RecordExprField(it) => {
- if let Some(expr) = it.expr() {
- cov_mark::hit!(expected_type_struct_field_with_leading_char);
- (
- self.sema.type_of_expr(&expr).map(TypeInfo::original),
- it.field_name().map(NameOrNameRef::NameRef),
- )
- } else {
- cov_mark::hit!(expected_type_struct_field_followed_by_comma);
- let ty = self.sema.resolve_record_field(&it)
- .map(|(_, _, ty)| ty);
- (
- ty,
- it.field_name().map(NameOrNameRef::NameRef),
- )
- }
- },
- // match foo { $0 }
- // match foo { ..., pat => $0 }
- ast::MatchExpr(it) => {
- let on_arrow = previous_non_trivia_token(self.token.clone()).map_or(false, |it| T![=>] == it.kind());
-
- let ty = if on_arrow {
- // match foo { ..., pat => $0 }
- cov_mark::hit!(expected_type_match_arm_body_without_leading_char);
- cov_mark::hit!(expected_type_match_arm_body_with_leading_char);
- self.sema.type_of_expr(&it.into())
- } else {
- // match foo { $0 }
- cov_mark::hit!(expected_type_match_arm_without_leading_char);
- it.expr().and_then(|e| self.sema.type_of_expr(&e))
- }.map(TypeInfo::original);
- (ty, None)
- },
- ast::IfExpr(it) => {
- let ty = it.condition()
- .and_then(|e| self.sema.type_of_expr(&e))
- .map(TypeInfo::original);
- (ty, None)
- },
- ast::IdentPat(it) => {
- cov_mark::hit!(expected_type_if_let_with_leading_char);
- cov_mark::hit!(expected_type_match_arm_with_leading_char);
- let ty = self.sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
- (ty, None)
- },
- ast::Fn(it) => {
- cov_mark::hit!(expected_type_fn_ret_with_leading_char);
- cov_mark::hit!(expected_type_fn_ret_without_leading_char);
- let def = self.sema.to_def(&it);
- (def.map(|def| def.ret_type(self.db)), None)
- },
- ast::ClosureExpr(it) => {
- let ty = self.sema.type_of_expr(&it.into());
- ty.and_then(|ty| ty.original.as_callable(self.db))
- .map(|c| (Some(c.return_type()), None))
- .unwrap_or((None, None))
- },
- ast::ParamList(_) => (None, None),
- ast::Stmt(_) => (None, None),
- ast::Item(_) => (None, None),
- _ => {
- match node.parent() {
- Some(n) => {
- node = n;
- continue;
- },
- None => (None, 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()),
+ }
+ } 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()));
}
- }
-
- /// Fill the completion context, this is what does semantic reasoning about the surrounding context
- /// of the completion location.
- fn analyze(
- &mut self,
- original_file: &SyntaxNode,
- file_with_fake_ident: SyntaxNode,
- offset: TextSize,
- derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
- ) -> Option<CompletionAnalysis> {
- let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased()?;
- 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;
+ };
+ let expected = expected_type_and_name(sema, &self_token, &name_like);
+ let mut qual_ctx = QualifierCtx::default();
+ let analysis = match name_like {
+ ast::NameLike::Lifetime(lifetime) => {
+ CompletionAnalysis::Lifetime(classify_lifetime(sema, &original_file, lifetime)?)
+ }
+ ast::NameLike::NameRef(name_ref) => {
+ let parent = name_ref.syntax().parent()?;
+ let (nameref_ctx, qualifier_ctx) =
+ classify_name_ref(sema, &original_file, name_ref, parent.clone())?;
+ qual_ctx = qualifier_ctx;
+ CompletionAnalysis::NameRef(nameref_ctx)
+ }
+ ast::NameLike::Name(name) => {
+ let name_ctx = classify_name(sema, &original_file, name)?;
+ CompletionAnalysis::Name(name_ctx)
}
+ };
+ Some((analysis, expected, qual_ctx))
+}
- // Overwrite the path kind for derives
- if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
- if let Some(ast::NameLike::NameRef(name_ref)) =
- find_node_at_offset(&file_with_fake_ident, offset)
- {
- let parent = name_ref.syntax().parent()?;
- let (mut nameref_ctx, _) =
- Self::classify_name_ref(&self.sema, &original_file, name_ref, parent)?;
- if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind {
- path_ctx.kind = PathKind::Derive {
- existing_derives: self
- .sema
- .resolve_derive_macro(&origin_attr)
- .into_iter()
- .flatten()
- .flatten()
- .collect(),
- };
+/// Calculate the expected type and name of the cursor position.
+fn expected_type_and_name(
+ sema: &Semantics<'_, RootDatabase>,
+ token: &SyntaxToken,
+ name_like: &ast::NameLike,
+) -> (Option<Type>, Option<NameOrNameRef>) {
+ let mut node = match token.parent() {
+ Some(it) => it,
+ None => return (None, None),
+ };
+
+ let strip_refs = |mut ty: Type| match name_like {
+ ast::NameLike::NameRef(n) => {
+ let p = match n.syntax().parent() {
+ Some(it) => it,
+ None => return ty,
+ };
+ let top_syn = match_ast! {
+ match p {
+ ast::FieldExpr(e) => e
+ .syntax()
+ .ancestors()
+ .map_while(ast::FieldExpr::cast)
+ .last()
+ .map(|it| it.syntax().clone()),
+ 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()),
+ _ => None
}
- return Some(CompletionAnalysis::NameRef(nameref_ctx));
+ };
+ let top_syn = match top_syn {
+ Some(it) => it,
+ None => return ty,
+ };
+ for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
+ cov_mark::hit!(expected_type_fn_param_ref);
+ ty = ty.strip_reference();
}
- return None;
+ ty
}
+ _ => ty,
+ };
- let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
- Some(it) => it,
- None => {
- let analysis =
- if let Some(original) = ast::String::cast(self.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)
+ loop {
+ break match_ast! {
+ match node {
+ ast::LetStmt(it) => {
+ cov_mark::hit!(expected_type_let_with_leading_char);
+ cov_mark::hit!(expected_type_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| sema.type_of_pat(&pat))
+ .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ let name = match it.pat() {
+ Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
+ Some(_) | None => None,
+ };
+
+ (ty, name)
+ },
+ ast::LetExpr(it) => {
+ cov_mark::hit!(expected_type_if_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| sema.type_of_pat(&pat))
+ .or_else(|| it.expr().and_then(|it| sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::ArgList(_) => {
+ cov_mark::hit!(expected_type_fn_param);
+ ActiveParameter::at_token(
+ &sema,
+ token.clone(),
+ ).map(|ap| {
+ let name = ap.ident().map(NameOrNameRef::Name);
+
+ let ty = strip_refs(ap.ty);
+ (Some(ty), name)
+ })
+ .unwrap_or((None, None))
+ },
+ ast::RecordExprFieldList(it) => {
+ // wouldn't try {} be nice...
+ (|| {
+ if token.kind() == T![..]
+ ||token.prev_token().map(|t| t.kind()) == Some(T![..])
{
- 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,
- }
+ cov_mark::hit!(expected_type_struct_func_update);
+ let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?;
+ let ty = sema.type_of_expr(&record_expr.into())?;
+ Some((
+ Some(ty.original),
+ None
+ ))
} else {
- return None;
+ cov_mark::hit!(expected_type_struct_field_without_leading_char);
+ let expr_field = token.prev_sibling_or_token()?
+ .into_node()
+ .and_then(ast::RecordExprField::cast)?;
+ let (_, _, ty) = sema.resolve_record_field(&expr_field)?;
+ Some((
+ Some(ty),
+ expr_field.field_name().map(NameOrNameRef::NameRef),
+ ))
}
- };
- return Some(analysis);
+ })().unwrap_or((None, None))
+ },
+ ast::RecordExprField(it) => {
+ if let Some(expr) = it.expr() {
+ cov_mark::hit!(expected_type_struct_field_with_leading_char);
+ (
+ sema.type_of_expr(&expr).map(TypeInfo::original),
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ } else {
+ cov_mark::hit!(expected_type_struct_field_followed_by_comma);
+ let ty = sema.resolve_record_field(&it)
+ .map(|(_, _, ty)| ty);
+ (
+ ty,
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ }
+ },
+ // match foo { $0 }
+ // match foo { ..., pat => $0 }
+ ast::MatchExpr(it) => {
+ let on_arrow = previous_non_trivia_token(token.clone()).map_or(false, |it| T![=>] == it.kind());
+
+ let ty = if on_arrow {
+ // match foo { ..., pat => $0 }
+ cov_mark::hit!(expected_type_match_arm_body_without_leading_char);
+ cov_mark::hit!(expected_type_match_arm_body_with_leading_char);
+ sema.type_of_expr(&it.into())
+ } else {
+ // match foo { $0 }
+ cov_mark::hit!(expected_type_match_arm_without_leading_char);
+ it.expr().and_then(|e| sema.type_of_expr(&e))
+ }.map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IfExpr(it) => {
+ let ty = it.condition()
+ .and_then(|e| sema.type_of_expr(&e))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IdentPat(it) => {
+ cov_mark::hit!(expected_type_if_let_with_leading_char);
+ cov_mark::hit!(expected_type_match_arm_with_leading_char);
+ let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::Fn(it) => {
+ cov_mark::hit!(expected_type_fn_ret_with_leading_char);
+ cov_mark::hit!(expected_type_fn_ret_without_leading_char);
+ let def = sema.to_def(&it);
+ (def.map(|def| def.ret_type(sema.db)), None)
+ },
+ ast::ClosureExpr(it) => {
+ let ty = sema.type_of_expr(&it.into());
+ ty.and_then(|ty| ty.original.as_callable(sema.db))
+ .map(|c| (Some(c.return_type()), None))
+ .unwrap_or((None, None))
+ },
+ ast::ParamList(_) => (None, None),
+ ast::Stmt(_) => (None, None),
+ ast::Item(_) => (None, None),
+ _ => {
+ match node.parent() {
+ Some(n) => {
+ node = n;
+ continue;
+ },
+ None => (None, None),
+ }
+ },
}
};
- (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like);
- let analysis = match name_like {
- ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime(
- Self::classify_lifetime(&self.sema, original_file, lifetime)?,
- ),
- ast::NameLike::NameRef(name_ref) => {
- let parent = name_ref.syntax().parent()?;
- let (nameref_ctx, qualifier_ctx) =
- Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone())?;
+ }
+}
- self.qualifier_ctx = qualifier_ctx;
- CompletionAnalysis::NameRef(nameref_ctx)
- }
- ast::NameLike::Name(name) => {
- let name_ctx = Self::classify_name(&self.sema, original_file, name)?;
- CompletionAnalysis::Name(name_ctx)
- }
- };
- Some(analysis)
+fn classify_lifetime(
+ _sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ lifetime: ast::Lifetime,
+) -> Option<LifetimeContext> {
+ let parent = lifetime.syntax().parent()?;
+ if parent.kind() == SyntaxKind::ERROR {
+ return None;
}
- fn classify_lifetime(
- _sema: &Semantics<'_, RootDatabase>,
- original_file: &SyntaxNode,
- lifetime: ast::Lifetime,
- ) -> Option<LifetimeContext> {
- let parent = lifetime.syntax().parent()?;
- if parent.kind() == SyntaxKind::ERROR {
- return None;
+ let kind = match_ast! {
+ match parent {
+ ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
+ is_decl: param.lifetime().as_ref() == Some(&lifetime),
+ param
+ },
+ ast::BreakExpr(_) => LifetimeKind::LabelRef,
+ ast::ContinueExpr(_) => LifetimeKind::LabelRef,
+ ast::Label(_) => LifetimeKind::LabelDef,
+ _ => LifetimeKind::Lifetime,
}
+ };
+ let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start());
- let kind = match_ast! {
- match parent {
- ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
- is_decl: param.lifetime().as_ref() == Some(&lifetime),
- param
- },
- ast::BreakExpr(_) => LifetimeKind::LabelRef,
- ast::ContinueExpr(_) => LifetimeKind::LabelRef,
- ast::Label(_) => LifetimeKind::LabelDef,
- _ => LifetimeKind::Lifetime,
- }
- };
- let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start());
+ Some(LifetimeContext { lifetime, kind })
+}
- Some(LifetimeContext { lifetime, kind })
- }
+fn classify_name(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name: ast::Name,
+) -> Option<NameContext> {
+ let parent = name.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::Const(_) => NameKind::Const,
+ ast::ConstParam(_) => NameKind::ConstParam,
+ ast::Enum(_) => NameKind::Enum,
+ ast::Fn(_) => NameKind::Function,
+ ast::IdentPat(bind_pat) => {
+ let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
+ if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
+ pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
+ }
- fn classify_name(
- sema: &Semantics<'_, RootDatabase>,
- original_file: &SyntaxNode,
- name: ast::Name,
- ) -> Option<NameContext> {
- let parent = name.syntax().parent()?;
- let kind = match_ast! {
- match parent {
- ast::Const(_) => NameKind::Const,
- ast::ConstParam(_) => NameKind::ConstParam,
- ast::Enum(_) => NameKind::Enum,
- ast::Fn(_) => NameKind::Function,
- ast::IdentPat(bind_pat) => {
- let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
- if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
- pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
- }
+ NameKind::IdentPat(pat_ctx)
+ },
+ ast::MacroDef(_) => NameKind::MacroDef,
+ ast::MacroRules(_) => NameKind::MacroRules,
+ ast::Module(module) => NameKind::Module(module),
+ ast::RecordField(_) => NameKind::RecordField,
+ ast::Rename(_) => NameKind::Rename,
+ ast::SelfParam(_) => NameKind::SelfParam,
+ ast::Static(_) => NameKind::Static,
+ ast::Struct(_) => NameKind::Struct,
+ ast::Trait(_) => NameKind::Trait,
+ ast::TypeAlias(_) => NameKind::TypeAlias,
+ ast::TypeParam(_) => NameKind::TypeParam,
+ ast::Union(_) => NameKind::Union,
+ ast::Variant(_) => NameKind::Variant,
+ _ => return None,
+ }
+ };
+ let name = find_node_at_offset(&original_file, name.syntax().text_range().start());
+ Some(NameContext { name, kind })
+}
- NameKind::IdentPat(pat_ctx)
- },
- ast::MacroDef(_) => NameKind::MacroDef,
- ast::MacroRules(_) => NameKind::MacroRules,
- ast::Module(module) => NameKind::Module(module),
- ast::RecordField(_) => NameKind::RecordField,
- ast::Rename(_) => NameKind::Rename,
- ast::SelfParam(_) => NameKind::SelfParam,
- ast::Static(_) => NameKind::Static,
- ast::Struct(_) => NameKind::Struct,
- ast::Trait(_) => NameKind::Trait,
- ast::TypeAlias(_) => NameKind::TypeAlias,
- ast::TypeParam(_) => NameKind::TypeParam,
- ast::Union(_) => NameKind::Union,
- ast::Variant(_) => NameKind::Variant,
- _ => return None,
- }
- };
- let name = find_node_at_offset(&original_file, name.syntax().text_range().start());
- Some(NameContext { name, kind })
+fn classify_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name_ref: ast::NameRef,
+ parent: SyntaxNode,
+) -> Option<(NameRefContext, QualifierCtx)> {
+ let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
+
+ let make_res = |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());
+
+ if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
+ let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone())
+ .map_or(false, |it| T![.] == it.kind());
+
+ return find_node_in_file_compensated(
+ sema,
+ original_file,
+ &record_field.parent_record_lit(),
+ )
+ .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix })
+ .map(make_res);
}
-
- fn classify_name_ref(
- sema: &Semantics<'_, RootDatabase>,
- original_file: &SyntaxNode,
- name_ref: ast::NameRef,
- parent: SyntaxNode,
- ) -> Option<(NameRefContext, QualifierCtx)> {
- let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
-
- let make_res =
- |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());
-
- if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
- let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone())
- .map_or(false, |it| T![.] == it.kind());
-
- return find_node_in_file_compensated(
+ if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
+ let kind = NameRefKind::Pattern(PatternContext {
+ param_ctx: None,
+ has_type_ascription: false,
+ ref_token: None,
+ mut_token: None,
+ record_pat: find_node_in_file_compensated(
sema,
original_file,
- &record_field.parent_record_lit(),
+ &record_field.parent_record_pat(),
+ ),
+ ..pattern_context_for(
+ sema,
+ original_file,
+ record_field.parent_record_pat().clone().into(),
)
- .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix })
- .map(make_res);
+ });
+ return Some(make_res(kind));
+ }
+
+ let segment = match_ast! {
+ match parent {
+ ast::PathSegment(segment) => segment,
+ ast::FieldExpr(field) => {
+ let receiver = find_opt_node_in_file(original_file, field.expr());
+ let receiver_is_ambiguous_float_literal = match &receiver {
+ Some(ast::Expr::Literal(l)) => matches! {
+ l.kind(),
+ ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.'))
+ },
+ _ => false,
+ };
+ 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 },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ ast::MethodCallExpr(method) => {
+ let receiver = find_opt_node_in_file(original_file, method.receiver());
+ let kind = NameRefKind::DotAccess(DotAccess {
+ receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
+ kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ _ => return None,
}
- if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
- let kind = NameRefKind::Pattern(PatternContext {
- param_ctx: None,
- has_type_ascription: false,
- ref_token: None,
- mut_token: None,
- record_pat: find_node_in_file_compensated(
- sema,
- original_file,
- &record_field.parent_record_pat(),
- ),
- ..pattern_context_for(
- sema,
- original_file,
- record_field.parent_record_pat().clone().into(),
- )
- });
- return Some(make_res(kind));
+ };
+
+ let path = segment.parent_path();
+ let original_path = find_node_in_file_compensated(sema, original_file, &path);
+
+ let mut path_ctx = PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ path: path.clone(),
+ original_path,
+ kind: PathKind::Item { kind: ItemListKind::SourceFile },
+ has_type_args: false,
+ use_tree_parent: false,
+ };
+
+ let is_in_block = |it: &SyntaxNode| {
+ it.parent()
+ .map(|node| {
+ ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind())
+ })
+ .unwrap_or(false)
+ };
+ let func_update_record = |syn: &SyntaxNode| {
+ if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
+ find_node_in_file_compensated(sema, original_file, &record_expr)
+ } else {
+ None
+ }
+ };
+ let after_if_expr = |node: SyntaxNode| {
+ let prev_expr = (|| {
+ let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
+ ast::ExprStmt::cast(prev_sibling)?.expr()
+ })();
+ matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
+ };
+
+ // We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
+ // ex. trait Foo $0 {}
+ // in these cases parser recovery usually kicks in for our inserted identifier, causing it
+ // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block
+ // expression or an item list.
+ // The following code checks if the body is missing, if it is we either cut off the body
+ // from the item or it was missing in the first place
+ let inbetween_body_and_decl_check = |node: SyntaxNode| {
+ if let Some(NodeOrToken::Node(n)) =
+ syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
+ {
+ if let Some(item) = ast::Item::cast(n) {
+ let is_inbetween = match &item {
+ ast::Item::Const(it) => it.body().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::Impl(it) => it.assoc_item_list().is_none(),
+ ast::Item::Module(it) => it.item_list().is_none(),
+ ast::Item::Static(it) => it.body().is_none(),
+ ast::Item::Struct(it) => it.field_list().is_none(),
+ ast::Item::Trait(it) => it.assoc_item_list().is_none(),
+ ast::Item::TypeAlias(it) => it.ty().is_none(),
+ ast::Item::Union(it) => it.record_field_list().is_none(),
+ _ => false,
+ };
+ if is_inbetween {
+ return Some(item);
+ }
+ }
}
+ None
+ };
- let segment = match_ast! {
+ let type_location = |node: &SyntaxNode| {
+ let parent = node.parent()?;
+ let res = match_ast! {
match parent {
- ast::PathSegment(segment) => segment,
- ast::FieldExpr(field) => {
- let receiver = find_opt_node_in_file(original_file, field.expr());
- let receiver_is_ambiguous_float_literal = match &receiver {
- Some(ast::Expr::Literal(l)) => matches! {
- l.kind(),
- ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.'))
- },
- _ => false,
+ ast::Const(it) => {
+ let name = find_opt_node_in_file(original_file, it.name())?;
+ let original = ast::Const::cast(name.syntax().parent()?)?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
+ },
+ ast::RetType(it) => {
+ if it.thin_arrow_token().is_none() {
+ return None;
+ }
+ let parent = match ast::Fn::cast(parent.parent()?) {
+ Some(x) => x.param_list(),
+ None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
};
- 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 },
- receiver
- });
- return Some(make_res(kind));
+
+ let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
+ match parent {
+ ast::ClosureExpr(it) => {
+ it.body()
+ },
+ ast::Fn(it) => {
+ it.body().map(ast::Expr::BlockExpr)
+ },
+ _ => return None,
+ }
+ }))
},
- ast::MethodCallExpr(method) => {
- let receiver = find_opt_node_in_file(original_file, method.receiver());
- let kind = NameRefKind::DotAccess(DotAccess {
- receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
- kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
- receiver
- });
- return Some(make_res(kind));
+ ast::Param(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
},
+ ast::LetStmt(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
+ },
+ ast::Impl(it) => {
+ match it.trait_() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTrait,
+ _ => match it.self_ty() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTarget,
+ _ => return None,
+ },
+ }
+ },
+ ast::TypeBound(_) => TypeLocation::TypeBound,
+ // is this case needed?
+ ast::TypeBoundList(_) => TypeLocation::TypeBound,
+ ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
+ // is this case needed?
+ ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
+ ast::TupleField(_) => TypeLocation::TupleField,
_ => return None,
}
};
+ Some(res)
+ };
- let path = segment.parent_path();
- let original_path = find_node_in_file_compensated(sema, original_file, &path);
-
- let mut path_ctx = PathCompletionCtx {
- has_call_parens: false,
- has_macro_bang: false,
- qualified: Qualified::No,
- parent: None,
- path: path.clone(),
- original_path,
- kind: PathKind::Item { kind: ItemListKind::SourceFile },
- has_type_args: false,
- use_tree_parent: false,
- };
-
- let is_in_block = |it: &SyntaxNode| {
- it.parent()
- .map(|node| {
- ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind())
- })
- .unwrap_or(false)
- };
- let func_update_record = |syn: &SyntaxNode| {
- if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
- find_node_in_file_compensated(sema, original_file, &record_expr)
+ let is_in_condition = |it: &ast::Expr| {
+ (|| {
+ let parent = it.syntax().parent()?;
+ if let Some(expr) = ast::WhileExpr::cast(parent.clone()) {
+ Some(expr.condition()? == *it)
+ } else if let Some(expr) = ast::IfExpr::cast(parent) {
+ Some(expr.condition()? == *it)
} else {
None
}
- };
- let after_if_expr = |node: SyntaxNode| {
- let prev_expr = (|| {
- let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
- ast::ExprStmt::cast(prev_sibling)?.expr()
- })();
- matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
- };
+ })()
+ .unwrap_or(false)
+ };
- // We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
- // ex. trait Foo $0 {}
- // in these cases parser recovery usually kicks in for our inserted identifier, causing it
- // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block
- // expression or an item list.
- // The following code checks if the body is missing, if it is we either cut off the body
- // from the item or it was missing in the first place
- let inbetween_body_and_decl_check = |node: SyntaxNode| {
- if let Some(NodeOrToken::Node(n)) =
- syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
- {
- if let Some(item) = ast::Item::cast(n) {
- let is_inbetween = match &item {
- ast::Item::Const(it) => it.body().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::Impl(it) => it.assoc_item_list().is_none(),
- ast::Item::Module(it) => it.item_list().is_none(),
- ast::Item::Static(it) => it.body().is_none(),
- ast::Item::Struct(it) => it.field_list().is_none(),
- ast::Item::Trait(it) => it.assoc_item_list().is_none(),
- ast::Item::TypeAlias(it) => it.ty().is_none(),
- ast::Item::Union(it) => it.record_field_list().is_none(),
- _ => false,
- };
- if is_inbetween {
- return Some(item);
+ let make_path_kind_expr = |expr: ast::Expr| {
+ let it = expr.syntax();
+ let in_block_expr = is_in_block(it);
+ let in_loop_body = is_in_loop_body(it);
+ let after_if_expr = after_if_expr(it.clone());
+ let ref_expr_parent =
+ path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
+ let (innermost_ret_ty, self_param) = {
+ let find_ret_ty = |it: SyntaxNode| {
+ if let Some(item) = ast::Item::cast(it.clone()) {
+ match item {
+ ast::Item::Fn(f) => Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))),
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
}
- }
- }
- None
- };
-
- let type_location = |node: &SyntaxNode| {
- let parent = node.parent()?;
- let res = match_ast! {
- match parent {
- ast::Const(it) => {
- let name = find_opt_node_in_file(original_file, it.name())?;
- let original = ast::Const::cast(name.syntax().parent()?)?;
- TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
- },
- ast::RetType(it) => {
- if it.thin_arrow_token().is_none() {
- return None;
- }
- let parent = match ast::Fn::cast(parent.parent()?) {
- Some(x) => x.param_list(),
- None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
- };
-
- let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
- TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
- match parent {
- ast::ClosureExpr(it) => {
- it.body()
- },
- ast::Fn(it) => {
- it.body().map(ast::Expr::BlockExpr)
- },
- _ => return None,
- }
- }))
- },
- ast::Param(it) => {
- if it.colon_token().is_none() {
- return None;
- }
- TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
- },
- ast::LetStmt(it) => {
- if it.colon_token().is_none() {
- return None;
- }
- TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
- },
- ast::Impl(it) => {
- match it.trait_() {
- Some(t) if t.syntax() == node => TypeLocation::ImplTrait,
- _ => match it.self_ty() {
- Some(t) if t.syntax() == node => TypeLocation::ImplTarget,
- _ => return None,
- },
- }
- },
- ast::TypeBound(_) => TypeLocation::TypeBound,
- // is this case needed?
- ast::TypeBoundList(_) => TypeLocation::TypeBound,
- ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
- // is this case needed?
- ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
- ast::TupleField(_) => TypeLocation::TupleField,
- _ => return None,
- }
- };
- Some(res)
- };
-
- let is_in_condition = |it: &ast::Expr| {
- (|| {
- let parent = it.syntax().parent()?;
- if let Some(expr) = ast::WhileExpr::cast(parent.clone()) {
- Some(expr.condition()? == *it)
- } else if let Some(expr) = ast::IfExpr::cast(parent) {
- Some(expr.condition()? == *it)
} else {
- None
- }
- })()
- .unwrap_or(false)
- };
-
- let make_path_kind_expr = |expr: ast::Expr| {
- let it = expr.syntax();
- let in_block_expr = is_in_block(it);
- let in_loop_body = is_in_loop_body(it);
- let after_if_expr = after_if_expr(it.clone());
- let ref_expr_parent =
- path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
- let (innermost_ret_ty, self_param) = {
- let find_ret_ty = |it: SyntaxNode| {
- if let Some(item) = ast::Item::cast(it.clone()) {
- match item {
- ast::Item::Fn(f) => {
- Some(sema.to_def(&f).map(|it| it.ret_type(sema.db)))
- }
- ast::Item::MacroCall(_) => None,
- _ => Some(None),
- }
- } else {
- let expr = ast::Expr::cast(it)?;
- let callable = match expr {
- // FIXME
- // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b),
- ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr),
- _ => return None,
- };
- Some(
- callable
- .and_then(|c| c.adjusted().as_callable(sema.db))
- .map(|it| it.return_type()),
- )
- }
- };
- let find_fn_self_param = |it| match it {
- ast::Item::Fn(fn_) => {
- Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db)))
- }
- ast::Item::MacroCall(_) => None,
- _ => Some(None),
- };
-
- match find_node_in_file_compensated(sema, original_file, &expr) {
- Some(it) => {
- let innermost_ret_ty = sema
- .ancestors_with_macros(it.syntax().clone())
- .find_map(find_ret_ty)
- .flatten();
-
- let self_param = sema
- .ancestors_with_macros(it.syntax().clone())
- .filter_map(ast::Item::cast)
- .find_map(find_fn_self_param)
- .flatten();
- (innermost_ret_ty, self_param)
- }
- None => (None, None),
+ let expr = ast::Expr::cast(it)?;
+ let callable = match expr {
+ // FIXME
+ // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b),
+ ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr),
+ _ => return None,
+ };
+ Some(
+ callable
+ .and_then(|c| c.adjusted().as_callable(sema.db))
+ .map(|it| it.return_type()),
+ )
}
};
- let is_func_update = func_update_record(it);
- let in_condition = is_in_condition(&expr);
- let incomplete_let = it
- .parent()
- .and_then(ast::LetStmt::cast)
- .map_or(false, |it| it.semicolon_token().is_none());
- let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());
-
- let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) {
- Some(arm) => arm
- .fat_arrow_token()
- .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()),
- None => false,
+ let find_fn_self_param = |it| match it {
+ ast::Item::Fn(fn_) => Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))),
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
};
- PathKind::Expr {
- expr_ctx: ExprCtx {
- in_block_expr,
- in_loop_body,
- after_if_expr,
- in_condition,
- ref_expr_parent,
- is_func_update,
- innermost_ret_ty,
- self_param,
- incomplete_let,
- impl_,
- in_match_guard,
- },
+ match find_node_in_file_compensated(sema, original_file, &expr) {
+ Some(it) => {
+ let innermost_ret_ty = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .find_map(find_ret_ty)
+ .flatten();
+
+ let self_param = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .filter_map(ast::Item::cast)
+ .find_map(find_fn_self_param)
+ .flatten();
+ (innermost_ret_ty, self_param)
+ }
+ None => (None, None),
}
};
- let make_path_kind_type = |ty: ast::Type| {
- let location = type_location(ty.syntax());
- PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
+ let is_func_update = func_update_record(it);
+ let in_condition = is_in_condition(&expr);
+ let incomplete_let = it
+ .parent()
+ .and_then(ast::LetStmt::cast)
+ .map_or(false, |it| it.semicolon_token().is_none());
+ let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());
+
+ let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) {
+ Some(arm) => arm
+ .fat_arrow_token()
+ .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()),
+ None => false,
};
- let mut kind_macro_call = |it: ast::MacroCall| {
- path_ctx.has_macro_bang = it.excl_token().is_some();
- let parent = it.syntax().parent()?;
- // Any path in an item list will be treated as a macro call by the parser
- let kind = match_ast! {
- match parent {
- ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
- ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
- ast::MacroType(ty) => make_path_kind_type(ty.into()),
- ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
- ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
- Some(it) => match_ast! {
- match it {
- ast::Trait(_) => ItemListKind::Trait,
- ast::Impl(it) => if it.trait_().is_some() {
- ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
- } else {
- ItemListKind::Impl
- },
- _ => return None
- }
- },
- None => return None,
- } },
- ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
- ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
- _ => return None,
- }
- };
- Some(kind)
- };
- let make_path_kind_attr = |meta: ast::Meta| {
- let attr = meta.parent_attr()?;
- let kind = attr.kind();
- let attached = attr.syntax().parent()?;
- let is_trailing_outer_attr = kind != AttrKind::Inner
- && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next)
- .is_none();
- let annotated_item_kind =
- if is_trailing_outer_attr { None } else { Some(attached.kind()) };
- Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
- };
+ PathKind::Expr {
+ expr_ctx: ExprCtx {
+ in_block_expr,
+ in_loop_body,
+ after_if_expr,
+ in_condition,
+ ref_expr_parent,
+ is_func_update,
+ innermost_ret_ty,
+ self_param,
+ incomplete_let,
+ impl_,
+ in_match_guard,
+ },
+ }
+ };
+ let make_path_kind_type = |ty: ast::Type| {
+ let location = type_location(ty.syntax());
+ PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
+ };
- // Infer the path kind
- let parent = path.syntax().parent()?;
+ let mut kind_macro_call = |it: ast::MacroCall| {
+ path_ctx.has_macro_bang = it.excl_token().is_some();
+ let parent = it.syntax().parent()?;
+ // Any path in an item list will be treated as a macro call by the parser
let kind = match_ast! {
match parent {
- ast::PathType(it) => make_path_kind_type(it.into()),
- ast::PathExpr(it) => {
- if let Some(p) = it.syntax().parent() {
- if ast::ExprStmt::can_cast(p.kind()) {
- if let Some(kind) = inbetween_body_and_decl_check(p) {
- return Some(make_res(NameRefKind::Keyword(kind)));
- }
- }
- }
-
- path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
-
- make_path_kind_expr(it.into())
- },
- ast::TupleStructPat(it) => {
- path_ctx.has_call_parens = true;
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
- },
- ast::RecordPat(it) => {
- path_ctx.has_call_parens = true;
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
- },
- ast::PathPat(it) => {
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
- },
- ast::MacroCall(it) => {
- // A macro call in this position is usually a result of parsing recovery, so check that
- if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
- return Some(make_res(NameRefKind::Keyword(kind)));
- }
-
- kind_macro_call(it)?
- },
- ast::Meta(meta) => make_path_kind_attr(meta)?,
- ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
- ast::UseTree(_) => PathKind::Use,
- // completing inside a qualifier
- ast::Path(parent) => {
- path_ctx.parent = Some(parent.clone());
- let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
- match_ast! {
- match parent {
- ast::PathType(it) => make_path_kind_type(it.into()),
- ast::PathExpr(it) => {
- path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
-
- make_path_kind_expr(it.into())
- },
- ast::TupleStructPat(it) => {
- path_ctx.has_call_parens = true;
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
- },
- ast::RecordPat(it) => {
- path_ctx.has_call_parens = true;
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
- },
- ast::PathPat(it) => {
- PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
- },
- ast::MacroCall(it) => {
- kind_macro_call(it)?
+ ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
+ ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
+ ast::MacroType(ty) => make_path_kind_type(ty.into()),
+ ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
+ ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
+ Some(it) => match_ast! {
+ match it {
+ ast::Trait(_) => ItemListKind::Trait,
+ ast::Impl(it) => if it.trait_().is_some() {
+ ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
+ } else {
+ ItemListKind::Impl
},
- ast::Meta(meta) => make_path_kind_attr(meta)?,
- ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
- ast::UseTree(_) => PathKind::Use,
- ast::RecordExpr(it) => make_path_kind_expr(it.into()),
- _ => return None,
+ _ => return None
}
- }
- },
- ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ },
+ None => return None,
+ } },
+ ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
+ ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
_ => return None,
}
};
+ Some(kind)
+ };
+ let make_path_kind_attr = |meta: ast::Meta| {
+ let attr = meta.parent_attr()?;
+ let kind = attr.kind();
+ let attached = attr.syntax().parent()?;
+ let is_trailing_outer_attr = kind != AttrKind::Inner
+ && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none();
+ let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) };
+ Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
+ };
- path_ctx.kind = kind;
- path_ctx.has_type_args = segment.generic_arg_list().is_some();
+ // Infer the path kind
+ let parent = path.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ if let Some(p) = it.syntax().parent() {
+ if ast::ExprStmt::can_cast(p.kind()) {
+ if let Some(kind) = inbetween_body_and_decl_check(p) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
+ }
+ }
- // calculate the qualifier context
- if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
- path_ctx.use_tree_parent = use_tree_parent;
- if !use_tree_parent && segment.coloncolon_token().is_some() {
- path_ctx.qualified = Qualified::Absolute;
- } else {
- let qualifier = qualifier
- .segment()
- .and_then(|it| find_node_in_file(original_file, &it))
- .map(|it| it.parent_path());
- if let Some(qualifier) = qualifier {
- let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
- Some(ast::PathSegmentKind::Type {
- type_ref: Some(type_ref),
- trait_ref,
- }) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)),
- _ => None,
- };
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ // A macro call in this position is usually a result of parsing recovery, so check that
+ if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
- path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
- let ty = match ty {
- ast::Type::InferType(_) => None,
- ty => sema.resolve_type(&ty),
- };
- let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
- Qualified::TypeAnchor { ty, trait_ }
- } else {
- let res = sema.resolve_path(&qualifier);
-
- // For understanding how and why super_chain_len is calculated the way it
- // is check the documentation at it's definition
- let mut segment_count = 0;
- let super_count =
- iter::successors(Some(qualifier.clone()), |p| p.qualifier())
- .take_while(|p| {
- p.segment()
- .and_then(|s| {
- segment_count += 1;
- s.super_token()
- })
- .is_some()
- })
- .count();
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ // completing inside a qualifier
+ ast::Path(parent) => {
+ path_ctx.parent = Some(parent.clone());
+ let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ }
+ },
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ };
- let super_chain_len =
- if segment_count > super_count { None } else { Some(super_count) };
+ path_ctx.kind = kind;
+ path_ctx.has_type_args = segment.generic_arg_list().is_some();
- Qualified::With { path: qualifier, resolution: res, super_chain_len }
+ // calculate the qualifier context
+ if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
+ path_ctx.use_tree_parent = use_tree_parent;
+ if !use_tree_parent && segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ } else {
+ let qualifier = qualifier
+ .segment()
+ .and_then(|it| find_node_in_file(original_file, &it))
+ .map(|it| it.parent_path());
+ if let Some(qualifier) = qualifier {
+ let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
+ Some(ast::PathSegmentKind::Type { type_ref: Some(type_ref), trait_ref })
+ if qualifier.qualifier().is_none() =>
+ {
+ Some((type_ref, trait_ref))
}
+ _ => None,
};
- }
- } else if let Some(segment) = path.segment() {
- if segment.coloncolon_token().is_some() {
- path_ctx.qualified = Qualified::Absolute;
- }
- }
- let mut qualifier_ctx = QualifierCtx::default();
- if path_ctx.is_trivial_path() {
- // fetch the full expression that may have qualifiers attached to it
- let top_node = match path_ctx.kind {
- PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => {
- parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| {
- let parent = p.parent()?;
- if ast::StmtList::can_cast(parent.kind()) {
- Some(p)
- } else if ast::ExprStmt::can_cast(parent.kind()) {
- Some(parent)
- } else {
- None
- }
- })
- }
- PathKind::Item { .. } => {
- parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind()))
+ path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
+ let ty = match ty {
+ ast::Type::InferType(_) => None,
+ ty => sema.resolve_type(&ty),
+ };
+ let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
+ Qualified::TypeAnchor { ty, trait_ }
+ } else {
+ let res = sema.resolve_path(&qualifier);
+
+ // For understanding how and why super_chain_len is calculated the way it
+ // is check the documentation at it's definition
+ let mut segment_count = 0;
+ let super_count = iter::successors(Some(qualifier.clone()), |p| p.qualifier())
+ .take_while(|p| {
+ p.segment()
+ .and_then(|s| {
+ segment_count += 1;
+ s.super_token()
+ })
+ .is_some()
+ })
+ .count();
+
+ let super_chain_len =
+ if segment_count > super_count { None } else { Some(super_count) };
+
+ Qualified::With { path: qualifier, resolution: res, super_chain_len }
}
- _ => None,
};
- if let Some(top) = top_node {
- if let Some(NodeOrToken::Node(error_node)) =
- syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
- {
- if error_node.kind() == SyntaxKind::ERROR {
- qualifier_ctx.unsafe_tok = error_node
- .children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .find(|it| it.kind() == T![unsafe]);
- qualifier_ctx.vis_node =
- error_node.children().find_map(ast::Visibility::cast);
+ }
+ } else if let Some(segment) = path.segment() {
+ if segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ }
+ }
+
+ let mut qualifier_ctx = QualifierCtx::default();
+ if path_ctx.is_trivial_path() {
+ // fetch the full expression that may have qualifiers attached to it
+ let top_node = match path_ctx.kind {
+ PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => {
+ parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| {
+ let parent = p.parent()?;
+ if ast::StmtList::can_cast(parent.kind()) {
+ Some(p)
+ } else if ast::ExprStmt::can_cast(parent.kind()) {
+ Some(parent)
+ } else {
+ None
}
+ })
+ }
+ PathKind::Item { .. } => {
+ parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind()))
+ }
+ _ => None,
+ };
+ if let Some(top) = top_node {
+ if let Some(NodeOrToken::Node(error_node)) =
+ syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
+ {
+ if error_node.kind() == SyntaxKind::ERROR {
+ qualifier_ctx.unsafe_tok = error_node
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|it| it.kind() == T![unsafe]);
+ qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast);
}
+ }
- if let PathKind::Item { .. } = path_ctx.kind {
- if qualifier_ctx.none() {
- if let Some(t) = top.first_token() {
- if let Some(prev) = t
- .prev_token()
- .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev))
- {
- if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) {
- // This was inferred to be an item position path, but it seems
- // to be part of some other broken node which leaked into an item
- // list
- return None;
- }
+ if let PathKind::Item { .. } = path_ctx.kind {
+ if qualifier_ctx.none() {
+ if let Some(t) = top.first_token() {
+ if let Some(prev) = t
+ .prev_token()
+ .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev))
+ {
+ if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) {
+ // This was inferred to be an item position path, but it seems
+ // to be part of some other broken node which leaked into an item
+ // list
+ return None;
}
}
}
}
}
}
- Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
}
+ Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
}
fn pattern_context_for(
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 ae1a440d0..9d0044e55 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -183,6 +183,7 @@ pub fn completions(
CompletionAnalysis::String { original, expanded: Some(expanded) } => {
completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
completions::format_string::format_string(acc, ctx, original, expanded);
+ completions::env_vars::complete_cargo_env_vars(acc, ctx, expanded);
}
CompletionAnalysis::UnexpandedAttrTT {
colon_prefix,
@@ -234,7 +235,12 @@ pub fn resolve_completion_edits(
);
let import = items_with_name
.filter_map(|candidate| {
- current_module.find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
+ current_module.find_use_path_prefixed(
+ db,
+ candidate,
+ config.insert_use.prefix_kind,
+ config.prefer_no_std,
+ )
})
.find(|mod_path| mod_path.to_string() == full_import_path);
if let Some(import_path) = import {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
index dc1039fa6..f3b8eae4f 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
@@ -174,8 +174,12 @@ fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option<V
hir::PathResolution::Def(def) => def.into(),
_ => return None,
};
- let path =
- ctx.module.find_use_path_prefixed(ctx.db, item, ctx.config.insert_use.prefix_kind)?;
+ let path = ctx.module.find_use_path_prefixed(
+ ctx.db,
+ item,
+ ctx.config.insert_use.prefix_kind,
+ ctx.config.prefer_no_std,
+ )?;
Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item, None)))
};
let mut res = Vec::with_capacity(requires.len());
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 cf826648d..9e2beb9ee 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
@@ -66,6 +66,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
enable_private_editable: false,
callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true),
+ prefer_no_std: false,
insert_use: InsertUseConfig {
granularity: ImportGranularity::Crate,
prefix_kind: PrefixKind::Plain,
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
index 85c4dbd66..db8bef664 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
@@ -714,3 +714,30 @@ impl Ty {
"#]],
);
}
+
+#[test]
+fn through_alias() {
+ check_empty(
+ r#"
+enum Enum<T> {
+ Unit,
+ Tuple(T),
+}
+
+type EnumAlias<T> = Enum<T>;
+
+fn f(x: EnumAlias<u8>) {
+ match x {
+ EnumAlias::$0 => (),
+ _ => (),
+ }
+
+}
+
+"#,
+ expect![[r#"
+ bn Tuple(…) Tuple($1)$0
+ bn Unit Unit$0
+ "#]],
+ );
+}