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.rs597
1 files changed, 353 insertions, 244 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index a42e0978b2..92fa76c96f 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, Not},
+};
use base_db::{FileId, FileRange};
use either::Either;
@@ -13,16 +17,21 @@ use hir_def::{
nameres::MacroSubNs,
resolver::{self, HasResolver, Resolver, TypeNs},
type_ref::Mutability,
- AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId,
+ AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId,
+};
+use hir_expand::{
+ db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo, InMacroFile, MacroCallId,
+ MacroFileId, MacroFileIdExt,
};
-use hir_expand::{db::ExpandDatabase, name::AsName, ExpansionInfo, MacroCallId};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
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 _, HasDocComments, HasGenericParams, HasLoopBody, IsString as _},
+ match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
+ TextRange, TextSize,
};
use crate::{
@@ -35,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),
@@ -114,11 +129,12 @@ pub struct Semantics<'db, DB> {
pub struct SemanticsImpl<'db> {
pub db: &'db dyn HirDatabase,
s2d_cache: RefCell<SourceToDefCache>,
- expansion_info_cache: RefCell<FxHashMap<HirFileId, Option<ExpansionInfo>>>,
- // Rootnode to HirFileId cache
+ /// Rootnode to HirFileId cache
cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
- // MacroCall to its expansion's HirFileId cache
- macro_call_cache: RefCell<FxHashMap<InFile<ast::MacroCall>, HirFileId>>,
+ // These 2 caches are mainly useful for semantic highlighting as nothing else descends a lot of tokens
+ expansion_info_cache: RefCell<FxHashMap<MacroFileId, ExpansionInfo>>,
+ /// MacroCall to its expansion's MacroFileId cache
+ macro_call_cache: RefCell<FxHashMap<InFile<ast::MacroCall>, MacroFileId>>,
}
impl<DB> fmt::Debug for Semantics<'_, DB> {
@@ -182,20 +198,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast))
}
- pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
- self.imp.resolve_method_call(call).map(Function::from)
- }
-
- /// Attempts to resolve this call expression as a method call falling back to resolving it as a field.
- pub fn resolve_method_call_field_fallback(
- &self,
- call: &ast::MethodCallExpr,
- ) -> Option<Either<Function, Field>> {
- self.imp
- .resolve_method_call_fallback(call)
- .map(|it| it.map_left(Function::from).map_right(Field::from))
- }
-
pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<Function> {
self.imp.resolve_await_to_poll(await_expr).map(Function::from)
}
@@ -255,7 +257,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
let sa = self.analyze_no_infer(macro_call.syntax())?;
let file_id = sa.expand(self.db, InFile::new(sa.file_id, macro_call))?;
- let node = self.parse_or_expand(file_id);
+ let node = self.parse_or_expand(file_id.into());
Some(node)
}
@@ -388,11 +390,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))
@@ -403,24 +466,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 {
@@ -437,7 +504,7 @@ impl<'db> SemanticsImpl<'db> {
}
}
}
- false
+ ControlFlow::Continue(())
},
);
}
@@ -449,32 +516,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);
@@ -482,44 +559,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
}
@@ -527,177 +606,193 @@ 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<()>,
) {
let _p = profile::span("descend_into_macros");
- let relative_token_offset = token.text_range().start().checked_sub(offset);
- let parent = match token.parent() {
+ let sa = match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
Some(it) => it,
None => return,
};
- let sa = match self.analyze_no_infer(&parent) {
- Some(it) => it,
- None => return,
+
+ let span = match sa.file_id.file_id() {
+ Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
+ None => {
+ stdx::never!();
+ return;
+ }
};
- let def_map = sa.resolver.def_map();
- let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)];
let mut cache = self.expansion_info_cache.borrow_mut();
let mut mcache = self.macro_call_cache.borrow_mut();
+ let def_map = sa.resolver.def_map();
- let mut process_expansion_for_token =
- |stack: &mut SmallVec<_>, macro_file, item, token: InFile<&_>| {
- let expansion_info = cache
- .entry(macro_file)
- .or_insert_with(|| macro_file.expansion_info(self.db.upcast()))
- .as_ref()?;
+ let mut process_expansion_for_token = |stack: &mut Vec<_>, 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 InMacroFile { file_id, value } = expansion_info.expanded();
+ self.cache(value, file_id.into());
+ }
- let mapped_tokens = expansion_info.map_token_down(
- self.db.upcast(),
- item,
- token,
- relative_token_offset,
- )?;
- let len = stack.len();
-
- // requeue the tokens we got from mapping our current token down
- stack.extend(mapped_tokens);
- // if the length changed we have found a mapping for the token
- (stack.len() != len).then_some(())
- };
+ let InMacroFile { file_id, value: mapped_tokens } =
+ expansion_info.map_range_down(span)?;
+ let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect();
- // 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,
- // otherwise push the remapped tokens back into the queue as they can potentially be remapped again.
- while let Some(token) = stack.pop() {
- self.db.unwind_if_cancelled();
- let was_not_remapped = (|| {
- // First expand into attribute invocations
- let containing_attribute_macro_call = self.with_ctx(|ctx| {
- token.value.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
- if item.attrs().next().is_none() {
- // Don't force populate the dyn cache for items that don't have an attribute anyways
- return None;
- }
- Some((ctx.item_to_macro_call(token.with_value(item.clone()))?, item))
- })
- });
- if let Some((call_id, item)) = containing_attribute_macro_call {
- let file_id = call_id.as_file();
- return process_expansion_for_token(
- &mut stack,
- file_id,
- Some(item),
- token.as_ref(),
- );
- }
+ // if the length changed we have found a mapping for the token
+ let res = mapped_tokens.is_empty().not().then_some(());
+ // requeue the tokens we got from mapping our current token down
+ stack.push((HirFileId::from(file_id), mapped_tokens));
+ res
+ };
- // Then check for token trees, that means we are either in a function-like macro or
- // secondary attribute inputs
- let tt = token.value.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
- let parent = tt.syntax().parent()?;
+ let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(sa.file_id, smallvec![token])];
+
+ while let Some((file_id, mut tokens)) = stack.pop() {
+ while let Some(token) = tokens.pop() {
+ let was_not_remapped = (|| {
+ // First expand into attribute invocations
+ let containing_attribute_macro_call = self.with_ctx(|ctx| {
+ token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
+ if item.attrs().next().is_none() {
+ // Don't force populate the dyn cache for items that don't have an attribute anyways
+ return None;
+ }
+ Some((
+ ctx.item_to_macro_call(InFile::new(file_id, item.clone()))?,
+ item,
+ ))
+ })
+ });
+ if let Some((call_id, item)) = containing_attribute_macro_call {
+ let file_id = call_id.as_macro_file();
+ let attr_id = match self.db.lookup_intern_macro_call(call_id).kind {
+ hir_expand::MacroCallKind::Attr { invoc_attr_index, .. } => {
+ invoc_attr_index.ast_index()
+ }
+ _ => 0,
+ };
+ let text_range = item.syntax().text_range();
+ let start = item
+ .doc_comments_and_attrs()
+ .nth(attr_id)
+ .map(|attr| match attr {
+ Either::Left(it) => it.syntax().text_range().start(),
+ Either::Right(it) => it.syntax().text_range().start(),
+ })
+ .unwrap_or_else(|| text_range.start());
+ let text_range = TextRange::new(start, text_range.end());
+ // 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);
+ }
- if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
- return None;
- }
- if tt.right_delimiter_token().map_or(false, |it| it == token.value) {
- return None;
- }
+ // Then check for token trees, that means we are either in a function-like macro or
+ // secondary attribute inputs
+ let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
+ let parent = tt.syntax().parent()?;
- if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
- let mcall = token.with_value(macro_call);
- let file_id = match mcache.get(&mcall) {
- Some(&it) => it,
- None => {
- let it = sa.expand(self.db, mcall.as_ref())?;
- mcache.insert(mcall, it);
- it
- }
- };
- process_expansion_for_token(&mut stack, file_id, None, token.as_ref())
- } 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
- let attr = meta.parent_attr()?;
-
- let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast) {
- // this might be a derive, or a derive helper on an ADT
- let derive_call = self.with_ctx(|ctx| {
- // so try downmapping the token into the pseudo derive expansion
- // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
- ctx.attr_to_derive_macro_call(
- token.with_value(&adt),
- token.with_value(attr.clone()),
- )
- .map(|(_, call_id, _)| call_id)
- });
-
- match derive_call {
- Some(call_id) => {
- // resolved to a derive
- let file_id = call_id.as_file();
- return process_expansion_for_token(
- &mut stack,
- file_id,
- Some(adt.into()),
- token.as_ref(),
- );
+ if tt.left_delimiter_token().map_or(false, |it| it == token) {
+ return None;
+ }
+ if tt.right_delimiter_token().map_or(false, |it| it == token) {
+ return None;
+ }
+
+ if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
+ let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
+ InFile::new(file_id, macro_call);
+ let file_id = match mcache.get(&mcall) {
+ Some(&it) => it,
+ None => {
+ let it = sa.expand(self.db, mcall.as_ref())?;
+ mcache.insert(mcall, it);
+ it
}
- None => Some(adt),
- }
- } else {
- // Otherwise this could be a derive helper on a variant or field
- if let Some(field) = attr.syntax().parent().and_then(ast::RecordField::cast)
+ };
+ 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()));
+ 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
+ let attr = meta.parent_attr()?;
+
+ let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
{
- field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
- } else if let Some(field) =
- attr.syntax().parent().and_then(ast::TupleField::cast)
- {
- field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
- } else if let Some(variant) =
- attr.syntax().parent().and_then(ast::Variant::cast)
- {
- variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
+ // this might be a derive, or a derive helper on an ADT
+ let derive_call = self.with_ctx(|ctx| {
+ // so try downmapping the token into the pseudo derive expansion
+ // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+ ctx.attr_to_derive_macro_call(
+ InFile::new(file_id, &adt),
+ InFile::new(file_id, attr.clone()),
+ )
+ .map(|(_, call_id, _)| call_id)
+ });
+
+ match derive_call {
+ Some(call_id) => {
+ // resolved to a derive
+ let file_id = call_id.as_macro_file();
+ let text_range = attr.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()));
+ return process_expansion_for_token(&mut stack, file_id);
+ }
+ None => Some(adt),
+ }
} else {
- None
+ // Otherwise this could be a derive helper on a variant or field
+ if let Some(field) =
+ attr.syntax().parent().and_then(ast::RecordField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(field) =
+ attr.syntax().parent().and_then(ast::TupleField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(variant) =
+ attr.syntax().parent().and_then(ast::Variant::cast)
+ {
+ variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
+ } else {
+ None
+ }
+ }?;
+ if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+ return None;
}
- }?;
- if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(token.file_id, &adt))) {
- return None;
- }
- // Not an attribute, nor a derive, so it's either a builtin or a derive helper
- // Try to resolve to a derive helper and downmap
- let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
- let id = self.db.ast_id_map(token.file_id).ast_id(&adt);
- let helpers =
- def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?;
- let item = Some(adt.into());
- 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_file(),
- item.clone(),
- token.as_ref(),
- ));
+ // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+ // Try to resolve to a derive helper and downmap
+ let attr_name =
+ attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+ let id = self.db.ast_id_map(file_id).ast_id(&adt);
+ let helpers = def_map.derive_helpers_in_scope(InFile::new(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(),
+ ));
+ }
+ res
+ } else {
+ None
}
- res
- } else {
- None
- }
- })()
- .is_none();
+ })()
+ .is_none();
- if was_not_remapped && f(token) {
- break;
+ if was_not_remapped && f(InFile::new(file_id, token)).is_break() {
+ break;
+ }
}
}
}
@@ -712,7 +807,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))
})
@@ -737,14 +832,16 @@ impl<'db> SemanticsImpl<'db> {
pub fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
let node = self.find_file(node);
node.original_file_range_opt(self.db.upcast())
+ .filter(|(_, ctx)| ctx.is_root())
+ .map(TupleExt::head)
}
/// Attempts to map the node out of macro expanded files.
/// This only work for attribute expansions, as other ones do not have nodes as input.
pub fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> {
self.wrap_node_infile(node).original_ast_node(self.db.upcast()).map(
- |InFile { file_id, value }| {
- self.cache(find_root(value.syntax()), file_id);
+ |InRealFile { file_id, value }| {
+ self.cache(find_root(value.syntax()), file_id.into());
value
},
)
@@ -755,8 +852,8 @@ impl<'db> SemanticsImpl<'db> {
pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
let InFile { file_id, .. } = self.find_file(node);
InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map(
- |InFile { file_id, value }| {
- self.cache(find_root(&value), file_id);
+ |InRealFile { file_id, value }| {
+ self.cache(find_root(&value), file_id.into());
value
},
)
@@ -787,7 +884,7 @@ impl<'db> SemanticsImpl<'db> {
Some(parent) => Some(InFile::new(file_id, parent)),
None => {
self.cache(value.clone(), file_id);
- file_id.call_node(db)
+ Some(file_id.macro_file()?.call_node(db))
}
}
})
@@ -851,9 +948,9 @@ impl<'db> SemanticsImpl<'db> {
pub fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> {
let analyze = self.analyze(path.syntax())?;
- let hygiene = hir_expand::hygiene::Hygiene::new(self.db.upcast(), analyze.file_id);
- let ctx = LowerCtx::with_hygiene(self.db.upcast(), &hygiene);
- let hir_path = Path::from_src(path.clone(), &ctx)?;
+ let span_map = self.db.span_map(analyze.file_id);
+ let ctx = LowerCtx::with_span_map(self.db.upcast(), span_map);
+ let hir_path = Path::from_src(&ctx, path.clone())?;
match analyze.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), &hir_path)? {
TypeNs::TraitId(id) => Some(Trait { id }),
_ => None,
@@ -937,14 +1034,15 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat)
}
- fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
+ pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
self.analyze(call.syntax())?.resolve_method_call(self.db, call)
}
- fn resolve_method_call_fallback(
+ /// Attempts to resolve this call expression as a method call falling back to resolving it as a field.
+ pub fn resolve_method_call_fallback(
&self,
call: &ast::MethodCallExpr,
- ) -> Option<Either<FunctionId, FieldId>> {
+ ) -> Option<Either<Function, Field>> {
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
}
@@ -976,6 +1074,13 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(field.syntax())?.resolve_field(self.db, field)
}
+ pub fn resolve_field_fallback(
+ &self,
+ field: &ast::FieldExpr,
+ ) -> Option<Either<Field, Function>> {
+ self.analyze(field.syntax())?.resolve_field_fallback(self.db, field)
+ }
+
pub fn resolve_record_field(
&self,
field: &ast::RecordExprField,
@@ -1037,7 +1142,7 @@ impl<'db> SemanticsImpl<'db> {
fn with_ctx<F: FnOnce(&mut SourceToDefCtx<'_, '_>) -> T, T>(&self, f: F) -> T {
let mut cache = self.s2d_cache.borrow_mut();
- let mut ctx = SourceToDefCtx { db: self.db, cache: &mut cache };
+ let mut ctx = SourceToDefCtx { db: self.db, dynmap_cache: &mut cache };
f(&mut ctx)
}
@@ -1187,7 +1292,7 @@ impl<'db> SemanticsImpl<'db> {
return None;
}
- let func = self.resolve_method_call(method_call_expr).map(Function::from)?;
+ let func = self.resolve_method_call(method_call_expr)?;
let res = match func.self_param(self.db)?.access(self.db) {
Access::Shared | Access::Exclusive => true,
Access::Owned => false,
@@ -1451,7 +1556,7 @@ impl SemanticsScope<'_> {
/// necessary a heuristic, as it doesn't take hygiene into account.
pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
let ctx = LowerCtx::with_file_id(self.db.upcast(), self.file_id);
- let path = Path::from_src(path.clone(), &ctx)?;
+ let path = Path::from_src(&ctx, path.clone())?;
resolve_hir_path(self.db, &self.resolver, &path)
}
@@ -1478,6 +1583,10 @@ impl SemanticsScope<'_> {
pub fn extern_crate_decls(&self) -> impl Iterator<Item = Name> + '_ {
self.resolver.extern_crate_decls_in_scope(self.db.upcast())
}
+
+ pub fn has_same_self_type(&self, other: &SemanticsScope<'_>) -> bool {
+ self.resolver.impl_def() == other.resolver.impl_def()
+ }
}
#[derive(Debug)]