Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/semantics.rs')
| -rw-r--r-- | crates/hir/src/semantics.rs | 263 |
1 files changed, 170 insertions, 93 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ed3d3f1a3b..ac70c27785 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2,7 +2,11 @@ mod source_to_def; -use std::{cell::RefCell, fmt, iter, mem, ops}; +use std::{ + cell::RefCell, + fmt, iter, mem, + ops::{self, ControlFlow}, +}; use base_db::{FileId, FileRange}; use either::Either; @@ -25,8 +29,9 @@ use smallvec::{smallvec, SmallVec}; use stdx::TupleExt; use syntax::{ algo::skip_trivia_token, - ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody}, - match_ast, AstNode, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize, + ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _}, + match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, + TextRange, TextSize, }; use crate::{ @@ -39,7 +44,13 @@ use crate::{ TypeAlias, TypeParam, VariantDef, }; -#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DescendPreference { + SameText, + SameKind, + None, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PathResolution { /// An item Def(ModuleDef), @@ -392,11 +403,72 @@ impl<'db> SemanticsImpl<'db> { ) } + pub fn as_format_args_parts( + &self, + string: &ast::String, + ) -> Option<Vec<(TextRange, Option<PathResolution>)>> { + 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 + } + + pub fn check_for_format_args_template( + &self, + original_token: SyntaxToken, + offset: TextSize, + ) -> Option<(TextRange, Option<PathResolution>)> { + 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.clone()) + .into_iter() + .find_map(|token| { + self.resolve_offset_in_format_args( + ast::String::cast(token)?, + offset - quote.end(), + ) + }) + .map(|(range, res)| (range + quote.end(), res)); + } + } + None + } + + fn resolve_offset_in_format_args( + &self, + string: ast::String, + offset: TextSize, + ) -> Option<(TextRange, Option<PathResolution>)> { + debug_assert!(offset <= string.syntax().text_range().len()); + 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); + source_analyzer.resolve_offset_in_format_args(self.db, format_args.as_ref(), offset) + } + /// Maps a node down by mapping its first and last token down. pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> { // This might not be the correct way to do this, but it works for now let mut res = smallvec![]; let tokens = (|| { + // FIXME: the trivia skipping should not be necessary let first = skip_trivia_token(node.syntax().first_token()?, Direction::Next)?; let last = skip_trivia_token(node.syntax().last_token()?, Direction::Prev)?; Some((first, last)) @@ -407,24 +479,28 @@ impl<'db> SemanticsImpl<'db> { }; if first == last { - self.descend_into_macros_impl(first, 0.into(), &mut |InFile { value, .. }| { - if let Some(node) = value.parent_ancestors().find_map(N::cast) { + // 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) } - false + ControlFlow::Continue(()) }); } 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, 0.into(), &mut |token| { + self.descend_into_macros_impl(first, &mut |token| { scratch.push(token); - false + ControlFlow::Continue(()) }); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( last, - 0.into(), &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 { @@ -441,7 +517,7 @@ impl<'db> SemanticsImpl<'db> { } } } - false + ControlFlow::Continue(()) }, ); } @@ -453,32 +529,42 @@ impl<'db> SemanticsImpl<'db> { /// be considered for the mapping in case of inline format args. pub fn descend_into_macros( &self, + mode: DescendPreference, token: SyntaxToken, - offset: TextSize, - ) -> SmallVec<[SyntaxToken; 1]> { - let mut res = smallvec![]; - self.descend_into_macros_impl(token, offset, &mut |InFile { value, .. }| { - res.push(value); - false - }); - res - } - - /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token. - /// - /// Returns the original non descended token if none of the mapped counterparts have the same text. - pub fn descend_into_macros_with_same_text( - &self, - token: SyntaxToken, - offset: TextSize, ) -> SmallVec<[SyntaxToken; 1]> { - let text = token.text(); + 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 = smallvec![]; - self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| { - if value.text() == text { + 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); } - false + ControlFlow::Continue(()) }); if res.is_empty() { res.push(token); @@ -486,44 +572,46 @@ impl<'db> SemanticsImpl<'db> { res } - pub fn descend_into_macros_with_kind_preference( + pub fn descend_into_macros_single( &self, + mode: DescendPreference, token: SyntaxToken, - offset: TextSize, ) -> 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) => { - node.parent().map_or(kind, |it| it.kind()) - } + kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind, _ => token.kind(), }, None => token.kind(), }; - let preferred_kind = fetch_kind(&token); - let mut res = None; - self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| { - if fetch_kind(&value) == preferred_kind { - res = Some(value); - true - } else { - if let None = res { - res = Some(value) - } - false - } - }); - res.unwrap_or(token) - } - - /// Descend the token into its macro call if it is part of one, returning the token in the - /// expansion that it is associated with. If `offset` points into the token's range, it will - /// be considered for the mapping in case of inline format args. - pub fn descend_into_macros_single(&self, token: SyntaxToken, offset: TextSize) -> SyntaxToken { + 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, offset, &mut |InFile { value, .. }| { + 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; - true + if is_a_match { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } }); res } @@ -532,10 +620,7 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros_impl( &self, token: SyntaxToken, - // FIXME: We might want this to be Option<TextSize> to be able to opt out of subrange - // mapping, specifically for node downmapping - _offset: TextSize, - f: &mut dyn FnMut(InFile<SyntaxToken>) -> bool, + f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>, ) { // FIXME: Clean this up let _p = profile::span("descend_into_macros"); @@ -560,25 +645,24 @@ impl<'db> SemanticsImpl<'db> { let def_map = sa.resolver.def_map(); let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)]; - let mut process_expansion_for_token = - |stack: &mut SmallVec<_>, macro_file, _token: InFile<&_>| { - let expansion_info = cache - .entry(macro_file) - .or_insert_with(|| macro_file.expansion_info(self.db.upcast())); + let mut process_expansion_for_token = |stack: &mut SmallVec<_>, macro_file| { + let expansion_info = cache + .entry(macro_file) + .or_insert_with(|| macro_file.expansion_info(self.db.upcast())); - { - let InFile { file_id, value } = expansion_info.expanded(); - self.cache(value, file_id); - } + { + let InFile { file_id, value } = expansion_info.expanded(); + self.cache(value, file_id); + } - let mapped_tokens = expansion_info.map_range_down(span, None)?; - let len = stack.len(); + let mapped_tokens = expansion_info.map_range_down(span)?; + let len = stack.len(); - // requeue the tokens we got from mapping our current token down - stack.extend(mapped_tokens.map(Into::into)); - // if the length changed we have found a mapping for the token - (stack.len() != len).then_some(()) - }; + // requeue the tokens we got from mapping our current token down + stack.extend(mapped_tokens.map(Into::into)); + // if the length changed we have found a mapping for the token + (stack.len() != len).then_some(()) + }; // Remap the next token in the queue into a macro call its in, if it is not being remapped // either due to not being in a macro-call or because its unused push it into the result vec, @@ -598,7 +682,7 @@ impl<'db> SemanticsImpl<'db> { }); if let Some(call_id) = containing_attribute_macro_call { let file_id = call_id.as_macro_file(); - return process_expansion_for_token(&mut stack, file_id, token.as_ref()); + 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 @@ -624,7 +708,7 @@ impl<'db> SemanticsImpl<'db> { it } }; - process_expansion_for_token(&mut stack, file_id, token.as_ref()) + process_expansion_for_token(&mut stack, file_id) } else if let Some(meta) = ast::Meta::cast(parent) { // attribute we failed expansion for earlier, this might be a derive invocation // or derive helper attribute @@ -646,11 +730,7 @@ impl<'db> SemanticsImpl<'db> { Some(call_id) => { // resolved to a derive let file_id = call_id.as_macro_file(); - return process_expansion_for_token( - &mut stack, - file_id, - token.as_ref(), - ); + return process_expansion_for_token(&mut stack, file_id); } None => Some(adt), } @@ -682,11 +762,8 @@ impl<'db> SemanticsImpl<'db> { def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?; let mut res = None; for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) { - res = res.or(process_expansion_for_token( - &mut stack, - derive.as_macro_file(), - token.as_ref(), - )); + res = + res.or(process_expansion_for_token(&mut stack, derive.as_macro_file())); } res } else { @@ -695,7 +772,7 @@ impl<'db> SemanticsImpl<'db> { })() .is_none(); - if was_not_remapped && f(token) { + if was_not_remapped && f(token).is_break() { break; } } @@ -711,7 +788,7 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ { node.token_at_offset(offset) - .map(move |token| self.descend_into_macros(token, offset)) + .map(move |token| self.descend_into_macros(DescendPreference::None, token)) .map(|descendants| { descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it)) }) |