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 | 93 |
1 files changed, 69 insertions, 24 deletions
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index 69aa09c4a5..9396b08b28 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::{SyntaxContextExt, Transparency}, + name::{known, AsName, Name}, + SpanMap, }; -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,7 +47,7 @@ impl ModPath { pub fn from_src( db: &dyn ExpandDatabase, path: ast::Path, - hygiene: &Hygiene, + hygiene: &SpanMap, ) -> Option<ModPath> { convert_path(db, None, path, hygiene) } @@ -193,7 +194,7 @@ fn convert_path( db: &dyn ExpandDatabase, prefix: Option<ModPath>, path: ast::Path, - hygiene: &Hygiene, + hygiene: &SpanMap, ) -> Option<ModPath> { let prefix = match path.qualifier() { Some(qual) => Some(convert_path(db, prefix, qual, hygiene)?), @@ -203,23 +204,26 @@ fn convert_path( 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; } + resolve_crate_root( + db, + hygiene + .span_for_range(name_ref.syntax().text_range()) + .map_or(SyntaxContextId::ROOT, |s| s.ctx), + ) + .map(PathKind::DollarCrate) + .map(ModPath::from_kind)? + } 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,15 @@ 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 = hygiene + .span_for_range(segment.syntax().text_range()) + .map_or(SyntaxContextId::ROOT, |s| s.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 { + if let Some(crate_root) = resolve_crate_root(db, syn_ctx) { + mod_path.kind = PathKind::DollarCrate(crate_root); + } + } } } } @@ -270,6 +281,40 @@ 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 = ctxt.marks(db).into_iter().rev().peekable(); + let mut result_mark = None; + // Find the last opaque mark from the end if it exists. + while let Some(&(mark, transparency)) = iter.peek() { + if transparency == Transparency::Opaque { + result_mark = Some(mark); + iter.next(); + } else { + break; + } + } + // Then find the last semi-transparent mark from the end if it exists. + for (mark, transparency) in iter { + if transparency == Transparency::SemiTransparent { + result_mark = Some(mark); + } else { + break; + } + } + + match result_mark { + Some(Some(call)) => Some(db.lookup_intern_macro_call(call.into()).def.krate), + Some(None) | None => None, + } +} + pub use crate::name as __name; #[macro_export] |