@@ -380,6 +380,27 @@ impl<'db> SemanticsImpl<'db> {
380380 self.with_ctx(|ctx| ctx.has_derives(adt))
381381 }
382382
383+ pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
384+ let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
385+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
386+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
387+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
388+ _ => None,
389+ })?;
390+ let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
391+ let sa = self.analyze_no_infer(adt.syntax())?;
392+ let id = self.db.ast_id_map(sa.file_id).ast_id(&adt);
393+ let res: Vec<_> = sa
394+ .resolver
395+ .def_map()
396+ .derive_helpers_in_scope(InFile::new(sa.file_id, id))?
397+ .iter()
398+ .filter(|&(name, _, _)| *name == attr_name)
399+ .map(|&(_, macro_, call)| (macro_.into(), call.as_macro_file()))
400+ .collect();
401+ res.is_empty().not().then_some(res)
402+ }
403+
383404 pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
384405 let file_id = self.find_file(item.syntax()).file_id;
385406 let src = InFile::new(file_id, item.clone());
@@ -409,6 +430,20 @@ impl<'db> SemanticsImpl<'db> {
409430 )
410431 }
411432
433+ pub fn speculative_expand_raw(
434+ &self,
435+ macro_file: MacroFileId,
436+ speculative_args: &SyntaxNode,
437+ token_to_map: SyntaxToken,
438+ ) -> Option<(SyntaxNode, SyntaxToken)> {
439+ hir_expand::db::expand_speculative(
440+ self.db.upcast(),
441+ macro_file.macro_call_id,
442+ speculative_args,
443+ token_to_map,
444+ )
445+ }
446+
412447 /// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the
413448 /// expansion. `token_to_map` should be a token from the `speculative args` node.
414449 pub fn speculative_expand_attr_macro(
@@ -826,99 +861,109 @@ impl<'db> SemanticsImpl<'db> {
826861
827862 // Then check for token trees, that means we are either in a function-like macro or
828863 // secondary attribute inputs
829- let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
830- let parent = tt.syntax().parent()?;
831-
832- if tt.left_delimiter_token().map_or(false, |it| it == token) {
833- return None;
834- }
835- if tt.right_delimiter_token().map_or(false, |it| it == token) {
836- return None;
837- }
838-
839- if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
840- let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
841- InFile::new(file_id, macro_call);
842- let file_id = match mcache.get(&mcall) {
843- Some(&it) => it,
844- None => {
845- let it = sa.expand(self.db, mcall.as_ref())?;
846- mcache.insert(mcall, it);
847- it
864+ let tt = token
865+ .parent_ancestors()
866+ .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
867+ .last()?;
868+ match tt {
869+ Either::Left(tt) => {
870+ if tt.left_delimiter_token().map_or(false, |it| it == token) {
871+ return None;
848872 }
849- };
850- let text_range = tt.syntax().text_range();
851- // remove any other token in this macro input, all their mappings are the
852- // same as this one
853- tokens.retain(|t| !text_range.contains_range(t.text_range()));
854-
855- process_expansion_for_token(&mut stack, file_id).or(file_id
856- .eager_arg(self.db.upcast())
857- .and_then(|arg| {
858- // also descend into eager expansions
859- process_expansion_for_token(&mut stack, arg.as_macro_file())
860- }))
861- } else if let Some(meta) = ast::Meta::cast(parent) {
862- // attribute we failed expansion for earlier, this might be a derive invocation
863- // or derive helper attribute
864- let attr = meta.parent_attr()?;
865- let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
866- {
867- // this might be a derive on an ADT
868- let derive_call = self.with_ctx(|ctx| {
869- // so try downmapping the token into the pseudo derive expansion
870- // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
871- ctx.attr_to_derive_macro_call(
872- InFile::new(file_id, &adt),
873- InFile::new(file_id, attr.clone()),
874- )
875- .map(|(_, call_id, _)| call_id)
876- });
877-
878- match derive_call {
879- Some(call_id) => {
880- // resolved to a derive
881- let file_id = call_id.as_macro_file();
882- let text_range = attr.syntax().text_range();
883- // remove any other token in this macro input, all their mappings are the
884- // same as this
885- tokens.retain(|t| !text_range.contains_range(t.text_range()));
886- return process_expansion_for_token(&mut stack, file_id);
887- }
888- None => Some(adt),
873+ if tt.right_delimiter_token().map_or(false, |it| it == token) {
874+ return None;
889875 }
890- } else {
891- // Otherwise this could be a derive helper on a variant or field
892- attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| {
893- match it {
894- ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
895- ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
896- ast::Item::Union(it) => Some(ast::Adt::Union(it)),
897- _ => None,
876+ let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
877+ let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
878+ InFile::new(file_id, macro_call);
879+ let file_id = match mcache.get(&mcall) {
880+ Some(&it) => it,
881+ None => {
882+ let it = sa.expand(self.db, mcall.as_ref())?;
883+ mcache.insert(mcall, it);
884+ it
898885 }
899- })
900- }?;
901- if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
902- return None;
886+ };
887+ let text_range = tt.syntax().text_range();
888+ // remove any other token in this macro input, all their mappings are the
889+ // same as this one
890+ tokens.retain(|t| !text_range.contains_range(t.text_range()));
891+
892+ process_expansion_for_token(&mut stack, file_id).or(file_id
893+ .eager_arg(self.db.upcast())
894+ .and_then(|arg| {
895+ // also descend into eager expansions
896+ process_expansion_for_token(&mut stack, arg.as_macro_file())
897+ }))
903898 }
904- // Not an attribute, nor a derive, so it's either a builtin or a derive helper
905- // Try to resolve to a derive helper and downmap
906- let attr_name =
907- attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
908- let id = self.db.ast_id_map(file_id).ast_id(&adt);
909- let helpers = def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
910- let mut res = None;
911- for (.., derive) in
912- helpers.iter().filter(|(helper, ..)| *helper == attr_name)
913- {
914- res = res.or(process_expansion_for_token(
915- &mut stack,
916- derive.as_macro_file(),
917- ));
899+ Either::Right(meta) => {
900+ // attribute we failed expansion for earlier, this might be a derive invocation
901+ // or derive helper attribute
902+ let attr = meta.parent_attr()?;
903+ let adt = match attr.syntax().parent().and_then(ast::Adt::cast) {
904+ Some(adt) => {
905+ // this might be a derive on an ADT
906+ let derive_call = self.with_ctx(|ctx| {
907+ // so try downmapping the token into the pseudo derive expansion
908+ // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
909+ ctx.attr_to_derive_macro_call(
910+ InFile::new(file_id, &adt),
911+ InFile::new(file_id, attr.clone()),
912+ )
913+ .map(|(_, call_id, _)| call_id)
914+ });
915+
916+ match derive_call {
917+ Some(call_id) => {
918+ // resolved to a derive
919+ let file_id = call_id.as_macro_file();
920+ let text_range = attr.syntax().text_range();
921+ // remove any other token in this macro input, all their mappings are the
922+ // same as this
923+ tokens.retain(|t| {
924+ !text_range.contains_range(t.text_range())
925+ });
926+ return process_expansion_for_token(
927+ &mut stack, file_id,
928+ );
929+ }
930+ None => Some(adt),
931+ }
932+ }
933+ None => {
934+ // Otherwise this could be a derive helper on a variant or field
935+ attr.syntax().ancestors().find_map(ast::Item::cast).and_then(
936+ |it| match it {
937+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
938+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
939+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
940+ _ => None,
941+ },
942+ )
943+ }
944+ }?;
945+ if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
946+ return None;
947+ }
948+ let attr_name =
949+ attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
950+ // Not an attribute, nor a derive, so it's either a builtin or a derive helper
951+ // Try to resolve to a derive helper and downmap
952+ let id = self.db.ast_id_map(file_id).ast_id(&adt);
953+ let helpers =
954+ def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
955+
956+ let mut res = None;
957+ for (.., derive) in
958+ helpers.iter().filter(|(helper, ..)| *helper == attr_name)
959+ {
960+ res = res.or(process_expansion_for_token(
961+ &mut stack,
962+ derive.as_macro_file(),
963+ ));
964+ }
965+ res
918966 }
919- res
920- } else {
921- None
922967 }
923968 })()
924969 .is_none();
0 commit comments