From f84553641c7c20d5c62cddc2ddd11255280eb505 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 12:33:32 +0200 Subject: [PATCH 1/9] Remove DescendPreference::SameText --- src/tools/rust-analyzer/Cargo.toml | 2 + .../crates/hir-expand/src/lib.rs | 7 + .../rust-analyzer/crates/hir/src/semantics.rs | 351 +++++++++++------- .../extract_expressions_from_format_string.rs | 14 +- .../src/syntax_helpers/format_string_exprs.rs | 1 + .../rust-analyzer/crates/ide/src/doc_links.rs | 2 +- .../crates/ide/src/extend_selection.rs | 10 +- .../crates/ide/src/goto_implementation.rs | 93 ++--- .../rust-analyzer/crates/ide/src/hover.rs | 33 +- .../crates/ide/src/references.rs | 4 +- .../crates/ide/src/signature_help.rs | 7 +- .../crates/ide/src/syntax_highlighting.rs | 3 +- .../test_data/highlight_macros.html | 4 +- 13 files changed, 303 insertions(+), 228 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 56e80e11e774..d8f9b2b7f6af 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -184,6 +184,8 @@ style = { level = "warn", priority = -1 } suspicious = { level = "warn", priority = -1 } ## allow following lints +# subjective +single_match = "allow" # () makes a fine error in most cases result_unit_err = "allow" # We don't expose public APIs that matter like this diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 2bea9026265a..741c2b2ebfe2 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -279,6 +279,7 @@ pub enum MacroCallKind { } pub trait HirFileIdExt { + fn edition(self, db: &dyn ExpandDatabase) -> Edition; /// Returns the original file of this macro call hierarchy. fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId; @@ -293,6 +294,12 @@ pub trait HirFileIdExt { } impl HirFileIdExt for HirFileId { + fn edition(self, db: &dyn ExpandDatabase) -> Edition { + match self.repr() { + HirFileIdRepr::FileId(file_id) => file_id.edition(), + HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition, + } + } fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId { let mut file_id = self; loop { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index d086aee42855..a8151e70a108 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -4,6 +4,7 @@ mod source_to_def; use std::{ cell::RefCell, + convert::Infallible, fmt, iter, mem, ops::{self, ControlFlow, Not}, }; @@ -48,12 +49,46 @@ use crate::{ Variant, VariantDef, }; +const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); + pub enum DescendPreference { - SameText, SameKind, None, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum MacroInputKind { + Root, + Bang, + AttrInput, + AttrTarget, + Derive, + DeriveHelper, + // Include, +} +impl MacroInputKind { + pub fn is_tt(self) -> bool { + matches!( + self, + MacroInputKind::AttrInput + | MacroInputKind::Bang + | MacroInputKind::DeriveHelper + | MacroInputKind::Derive + ) + } +} + +impl ops::BitOr for MacroInputKind { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + match self { + Self::Root => rhs, + _ => self, + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { /// An item @@ -545,51 +580,50 @@ impl<'db> SemanticsImpl<'db> { ) } + /// Retrieves all the formatting parts of the format_args! template string. pub fn as_format_args_parts( &self, string: &ast::String, ) -> Option)>> { - if let Some(quote) = string.open_quote_text_range() { - return self - .descend_into_macros(DescendPreference::SameText, string.syntax().clone()) - .into_iter() - .find_map(|token| { - let string = ast::String::cast(token)?; - let literal = - string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?; - let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?; - let source_analyzer = self.analyze_no_infer(format_args.syntax())?; - let format_args = self.wrap_node_infile(format_args); - let res = source_analyzer - .as_format_args_parts(self.db, format_args.as_ref())? - .map(|(range, res)| (range + quote.end(), res)) - .collect(); - Some(res) - }); - } - None + let quote = string.open_quote_text_range()?; + self.descend_into_macros_ng_b(string.syntax().clone(), |kind, token| { + (|| { + let token = token.value; + let string = ast::String::cast(token)?; + let literal = + string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?; + let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?; + let source_analyzer = self.analyze_no_infer(format_args.syntax())?; + let format_args = self.wrap_node_infile(format_args); + let res = source_analyzer + .as_format_args_parts(self.db, format_args.as_ref())? + .map(|(range, res)| (range + quote.end(), res)) + .collect(); + Some(res) + })() + .map_or(ControlFlow::Continue(()), ControlFlow::Break) + }) } + /// Retrieves the formatting part of the format_args! template string at the given offset. pub fn check_for_format_args_template( &self, original_token: SyntaxToken, offset: TextSize, ) -> Option<(TextRange, Option)> { - if let Some(original_string) = ast::String::cast(original_token.clone()) { - if let Some(quote) = original_string.open_quote_text_range() { - return self - .descend_into_macros(DescendPreference::SameText, original_token) - .into_iter() - .find_map(|token| { - self.resolve_offset_in_format_args( - ast::String::cast(token)?, - offset.checked_sub(quote.end())?, - ) - }) - .map(|(range, res)| (range + quote.end(), res)); - } - } - None + let original_string = ast::String::cast(original_token.clone())?; + let quote = original_string.open_quote_text_range()?; + self.descend_into_macros_ng_b(original_token.clone(), |kind, token| { + (|| { + let token = token.value; + self.resolve_offset_in_format_args( + ast::String::cast(token)?, + offset.checked_sub(quote.end())?, + ) + .map(|(range, res)| (range + quote.end(), res)) + })() + .map_or(ControlFlow::Continue(()), ControlFlow::Break) + }) } fn resolve_offset_in_format_args( @@ -622,7 +656,7 @@ impl<'db> SemanticsImpl<'db> { if first == last { // node is just the token, so descend the token - self.descend_into_macros_impl(first, &mut |InFile { value, .. }| { + self.descend_into_macros_impl(first, &mut |_kind, InFile { value, .. }| { if let Some(node) = value .parent_ancestors() .take_while(|it| it.text_range() == value.text_range()) @@ -630,20 +664,20 @@ impl<'db> SemanticsImpl<'db> { { res.push(node) } - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }); } 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(first, &mut |token| { + self.descend_into_macros_impl(first, &mut |_kind, token| { scratch.push(token); - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( last, - &mut |InFile { value: last, file_id: last_fid }| { + &mut |_kind, InFile { value: last, file_id: last_fid }| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { if let Some(p) = first.parent() { @@ -659,7 +693,7 @@ impl<'db> SemanticsImpl<'db> { } } } - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }, ); } @@ -673,8 +707,8 @@ impl<'db> SemanticsImpl<'db> { mode: DescendPreference, token: SyntaxToken, ) -> SmallVec<[SyntaxToken; 1]> { - enum Dp<'t> { - SameText(&'t str), + enum Dp { + // SameText(&'t str), SameKind(SyntaxKind), None, } @@ -686,104 +720,123 @@ impl<'db> SemanticsImpl<'db> { None => token.kind(), }; let mode = match mode { - DescendPreference::SameText => Dp::SameText(token.text()), + // DescendPreference::SameText => Dp::SameText(token.text()), DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)), DescendPreference::None => Dp::None, }; let mut res = smallvec![]; - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { - let is_a_match = match mode { - Dp::SameText(text) => value.text() == text, - Dp::SameKind(preferred_kind) => { - let kind = fetch_kind(&value); - kind == preferred_kind + self.descend_into_macros_impl::( + token.clone(), + &mut |kind, InFile { value, .. }| { + let is_a_match = match mode { + // Dp::SameText(text) => value.text() == text, + Dp::SameKind(preferred_kind) => { + let kind = fetch_kind(&value); + kind == preferred_kind // special case for derive macros || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) + } + Dp::None => true, + }; + if is_a_match { + res.push(value); } - Dp::None => true, - }; - if is_a_match { - res.push(value); - } - ControlFlow::Continue(()) - }); + ControlFlow::Continue(()) + }, + ); if res.is_empty() { res.push(token); } res } - pub fn descend_into_macros_single( + pub fn descend_into_macros_ng( &self, - mode: DescendPreference, token: SyntaxToken, - ) -> SyntaxToken { - enum Dp<'t> { - SameText(&'t str), - SameKind(SyntaxKind), - None, + mut cb: impl FnMut(MacroInputKind, InFile), + ) { + self.descend_into_macros_impl(token.clone(), &mut |kind, t| { + cb(kind, t); + CONTINUE_NO_BREAKS + }); + } + + pub fn descend_into_macros_ng_b( + &self, + token: SyntaxToken, + mut cb: impl FnMut(MacroInputKind, InFile) -> ControlFlow, + ) -> Option { + self.descend_into_macros_impl(token.clone(), &mut |kind, t| cb(kind, t)) + } + + /// Descends the token into expansions, returning the tokens that matches the input + /// token's [`SyntaxKind`] and text. + pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + let mut r = smallvec![]; + let text = token.text(); + let kind = token.kind(); + + self.descend_into_macros_ng(token.clone(), |m_kind, InFile { value, file_id }| { + let mapped_kind = value.kind(); + let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + r.push(value); + } + }); + if r.is_empty() { + r.push(token); } - let fetch_kind = |token: &SyntaxToken| match token.parent() { - Some(node) => match node.kind() { - kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind, - _ => token.kind(), - }, - None => token.kind(), - }; - let mode = match mode { - DescendPreference::SameText => Dp::SameText(token.text()), - DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)), - DescendPreference::None => Dp::None, - }; - let mut res = token.clone(); - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { - let is_a_match = match mode { - Dp::SameText(text) => value.text() == text, - Dp::SameKind(preferred_kind) => { - let kind = fetch_kind(&value); - kind == preferred_kind - // special case for derive macros - || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) - } - Dp::None => true, - }; - res = value; - if is_a_match { - ControlFlow::Break(()) + r + } + + /// Descends the token into expansions, returning the first token that matches the input + /// token's [`SyntaxKind`] and text. + pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken { + let text = token.text(); + let kind = token.kind(); + + self.descend_into_macros_ng_b(token.clone(), |m_kind, InFile { value, file_id }| { + let mapped_kind = value.kind(); + let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + ControlFlow::Break(value) } else { ControlFlow::Continue(()) } - }); - res + }) + .unwrap_or(token) } - fn descend_into_macros_impl( + fn descend_into_macros_impl( &self, token: SyntaxToken, - f: &mut dyn FnMut(InFile) -> ControlFlow<()>, - ) { + f: &mut dyn FnMut(MacroInputKind, InFile) -> ControlFlow, + ) -> Option { let _p = tracing::info_span!("descend_into_macros_impl").entered(); let (sa, span, file_id) = - match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) { - Some(sa) => match sa.file_id.file_id() { - Some(file_id) => ( - sa, - self.db.real_span_map(file_id).span_for_range(token.text_range()), - file_id.into(), - ), - None => { - stdx::never!(); - return; - } - }, - None => return, - }; + token.parent().and_then(|parent| self.analyze_no_infer(&parent)).and_then(|sa| { + let file_id = sa.file_id.file_id()?; + Some(( + sa, + self.db.real_span_map(file_id).span_for_range(token.text_range()), + HirFileId::from(file_id), + )) + })?; let mut m_cache = self.macro_call_cache.borrow_mut(); let def_map = sa.resolver.def_map(); - let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; - let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { + // 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![(file_id, MacroInputKind::Root, smallvec![token])]; + + // 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, remap_kind| { let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { Some( ctx.cache @@ -805,11 +858,17 @@ impl<'db> SemanticsImpl<'db> { // 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)); + stack.push((HirFileId::from(file_id), remap_kind, mapped_tokens)); res }; - while let Some((file_id, mut tokens)) = stack.pop() { + // Filters out all tokens that contain the given range (usually the macro call), any such + // token is redundant as the corresponding macro call has already been processed + let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| { + tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range())) + }; + + while let Some((expansion, remap_kind, ref mut tokens)) = stack.pop() { while let Some(token) = tokens.pop() { let was_not_remapped = (|| { // First expand into attribute invocations @@ -817,7 +876,7 @@ impl<'db> SemanticsImpl<'db> { 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(file_id, &item))?, item)) + Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item)) }) }); if let Some((call_id, item)) = containing_attribute_macro_call { @@ -849,10 +908,12 @@ impl<'db> SemanticsImpl<'db> { }) .unwrap_or_else(|| text_range.start()); let text_range = TextRange::new(start, text_range.end()); - // 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())); - return process_expansion_for_token(&mut stack, file_id); + filter_duplicates(tokens, text_range); + return process_expansion_for_token( + &mut stack, + file_id, + remap_kind | MacroInputKind::AttrTarget, + ); } // Then check for token trees, that means we are either in a function-like macro or @@ -862,6 +923,7 @@ impl<'db> SemanticsImpl<'db> { .map_while(Either::::cast) .last()?; match tt { + // function-like macro call Either::Left(tt) => { if tt.left_delimiter_token().map_or(false, |it| it == token) { return None; @@ -870,7 +932,7 @@ impl<'db> SemanticsImpl<'db> { return None; } let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; - let mcall = InFile::new(file_id, macro_call); + let mcall = InFile::new(expansion, macro_call); let file_id = match m_cache.get(&mcall) { Some(&it) => it, None => { @@ -888,17 +950,25 @@ impl<'db> SemanticsImpl<'db> { } }; 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())); + filter_duplicates(tokens, text_range); - process_expansion_for_token(&mut stack, file_id).or(file_id + process_expansion_for_token( + &mut stack, + file_id, + remap_kind | MacroInputKind::Bang, + ) + .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()) + process_expansion_for_token( + &mut stack, + arg.as_macro_file(), + remap_kind | MacroInputKind::Bang, + ) })) } + // derive or derive helper Either::Right(meta) => { // attribute we failed expansion for earlier, this might be a derive invocation // or derive helper attribute @@ -910,8 +980,8 @@ impl<'db> SemanticsImpl<'db> { // 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()), + InFile::new(expansion, &adt), + InFile::new(expansion, attr.clone()), ) .map(|(_, call_id, _)| call_id) }); @@ -927,7 +997,9 @@ impl<'db> SemanticsImpl<'db> { !text_range.contains_range(t.text_range()) }); return process_expansion_for_token( - &mut stack, file_id, + &mut stack, + file_id, + remap_kind | MacroInputKind::Derive, ); } None => Some(adt), @@ -945,31 +1017,33 @@ impl<'db> SemanticsImpl<'db> { ) } }?; - if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) { + 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 a builtin or a derive helper + // Not an attribute, nor a derive, so it's either an intert attribute 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(expansion).ast_id(&adt); let helpers = - def_map.derive_helpers_in_scope(InFile::new(file_id, id))?; + def_map.derive_helpers_in_scope(InFile::new(expansion, id))?; if !helpers.is_empty() { 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())); + filter_duplicates(tokens, text_range); } 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.as_macro_file(), + remap_kind | MacroInputKind::DeriveHelper, )); } res @@ -978,11 +1052,14 @@ impl<'db> SemanticsImpl<'db> { })() .is_none(); - if was_not_remapped && f(InFile::new(file_id, token)).is_break() { - break; + if was_not_remapped { + if let ControlFlow::Break(b) = f(remap_kind, InFile::new(expansion, token)) { + return Some(b); + } } } } + None } // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 9180d8dfcbb2..e4d347ef16bd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -1,11 +1,7 @@ use crate::{utils, AssistContext, Assists}; -use hir::DescendPreference; use ide_db::{ assists::{AssistId, AssistKind}, - syntax_helpers::{ - format_string::is_format_string, - format_string_exprs::{parse_format_exprs, Arg}, - }, + syntax_helpers::format_string_exprs::{parse_format_exprs, Arg}, }; use itertools::Itertools; use syntax::{ @@ -40,13 +36,7 @@ pub(crate) fn extract_expressions_from_format_string( let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; let tt_delimiter = tt.left_delimiter_token()?.kind(); - let expanded_t = ast::String::cast( - ctx.sema - .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()), - )?; - if !is_format_string(&expanded_t) { - return None; - } + let _ = ctx.sema.as_format_args_parts(&fmt_string)?; let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?; if extracted_args.is_empty() { 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 index 8ab5a6ede3bd..c104aa571894 100644 --- 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 @@ -31,6 +31,7 @@ pub fn with_placeholders(args: Vec) -> Vec { .collect() } +// FIXME Remove this, we have this information in the HIR now /// 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. /// diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index cad0d06eeab1..1304b057021b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -144,7 +144,7 @@ pub(crate) fn external_docs( kind if kind.is_trivia() => 0, _ => 1, })?; - let token = sema.descend_into_macros_single(DescendPreference::None, token); + let token = sema.descend_into_macros_single_exact(token); let node = token.parent()?; let definition = match_ast! { diff --git a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs index 5f6aaeaabb60..3d49082f2858 100644 --- a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs +++ b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs @@ -1,6 +1,6 @@ use std::iter::successors; -use hir::{DescendPreference, Semantics}; +use hir::Semantics; use ide_db::RootDatabase; use syntax::{ algo::{self, skip_trivia_token}, @@ -140,10 +140,8 @@ fn extend_tokens_from_range( // compute original mapped token range let extended = { - let fst_expanded = - sema.descend_into_macros_single(DescendPreference::None, first_token.clone()); - let lst_expanded = - sema.descend_into_macros_single(DescendPreference::None, last_token.clone()); + let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone()); + let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone()); let mut lca = algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?; lca = shallowest_node(&lca); @@ -157,7 +155,7 @@ fn extend_tokens_from_range( let validate = || { let extended = &extended; move |token: &SyntaxToken| -> bool { - let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone()); + let expanded = sema.descend_into_macros_single_exact(token.clone()); let parent = match expanded.parent() { Some(it) => it, None => return false, diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs index 2eff7796d548..e36c8ee2f3f7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs @@ -1,4 +1,4 @@ -use hir::{AsAssocItem, DescendPreference, Impl, Semantics}; +use hir::{AsAssocItem, Impl, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, @@ -10,7 +10,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // Feature: Go to Implementation // -// Navigates to the impl blocks of types. +// Navigates to the impl items of types. // // |=== // | Editor | Shortcut @@ -32,48 +32,55 @@ pub(crate) fn goto_implementation( _ => 0, })?; let range = original_token.text_range(); - let navs = - sema.descend_into_macros_single(DescendPreference::SameText, original_token) - .parent() - .and_then(ast::NameLike::cast) - .and_then(|node| match &node { - ast::NameLike::Name(name) => { - NameClass::classify(&sema, name).and_then(|class| match class { - NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), - NameClass::PatFieldShorthand { .. } => None, - }) - } - ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref) - .and_then(|class| match class { - NameRefClass::Definition(def) => Some(def), - NameRefClass::FieldShorthand { .. } - | NameRefClass::ExternCrateShorthand { .. } => None, - }), - ast::NameLike::Lifetime(_) => None, - }) - .and_then(|def| { - let navs = match def { - Definition::Trait(trait_) => impls_for_trait(&sema, trait_), - Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), - Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), - Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)), - Definition::Function(f) => { - let assoc = f.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.container_or_implemented_trait(sema.db)?; - impls_for_trait_item(&sema, trait_, name) + let navs = sema + .descend_into_macros_exact(original_token) + .iter() + .filter_map(|token| { + token + .parent() + .and_then(ast::NameLike::cast) + .and_then(|node| match &node { + ast::NameLike::Name(name) => { + NameClass::classify(&sema, name).and_then(|class| match class { + NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), + NameClass::PatFieldShorthand { .. } => None, + }) } - Definition::Const(c) => { - let assoc = c.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.container_or_implemented_trait(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - _ => return None, - }; - Some(navs) - }) - .unwrap_or_default(); + ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref) + .and_then(|class| match class { + NameRefClass::Definition(def) => Some(def), + NameRefClass::FieldShorthand { .. } + | NameRefClass::ExternCrateShorthand { .. } => None, + }), + ast::NameLike::Lifetime(_) => None, + }) + .and_then(|def| { + let navs = match def { + Definition::Trait(trait_) => impls_for_trait(&sema, trait_), + Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), + Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), + Definition::BuiltinType(builtin) => { + impls_for_ty(&sema, builtin.ty(sema.db)) + } + Definition::Function(f) => { + let assoc = f.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.container_or_implemented_trait(sema.db)?; + impls_for_trait_item(&sema, trait_, name) + } + Definition::Const(c) => { + let assoc = c.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.container_or_implemented_trait(sema.db)?; + impls_for_trait_item(&sema, trait_, name) + } + _ => return None, + }; + Some(navs) + }) + }) + .flatten() + .collect(); Some(RangeInfo { range, info: navs }) } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 6c646cf4ee1f..b04f5c8411e6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -6,7 +6,7 @@ mod tests; use std::{iter, ops::Not}; use either::Either; -use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics}; +use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics}; use ide_db::{ defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, @@ -178,29 +178,24 @@ fn hover_simple( return Some(RangeInfo::new(range, res)); } - let in_attr = original_token - .parent_ancestors() - .filter_map(ast::Item::cast) - .any(|item| sema.is_attr_macro_call(&item)) - && !matches!( - original_token.parent().and_then(ast::TokenTree::cast), - Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())) - ); - // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important - let descended = sema.descend_into_macros( - if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText }, - original_token.clone(), - ); + let mut descended = vec![]; + sema.descend_into_macros_ng(original_token.clone(), |_, token| { + descended.push(token.value); + }); let descended = || descended.iter(); - let result = descended() + // FIXME: WE should not try these step by step, instead to accommodate for macros we should run + // all of these in "parallel" and rank their results + let result = None // try lint hover - .find_map(|token| { - // FIXME: Definition should include known lints and the like instead of having this special case here - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - render::try_for_lint(&attr, token) + .or_else(|| { + descended().find_map(|token| { + // FIXME: Definition should include known lints and the like instead of having this special case here + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + render::try_for_lint(&attr, token) + }) }) // try definitions .or_else(|| { diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 3e3d5ef6e655..55afcb59bae7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -9,7 +9,7 @@ //! at the index that the match starts at and its tree parent is //! resolved to the search element definition, we get a reference. -use hir::{DescendPreference, PathResolution, Semantics}; +use hir::{PathResolution, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, search::{ReferenceCategory, SearchScope, UsageSearchResult}, @@ -149,7 +149,7 @@ pub(crate) fn find_defs<'a>( } Some( - sema.descend_into_macros(DescendPreference::SameText, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|it| ast::NameLike::cast(it.parent()?)) .filter_map(move |name_like| { diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index 9d3b8c6ebd19..516f64959cef 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -4,10 +4,7 @@ use std::collections::BTreeSet; use either::Either; -use hir::{ - AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, - Trait, -}; +use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait}; use ide_db::{ active_parameter::{callable_for_node, generic_def_for_node}, documentation::{Documentation, HasDocs}, @@ -82,7 +79,7 @@ pub(crate) fn signature_help( // if the cursor is sandwiched between two space tokens and the call is unclosed // this prevents us from leaving the CallExpression .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; - let token = sema.descend_into_macros_single(DescendPreference::None, token); + let token = sema.descend_into_macros_single_exact(token); let edition = sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index e082fcdc7658..94df794ccbbd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -410,7 +410,8 @@ fn traverse( }) .unwrap() } else { - sema.descend_into_macros_single(DescendPreference::SameKind, token) + // FIXME: We should probably rank the tokens and find the most suitable? + sema.descend_into_macros_single_exact(token) }; match token.parent().and_then(ast::NameLike::cast) { // Remap the token into the wrapping single token nodes diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index 17411fefbd97..d6b7012a1f99 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -58,7 +58,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } def_fn! { - fn bar() -> u32 { + fn bar() -> u32 { 100 } } @@ -100,7 +100,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd }; } -include!(concat!("foo/", "foo.rs")); +include!(concat!("foo/", "foo.rs")); struct S<T>(T); fn main() { From d893dcc43fec3bb12ca0ca6b77d541a355296b1c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 12:39:53 +0200 Subject: [PATCH 2/9] Drop MacroInputKind --- .../rust-analyzer/crates/hir/src/semantics.rs | 96 +++++-------------- .../rust-analyzer/crates/ide/src/hover.rs | 2 +- 2 files changed, 24 insertions(+), 74 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index a8151e70a108..6e9fc3996ccd 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -56,39 +56,6 @@ pub enum DescendPreference { None, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MacroInputKind { - Root, - Bang, - AttrInput, - AttrTarget, - Derive, - DeriveHelper, - // Include, -} -impl MacroInputKind { - pub fn is_tt(self) -> bool { - matches!( - self, - MacroInputKind::AttrInput - | MacroInputKind::Bang - | MacroInputKind::DeriveHelper - | MacroInputKind::Derive - ) - } -} - -impl ops::BitOr for MacroInputKind { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { - match self { - Self::Root => rhs, - _ => self, - } - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { /// An item @@ -586,7 +553,7 @@ impl<'db> SemanticsImpl<'db> { string: &ast::String, ) -> Option)>> { let quote = string.open_quote_text_range()?; - self.descend_into_macros_ng_b(string.syntax().clone(), |kind, token| { + self.descend_into_macros_ng_b(string.syntax().clone(), |token| { (|| { let token = token.value; let string = ast::String::cast(token)?; @@ -613,7 +580,7 @@ impl<'db> SemanticsImpl<'db> { ) -> Option<(TextRange, Option)> { let original_string = ast::String::cast(original_token.clone())?; let quote = original_string.open_quote_text_range()?; - self.descend_into_macros_ng_b(original_token.clone(), |kind, token| { + self.descend_into_macros_ng_b(original_token.clone(), |token| { (|| { let token = token.value; self.resolve_offset_in_format_args( @@ -656,7 +623,7 @@ impl<'db> SemanticsImpl<'db> { if first == last { // node is just the token, so descend the token - self.descend_into_macros_impl(first, &mut |_kind, InFile { value, .. }| { + self.descend_into_macros_impl(first, &mut |InFile { value, .. }| { if let Some(node) = value .parent_ancestors() .take_while(|it| it.text_range() == value.text_range()) @@ -669,7 +636,7 @@ 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(first, &mut |_kind, token| { + self.descend_into_macros_impl(first, &mut |token| { scratch.push(token); CONTINUE_NO_BREAKS }); @@ -677,7 +644,7 @@ impl<'db> SemanticsImpl<'db> { let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( last, - &mut |_kind, InFile { value: last, file_id: last_fid }| { + &mut |InFile { value: last, file_id: last_fid }| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { if let Some(p) = first.parent() { @@ -727,7 +694,7 @@ impl<'db> SemanticsImpl<'db> { let mut res = smallvec![]; self.descend_into_macros_impl::( token.clone(), - &mut |kind, InFile { value, .. }| { + &mut |InFile { value, .. }| { let is_a_match = match mode { // Dp::SameText(text) => value.text() == text, Dp::SameKind(preferred_kind) => { @@ -753,10 +720,10 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros_ng( &self, token: SyntaxToken, - mut cb: impl FnMut(MacroInputKind, InFile), + mut cb: impl FnMut(InFile), ) { - self.descend_into_macros_impl(token.clone(), &mut |kind, t| { - cb(kind, t); + self.descend_into_macros_impl(token.clone(), &mut |t| { + cb(t); CONTINUE_NO_BREAKS }); } @@ -764,9 +731,9 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros_ng_b( &self, token: SyntaxToken, - mut cb: impl FnMut(MacroInputKind, InFile) -> ControlFlow, + mut cb: impl FnMut(InFile) -> ControlFlow, ) -> Option { - self.descend_into_macros_impl(token.clone(), &mut |kind, t| cb(kind, t)) + self.descend_into_macros_impl(token.clone(), &mut |t| cb(t)) } /// Descends the token into expansions, returning the tokens that matches the input @@ -776,7 +743,7 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); - self.descend_into_macros_ng(token.clone(), |m_kind, InFile { value, file_id }| { + self.descend_into_macros_ng(token.clone(), |InFile { value, file_id: _ }| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); @@ -796,7 +763,7 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); - self.descend_into_macros_ng_b(token.clone(), |m_kind, InFile { value, file_id }| { + self.descend_into_macros_ng_b(token.clone(), |InFile { value, file_id: _ }| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); @@ -812,7 +779,7 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros_impl( &self, token: SyntaxToken, - f: &mut dyn FnMut(MacroInputKind, InFile) -> ControlFlow, + f: &mut dyn FnMut(InFile) -> ControlFlow, ) -> Option { let _p = tracing::info_span!("descend_into_macros_impl").entered(); let (sa, span, file_id) = @@ -832,11 +799,10 @@ impl<'db> SemanticsImpl<'db> { // 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![(file_id, MacroInputKind::Root, smallvec![token])]; + let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; // 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, remap_kind| { + let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { Some( ctx.cache @@ -858,7 +824,7 @@ impl<'db> SemanticsImpl<'db> { // 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), remap_kind, mapped_tokens)); + stack.push((HirFileId::from(file_id), mapped_tokens)); res }; @@ -868,7 +834,7 @@ impl<'db> SemanticsImpl<'db> { tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range())) }; - while let Some((expansion, remap_kind, ref mut tokens)) = stack.pop() { + while let Some((expansion, ref mut tokens)) = stack.pop() { while let Some(token) = tokens.pop() { let was_not_remapped = (|| { // First expand into attribute invocations @@ -909,11 +875,7 @@ impl<'db> SemanticsImpl<'db> { .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, - file_id, - remap_kind | MacroInputKind::AttrTarget, - ); + return process_expansion_for_token(&mut stack, file_id); } // Then check for token trees, that means we are either in a function-like macro or @@ -952,20 +914,11 @@ impl<'db> SemanticsImpl<'db> { let text_range = tt.syntax().text_range(); filter_duplicates(tokens, text_range); - process_expansion_for_token( - &mut stack, - file_id, - remap_kind | MacroInputKind::Bang, - ) - .or(file_id + 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(), - remap_kind | MacroInputKind::Bang, - ) + process_expansion_for_token(&mut stack, arg.as_macro_file()) })) } // derive or derive helper @@ -997,9 +950,7 @@ impl<'db> SemanticsImpl<'db> { !text_range.contains_range(t.text_range()) }); return process_expansion_for_token( - &mut stack, - file_id, - remap_kind | MacroInputKind::Derive, + &mut stack, file_id, ); } None => Some(adt), @@ -1043,7 +994,6 @@ impl<'db> SemanticsImpl<'db> { res = res.or(process_expansion_for_token( &mut stack, derive.as_macro_file(), - remap_kind | MacroInputKind::DeriveHelper, )); } res @@ -1053,7 +1003,7 @@ impl<'db> SemanticsImpl<'db> { .is_none(); if was_not_remapped { - if let ControlFlow::Break(b) = f(remap_kind, InFile::new(expansion, token)) { + if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) { return Some(b); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index b04f5c8411e6..5443b505756a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -181,7 +181,7 @@ fn hover_simple( // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important let mut descended = vec![]; - sema.descend_into_macros_ng(original_token.clone(), |_, token| { + sema.descend_into_macros_ng(original_token.clone(), |token| { descended.push(token.value); }); let descended = || descended.iter(); From 1179cbb258483dbccec6b0a221a8c9be88999de7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 15:57:19 +0200 Subject: [PATCH 3/9] Remove DescendPreference::SameKind --- .../rust-analyzer/crates/hir/src/semantics.rs | 21 +----- .../rust-analyzer/crates/ide/src/hover.rs | 2 +- .../crates/ide/src/syntax_highlighting.rs | 65 ++++++++++++++----- .../test_data/highlight_macros.html | 2 +- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 6e9fc3996ccd..d96f8777e0f1 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -52,7 +52,6 @@ use crate::{ const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); pub enum DescendPreference { - SameKind, None, } @@ -675,20 +674,9 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, ) -> SmallVec<[SyntaxToken; 1]> { enum Dp { - // SameText(&'t str), - SameKind(SyntaxKind), None, } - let fetch_kind = |token: &SyntaxToken| match token.parent() { - Some(node) => match node.kind() { - kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind, - _ => token.kind(), - }, - None => token.kind(), - }; let mode = match mode { - // DescendPreference::SameText => Dp::SameText(token.text()), - DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)), DescendPreference::None => Dp::None, }; let mut res = smallvec![]; @@ -696,13 +684,6 @@ impl<'db> SemanticsImpl<'db> { token.clone(), &mut |InFile { value, .. }| { let is_a_match = match mode { - // Dp::SameText(text) => value.text() == text, - Dp::SameKind(preferred_kind) => { - let kind = fetch_kind(&value); - kind == preferred_kind - // special case for derive macros - || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) - } Dp::None => true, }; if is_a_match { @@ -733,7 +714,7 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, mut cb: impl FnMut(InFile) -> ControlFlow, ) -> Option { - self.descend_into_macros_impl(token.clone(), &mut |t| cb(t)) + self.descend_into_macros_impl(token.clone(), &mut cb) } /// Descends the token into expansions, returning the tokens that matches the input diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 5443b505756a..77c56b3b3ac7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -186,7 +186,7 @@ fn hover_simple( }); let descended = || descended.iter(); - // FIXME: WE should not try these step by step, instead to accommodate for macros we should run + // TODO: WE should not try these step by step, instead to accommodate for macros we should run // all of these in "parallel" and rank their results let result = None // try lint hover diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 94df794ccbbd..a4dc91b2fcc1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -13,7 +13,9 @@ mod html; #[cfg(test)] mod tests; -use hir::{DescendPreference, Name, Semantics}; +use std::ops::ControlFlow; + +use hir::{Name, Semantics}; use ide_db::{FxHashMap, RootDatabase, SymbolKind}; use span::EditionedFileId; use syntax::{ @@ -399,20 +401,53 @@ fn traverse( // Attempt to descend tokens into macro-calls. let res = match element { NodeOrToken::Token(token) if token.kind() != COMMENT => { - let token = if token.kind() == STRING { - // for strings, try to prefer a string that has not been lost in a token - // tree - // FIXME: This should be done for everything, but check perf first - sema.descend_into_macros(DescendPreference::SameKind, token) - .into_iter() - .max_by_key(|it| { - it.parent().map_or(false, |it| it.kind() != TOKEN_TREE) - }) - .unwrap() - } else { - // FIXME: We should probably rank the tokens and find the most suitable? - sema.descend_into_macros_single_exact(token) - }; + let kind = token.kind(); + let text = token.text(); + let ident_kind = kind.is_any_identifier(); + + let mut t = None; + let mut r = 0; + // FIXME: Add an extra API that takes the file id of this. That is a simple way + // to prevent us constantly walking up the tree to fetch the file + sema.descend_into_macros_ng_b(token.clone(), |tok| { + let tok = tok.value; + let tok_kind = tok.kind(); + + let exact_same_kind = tok_kind == kind; + let both_idents = + exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); + let same_text = tok.text() == text; + // anything that mapped into a token tree has likely no semantic information + let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); + let my_rank = (both_idents as usize) + | ((exact_same_kind as usize) << 1) + | ((same_text as usize) << 2) + | ((no_tt_parent as usize) << 3); + + if my_rank > 0b1110 { + // a rank of 0b1110 means that we have found a maximally interesting + // token so stop early. + t = Some(tok); + return ControlFlow::Break(()); + } + + // r = r.max(my_rank); + // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok)); + match &mut t { + Some(prev) if r < my_rank => { + *prev = tok; + r = my_rank; + } + Some(_) => (), + None => { + r = my_rank; + t = Some(tok) + } + } + ControlFlow::Continue(()) + }); + + let token = t.unwrap_or(token); match token.parent().and_then(ast::NameLike::cast) { // Remap the token into the wrapping single token nodes Some(parent) => match (token.kind(), parent.syntax().kind()) { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index d6b7012a1f99..196552020ab6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -58,7 +58,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } def_fn! { - fn bar() -> u32 { + fn bar() -> u32 { 100 } } From 8e82e44677ac08ddcb40e188034177ec745120db Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 16:18:01 +0200 Subject: [PATCH 4/9] Fully remove old macro descension API --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 3 +- .../rust-analyzer/crates/hir/src/semantics.rs | 54 ++++++------------ .../src/handlers/extract_function.rs | 6 +- .../crates/ide-db/src/helpers.rs | 5 +- .../rust-analyzer/crates/ide-db/src/search.rs | 8 +-- .../crates/ide/src/call_hierarchy.rs | 4 +- .../rust-analyzer/crates/ide/src/doc_links.rs | 7 +-- .../crates/ide/src/expand_macro.rs | 55 ++++++++----------- .../crates/ide/src/goto_declaration.rs | 4 +- .../crates/ide/src/goto_definition.rs | 11 ++-- .../crates/ide/src/goto_type_definition.rs | 4 +- .../crates/ide/src/highlight_related.rs | 4 +- .../rust-analyzer/crates/ide/src/moniker.rs | 4 +- 13 files changed, 66 insertions(+), 103 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 57f810c7c79c..6e26af55af82 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -93,8 +93,7 @@ pub use crate::{ diagnostics::*, has_source::HasSource, semantics::{ - DescendPreference, PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, - VisibleTraits, + PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits, }, }; pub use hir_ty::method_resolution::TyFingerprint; diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index d96f8777e0f1..f9346af6255b 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -51,10 +51,6 @@ use crate::{ const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); -pub enum DescendPreference { - None, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { /// An item @@ -183,6 +179,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*, /// descend it and find again + // FIXME: Rethink this API pub fn find_node_at_offset_with_descend( &self, node: &SyntaxNode, @@ -191,8 +188,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast) } - /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*, + /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribte macro call, /// descend it and find again + // FIXME: Rethink this API pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>( &'slf self, node: &SyntaxNode, @@ -666,38 +664,6 @@ impl<'db> SemanticsImpl<'db> { res } - /// Descend the token into its macro call if it is part of one, returning the tokens in the - /// expansion that it is associated with. - pub fn descend_into_macros( - &self, - mode: DescendPreference, - token: SyntaxToken, - ) -> SmallVec<[SyntaxToken; 1]> { - enum Dp { - None, - } - let mode = match mode { - DescendPreference::None => Dp::None, - }; - let mut res = smallvec![]; - self.descend_into_macros_impl::( - token.clone(), - &mut |InFile { value, .. }| { - let is_a_match = match mode { - Dp::None => true, - }; - if is_a_match { - res.push(value); - } - ControlFlow::Continue(()) - }, - ); - if res.is_empty() { - res.push(token); - } - res - } - pub fn descend_into_macros_ng( &self, token: SyntaxToken, @@ -709,6 +675,18 @@ impl<'db> SemanticsImpl<'db> { }); } + pub fn descend_into_macros_ng_v(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + let mut res = smallvec![]; + self.descend_into_macros_impl(token.clone(), &mut |t| { + res.push(t.value); + CONTINUE_NO_BREAKS + }); + if res.is_empty() { + res.push(token); + } + res + } + pub fn descend_into_macros_ng_b( &self, token: SyntaxToken, @@ -1003,7 +981,7 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> impl Iterator + '_> + '_ { node.token_at_offset(offset) - .map(move |token| self.descend_into_macros(DescendPreference::None, token)) + .map(move |token| self.descend_into_macros_exact(token)) .map(|descendants| { descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it)) }) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index cfcb09b9f7d9..fd379cb88daa 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -3,8 +3,8 @@ use std::{iter, ops::RangeInclusive}; use ast::make; use either::Either; use hir::{ - DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, - PathResolution, Semantics, TypeInfo, TypeParam, + HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics, + TypeInfo, TypeParam, }; use ide_db::{ defs::{Definition, NameRefClass}, @@ -834,7 +834,7 @@ impl FunctionBody { .descendants_with_tokens() .filter_map(SyntaxElement::into_token) .filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self])) - .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t)) + .flat_map(|t| sema.descend_into_macros_exact(t)) .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast))); } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs index 63c09af3d687..84fa58d743bb 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use base_db::SourceRootDatabase; -use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics}; +use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics}; use span::{Edition, FileId}; use syntax::{ ast::{self, make}, @@ -112,11 +112,12 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool { !db.source_root(source_root_id).is_library } +// FIXME: This is a weird function pub fn get_definition( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, ) -> Option { - for token in sema.descend_into_macros(DescendPreference::None, token) { + for token in sema.descend_into_macros_exact(token) { let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); if let Some(&[x]) = def.as_deref() { return Some(x); 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 7319b7ac02da..197c327ee4ed 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -9,8 +9,8 @@ use std::mem; use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase}; use hir::{ - sym, AsAssocItem, DefWithBody, DescendPreference, FileRange, HasAttrs, HasSource, HirFileIdExt, - InFile, InRealFile, ModuleSource, PathResolution, Semantics, Visibility, + sym, AsAssocItem, DefWithBody, FileRange, HasAttrs, HasSource, HirFileIdExt, InFile, + InRealFile, ModuleSource, PathResolution, Semantics, Visibility, }; use memchr::memmem::Finder; use parser::SyntaxKind; @@ -549,9 +549,7 @@ impl<'a> FindUsages<'a> { // every textual hit. That function is notoriously // expensive even for things that do not get down mapped // into macros. - sema.descend_into_macros(DescendPreference::None, token) - .into_iter() - .filter_map(|it| it.parent()) + sema.descend_into_macros_exact(token).into_iter().filter_map(|it| it.parent()) }) }; diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index 87093104852f..155259a13804 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -2,7 +2,7 @@ use std::iter; -use hir::{DescendPreference, Semantics}; +use hir::Semantics; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, @@ -86,7 +86,7 @@ pub(crate) fn outgoing_calls( })?; let mut calls = CallLocations::default(); - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast)) .filter_map(|item| match item { diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index 1304b057021b..1afc7e3c6183 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -10,10 +10,7 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions use stdx::format_to; use url::Url; -use hir::{ - db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference, - HasAttrs, -}; +use hir::{db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs}; use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase}, defs::{Definition, NameClass, NameRefClass}, @@ -289,7 +286,7 @@ impl DocCommentToken { let original_start = doc_token.text_range().start(); let relative_comment_offset = offset - original_start - prefix_len; - sema.descend_into_macros(DescendPreference::None, doc_token).into_iter().find_map(|t| { + sema.descend_into_macros_ng_v(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len) = match_ast! { match t { ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?), diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index 9788c0154423..a939ed214ad8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -1,4 +1,4 @@ -use hir::{DescendPreference, InFile, MacroFileIdExt, Semantics}; +use hir::{InFile, MacroFileIdExt, Semantics}; use ide_db::{ helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId, RootDatabase, @@ -41,37 +41,30 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // struct Bar; // ``` - let derive = sema - .descend_into_macros(DescendPreference::None, tok.clone()) - .into_iter() - .find_map(|descended| { - let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?; - if !macro_file.is_derive_attr_pseudo_expansion(db) { - return None; - } + let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| { + let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?; + if !macro_file.is_derive_attr_pseudo_expansion(db) { + return None; + } - let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); - // up map out of the #[derive] expansion - let InFile { file_id, value: tokens } = - hir::InMacroFile::new(macro_file, descended).upmap_once(db); - let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?; - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - let expansions = sema.expand_derive_macro(&attr)?; - let idx = attr - .token_tree()? - .token_trees_and_tokens() - .filter_map(NodeOrToken::into_token) - .take_while(|it| it != &token) - .filter(|it| it.kind() == T![,]) - .count(); - let expansion = format( - db, - SyntaxKind::MACRO_ITEMS, - position.file_id, - expansions.get(idx).cloned()?, - ); - Some(ExpandedMacro { name, expansion }) - }); + let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); + // up map out of the #[derive] expansion + let InFile { file_id, value: tokens } = + hir::InMacroFile::new(macro_file, descended).upmap_once(db); + let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?; + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + let expansions = sema.expand_derive_macro(&attr)?; + let idx = attr + .token_tree()? + .token_trees_and_tokens() + .filter_map(NodeOrToken::into_token) + .take_while(|it| it != &token) + .filter(|it| it.kind() == T![,]) + .count(); + let expansion = + format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?); + Some(ExpandedMacro { name, expansion }) + }); if derive.is_some() { return derive; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index 6076de54ebaf..dd71bcf050ee 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -1,4 +1,4 @@ -use hir::{AsAssocItem, DescendPreference, Semantics}; +use hir::{AsAssocItem, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, RootDatabase, @@ -29,7 +29,7 @@ pub(crate) fn goto_declaration( .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?; let range = original_token.text_range(); let info: Vec = sema - .descend_into_macros(DescendPreference::None, original_token) + .descend_into_macros_ng_v(original_token) .iter() .filter_map(|token| { let parent = token.parent()?; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 6a6fbcb9a6b7..9fe1488e3682 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -5,10 +5,7 @@ use crate::{ navigation_target::{self, ToNav}, FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, }; -use hir::{ - AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef, - Semantics, -}; +use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileLoader, SourceDatabase}, defs::{Definition, IdentClass}, @@ -86,7 +83,7 @@ pub(crate) fn goto_definition( } let navs = sema - .descend_into_macros(DescendPreference::None, original_token.clone()) + .descend_into_macros_ng_v(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; @@ -251,7 +248,7 @@ pub(crate) fn find_fn_or_blocks( None }; - sema.descend_into_macros(DescendPreference::None, token.clone()) + sema.descend_into_macros_ng_v(token.clone()) .into_iter() .filter_map(find_ancestors) .collect_vec() @@ -369,7 +366,7 @@ pub(crate) fn find_loops( None }; - sema.descend_into_macros(DescendPreference::None, token.clone()) + sema.descend_into_macros_ng_v(token.clone()) .into_iter() .filter_map(find_ancestors) .collect_vec() diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index f75b8fb7d022..da2be7873205 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -1,4 +1,4 @@ -use hir::{DescendPreference, GenericParam}; +use hir::GenericParam; use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase}; use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T}; @@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition( } let range = token.text_range(); - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros_ng_v(token) .into_iter() .filter_map(|token| { let ty = sema diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index a88261df1063..5348e855be4b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -1,6 +1,6 @@ use std::iter; -use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics}; +use hir::{db, FilePosition, FileRange, HirFileId, InFile, Semantics}; use ide_db::{ defs::{Definition, IdentClass}, helpers::pick_best_token, @@ -542,7 +542,7 @@ fn cover_range(r0: Option, r1: Option) -> Option, token: SyntaxToken) -> FxHashSet { - sema.descend_into_macros(DescendPreference::None, token) + sema.descend_into_macros_exact(token) .into_iter() .filter_map(|token| IdentClass::classify_token(sema, &token)) .flat_map(IdentClass::definitions_no_ops) diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs index 7b5fd651e3d7..4be1b570981f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs +++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs @@ -3,7 +3,7 @@ use core::fmt; -use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, DescendPreference, MacroKind, Semantics}; +use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics}; use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin}, defs::{Definition, IdentClass}, @@ -154,7 +154,7 @@ pub(crate) fn moniker( }); } let navs = sema - .descend_into_macros(DescendPreference::None, original_token.clone()) + .descend_into_macros_exact(original_token.clone()) .into_iter() .filter_map(|token| { IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| { From 4d614444b950967db2f1d9b18fc144117df4e318 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 16:18:56 +0200 Subject: [PATCH 5/9] Rename macro descension functions --- .../rust-analyzer/crates/hir/src/semantics.rs | 16 ++++++++-------- .../rust-analyzer/crates/ide/src/doc_links.rs | 2 +- .../crates/ide/src/goto_declaration.rs | 2 +- .../crates/ide/src/goto_definition.rs | 9 +++------ .../crates/ide/src/goto_type_definition.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/hover.rs | 2 +- .../crates/ide/src/syntax_highlighting.rs | 2 +- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index f9346af6255b..ec7a89c64320 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -188,7 +188,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast) } - /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribte macro call, + /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribute macro call, /// descend it and find again // FIXME: Rethink this API pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>( @@ -550,7 +550,7 @@ impl<'db> SemanticsImpl<'db> { string: &ast::String, ) -> Option)>> { let quote = string.open_quote_text_range()?; - self.descend_into_macros_ng_b(string.syntax().clone(), |token| { + self.descend_into_macros_breakable(string.syntax().clone(), |token| { (|| { let token = token.value; let string = ast::String::cast(token)?; @@ -577,7 +577,7 @@ impl<'db> SemanticsImpl<'db> { ) -> Option<(TextRange, Option)> { let original_string = ast::String::cast(original_token.clone())?; let quote = original_string.open_quote_text_range()?; - self.descend_into_macros_ng_b(original_token.clone(), |token| { + self.descend_into_macros_breakable(original_token.clone(), |token| { (|| { let token = token.value; self.resolve_offset_in_format_args( @@ -664,7 +664,7 @@ impl<'db> SemanticsImpl<'db> { res } - pub fn descend_into_macros_ng( + pub fn descend_into_macros_cb( &self, token: SyntaxToken, mut cb: impl FnMut(InFile), @@ -675,7 +675,7 @@ impl<'db> SemanticsImpl<'db> { }); } - pub fn descend_into_macros_ng_v(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; self.descend_into_macros_impl(token.clone(), &mut |t| { res.push(t.value); @@ -687,7 +687,7 @@ impl<'db> SemanticsImpl<'db> { res } - pub fn descend_into_macros_ng_b( + pub fn descend_into_macros_breakable( &self, token: SyntaxToken, mut cb: impl FnMut(InFile) -> ControlFlow, @@ -702,7 +702,7 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); - self.descend_into_macros_ng(token.clone(), |InFile { value, file_id: _ }| { + self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); @@ -722,7 +722,7 @@ impl<'db> SemanticsImpl<'db> { let text = token.text(); let kind = token.kind(); - self.descend_into_macros_ng_b(token.clone(), |InFile { value, file_id: _ }| { + self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index 1afc7e3c6183..925ae620231d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -286,7 +286,7 @@ impl DocCommentToken { let original_start = doc_token.text_range().start(); let relative_comment_offset = offset - original_start - prefix_len; - sema.descend_into_macros_ng_v(doc_token).into_iter().find_map(|t| { + sema.descend_into_macros(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len) = match_ast! { match t { ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?), diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index dd71bcf050ee..6ae9dde84be5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -29,7 +29,7 @@ pub(crate) fn goto_declaration( .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?; let range = original_token.text_range(); let info: Vec = sema - .descend_into_macros_ng_v(original_token) + .descend_into_macros(original_token) .iter() .filter_map(|token| { let parent = token.parent()?; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 9fe1488e3682..5769f2cabc75 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -83,7 +83,7 @@ pub(crate) fn goto_definition( } let navs = sema - .descend_into_macros_ng_v(original_token.clone()) + .descend_into_macros(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; @@ -248,10 +248,7 @@ pub(crate) fn find_fn_or_blocks( None }; - sema.descend_into_macros_ng_v(token.clone()) - .into_iter() - .filter_map(find_ancestors) - .collect_vec() + sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec() } fn nav_for_exit_points( @@ -366,7 +363,7 @@ pub(crate) fn find_loops( None }; - sema.descend_into_macros_ng_v(token.clone()) + sema.descend_into_macros(token.clone()) .into_iter() .filter_map(find_ancestors) .collect_vec() diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index da2be7873205..ca04b7bb5a9b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition( } let range = token.text_range(); - sema.descend_into_macros_ng_v(token) + sema.descend_into_macros(token) .into_iter() .filter_map(|token| { let ty = sema diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 77c56b3b3ac7..d76d9afc1858 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -181,7 +181,7 @@ fn hover_simple( // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important let mut descended = vec![]; - sema.descend_into_macros_ng(original_token.clone(), |token| { + sema.descend_into_macros_cb(original_token.clone(), |token| { descended.push(token.value); }); let descended = || descended.iter(); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index a4dc91b2fcc1..333f97c27439 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -409,7 +409,7 @@ fn traverse( let mut r = 0; // FIXME: Add an extra API that takes the file id of this. That is a simple way // to prevent us constantly walking up the tree to fetch the file - sema.descend_into_macros_ng_b(token.clone(), |tok| { + sema.descend_into_macros_breakable(token.clone(), |tok| { let tok = tok.value; let tok_kind = tok.kind(); From d79999aaa00679bfc581d30858d703b5b959137c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 16:45:37 +0200 Subject: [PATCH 6/9] Thread file id through descension API for semantic highlighting --- .../crates/hir-expand/src/files.rs | 9 ++ .../rust-analyzer/crates/hir/src/semantics.rs | 107 +++++++++++------- .../crates/ide/src/syntax_highlighting.rs | 72 ++++++------ 3 files changed, 115 insertions(+), 73 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs index 20f484f672af..d41f69812ee6 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs @@ -461,3 +461,12 @@ impl InFile { Some(InRealFile::new(file_id, value)) } } + +impl InFile { + pub fn into_real_file(self) -> Result, InFile> { + match self.file_id.repr() { + HirFileIdRepr::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }), + HirFileIdRepr::MacroFile(_) => Err(self), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index ec7a89c64320..3d6c98504308 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -550,7 +550,9 @@ impl<'db> SemanticsImpl<'db> { string: &ast::String, ) -> Option)>> { let quote = string.open_quote_text_range()?; - self.descend_into_macros_breakable(string.syntax().clone(), |token| { + + let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?; + self.descend_into_macros_breakable(token, |token| { (|| { let token = token.value; let string = ast::String::cast(token)?; @@ -576,8 +578,9 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> Option<(TextRange, Option)> { let original_string = ast::String::cast(original_token.clone())?; + let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?; let quote = original_string.open_quote_text_range()?; - self.descend_into_macros_breakable(original_token.clone(), |token| { + self.descend_into_macros_breakable(original_token, |token| { (|| { let token = token.value; self.resolve_offset_in_format_args( @@ -617,30 +620,37 @@ impl<'db> SemanticsImpl<'db> { Some(it) => it, None => return res, }; + let file = self.find_file(node.syntax()); + let Some(file_id) = file.file_id.file_id() else { + return res; + }; if first == last { // node is just the token, so descend the token - self.descend_into_macros_impl(first, &mut |InFile { value, .. }| { - if let Some(node) = value - .parent_ancestors() - .take_while(|it| it.text_range() == value.text_range()) - .find_map(N::cast) - { - res.push(node) - } - CONTINUE_NO_BREAKS - }); + self.descend_into_macros_impl( + InRealFile::new(file_id, first), + &mut |InFile { value, .. }| { + if let Some(node) = value + .parent_ancestors() + .take_while(|it| it.text_range() == value.text_range()) + .find_map(N::cast) + { + res.push(node) + } + CONTINUE_NO_BREAKS + }, + ); } 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(first, &mut |token| { + self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| { scratch.push(token); CONTINUE_NO_BREAKS }); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( - last, + InRealFile::new(file_id, last), &mut |InFile { value: last, file_id: last_fid }| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { @@ -669,18 +679,22 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, mut cb: impl FnMut(InFile), ) { - self.descend_into_macros_impl(token.clone(), &mut |t| { - cb(t); - CONTINUE_NO_BREAKS - }); + if let Ok(token) = self.wrap_token_infile(token).into_real_file() { + self.descend_into_macros_impl(token, &mut |t| { + cb(t); + CONTINUE_NO_BREAKS + }); + } } pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - self.descend_into_macros_impl(token.clone(), &mut |t| { - res.push(t.value); - CONTINUE_NO_BREAKS - }); + if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { + self.descend_into_macros_impl(token, &mut |t| { + res.push(t.value); + CONTINUE_NO_BREAKS + }); + } if res.is_empty() { res.push(token); } @@ -689,7 +703,7 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros_breakable( &self, - token: SyntaxToken, + token: InRealFile, mut cb: impl FnMut(InFile) -> ControlFlow, ) -> Option { self.descend_into_macros_impl(token.clone(), &mut cb) @@ -721,28 +735,36 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken { let text = token.text(); let kind = token.kind(); - - self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| { - let mapped_kind = value.kind(); - let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); - let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); - if matches { - ControlFlow::Break(value) - } else { - ControlFlow::Continue(()) - } - }) + if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { + self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| { + let mapped_kind = value.kind(); + let any_ident_match = + || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); + if matches { + ControlFlow::Break(value) + } else { + ControlFlow::Continue(()) + } + }) + } else { + None + } .unwrap_or(token) } fn descend_into_macros_impl( &self, - token: SyntaxToken, + InRealFile { value: token, file_id }: InRealFile, f: &mut dyn FnMut(InFile) -> ControlFlow, ) -> Option { let _p = tracing::info_span!("descend_into_macros_impl").entered(); - let (sa, span, file_id) = - token.parent().and_then(|parent| self.analyze_no_infer(&parent)).and_then(|sa| { + let (sa, span, file_id) = token + .parent() + .and_then(|parent| { + self.analyze_impl(InRealFile::new(file_id, &parent).into(), None, false) + }) + .and_then(|sa| { let file_id = sa.file_id.file_id()?; Some(( sa, @@ -1400,11 +1422,13 @@ impl<'db> SemanticsImpl<'db> { /// Returns none if the file of the node is not part of a crate. fn analyze(&self, node: &SyntaxNode) -> Option { + let node = self.find_file(node); self.analyze_impl(node, None, true) } /// Returns none if the file of the node is not part of a crate. fn analyze_no_infer(&self, node: &SyntaxNode) -> Option { + let node = self.find_file(node); self.analyze_impl(node, None, false) } @@ -1413,17 +1437,17 @@ impl<'db> SemanticsImpl<'db> { node: &SyntaxNode, offset: TextSize, ) -> Option { + let node = self.find_file(node); self.analyze_impl(node, Some(offset), false) } fn analyze_impl( &self, - node: &SyntaxNode, + node: InFile<&SyntaxNode>, offset: Option, infer_body: bool, ) -> Option { let _p = tracing::info_span!("SemanticsImpl::analyze_impl").entered(); - let node = self.find_file(node); let container = self.with_ctx(|ctx| ctx.find_container(node))?; @@ -1468,6 +1492,11 @@ impl<'db> SemanticsImpl<'db> { InFile::new(file_id, node) } + fn wrap_token_infile(&self, token: SyntaxToken) -> InFile { + let InFile { file_id, .. } = self.find_file(&token.parent().unwrap()); + InFile::new(file_id, token) + } + /// Wraps the node in a [`InFile`] with the file id it belongs to. fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> { let root_node = find_root(node); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 333f97c27439..bfab5ceb1291 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -15,7 +15,7 @@ mod tests; use std::ops::ControlFlow; -use hir::{Name, Semantics}; +use hir::{InRealFile, Name, Semantics}; use ide_db::{FxHashMap, RootDatabase, SymbolKind}; use span::EditionedFileId; use syntax::{ @@ -409,43 +409,47 @@ fn traverse( let mut r = 0; // FIXME: Add an extra API that takes the file id of this. That is a simple way // to prevent us constantly walking up the tree to fetch the file - sema.descend_into_macros_breakable(token.clone(), |tok| { - let tok = tok.value; - let tok_kind = tok.kind(); + sema.descend_into_macros_breakable( + InRealFile::new(file_id, token.clone()), + |tok| { + let tok = tok.value; + let tok_kind = tok.kind(); - let exact_same_kind = tok_kind == kind; - let both_idents = - exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); - let same_text = tok.text() == text; - // anything that mapped into a token tree has likely no semantic information - let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); - let my_rank = (both_idents as usize) - | ((exact_same_kind as usize) << 1) - | ((same_text as usize) << 2) - | ((no_tt_parent as usize) << 3); + let exact_same_kind = tok_kind == kind; + let both_idents = + exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); + let same_text = tok.text() == text; + // anything that mapped into a token tree has likely no semantic information + let no_tt_parent = + tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); + let my_rank = (both_idents as usize) + | ((exact_same_kind as usize) << 1) + | ((same_text as usize) << 2) + | ((no_tt_parent as usize) << 3); - if my_rank > 0b1110 { - // a rank of 0b1110 means that we have found a maximally interesting - // token so stop early. - t = Some(tok); - return ControlFlow::Break(()); - } - - // r = r.max(my_rank); - // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok)); - match &mut t { - Some(prev) if r < my_rank => { - *prev = tok; - r = my_rank; + if my_rank > 0b1110 { + // a rank of 0b1110 means that we have found a maximally interesting + // token so stop early. + t = Some(tok); + return ControlFlow::Break(()); } - Some(_) => (), - None => { - r = my_rank; - t = Some(tok) + + // r = r.max(my_rank); + // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok)); + match &mut t { + Some(prev) if r < my_rank => { + *prev = tok; + r = my_rank; + } + Some(_) => (), + None => { + r = my_rank; + t = Some(tok) + } } - } - ControlFlow::Continue(()) - }); + ControlFlow::Continue(()) + }, + ); let token = t.unwrap_or(token); match token.parent().and_then(ast::NameLike::cast) { From b0e7ef4031cc6215cfb9045512d44ebba3ae82fd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 17:01:51 +0200 Subject: [PATCH 7/9] Sort hover results by relevance --- .../rust-analyzer/crates/ide/src/hover.rs | 22 +++++++++++++++---- .../crates/ide/src/syntax_highlighting.rs | 2 -- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index d76d9afc1858..edf14a6f4bf0 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -180,11 +180,25 @@ fn hover_simple( // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important - let mut descended = vec![]; - sema.descend_into_macros_cb(original_token.clone(), |token| { - descended.push(token.value); + let mut descended = sema.descend_into_macros(original_token.clone()); + + let kind = original_token.kind(); + let text = original_token.text(); + let ident_kind = kind.is_any_identifier(); + + descended.sort_by_key(|tok| { + let tok_kind = tok.kind(); + + let exact_same_kind = tok_kind == kind; + let both_idents = exact_same_kind || (tok_kind.is_any_identifier() && ident_kind); + let same_text = tok.text() == text; + // anything that mapped into a token tree has likely no semantic information + let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); + (both_idents as usize) + | ((exact_same_kind as usize) << 1) + | ((same_text as usize) << 2) + | ((no_tt_parent as usize) << 3) }); - let descended = || descended.iter(); // TODO: WE should not try these step by step, instead to accommodate for macros we should run // all of these in "parallel" and rank their results diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index bfab5ceb1291..927fdaa178ca 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -407,8 +407,6 @@ fn traverse( let mut t = None; let mut r = 0; - // FIXME: Add an extra API that takes the file id of this. That is a simple way - // to prevent us constantly walking up the tree to fetch the file sema.descend_into_macros_breakable( InRealFile::new(file_id, token.clone()), |tok| { From f9db48f8c93207bcca411345a5785fa37d847ad4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 17:29:32 +0200 Subject: [PATCH 8/9] Consider interleaving hover kinds --- .../rust-analyzer/crates/ide/src/hover.rs | 177 ++++++++++-------- .../rust-analyzer/crates/ide/src/markup.rs | 2 +- 2 files changed, 95 insertions(+), 84 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index edf14a6f4bf0..dde98d40dc0e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -58,7 +58,7 @@ pub enum HoverDocFormat { PlainText, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum HoverAction { Runnable(Runnable), Implementation(FilePosition), @@ -97,7 +97,7 @@ pub struct HoverGotoTypeData { } /// Contains the results when hovering over an item -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] pub struct HoverResult { pub markup: Markup, pub actions: Vec, @@ -200,21 +200,24 @@ fn hover_simple( | ((no_tt_parent as usize) << 3) }); - // TODO: WE should not try these step by step, instead to accommodate for macros we should run - // all of these in "parallel" and rank their results - let result = None - // try lint hover - .or_else(|| { - descended().find_map(|token| { - // FIXME: Definition should include known lints and the like instead of having this special case here - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - render::try_for_lint(&attr, token) - }) - }) - // try definitions - .or_else(|| { - descended() - .filter_map(|token| { + let mut res = vec![]; + // let mut merge_result = |next: HoverResult| { + // res.markup = Markup::from(format!("{}\n---\n{}", res.markup, next.markup)); + // res.actions.extend(next.actions); + // }; + for token in descended { + let lint_hover = (|| { + // FIXME: Definition should include known lints and the like instead of having this special case here + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + render::try_for_lint(&attr, &token) + })(); + if let Some(lint_hover) = lint_hover { + res.push(lint_hover); + continue; + } + let definitions = (|| { + Some( + 'a: { let node = token.parent()?; // special case macro calls, we wanna render the invoked arm index @@ -229,11 +232,11 @@ fn hover_simple( .and_then(ast::MacroCall::cast) { if let Some(macro_) = sema.resolve_macro_call(¯o_call) { - return Some(vec![( + break 'a vec![( Definition::Macro(macro_), sema.resolve_macro_call_arm(¯o_call), node, - )]); + )]; } } } @@ -242,88 +245,96 @@ fn hover_simple( match IdentClass::classify_node(sema, &node)? { // It's better for us to fall back to the keyword hover here, // rendering poll is very confusing - IdentClass::Operator(OperatorClass::Await(_)) => None, + IdentClass::Operator(OperatorClass::Await(_)) => return None, IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, .. - }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]), + }) => { + vec![(Definition::ExternCrateDecl(decl), None, node)] + } - class => Some( + class => { multizip((class.definitions(), iter::repeat(None), iter::repeat(node))) - .collect::>(), - ), + .collect::>() + } } - }) - .flatten() + } + .into_iter() .unique_by(|&(def, _, _)| def) .map(|(def, macro_arm, node)| { hover_for_definition(sema, file_id, def, &node, macro_arm, config, edition) }) - .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { - acc.actions.extend(actions); - acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup)); - acc - }) - }) - // try keywords - .or_else(|| descended().find_map(|token| render::keyword(sema, config, token, edition))) - // try _ hovers - .or_else(|| descended().find_map(|token| render::underscore(sema, config, token, edition))) - // try rest pattern hover - .or_else(|| { - descended().find_map(|token| { - if token.kind() != DOT2 { - return None; - } + .collect::>(), + ) + })(); + if let Some(definitions) = definitions { + res.extend(definitions); + continue; + } + let keywords = || render::keyword(sema, config, &token, edition); + let underscore = || render::underscore(sema, config, &token, edition); + let rest_pat = || { + if token.kind() != DOT2 { + return None; + } - let rest_pat = token.parent().and_then(ast::RestPat::cast)?; - let record_pat_field_list = - rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; + let rest_pat = token.parent().and_then(ast::RestPat::cast)?; + let record_pat_field_list = + rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; - let record_pat = - record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; + let record_pat = + record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; - Some(render::struct_rest_pat(sema, config, &record_pat, edition)) - }) - }) - // try () call hovers - .or_else(|| { - descended().find_map(|token| { - if token.kind() != T!['('] && token.kind() != T![')'] { - return None; + Some(render::struct_rest_pat(sema, config, &record_pat, edition)) + }; + let call = || { + if token.kind() != T!['('] && token.kind() != T![')'] { + return None; + } + let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; + let call_expr = syntax::match_ast! { + match arg_list { + ast::CallExpr(expr) => expr.into(), + ast::MethodCallExpr(expr) => expr.into(), + _ => return None, } - let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; - let call_expr = syntax::match_ast! { - match arg_list { - ast::CallExpr(expr) => expr.into(), - ast::MethodCallExpr(expr) => expr.into(), - _ => return None, - } - }; - render::type_info_of(sema, config, &Either::Left(call_expr), edition) - }) - }) - // try closure - .or_else(|| { - descended().find_map(|token| { - if token.kind() != T![|] { - return None; - } - let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; - render::closure_expr(sema, config, c, edition) - }) - }) - // tokens - .or_else(|| { + }; + render::type_info_of(sema, config, &Either::Left(call_expr), edition) + }; + let closure = || { + if token.kind() != T![|] { + return None; + } + let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; + render::closure_expr(sema, config, c, edition) + }; + let literal = || { render::literal(sema, original_token.clone(), edition) .map(|markup| HoverResult { markup, actions: vec![] }) - }); + }; + if let Some(result) = keywords() + .or_else(underscore) + .or_else(rest_pat) + .or_else(call) + .or_else(closure) + .or_else(literal) + { + res.push(result) + } + } - result.map(|mut res: HoverResult| { - res.actions = dedupe_or_merge_hover_actions(res.actions); - RangeInfo::new(original_token.text_range(), res) - }) + res.into_iter() + .unique() + .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { + acc.actions.extend(actions); + acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup)); + acc + }) + .map(|mut res: HoverResult| { + res.actions = dedupe_or_merge_hover_actions(res.actions); + RangeInfo::new(original_token.text_range(), res) + }) } fn hover_ranged( diff --git a/src/tools/rust-analyzer/crates/ide/src/markup.rs b/src/tools/rust-analyzer/crates/ide/src/markup.rs index 4a4e29fa33b8..750d12542605 100644 --- a/src/tools/rust-analyzer/crates/ide/src/markup.rs +++ b/src/tools/rust-analyzer/crates/ide/src/markup.rs @@ -5,7 +5,7 @@ //! what is used by LSP, so let's keep it simple. use std::fmt; -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)] pub struct Markup { text: String, } From 71c7ceaf529d4cd241ab247e62c87a859728bb99 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 22 Aug 2024 18:08:36 +0200 Subject: [PATCH 9/9] Fix sorting order for tokens in hover --- .../rust-analyzer/crates/ide/src/hover.rs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index dde98d40dc0e..124db2985bf0 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -119,7 +119,7 @@ pub(crate) fn hover( let edition = sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); let mut res = if range.is_empty() { - hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config, edition) + hover_offset(sema, FilePosition { file_id, offset: range.start() }, file, config, edition) } else { hover_ranged(sema, frange, file, config, edition) }?; @@ -131,7 +131,7 @@ pub(crate) fn hover( } #[allow(clippy::field_reassign_with_default)] -fn hover_simple( +fn hover_offset( sema: &Semantics<'_, RootDatabase>, FilePosition { file_id, offset }: FilePosition, file: SyntaxNode, @@ -186,7 +186,7 @@ fn hover_simple( let text = original_token.text(); let ident_kind = kind.is_any_identifier(); - descended.sort_by_key(|tok| { + descended.sort_by_cached_key(|tok| { let tok_kind = tok.kind(); let exact_same_kind = tok_kind == kind; @@ -194,18 +194,15 @@ fn hover_simple( let same_text = tok.text() == text; // anything that mapped into a token tree has likely no semantic information let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE); - (both_idents as usize) + !((both_idents as usize) | ((exact_same_kind as usize) << 1) | ((same_text as usize) << 2) - | ((no_tt_parent as usize) << 3) + | ((no_tt_parent as usize) << 3)) }); let mut res = vec![]; - // let mut merge_result = |next: HoverResult| { - // res.markup = Markup::from(format!("{}\n---\n{}", res.markup, next.markup)); - // res.actions.extend(next.actions); - // }; for token in descended { + let is_same_kind = token.kind() == kind; let lint_hover = (|| { // FIXME: Definition should include known lints and the like instead of having this special case here let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; @@ -273,9 +270,14 @@ fn hover_simple( continue; } let keywords = || render::keyword(sema, config, &token, edition); - let underscore = || render::underscore(sema, config, &token, edition); + let underscore = || { + if !is_same_kind { + return None; + } + render::underscore(sema, config, &token, edition) + }; let rest_pat = || { - if token.kind() != DOT2 { + if !is_same_kind || token.kind() != DOT2 { return None; } @@ -289,7 +291,7 @@ fn hover_simple( Some(render::struct_rest_pat(sema, config, &record_pat, edition)) }; let call = || { - if token.kind() != T!['('] && token.kind() != T![')'] { + if !is_same_kind || token.kind() != T!['('] && token.kind() != T![')'] { return None; } let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; @@ -303,7 +305,7 @@ fn hover_simple( render::type_info_of(sema, config, &Either::Left(call_expr), edition) }; let closure = || { - if token.kind() != T![|] { + if !is_same_kind || token.kind() != T![|] { return None; } let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;