diff --git a/Cargo.toml b/Cargo.toml index 56e80e11e774..d8f9b2b7f6af 100644 --- a/Cargo.toml +++ b/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/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index 20f484f672af..d41f69812ee6 100644 --- a/crates/hir-expand/src/files.rs +++ b/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/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 2bea9026265a..741c2b2ebfe2 100644 --- a/crates/hir-expand/src/lib.rs +++ b/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/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 57f810c7c79c..6e26af55af82 100644 --- a/crates/hir/src/lib.rs +++ b/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/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index d086aee42855..3d6c98504308 100644 --- a/crates/hir/src/semantics.rs +++ b/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,11 +49,7 @@ use crate::{ Variant, VariantDef, }; -pub enum DescendPreference { - SameText, - SameKind, - None, -} +const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { @@ -182,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, @@ -190,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 attribute 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, @@ -545,51 +544,53 @@ 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()?; + + 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)?; + 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 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, |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( @@ -619,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) - } - ControlFlow::Continue(()) - }); + 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); - ControlFlow::Continue(()) + 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 { @@ -659,130 +667,122 @@ impl<'db> SemanticsImpl<'db> { } } } - ControlFlow::Continue(()) + CONTINUE_NO_BREAKS }, ); } 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( + pub fn descend_into_macros_cb( &self, - mode: DescendPreference, token: SyntaxToken, - ) -> SmallVec<[SyntaxToken; 1]> { - enum Dp<'t> { - SameText(&'t str), - SameKind(SyntaxKind), - None, + mut cb: impl FnMut(InFile), + ) { + 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 + }); } - 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, - }; + } + + pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { 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 - // special case for derive macros - || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF) - } - Dp::None => true, - }; - if is_a_match { - res.push(value); - } - ControlFlow::Continue(()) - }); + 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); } res } - pub fn descend_into_macros_single( + pub fn descend_into_macros_breakable( &self, - mode: DescendPreference, - token: SyntaxToken, - ) -> SyntaxToken { - enum Dp<'t> { - 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 = 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(()) - } else { - ControlFlow::Continue(()) + token: InRealFile, + mut cb: impl FnMut(InFile) -> ControlFlow, + ) -> Option { + self.descend_into_macros_impl(token.clone(), &mut cb) + } + + /// 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_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(); + if matches { + r.push(value); } }); - res + if r.is_empty() { + r.push(token); + } + 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(); + 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( + fn descend_into_macros_impl( &self, - token: SyntaxToken, - f: &mut dyn FnMut(InFile) -> ControlFlow<()>, - ) { + 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) = - 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, - }; + 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, + 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(); + // 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, 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| { let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| { Some( @@ -809,7 +809,13 @@ impl<'db> SemanticsImpl<'db> { 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, ref mut tokens)) = stack.pop() { while let Some(token) = tokens.pop() { let was_not_remapped = (|| { // First expand into attribute invocations @@ -817,7 +823,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,9 +855,7 @@ 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())); + filter_duplicates(tokens, text_range); return process_expansion_for_token(&mut stack, file_id); } @@ -862,6 +866,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 +875,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,9 +893,7 @@ 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 .eager_arg(self.db.upcast()) @@ -899,6 +902,7 @@ impl<'db> SemanticsImpl<'db> { process_expansion_for_token(&mut stack, arg.as_macro_file()) })) } + // 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 +914,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) }); @@ -945,28 +949,29 @@ 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(), @@ -978,11 +983,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(InFile::new(expansion, token)) { + return Some(b); + } } } } + None } // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop @@ -995,7 +1003,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)) }) @@ -1414,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) } @@ -1427,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))?; @@ -1482,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/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 9180d8dfcbb2..e4d347ef16bd 100644 --- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/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/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index cfcb09b9f7d9..fd379cb88daa 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/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/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs index 63c09af3d687..84fa58d743bb 100644 --- a/crates/ide-db/src/helpers.rs +++ b/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/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index 7319b7ac02da..197c327ee4ed 100644 --- a/crates/ide-db/src/search.rs +++ b/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/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs index 8ab5a6ede3bd..c104aa571894 100644 --- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs +++ b/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/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index 87093104852f..155259a13804 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/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/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index cad0d06eeab1..925ae620231d 100644 --- a/crates/ide/src/doc_links.rs +++ b/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}, @@ -144,7 +141,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! { @@ -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(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/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 9788c0154423..a939ed214ad8 100644 --- a/crates/ide/src/expand_macro.rs +++ b/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/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 5f6aaeaabb60..3d49082f2858 100644 --- a/crates/ide/src/extend_selection.rs +++ b/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/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs index 6076de54ebaf..6ae9dde84be5 100644 --- a/crates/ide/src/goto_declaration.rs +++ b/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(original_token) .iter() .filter_map(|token| { let parent = token.parent()?; diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 6a6fbcb9a6b7..5769f2cabc75 100644 --- a/crates/ide/src/goto_definition.rs +++ b/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(original_token.clone()) .into_iter() .filter_map(|token| { let parent = token.parent()?; @@ -251,10 +248,7 @@ pub(crate) fn find_fn_or_blocks( None }; - sema.descend_into_macros(DescendPreference::None, 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( @@ -369,7 +363,7 @@ pub(crate) fn find_loops( None }; - sema.descend_into_macros(DescendPreference::None, token.clone()) + sema.descend_into_macros(token.clone()) .into_iter() .filter_map(find_ancestors) .collect_vec() diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 2eff7796d548..e36c8ee2f3f7 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/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/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index f75b8fb7d022..ca04b7bb5a9b 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/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(token) .into_iter() .filter_map(|token| { let ty = sema diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index a88261df1063..5348e855be4b 100644 --- a/crates/ide/src/highlight_related.rs +++ b/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/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6c646cf4ee1f..124db2985bf0 100644 --- a/crates/ide/src/hover.rs +++ b/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, @@ -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, @@ -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, @@ -178,34 +178,43 @@ 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 descended = || descended.iter(); + 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_cached_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 result = descended() - // try lint hover - .find_map(|token| { + let mut res = vec![]; + 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)?; - render::try_for_lint(&attr, token) - }) - // try definitions - .or_else(|| { - descended() - .filter_map(|token| { + 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 @@ -220,11 +229,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, - )]); + )]; } } } @@ -233,88 +242,101 @@ 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 = || { + if !is_same_kind { + return None; + } + render::underscore(sema, config, &token, edition) + }; + let rest_pat = || { + if !is_same_kind || 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; - } - 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; + Some(render::struct_rest_pat(sema, config, &record_pat, edition)) + }; + let call = || { + if !is_same_kind || 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 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 !is_same_kind || 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/crates/ide/src/markup.rs b/crates/ide/src/markup.rs index 4a4e29fa33b8..750d12542605 100644 --- a/crates/ide/src/markup.rs +++ b/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, } diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index 7b5fd651e3d7..4be1b570981f 100644 --- a/crates/ide/src/moniker.rs +++ b/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| { diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 3e3d5ef6e655..55afcb59bae7 100644 --- a/crates/ide/src/references.rs +++ b/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/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index 9d3b8c6ebd19..516f64959cef 100644 --- a/crates/ide/src/signature_help.rs +++ b/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/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index e082fcdc7658..927fdaa178ca 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/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::{InRealFile, Name, Semantics}; use ide_db::{FxHashMap, RootDatabase, SymbolKind}; use span::EditionedFileId; use syntax::{ @@ -399,19 +401,55 @@ 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 { - sema.descend_into_macros_single(DescendPreference::SameKind, token) - }; + let kind = token.kind(); + let text = token.text(); + let ident_kind = kind.is_any_identifier(); + + let mut t = None; + let mut r = 0; + 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); + + 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/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index 17411fefbd97..196552020ab6 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -100,7 +100,7 @@ }; } -include!(concat!("foo/", "foo.rs")); +include!(concat!("foo/", "foo.rs")); struct S<T>(T); fn main() {