mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +00:00
Auto merge of #17328 - Veykril:derive-helper-completions, r=Veykril
feat: Enable completions within derive helper attributes ![Code_zG5qInoQ6B](https://github.com/rust-lang/rust-analyzer/assets/3757771/db30b98d-4981-45e3-83a5-7ff23fbd3f66)
This commit is contained in:
commit
6b9baed80e
2 changed files with 209 additions and 98 deletions
|
@ -380,6 +380,27 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
self.with_ctx(|ctx| ctx.has_derives(adt))
|
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 {
|
pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
|
||||||
let file_id = self.find_file(item.syntax()).file_id;
|
let file_id = self.find_file(item.syntax()).file_id;
|
||||||
let src = InFile::new(file_id, item.clone());
|
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
|
/// 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.
|
/// expansion. `token_to_map` should be a token from the `speculative args` node.
|
||||||
pub fn speculative_expand_attr_macro(
|
pub fn speculative_expand_attr_macro(
|
||||||
|
@ -826,17 +861,19 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
|
|
||||||
// Then check for token trees, that means we are either in a function-like macro or
|
// Then check for token trees, that means we are either in a function-like macro or
|
||||||
// secondary attribute inputs
|
// secondary attribute inputs
|
||||||
let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
|
let tt = token
|
||||||
let parent = tt.syntax().parent()?;
|
.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) {
|
if tt.left_delimiter_token().map_or(false, |it| it == token) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if tt.right_delimiter_token().map_or(false, |it| it == token) {
|
if tt.right_delimiter_token().map_or(false, |it| it == token) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
|
||||||
if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
|
|
||||||
let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
|
let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
|
||||||
InFile::new(file_id, macro_call);
|
InFile::new(file_id, macro_call);
|
||||||
let file_id = match mcache.get(&mcall) {
|
let file_id = match mcache.get(&mcall) {
|
||||||
|
@ -858,12 +895,13 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
// also descend into eager expansions
|
// also descend into eager expansions
|
||||||
process_expansion_for_token(&mut stack, arg.as_macro_file())
|
process_expansion_for_token(&mut stack, arg.as_macro_file())
|
||||||
}))
|
}))
|
||||||
} else if let Some(meta) = ast::Meta::cast(parent) {
|
}
|
||||||
|
Either::Right(meta) => {
|
||||||
// attribute we failed expansion for earlier, this might be a derive invocation
|
// attribute we failed expansion for earlier, this might be a derive invocation
|
||||||
// or derive helper attribute
|
// or derive helper attribute
|
||||||
let attr = meta.parent_attr()?;
|
let attr = meta.parent_attr()?;
|
||||||
let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
|
let adt = match attr.syntax().parent().and_then(ast::Adt::cast) {
|
||||||
{
|
Some(adt) => {
|
||||||
// this might be a derive on an ADT
|
// this might be a derive on an ADT
|
||||||
let derive_call = self.with_ctx(|ctx| {
|
let derive_call = self.with_ctx(|ctx| {
|
||||||
// so try downmapping the token into the pseudo derive expansion
|
// so try downmapping the token into the pseudo derive expansion
|
||||||
|
@ -882,31 +920,39 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
let text_range = attr.syntax().text_range();
|
let text_range = attr.syntax().text_range();
|
||||||
// remove any other token in this macro input, all their mappings are the
|
// remove any other token in this macro input, all their mappings are the
|
||||||
// same as this
|
// same as this
|
||||||
tokens.retain(|t| !text_range.contains_range(t.text_range()));
|
tokens.retain(|t| {
|
||||||
return process_expansion_for_token(&mut stack, file_id);
|
!text_range.contains_range(t.text_range())
|
||||||
|
});
|
||||||
|
return process_expansion_for_token(
|
||||||
|
&mut stack, file_id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
None => Some(adt),
|
None => Some(adt),
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
None => {
|
||||||
// Otherwise this could be a derive helper on a variant or field
|
// Otherwise this could be a derive helper on a variant or field
|
||||||
attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| {
|
attr.syntax().ancestors().find_map(ast::Item::cast).and_then(
|
||||||
match it {
|
|it| match it {
|
||||||
ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
|
ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
|
||||||
ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
|
ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
|
||||||
ast::Item::Union(it) => Some(ast::Adt::Union(it)),
|
ast::Item::Union(it) => Some(ast::Adt::Union(it)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}?;
|
}?;
|
||||||
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
|
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// 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 =
|
let attr_name =
|
||||||
attr.path().and_then(|it| it.as_single_name_ref())?.as_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 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 helpers =
|
||||||
|
def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
|
||||||
|
|
||||||
let mut res = None;
|
let mut res = None;
|
||||||
for (.., derive) in
|
for (.., derive) in
|
||||||
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
|
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
|
||||||
|
@ -917,8 +963,7 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
.is_none();
|
.is_none();
|
||||||
|
|
|
@ -3,8 +3,9 @@ use std::iter;
|
||||||
|
|
||||||
use hir::{Semantics, Type, TypeInfo, Variant};
|
use hir::{Semantics, Type, TypeInfo, Variant};
|
||||||
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
|
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
|
||||||
|
use itertools::Either;
|
||||||
use syntax::{
|
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},
|
ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
|
||||||
match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
|
match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
|
||||||
SyntaxToken, TextRange, TextSize, T,
|
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
|
// 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,
|
Some(it) => it,
|
||||||
None => break 'expansion,
|
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,
|
Some(it) => it,
|
||||||
None => break 'expansion,
|
None => break 'expansion,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expand pseudo-derive expansion
|
let (tts, attrs) = match (orig_tt, spec_tt) {
|
||||||
if let (Some(orig_attr), Some(spec_attr)) = (
|
(Either::Left(orig_tt), Either::Left(spec_tt)) => {
|
||||||
orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
|
let attrs = orig_tt
|
||||||
spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
|
.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))) = (
|
if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
|
||||||
sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
|
sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
|
||||||
sema.speculative_expand_derive_as_pseudo_attr_macro(
|
sema.speculative_expand_derive_as_pseudo_attr_macro(
|
||||||
|
@ -147,15 +173,54 @@ fn expand(
|
||||||
fake_mapped_token.text_range().start(),
|
fake_mapped_token.text_range().start(),
|
||||||
orig_attr,
|
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
|
// at this point we won't have any more successful expansions, so stop
|
||||||
break 'expansion;
|
break 'expansion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand fn-like macro calls
|
// 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)) = (
|
if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
|
||||||
orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
|
orig_tt.syntax().parent().and_then(ast::MacroCall::cast),
|
||||||
spec_tt.syntax().ancestors().find_map(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_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
|
||||||
let mac_call_path1 =
|
let mac_call_path1 =
|
||||||
|
@ -201,6 +266,7 @@ fn expand(
|
||||||
// none of our states have changed so stop the loop
|
// none of our states have changed so stop the loop
|
||||||
break 'expansion;
|
break 'expansion;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
|
ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue