Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/mod_path.rs')
| -rw-r--r-- | crates/hir-expand/src/mod_path.rs | 85 |
1 files changed, 59 insertions, 26 deletions
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index 69aa09c4a5..9534b5039f 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -7,11 +7,11 @@ use std::{ use crate::{ db::ExpandDatabase, - hygiene::Hygiene, - name::{known, Name}, + hygiene::{marks_rev, SyntaxContextExt, Transparency}, + name::{known, AsName, Name}, + span::SpanMapRef, }; -use base_db::CrateId; -use either::Either; +use base_db::{span::SyntaxContextId, CrateId}; use smallvec::SmallVec; use syntax::{ast, AstNode}; @@ -38,6 +38,7 @@ pub enum PathKind { Crate, /// Absolute path (::foo) Abs, + // FIXME: Remove this /// `$crate` from macro expansion DollarCrate(CrateId), } @@ -46,9 +47,9 @@ impl ModPath { pub fn from_src( db: &dyn ExpandDatabase, path: ast::Path, - hygiene: &Hygiene, + span_map: SpanMapRef<'_>, ) -> Option<ModPath> { - convert_path(db, None, path, hygiene) + convert_path(db, None, path, span_map) } pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath { @@ -193,33 +194,36 @@ fn convert_path( db: &dyn ExpandDatabase, prefix: Option<ModPath>, path: ast::Path, - hygiene: &Hygiene, + span_map: SpanMapRef<'_>, ) -> Option<ModPath> { let prefix = match path.qualifier() { - Some(qual) => Some(convert_path(db, prefix, qual, hygiene)?), + Some(qual) => Some(convert_path(db, prefix, qual, span_map)?), None => prefix, }; let segment = path.segment()?; let mut mod_path = match segment.kind()? { ast::PathSegmentKind::Name(name_ref) => { - match hygiene.name_ref_to_name(db, name_ref) { - Either::Left(name) => { - // no type args in use - let mut res = prefix.unwrap_or_else(|| { - ModPath::from_kind( - segment.coloncolon_token().map_or(PathKind::Plain, |_| PathKind::Abs), - ) - }); - res.segments.push(name); - res - } - Either::Right(crate_id) => { - return Some(ModPath::from_segments( - PathKind::DollarCrate(crate_id), - iter::empty(), - )) + if name_ref.text() == "$crate" { + if prefix.is_some() { + return None; } + ModPath::from_kind( + resolve_crate_root( + db, + span_map.span_for_range(name_ref.syntax().text_range()).ctx, + ) + .map(PathKind::DollarCrate) + .unwrap_or(PathKind::Crate), + ) + } else { + let mut res = prefix.unwrap_or_else(|| { + ModPath::from_kind( + segment.coloncolon_token().map_or(PathKind::Plain, |_| PathKind::Abs), + ) + }); + res.segments.push(name_ref.as_name()); + res } } ast::PathSegmentKind::SelfTypeKw => { @@ -261,8 +265,14 @@ fn convert_path( // We follow what it did anyway :) if mod_path.segments.len() == 1 && mod_path.kind == PathKind::Plain { if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) { - if let Some(crate_id) = hygiene.local_inner_macros(db, path) { - mod_path.kind = PathKind::DollarCrate(crate_id); + let syn_ctx = span_map.span_for_range(segment.syntax().text_range()).ctx; + if let Some(macro_call_id) = db.lookup_intern_syntax_context(syn_ctx).outer_expn { + if db.lookup_intern_macro_call(macro_call_id).def.local_inner { + mod_path.kind = match resolve_crate_root(db, syn_ctx) { + Some(crate_root) => PathKind::DollarCrate(crate_root), + None => PathKind::Crate, + } + } } } } @@ -270,6 +280,29 @@ fn convert_path( Some(mod_path) } +pub fn resolve_crate_root(db: &dyn ExpandDatabase, mut ctxt: SyntaxContextId) -> Option<CrateId> { + // When resolving `$crate` from a `macro_rules!` invoked in a `macro`, + // we don't want to pretend that the `macro_rules!` definition is in the `macro` + // as described in `SyntaxContext::apply_mark`, so we ignore prepended opaque marks. + // FIXME: This is only a guess and it doesn't work correctly for `macro_rules!` + // definitions actually produced by `macro` and `macro` definitions produced by + // `macro_rules!`, but at least such configurations are not stable yet. + ctxt = ctxt.normalize_to_macro_rules(db); + let mut iter = marks_rev(ctxt, db).peekable(); + let mut result_mark = None; + // Find the last opaque mark from the end if it exists. + while let Some(&(mark, Transparency::Opaque)) = iter.peek() { + result_mark = Some(mark); + iter.next(); + } + // Then find the last semi-transparent mark from the end if it exists. + while let Some((mark, Transparency::SemiTransparent)) = iter.next() { + result_mark = Some(mark); + } + + result_mark.flatten().map(|call| db.lookup_intern_macro_call(call.into()).def.krate) +} + pub use crate::name as __name; #[macro_export] |