diff --git a/Cargo.toml b/Cargo.toml index 8c50718984..975fe277b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ smol_str.opt-level = 3 text-size.opt-level = 3 serde.opt-level = 3 salsa.opt-level = 3 +dissimilar.opt-level = 3 + # This speeds up `cargo xtask dist`. miniz_oxide.opt-level = 3 diff --git a/crates/hir-def/src/dyn_map.rs b/crates/hir-def/src/dyn_map.rs index eed1490a7a..20018b61e5 100644 --- a/crates/hir-def/src/dyn_map.rs +++ b/crates/hir-def/src/dyn_map.rs @@ -67,8 +67,14 @@ pub mod keys { pub const PROC_MACRO: Key = Key::new(); pub const MACRO_CALL: Key = Key::new(); pub const ATTR_MACRO_CALL: Key = Key::new(); - pub const DERIVE_MACRO_CALL: Key]>)> = - Key::new(); + pub const DERIVE_MACRO_CALL: Key< + ast::Attr, + ( + AttrId, + /* derive() */ MacroCallId, + /* actual derive macros */ Box<[Option]>, + ), + > = Key::new(); /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are /// equal if they point to exactly the same object. diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index a75c56aa8f..587e1013b9 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -550,7 +550,7 @@ impl<'db> SemanticsImpl<'db> { } pub fn is_derive_annotated(&self, adt: InFile<&ast::Adt>) -> bool { - self.with_ctx(|ctx| ctx.has_derives(adt)) + self.with_ctx(|ctx| ctx.file_of_adt_has_derives(adt)) } pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option> { @@ -894,6 +894,7 @@ impl<'db> SemanticsImpl<'db> { // node is just the token, so descend the token self.descend_into_macros_impl( InFile::new(file.file_id, first), + false, &mut |InFile { value, .. }, _ctx| { if let Some(node) = value .parent_ancestors() @@ -908,14 +909,19 @@ impl<'db> SemanticsImpl<'db> { } else { // Descend first and last token, then zip them to look for the node they belong to let mut scratch: SmallVec<[_; 1]> = smallvec![]; - self.descend_into_macros_impl(InFile::new(file.file_id, first), &mut |token, _ctx| { - scratch.push(token); - CONTINUE_NO_BREAKS - }); + self.descend_into_macros_impl( + InFile::new(file.file_id, first), + false, + &mut |token, _ctx| { + scratch.push(token); + CONTINUE_NO_BREAKS + }, + ); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( InFile::new(file.file_id, last), + false, &mut |InFile { value: last, file_id: last_fid }, _ctx| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { @@ -967,7 +973,7 @@ impl<'db> SemanticsImpl<'db> { ast::Item::Union(it) => it.into(), _ => return false, }; - ctx.has_derives(token.with_value(&adt)) + ctx.file_of_adt_has_derives(token.with_value(&adt)) }) }) } @@ -977,7 +983,7 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, mut cb: impl FnMut(InFile, SyntaxContext), ) { - self.descend_into_macros_impl(self.wrap_token_infile(token), &mut |t, ctx| { + self.descend_into_macros_impl(self.wrap_token_infile(token), false, &mut |t, ctx| { cb(t, ctx); CONTINUE_NO_BREAKS }); @@ -985,10 +991,14 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - self.descend_into_macros_impl(self.wrap_token_infile(token.clone()), &mut |t, _ctx| { - res.push(t.value); - CONTINUE_NO_BREAKS - }); + self.descend_into_macros_impl( + self.wrap_token_infile(token.clone()), + false, + &mut |t, _ctx| { + res.push(t.value); + CONTINUE_NO_BREAKS + }, + ); if res.is_empty() { res.push(token); } @@ -1001,7 +1011,7 @@ impl<'db> SemanticsImpl<'db> { ) -> SmallVec<[InFile; 1]> { let mut res = smallvec![]; let token = self.wrap_token_infile(token); - self.descend_into_macros_impl(token.clone(), &mut |t, ctx| { + self.descend_into_macros_impl(token.clone(), true, &mut |t, ctx| { if !ctx.is_opaque(self.db) { // Don't descend into opaque contexts res.push(t); @@ -1019,7 +1029,7 @@ impl<'db> SemanticsImpl<'db> { token: InFile, mut cb: impl FnMut(InFile, SyntaxContext) -> ControlFlow, ) -> Option { - self.descend_into_macros_impl(token, &mut cb) + self.descend_into_macros_impl(token, false, &mut cb) } /// Descends the token into expansions, returning the tokens that matches the input @@ -1092,41 +1102,41 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros_impl( &self, InFile { value: token, file_id }: InFile, + always_descend_into_derives: bool, f: &mut dyn FnMut(InFile, SyntaxContext) -> ControlFlow, ) -> Option { let _p = tracing::info_span!("descend_into_macros_impl").entered(); - let span = self.db.span_map(file_id).span_for_range(token.text_range()); + let db = self.db; + let span = db.span_map(file_id).span_for_range(token.text_range()); // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack - let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { - let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { - Some( - ctx.cache - .get_or_insert_expansion(ctx.db, macro_file) - .map_range_down(span)? - .map(SmallVec::<[_; 2]>::from_iter), - ) - })?; - // we have found a mapping for the token if the vec is non-empty - let res = mapped_tokens.is_empty().not().then_some(()); - // requeue the tokens we got from mapping our current token down - stack.push((HirFileId::from(file_id), mapped_tokens)); - res - }; + let process_expansion_for_token = + |ctx: &mut SourceToDefCtx<'_, '_>, stack: &mut Vec<_>, macro_file| { + let InMacroFile { file_id, value: mapped_tokens } = ctx + .cache + .get_or_insert_expansion(ctx.db, macro_file) + .map_range_down(span)? + .map(SmallVec::<[_; 2]>::from_iter); + // we have found a mapping for the token if the vec is non-empty + let res = mapped_tokens.is_empty().not().then_some(()); + // requeue the tokens we got from mapping our current token down + stack.push((HirFileId::from(file_id), mapped_tokens)); + res + }; // A stack of tokens to process, along with the file they came from // These are tracked to know which macro calls we still have to look into // the tokens themselves aren't that interesting as the span that is being used to map // things down never changes. let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![]; - let include = file_id.file_id().and_then(|file_id| { - self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id) - }); + let include = file_id + .file_id() + .and_then(|file_id| self.s2d_cache.borrow_mut().get_or_insert_include_for(db, file_id)); match include { Some(include) => { // include! inputs are always from real files, so they only need to be handled once upfront - process_expansion_for_token(&mut stack, include)?; + self.with_ctx(|ctx| process_expansion_for_token(ctx, &mut stack, include))?; } None => { stack.push((file_id, smallvec![(token, span.ctx)])); @@ -1148,62 +1158,120 @@ impl<'db> SemanticsImpl<'db> { tokens.reverse(); while let Some((token, ctx)) = tokens.pop() { let was_not_remapped = (|| { - // First expand into attribute invocations - let containing_attribute_macro_call = self.with_ctx(|ctx| { - token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| { - // Don't force populate the dyn cache for items that don't have an attribute anyways - item.attrs().next()?; - Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item)) - }) - }); - if let Some((call_id, item)) = containing_attribute_macro_call { - let attr_id = match self.db.lookup_intern_macro_call(call_id).kind { - hir_expand::MacroCallKind::Attr { invoc_attr_index, .. } => { - invoc_attr_index.ast_index() - } - _ => 0, - }; - // FIXME: here, the attribute's text range is used to strip away all - // entries from the start of the attribute "list" up the invoking - // attribute. But in - // ``` - // mod foo { - // #![inner] - // } - // ``` - // we don't wanna strip away stuff in the `mod foo {` range, that is - // here if the id corresponds to an inner attribute we got strip all - // text ranges of the outer ones, and then all of the inner ones up - // to the invoking attribute so that the inbetween is ignored. - let text_range = item.syntax().text_range(); - let start = collect_attrs(&item) - .nth(attr_id) - .map(|attr| match attr.1 { - Either::Left(it) => it.syntax().text_range().start(), - Either::Right(it) => it.syntax().text_range().start(), + // First expand into attribute invocations, this is required to be handled + // upfront as any other macro call within will not semantically resolve unless + // also descended. + let res = self.with_ctx(|ctx| { + token + .parent_ancestors() + .filter_map(ast::Item::cast) + // FIXME: This might work incorrectly when we have a derive, followed by + // an attribute on an item, like: + // ``` + // #[derive(Debug$0)] + // #[my_attr] + // struct MyStruct; + // ``` + // here we should not consider the attribute at all, as our cursor + // technically lies outside of its expansion + .find_map(|item| { + // Don't force populate the dyn cache for items that don't have an attribute anyways + item.attrs().next()?; + ctx.item_to_macro_call(InFile::new(expansion, &item)) + .zip(Some(item)) }) - .unwrap_or_else(|| text_range.start()); - let text_range = TextRange::new(start, text_range.end()); - filter_duplicates(tokens, text_range); - return process_expansion_for_token(&mut stack, call_id); + .map(|(call_id, item)| { + let attr_id = match db.lookup_intern_macro_call(call_id).kind { + hir_expand::MacroCallKind::Attr { + invoc_attr_index, .. + } => invoc_attr_index.ast_index(), + _ => 0, + }; + // FIXME: here, the attribute's text range is used to strip away all + // entries from the start of the attribute "list" up the invoking + // attribute. But in + // ``` + // mod foo { + // #![inner] + // } + // ``` + // we don't wanna strip away stuff in the `mod foo {` range, that is + // here if the id corresponds to an inner attribute we got strip all + // text ranges of the outer ones, and then all of the inner ones up + // to the invoking attribute so that the inbetween is ignored. + let text_range = item.syntax().text_range(); + let start = collect_attrs(&item) + .nth(attr_id) + .map(|attr| match attr.1 { + Either::Left(it) => it.syntax().text_range().start(), + Either::Right(it) => it.syntax().text_range().start(), + }) + .unwrap_or_else(|| text_range.start()); + let text_range = TextRange::new(start, text_range.end()); + filter_duplicates(tokens, text_range); + process_expansion_for_token(ctx, &mut stack, call_id) + }) + }); + + if let Some(res) = res { + return res; } + if always_descend_into_derives { + let res = self.with_ctx(|ctx| { + let (derives, adt) = token + .parent_ancestors() + .filter_map(ast::Adt::cast) + .find_map(|adt| { + Some(( + ctx.derive_macro_calls(InFile::new(expansion, &adt))? + .map(|(a, b, c)| (a, b, c.to_owned())) + .collect::>(), + adt, + )) + })?; + let mut res = None; + for (_, derive_attr, derives) in derives { + // as there may be multiple derives registering the same helper + // name, we gotta make sure to call this for all of them! + // FIXME: We need to call `f` for all of them as well though! + res = res.or(process_expansion_for_token( + ctx, + &mut stack, + derive_attr, + )); + for derive in derives.into_iter().flatten() { + res = res + .or(process_expansion_for_token(ctx, &mut stack, derive)); + } + } + // remove all tokens that are within the derives expansion + filter_duplicates(tokens, adt.syntax().text_range()); + Some(res) + }); + // if we found derives, we can early exit. There is no way we can be in any + // macro call at this point given we are not in a token tree + if let Some(res) = res { + return res; + } + } // 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(Either::::cast) .last()?; + match tt { // function-like macro call Either::Left(tt) => { + let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; 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; } - let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; let mcall = InFile::new(expansion, macro_call); let file_id = match m_cache.get(&mcall) { Some(&it) => it, @@ -1216,13 +1284,16 @@ impl<'db> SemanticsImpl<'db> { let text_range = tt.syntax().text_range(); filter_duplicates(tokens, text_range); - process_expansion_for_token(&mut stack, file_id).or(file_id - .eager_arg(self.db) - .and_then(|arg| { - // also descend into eager expansions - process_expansion_for_token(&mut stack, arg) - })) + self.with_ctx(|ctx| { + process_expansion_for_token(ctx, &mut stack, file_id).or(file_id + .eager_arg(db) + .and_then(|arg| { + // also descend into eager expansions + process_expansion_for_token(ctx, &mut stack, arg) + })) + }) } + Either::Right(_) if always_descend_into_derives => None, // derive or derive helper Either::Right(meta) => { // attribute we failed expansion for earlier, this might be a derive invocation @@ -1231,31 +1302,33 @@ impl<'db> SemanticsImpl<'db> { 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| { + let res = 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(expansion, &adt), - InFile::new(expansion, attr.clone()), - ) - .map(|(_, call_id, _)| call_id) - }); + let derive_call = ctx + .attr_to_derive_macro_call( + InFile::new(expansion, &adt), + InFile::new(expansion, attr.clone()), + )? + .1; - match derive_call { - Some(call_id) => { - // resolved to a derive - 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, call_id, - ); - } - None => Some(adt), + // resolved to a derive + 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()) + }); + Some(process_expansion_for_token( + ctx, + &mut stack, + derive_call, + )) + }); + if let Some(res) = res { + return res; } + Some(adt) } None => { // Otherwise this could be a derive helper on a variant or field @@ -1269,12 +1342,9 @@ impl<'db> SemanticsImpl<'db> { ) } }?; - if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &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 an intert attribute or a derive helper + // Not an attribute, nor a derive, so it's either an inert attribute or a derive helper // Try to resolve to a derive helper and downmap let resolver = &token .parent() @@ -1282,7 +1352,7 @@ impl<'db> SemanticsImpl<'db> { self.analyze_impl(InFile::new(expansion, &parent), None, false) })? .resolver; - let id = self.db.ast_id_map(expansion).ast_id(&adt); + let id = db.ast_id_map(expansion).ast_id(&adt); let helpers = resolver .def_map() .derive_helpers_in_scope(InFile::new(expansion, id))?; @@ -1293,20 +1363,22 @@ impl<'db> SemanticsImpl<'db> { } let mut res = None; - for (.., derive) in - helpers.iter().filter(|(helper, ..)| *helper == attr_name) - { - // as there may be multiple derives registering the same helper - // name, we gotta make sure to call this for all of them! - // FIXME: We need to call `f` for all of them as well though! - res = res.or(process_expansion_for_token(&mut stack, *derive)); - } - res + self.with_ctx(|ctx| { + for (.., derive) in + helpers.iter().filter(|(helper, ..)| *helper == attr_name) + { + // as there may be multiple derives registering the same helper + // name, we gotta make sure to call this for all of them! + // FIXME: We need to call `f` for all of them as well though! + res = res + .or(process_expansion_for_token(ctx, &mut stack, *derive)); + } + res + }) } } })() .is_none(); - if was_not_remapped { if let ControlFlow::Break(b) = f(InFile::new(expansion, token), ctx) { return Some(b); diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 7f6c9af474..7dac01a268 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -108,7 +108,7 @@ use span::FileId; use stdx::impl_from; use syntax::{ AstNode, AstPtr, SyntaxNode, - ast::{self, HasName}, + ast::{self, HasAttrs, HasName}, }; use tt::TextRange; @@ -411,10 +411,24 @@ impl SourceToDefCtx<'_, '_> { .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) } - pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { + pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty()) } + pub(super) fn derive_macro_calls<'slf>( + &'slf mut self, + adt: InFile<&ast::Adt>, + ) -> Option])> + use<'slf>> + { + self.dyn_map(adt).as_ref().map(|&map| { + let dyn_map = &map[keys::DERIVE_MACRO_CALL]; + adt.value + .attrs() + .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr))) + .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) + }) + } + fn to_def( &mut self, src: InFile<&Ast>,