summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-db
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-db')
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/Cargo.toml5
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs13
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/search.rs46
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string_exprs.rs267
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs9
9 files changed, 326 insertions, 26 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
index a1b0bd6cb..cf0bcd5c9 100644
--- a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
@@ -15,11 +15,12 @@ tracing = "0.1.35"
rayon = "1.5.3"
fst = { version = "0.4.7", default-features = false }
rustc-hash = "1.1.0"
-once_cell = "1.12.0"
+once_cell = "1.15.0"
either = "1.7.0"
-itertools = "0.10.3"
+itertools = "0.10.5"
arrayvec = "0.7.2"
indexmap = "1.9.1"
+memchr = "2.5.0"
stdx = { path = "../stdx", version = "0.0.0" }
parser = { path = "../parser", version = "0.0.0" }
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
index 26ef86155..40a6a3e89 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
@@ -212,18 +212,20 @@ impl ImportAssets {
&self,
sema: &Semantics<'_, RootDatabase>,
prefix_kind: PrefixKind,
+ prefer_no_std: bool,
) -> Vec<LocatedImport> {
let _p = profile::span("import_assets::search_for_imports");
- self.search_for(sema, Some(prefix_kind))
+ self.search_for(sema, Some(prefix_kind), prefer_no_std)
}
/// This may return non-absolute paths if a part of the returned path is already imported into scope.
pub fn search_for_relative_paths(
&self,
sema: &Semantics<'_, RootDatabase>,
+ prefer_no_std: bool,
) -> Vec<LocatedImport> {
let _p = profile::span("import_assets::search_for_relative_paths");
- self.search_for(sema, None)
+ self.search_for(sema, None, prefer_no_std)
}
pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
@@ -242,6 +244,7 @@ impl ImportAssets {
&self,
sema: &Semantics<'_, RootDatabase>,
prefixed: Option<PrefixKind>,
+ prefer_no_std: bool,
) -> Vec<LocatedImport> {
let _p = profile::span("import_assets::search_for");
@@ -252,6 +255,7 @@ impl ImportAssets {
item_for_path_search(sema.db, item)?,
&self.module_with_candidate,
prefixed,
+ prefer_no_std,
)
};
@@ -564,11 +568,12 @@ fn get_mod_path(
item_to_search: ItemInNs,
module_with_candidate: &Module,
prefixed: Option<PrefixKind>,
+ prefer_no_std: bool,
) -> Option<ModPath> {
if let Some(prefix_kind) = prefixed {
- module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
+ module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind, prefer_no_std)
} else {
- module_with_candidate.find_use_path(db, item_to_search)
+ module_with_candidate.find_use_path(db, item_to_search, prefer_no_std)
}
}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs
index 7fb4b90e6..371d642c1 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs
@@ -225,7 +225,7 @@ fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
}
/// Compares two paths, if one ends earlier than the other the has_tl parameters decide which is
-/// greater as a a path that has a tree list should be greater, while one that just ends without
+/// greater as a path that has a tree list should be greater, while one that just ends without
/// a tree list should be considered less.
pub(super) fn use_tree_path_cmp(
a: &ast::Path,
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
index 1ec62a842..e0bc0f89f 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
@@ -38,6 +38,7 @@ pub mod syntax_helpers {
pub mod node_ext;
pub mod insert_whitespace_into_node;
pub mod format_string;
+ pub mod format_string_exprs;
pub use parser::LexedStr;
}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
index 40af9e6fe..12d873b4a 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
@@ -173,6 +173,7 @@ impl<'a> Ctx<'a> {
let found_path = self.target_module.find_use_path(
self.source_scope.db.upcast(),
hir::ModuleDef::Trait(trait_ref),
+ false,
)?;
match ast::make::ty_path(mod_path_to_ast(&found_path)) {
ast::Type::PathType(path_ty) => Some(path_ty),
@@ -209,7 +210,7 @@ impl<'a> Ctx<'a> {
}
let found_path =
- self.target_module.find_use_path(self.source_scope.db.upcast(), def)?;
+ self.target_module.find_use_path(self.source_scope.db.upcast(), def, false)?;
let res = mod_path_to_ast(&found_path).clone_for_update();
if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
if let Some(segment) = res.segment() {
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
index 7deffe8e0..82b85f2fa 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
@@ -8,7 +8,9 @@ use std::{mem, sync::Arc};
use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility};
+use memchr::memmem::Finder;
use once_cell::unsync::Lazy;
+use parser::SyntaxKind;
use stdx::hash::NoHashHashMap;
use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
@@ -67,6 +69,7 @@ pub enum ReferenceCategory {
// Create
Write,
Read,
+ Import,
// FIXME: Some day should be able to search in doc comments. Would probably
// need to switch from enum to bitflags then?
// DocComment
@@ -236,6 +239,7 @@ impl Definition {
DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
};
return match def {
Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
@@ -409,14 +413,17 @@ impl<'a> FindUsages<'a> {
Some(s) => s.as_str(),
None => return,
};
+ let finder = &Finder::new(name);
+ let include_self_kw_refs =
+ self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
- // these can't be closures because rust infers the lifetimes wrong ...
+ // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
fn match_indices<'a>(
text: &'a str,
- name: &'a str,
+ finder: &'a Finder<'a>,
search_range: TextRange,
) -> impl Iterator<Item = TextSize> + 'a {
- text.match_indices(name).filter_map(move |(idx, _)| {
+ finder.find_iter(text.as_bytes()).filter_map(move |idx| {
let offset: TextSize = idx.try_into().unwrap();
if !search_range.contains_inclusive(offset) {
return None;
@@ -425,6 +432,7 @@ impl<'a> FindUsages<'a> {
})
}
+ // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a { ... }
fn scope_files<'a>(
sema: &'a Semantics<'_, RootDatabase>,
scope: &'a SearchScope,
@@ -448,7 +456,7 @@ impl<'a> FindUsages<'a> {
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
// Search for occurrences of the items name
- for offset in match_indices(&text, name, search_range) {
+ for offset in match_indices(&text, finder, search_range) {
for name in sema.find_nodes_at_offset_with_descend(&tree, offset) {
if match name {
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
@@ -460,8 +468,8 @@ impl<'a> FindUsages<'a> {
}
}
// Search for occurrences of the `Self` referring to our type
- if let Some(self_ty) = &self.include_self_kw_refs {
- for offset in match_indices(&text, "Self", search_range) {
+ if let Some((self_ty, finder)) = &include_self_kw_refs {
+ for offset in match_indices(&text, finder, search_range) {
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
return;
@@ -477,20 +485,22 @@ impl<'a> FindUsages<'a> {
let scope = search_scope
.intersection(&SearchScope::module_and_children(self.sema.db, module));
- let is_crate_root = module.is_crate_root(self.sema.db);
+ let is_crate_root =
+ module.is_crate_root(self.sema.db).then(|| Finder::new("crate"));
+ let finder = &Finder::new("super");
for (text, file_id, search_range) in scope_files(sema, &scope) {
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
- for offset in match_indices(&text, "super", search_range) {
+ for offset in match_indices(&text, finder, search_range) {
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
if self.found_name_ref(&name_ref, sink) {
return;
}
}
}
- if is_crate_root {
- for offset in match_indices(&text, "crate", search_range) {
+ if let Some(finder) = &is_crate_root {
+ for offset in match_indices(&text, finder, search_range) {
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
if self.found_name_ref(&name_ref, sink) {
return;
@@ -531,8 +541,9 @@ impl<'a> FindUsages<'a> {
search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(text.as_str())));
let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
+ let finder = &Finder::new("self");
- for offset in match_indices(&text, "self", search_range) {
+ for offset in match_indices(&text, finder, search_range) {
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
if self.found_self_module_name_ref(&name_ref, sink) {
return;
@@ -577,7 +588,7 @@ impl<'a> FindUsages<'a> {
let reference = FileReference {
range,
name: ast::NameLike::NameRef(name_ref.clone()),
- category: None,
+ category: is_name_ref_in_import(name_ref).then(|| ReferenceCategory::Import),
};
sink(file_id, reference)
}
@@ -756,7 +767,7 @@ impl ReferenceCategory {
fn new(def: &Definition, r: &ast::NameRef) -> Option<ReferenceCategory> {
// Only Locals and Fields have accesses for now.
if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
- return None;
+ return is_name_ref_in_import(r).then(|| ReferenceCategory::Import);
}
let mode = r.syntax().ancestors().find_map(|node| {
@@ -783,3 +794,12 @@ impl ReferenceCategory {
mode.or(Some(ReferenceCategory::Read))
}
}
+
+fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
+ name_ref
+ .syntax()
+ .parent()
+ .and_then(ast::PathSegment::cast)
+ .and_then(|it| it.parent_path().top_path().syntax().parent())
+ .map_or(false, |it| it.kind() == SyntaxKind::USE_TREE)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs
index f48a57008..2d6927cee 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs
@@ -1,7 +1,8 @@
//! Tools to work with format string literals for the `format_args!` family of macros.
+use crate::syntax_helpers::node_ext::macro_call_for_string_token;
use syntax::{
ast::{self, IsString},
- AstNode, AstToken, TextRange, TextSize,
+ TextRange, TextSize,
};
pub fn is_format_string(string: &ast::String) -> bool {
@@ -14,8 +15,7 @@ pub fn is_format_string(string: &ast::String) -> bool {
// This setup lets us correctly highlight the components of `concat!("{}", "bla")` format
// strings. It still fails for `concat!("{", "}")`, but that is rare.
(|| {
- let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
- let name = macro_call.path()?.segment()?.name_ref()?;
+ let name = macro_call_for_string_token(string)?.path()?.segment()?.name_ref()?;
if !matches!(
name.text().as_str(),
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
new file mode 100644
index 000000000..ac6c6e8fe
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
@@ -0,0 +1,267 @@
+//! Tools to work with expressions present in format string literals for the `format_args!` family of macros.
+//! Primarily meant for assists and completions.
+
+/// Enum for represenging extraced format string args.
+/// Can either be extracted expressions (which includes identifiers),
+/// or placeholders `{}`.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Arg {
+ Placeholder,
+ Ident(String),
+ Expr(String),
+}
+
+/**
+ Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
+ and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
+ ```rust
+ # use ide_db::syntax_helpers::format_string_exprs::*;
+ assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
+ ```
+*/
+
+pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
+ let mut placeholder_id = 1;
+ args.into_iter()
+ .map(move |a| match a {
+ Arg::Expr(s) | Arg::Ident(s) => s,
+ Arg::Placeholder => {
+ let s = format!("${placeholder_id}");
+ placeholder_id += 1;
+ s
+ }
+ })
+ .collect()
+}
+
+/**
+ 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.
+
+ Built for completions and assists, and escapes `\` and `$` in output.
+ (See the comments on `get_receiver_text()` for detail.)
+ Splits a format string that may contain expressions
+ like
+ ```rust
+ assert_eq!(parse("{ident} {} {expr + 42} ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident"), Arg::Placeholder, Arg::Expr("expr + 42")]));
+ ```
+*/
+pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
+ #[derive(Debug, Clone, Copy, PartialEq)]
+ enum State {
+ NotArg,
+ MaybeArg,
+ Expr,
+ Ident,
+ MaybeIncorrect,
+ FormatOpts,
+ }
+
+ let mut state = State::NotArg;
+ let mut current_expr = String::new();
+ let mut extracted_expressions = Vec::new();
+ let mut output = String::new();
+
+ // 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;
+
+ let mut chars = input.chars().peekable();
+ while let Some(chr) = chars.next() {
+ match (state, chr) {
+ (State::NotArg, '{') => {
+ output.push(chr);
+ state = State::MaybeArg;
+ }
+ (State::NotArg, '}') => {
+ output.push(chr);
+ state = State::MaybeIncorrect;
+ }
+ (State::NotArg, _) => {
+ if matches!(chr, '\\' | '$') {
+ output.push('\\');
+ }
+ output.push(chr);
+ }
+ (State::MaybeIncorrect, '}') => {
+ // It's okay, we met "}}".
+ output.push(chr);
+ state = State::NotArg;
+ }
+ (State::MaybeIncorrect, _) => {
+ // Error in the string.
+ return Err(());
+ }
+ // Escaped braces `{{`
+ (State::MaybeArg, '{') => {
+ output.push(chr);
+ state = State::NotArg;
+ }
+ (State::MaybeArg, '}') => {
+ // This is an empty sequence '{}'.
+ output.push(chr);
+ extracted_expressions.push(Arg::Placeholder);
+ state = State::NotArg;
+ }
+ (State::MaybeArg, _) => {
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+
+ // While Rust uses the unicode sets of XID_start and XID_continue for Identifiers
+ // this is probably the best we can do to avoid a false positive
+ if chr.is_alphabetic() || chr == '_' {
+ state = State::Ident;
+ } else {
+ state = State::Expr;
+ }
+ }
+ (State::Ident | State::Expr, '}') => {
+ if inexpr_open_count == 0 {
+ output.push(chr);
+
+ if matches!(state, State::Expr) {
+ extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
+ } else {
+ extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
+ }
+
+ current_expr = String::new();
+ state = State::NotArg;
+ } else {
+ // We're closing one brace met before inside of the expression.
+ current_expr.push(chr);
+ inexpr_open_count -= 1;
+ }
+ }
+ (State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => {
+ // path separator
+ state = State::Expr;
+ current_expr.push_str("::");
+ chars.next();
+ }
+ (State::Ident | State::Expr, ':') => {
+ if inexpr_open_count == 0 {
+ // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
+ output.push(chr);
+
+ if matches!(state, State::Expr) {
+ extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
+ } else {
+ extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
+ }
+
+ current_expr = String::new();
+ 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::Ident | State::Expr, '{') => {
+ state = State::Expr;
+ current_expr.push(chr);
+ inexpr_open_count += 1;
+ }
+ (State::Ident | State::Expr, _) => {
+ if !(chr.is_alphanumeric() || chr == '_' || chr == '#') {
+ state = State::Expr;
+ }
+
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+ }
+ (State::FormatOpts, '}') => {
+ output.push(chr);
+ state = State::NotArg;
+ }
+ (State::FormatOpts, _) => {
+ if matches!(chr, '\\' | '$') {
+ output.push('\\');
+ }
+ output.push(chr);
+ }
+ }
+ }
+
+ if state != State::NotArg {
+ return Err(());
+ }
+
+ Ok((output, extracted_expressions))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use expect_test::{expect, Expect};
+
+ fn check(input: &str, expect: &Expect) {
+ let (output, exprs) = parse_format_exprs(input).unwrap_or(("-".to_string(), vec![]));
+ let outcome_repr = if !exprs.is_empty() {
+ format!("{}; {}", output, with_placeholders(exprs).join(", "))
+ } else {
+ output
+ };
+
+ 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 arg_type() {
+ assert_eq!(
+ parse_format_exprs("{_ident} {r#raw_ident} {expr.obj} {name {thing: 42} } {}")
+ .unwrap()
+ .1,
+ vec![
+ Arg::Ident("_ident".to_owned()),
+ Arg::Ident("r#raw_ident".to_owned()),
+ Arg::Expr("expr.obj".to_owned()),
+ Arg::Expr("name {thing: 42}".to_owned()),
+ Arg::Placeholder
+ ]
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs
index b890e2b58..39710b8f1 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -2,8 +2,8 @@
use itertools::Itertools;
use parser::T;
use syntax::{
- ast::{self, HasLoopBody, PathSegmentKind, VisibilityKind},
- AstNode, Preorder, RustLanguage, WalkEvent,
+ ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
+ AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
};
pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
@@ -457,3 +457,8 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Pat
.collect();
Some(paths)
}
+
+pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
+ let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
+ Some(macro_call)
+}