Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir/src/semantics.rs221
-rw-r--r--crates/ide-completion/src/context/analysis.rs86
2 files changed, 209 insertions, 98 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 53242611f8..43de2a6ee7 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -380,6 +380,27 @@ impl<'db> SemanticsImpl<'db> {
self.with_ctx(|ctx| ctx.has_derives(adt))
}
+ pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
+ let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+ _ => None,
+ })?;
+ let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+ let sa = self.analyze_no_infer(adt.syntax())?;
+ let id = self.db.ast_id_map(sa.file_id).ast_id(&adt);
+ let res: Vec<_> = sa
+ .resolver
+ .def_map()
+ .derive_helpers_in_scope(InFile::new(sa.file_id, id))?
+ .iter()
+ .filter(|&(name, _, _)| *name == attr_name)
+ .map(|&(_, macro_, call)| (macro_.into(), call.as_macro_file()))
+ .collect();
+ res.is_empty().not().then_some(res)
+ }
+
pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
let file_id = self.find_file(item.syntax()).file_id;
let src = InFile::new(file_id, item.clone());
@@ -409,6 +430,20 @@ impl<'db> SemanticsImpl<'db> {
)
}
+ pub fn speculative_expand_raw(
+ &self,
+ macro_file: MacroFileId,
+ speculative_args: &SyntaxNode,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ hir_expand::db::expand_speculative(
+ self.db.upcast(),
+ macro_file.macro_call_id,
+ speculative_args,
+ token_to_map,
+ )
+ }
+
/// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the
/// expansion. `token_to_map` should be a token from the `speculative args` node.
pub fn speculative_expand_attr_macro(
@@ -826,99 +861,109 @@ impl<'db> SemanticsImpl<'db> {
// Then check for token trees, that means we are either in a function-like macro or
// secondary attribute inputs
- let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
- let parent = tt.syntax().parent()?;
-
- if tt.left_delimiter_token().map_or(false, |it| it == token) {
- return None;
- }
- if tt.right_delimiter_token().map_or(false, |it| it == token) {
- return None;
- }
-
- if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
- let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
- InFile::new(file_id, macro_call);
- let file_id = match mcache.get(&mcall) {
- Some(&it) => it,
- None => {
- let it = sa.expand(self.db, mcall.as_ref())?;
- mcache.insert(mcall, it);
- it
+ let tt = token
+ .parent_ancestors()
+ .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+ .last()?;
+ match tt {
+ Either::Left(tt) => {
+ if tt.left_delimiter_token().map_or(false, |it| it == token) {
+ return None;
}
- };
- let text_range = tt.syntax().text_range();
- // remove any other token in this macro input, all their mappings are the
- // same as this one
- tokens.retain(|t| !text_range.contains_range(t.text_range()));
-
- process_expansion_for_token(&mut stack, file_id).or(file_id
- .eager_arg(self.db.upcast())
- .and_then(|arg| {
- // also descend into eager expansions
- process_expansion_for_token(&mut stack, arg.as_macro_file())
- }))
- } else if let Some(meta) = ast::Meta::cast(parent) {
- // attribute we failed expansion for earlier, this might be a derive invocation
- // or derive helper attribute
- let attr = meta.parent_attr()?;
- let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
- {
- // this might be a derive on an ADT
- let derive_call = self.with_ctx(|ctx| {
- // so try downmapping the token into the pseudo derive expansion
- // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
- ctx.attr_to_derive_macro_call(
- InFile::new(file_id, &adt),
- InFile::new(file_id, attr.clone()),
- )
- .map(|(_, call_id, _)| call_id)
- });
-
- match derive_call {
- Some(call_id) => {
- // resolved to a derive
- let file_id = call_id.as_macro_file();
- let text_range = attr.syntax().text_range();
- // remove any other token in this macro input, all their mappings are the
- // same as this
- tokens.retain(|t| !text_range.contains_range(t.text_range()));
- return process_expansion_for_token(&mut stack, file_id);
- }
- None => Some(adt),
+ if tt.right_delimiter_token().map_or(false, |it| it == token) {
+ return None;
}
- } else {
- // Otherwise this could be a derive helper on a variant or field
- attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| {
- match it {
- ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
- ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
- ast::Item::Union(it) => Some(ast::Adt::Union(it)),
- _ => None,
+ let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+ let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
+ InFile::new(file_id, macro_call);
+ let file_id = match mcache.get(&mcall) {
+ Some(&it) => it,
+ None => {
+ let it = sa.expand(self.db, mcall.as_ref())?;
+ mcache.insert(mcall, it);
+ it
}
- })
- }?;
- if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
- return None;
+ };
+ let text_range = tt.syntax().text_range();
+ // remove any other token in this macro input, all their mappings are the
+ // same as this one
+ tokens.retain(|t| !text_range.contains_range(t.text_range()));
+
+ process_expansion_for_token(&mut stack, file_id).or(file_id
+ .eager_arg(self.db.upcast())
+ .and_then(|arg| {
+ // also descend into eager expansions
+ process_expansion_for_token(&mut stack, arg.as_macro_file())
+ }))
}
- // Not an attribute, nor a derive, so it's either a builtin or a derive helper
- // Try to resolve to a derive helper and downmap
- let attr_name =
- attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
- let id = self.db.ast_id_map(file_id).ast_id(&adt);
- let helpers = def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
- let mut res = None;
- for (.., derive) in
- helpers.iter().filter(|(helper, ..)| *helper == attr_name)
- {
- res = res.or(process_expansion_for_token(
- &mut stack,
- derive.as_macro_file(),
- ));
+ Either::Right(meta) => {
+ // attribute we failed expansion for earlier, this might be a derive invocation
+ // or derive helper attribute
+ let attr = meta.parent_attr()?;
+ let adt = match attr.syntax().parent().and_then(ast::Adt::cast) {
+ Some(adt) => {
+ // this might be a derive on an ADT
+ let derive_call = self.with_ctx(|ctx| {
+ // so try downmapping the token into the pseudo derive expansion
+ // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+ ctx.attr_to_derive_macro_call(
+ InFile::new(file_id, &adt),
+ InFile::new(file_id, attr.clone()),
+ )
+ .map(|(_, call_id, _)| call_id)
+ });
+
+ match derive_call {
+ Some(call_id) => {
+ // resolved to a derive
+ let file_id = call_id.as_macro_file();
+ let text_range = attr.syntax().text_range();
+ // remove any other token in this macro input, all their mappings are the
+ // same as this
+ tokens.retain(|t| {
+ !text_range.contains_range(t.text_range())
+ });
+ return process_expansion_for_token(
+ &mut stack, file_id,
+ );
+ }
+ None => Some(adt),
+ }
+ }
+ None => {
+ // Otherwise this could be a derive helper on a variant or field
+ attr.syntax().ancestors().find_map(ast::Item::cast).and_then(
+ |it| match it {
+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+ _ => None,
+ },
+ )
+ }
+ }?;
+ if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+ return None;
+ }
+ let attr_name =
+ attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+ // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+ // Try to resolve to a derive helper and downmap
+ let id = self.db.ast_id_map(file_id).ast_id(&adt);
+ let helpers =
+ def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
+
+ let mut res = None;
+ for (.., derive) in
+ helpers.iter().filter(|(helper, ..)| *helper == attr_name)
+ {
+ res = res.or(process_expansion_for_token(
+ &mut stack,
+ derive.as_macro_file(),
+ ));
+ }
+ res
}
- res
- } else {
- None
}
})()
.is_none();
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 79c503e0a1..f0c6e7a63b 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -3,8 +3,9 @@ use std::iter;
use hir::{Semantics, Type, TypeInfo, Variant};
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
+use itertools::Either;
use syntax::{
- algo::{find_node_at_offset, non_trivia_sibling},
+ algo::{ancestors_at_offset, find_node_at_offset, non_trivia_sibling},
ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
SyntaxToken, TextRange, TextSize, T,
@@ -119,20 +120,45 @@ fn expand(
}
// 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) {
+ let orig_tt = match ancestors_at_offset(&original_file, offset)
+ .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+ .last()
+ {
Some(it) => it,
None => break 'expansion,
};
- let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+ let spec_tt = match ancestors_at_offset(&speculative_file, offset)
+ .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+ .last()
+ {
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()),
- ) {
+ let (tts, attrs) = match (orig_tt, spec_tt) {
+ (Either::Left(orig_tt), Either::Left(spec_tt)) => {
+ let attrs = orig_tt
+ .syntax()
+ .parent()
+ .and_then(ast::Meta::cast)
+ .and_then(|it| it.parent_attr())
+ .zip(
+ spec_tt
+ .syntax()
+ .parent()
+ .and_then(ast::Meta::cast)
+ .and_then(|it| it.parent_attr()),
+ );
+ (Some((orig_tt, spec_tt)), attrs)
+ }
+ (Either::Right(orig_path), Either::Right(spec_path)) => {
+ (None, orig_path.parent_attr().zip(spec_path.parent_attr()))
+ }
+ _ => break 'expansion,
+ };
+
+ // Expand pseudo-derive expansion aka `derive(Debug$0)`
+ if let Some((orig_attr, spec_attr)) = attrs {
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(
@@ -147,15 +173,54 @@ fn expand(
fake_mapped_token.text_range().start(),
orig_attr,
));
+ break 'expansion;
+ }
+
+ if let Some(spec_adt) =
+ spec_attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+ _ => None,
+ })
+ {
+ // might be the path of derive helper or a token tree inside of one
+ if let Some(helpers) = sema.derive_helper(&orig_attr) {
+ for (_mac, file) in helpers {
+ if let Some((fake_expansion, fake_mapped_token)) = sema
+ .speculative_expand_raw(
+ file,
+ spec_adt.syntax(),
+ fake_ident_token.clone(),
+ )
+ {
+ // we are inside a derive helper token tree, treat this as being inside
+ // the derive expansion
+ let actual_expansion = sema.parse_or_expand(file.into());
+ let new_offset = fake_mapped_token.text_range().start();
+ if new_offset + relative_offset > actual_expansion.text_range().end() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
+ }
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
+ }
+ }
+ }
}
// at this point we won't have any more successful expansions, so stop
break 'expansion;
}
// Expand fn-like macro calls
+ let Some((orig_tt, spec_tt)) = tts else { break 'expansion };
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),
+ orig_tt.syntax().parent().and_then(ast::MacroCall::cast),
+ spec_tt.syntax().parent().and_then(ast::MacroCall::cast),
) {
let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
let mac_call_path1 =
@@ -201,6 +266,7 @@ fn expand(
// none of our states have changed so stop the loop
break 'expansion;
}
+
ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
}