diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index 38c16503cc0..4aeb714189a 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs @@ -713,7 +713,7 @@ fn from_src( hygiene: &Hygiene, id: AttrId, ) -> Option { - let path = Interned::new(ModPath::from_src(db, ast.path()?, hygiene)?); + let path = Interned::new(ModPath::from_src(db.upcast(), ast.path()?, hygiene)?); let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { let value = match lit.kind() { ast::LiteralKind::String(string) => string.value()?.into(), diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs index 3251638c7aa..22d593a7d97 100644 --- a/crates/hir_def/src/find_path.rs +++ b/crates/hir_def/src/find_path.rs @@ -35,7 +35,12 @@ pub fn find_path_prefixed( const MAX_PATH_LEN: usize = 15; -impl ModPath { +trait ModPathExt { + fn starts_with_std(&self) -> bool; + fn can_start_with_std(&self) -> bool; +} + +impl ModPathExt for ModPath { fn starts_with_std(&self) -> bool { self.segments().first() == Some(&known::std) } diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index f31fcf0d97b..4547bfc55e6 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs @@ -502,7 +502,7 @@ fn lower_extern_crate( } fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option> { - let path = Interned::new(ModPath::from_src(self.db, m.path()?, self.hygiene())?); + let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, self.hygiene())?); let ast_id = self.source_ast_id_map.ast_id(m); let expand_to = hir_expand::ExpandTo::from_call_site(m); let res = MacroCall { path, ast_id, expand_to }; @@ -769,7 +769,7 @@ fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option { // E.g. `use something::{inner}` (prefix is `None`, path is `something`) // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`) Some(path) => { - match ModPath::from_src(self.db, path, self.hygiene) { + match ModPath::from_src(self.db.upcast(), path, self.hygiene) { Some(it) => Some(it), None => return None, // FIXME: report errors somewhere } @@ -788,7 +788,7 @@ fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option { } else { let is_glob = tree.star_token().is_some(); let path = match tree.path() { - Some(path) => Some(ModPath::from_src(self.db, path, self.hygiene)?), + Some(path) => Some(ModPath::from_src(self.db.upcast(), path, self.hygiene)?), None => None, }; let alias = tree.rename().map(|a| { diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index c38b498b9c6..db1e65d080e 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs @@ -64,11 +64,11 @@ macro_rules! eprintln { eager::{expand_eager_macro, ErrorEmitted, ErrorSink}, hygiene::Hygiene, AstId, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, + UnresolvedMacro, }; use item_tree::ExternBlock; use la_arena::Idx; use nameres::DefMap; -use path::ModPath; use stdx::impl_from; use syntax::ast; @@ -677,7 +677,8 @@ fn as_call_id_with_errors( let expands_to = hir_expand::ExpandTo::from_call_site(self.value); let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); let h = Hygiene::new(db.upcast(), self.file_id); - let path = self.value.path().and_then(|path| path::ModPath::from_src(db, path, &h)); + let path = + self.value.path().and_then(|path| path::ModPath::from_src(db.upcast(), path, &h)); let path = match error_sink .option(path, || mbe::ExpandError::Other("malformed macro invocation".into())) @@ -712,11 +713,6 @@ fn new(file_id: HirFileId, ast_id: FileAstId, path: path::ModPath) -> AstIdWi } } -#[derive(Debug)] -pub struct UnresolvedMacro { - pub path: ModPath, -} - fn macro_call_as_call_id( call: &AstIdWithPath, expand_to: ExpandTo, @@ -730,16 +726,8 @@ fn macro_call_as_call_id( let res = if let MacroDefKind::BuiltInEager(..) = def.kind { let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast())); - let hygiene = Hygiene::new(db.upcast(), call.ast_id.file_id); - expand_eager_macro( - db.upcast(), - krate, - macro_call, - def, - &|path: ast::Path| resolver(path::ModPath::from_src(db, path, &hygiene)?), - error_sink, - ) + expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver, error_sink)? } else { Ok(def.as_lazy_macro( db.upcast(), diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs index fbb1320d6e8..5d770ecab0e 100644 --- a/crates/hir_def/src/nameres/tests/macros.rs +++ b/crates/hir_def/src/nameres/tests/macros.rs @@ -1046,3 +1046,71 @@ macro_rules! m { "#]], ) } + +#[test] +fn eager_macro_correctly_resolves_contents() { + // Eager macros resolve any contained macros when expanded. This should work correctly with the + // usual name resolution rules, so both of these `include!`s should include the right file. + + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +include!(inner_a!()); +include!(crate::inner_b!()); + +#[macro_export] +macro_rules! inner_a { + () => { "inc_a.rs" }; +} +#[macro_export] +macro_rules! inner_b { + () => { "inc_b.rs" }; +} +//- /inc_a.rs +struct A; +//- /inc_b.rs +struct B; +"#, + expect![[r#" + crate + A: t v + B: t v + inner_a: m + inner_b: m + "#]], + ); +} + +#[test] +fn eager_macro_correctly_resolves_dollar_crate() { + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +#[macro_export] +macro_rules! inner { + () => { "inc.rs" }; +} + +macro_rules! m { + () => { include!($crate::inner!()); }; +} + +m!(); + +//- /inc.rs +struct A; +"#, + expect![[r#" + crate + inner: m + "#]], + ); + + // FIXME: This currently fails. The result should contain `A: t v`. +} diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs index 5bb8afa5cc9..fc81b88db3f 100644 --- a/crates/hir_def/src/path.rs +++ b/crates/hir_def/src/path.rs @@ -6,33 +6,13 @@ iter, }; -use crate::{body::LowerCtx, db::DefDatabase, intern::Interned, type_ref::LifetimeRef}; -use base_db::CrateId; -use hir_expand::{ - hygiene::Hygiene, - name::{name, Name}, -}; +use crate::{body::LowerCtx, intern::Interned, type_ref::LifetimeRef}; +use hir_expand::name::{name, Name}; use syntax::ast; use crate::type_ref::{TypeBound, TypeRef}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ModPath { - pub kind: PathKind, - segments: Vec, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum PathKind { - Plain, - /// `self::` is `Super(0)` - Super(u8), - Crate, - /// Absolute path (::foo) - Abs, - /// `$crate` from macro expansion - DollarCrate(CrateId), -} +pub use hir_expand::mod_path::{path, ModPath, PathKind}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportAlias { @@ -51,67 +31,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -impl ModPath { - pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option { - lower::convert_path(db, None, path, hygiene) - } - - pub fn from_segments(kind: PathKind, segments: impl IntoIterator) -> ModPath { - let segments = segments.into_iter().collect::>(); - ModPath { kind, segments } - } - - /// Creates a `ModPath` from a `PathKind`, with no extra path segments. - pub const fn from_kind(kind: PathKind) -> ModPath { - ModPath { kind, segments: Vec::new() } - } - - pub fn segments(&self) -> &[Name] { - &self.segments - } - - pub fn push_segment(&mut self, segment: Name) { - self.segments.push(segment); - } - - pub fn pop_segment(&mut self) -> Option { - self.segments.pop() - } - - /// Returns the number of segments in the path (counting special segments like `$crate` and - /// `super`). - pub fn len(&self) -> usize { - self.segments.len() - + match self.kind { - PathKind::Plain => 0, - PathKind::Super(i) => i as usize, - PathKind::Crate => 1, - PathKind::Abs => 0, - PathKind::DollarCrate(_) => 1, - } - } - - pub fn is_ident(&self) -> bool { - self.as_ident().is_some() - } - - pub fn is_self(&self) -> bool { - self.kind == PathKind::Super(0) && self.segments.is_empty() - } - - /// If this path is a single identifier, like `foo`, return its name. - pub fn as_ident(&self) -> Option<&Name> { - if self.kind != PathKind::Plain { - return None; - } - - match &*self.segments { - [name] => Some(name), - _ => None, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { /// Type based path like `::foo`. @@ -185,10 +104,7 @@ pub fn type_anchor(&self) -> Option<&TypeRef> { } pub fn segments(&self) -> PathSegments<'_> { - PathSegments { - segments: self.mod_path.segments.as_slice(), - generic_args: &self.generic_args, - } + PathSegments { segments: self.mod_path.segments(), generic_args: &self.generic_args } } pub fn mod_path(&self) -> &ModPath { @@ -203,7 +119,7 @@ pub fn qualifier(&self) -> Option { type_anchor: self.type_anchor.clone(), mod_path: Interned::new(ModPath::from_segments( self.mod_path.kind.clone(), - self.mod_path.segments[..self.mod_path.segments.len() - 1].iter().cloned(), + self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(), )), generic_args: self.generic_args[..self.generic_args.len() - 1].to_vec().into(), }; @@ -296,76 +212,3 @@ fn from(name: Name) -> Box { Box::new(Path::from(name)) } } - -impl From for ModPath { - fn from(name: Name) -> ModPath { - ModPath::from_segments(PathKind::Plain, iter::once(name)) - } -} - -impl Display for ModPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut first_segment = true; - let mut add_segment = |s| -> fmt::Result { - if !first_segment { - f.write_str("::")?; - } - first_segment = false; - f.write_str(s)?; - Ok(()) - }; - match self.kind { - PathKind::Plain => {} - PathKind::Super(0) => add_segment("self")?, - PathKind::Super(n) => { - for _ in 0..n { - add_segment("super")?; - } - } - PathKind::Crate => add_segment("crate")?, - PathKind::Abs => add_segment("")?, - PathKind::DollarCrate(_) => add_segment("$crate")?, - } - for segment in &self.segments { - if !first_segment { - f.write_str("::")?; - } - first_segment = false; - write!(f, "{}", segment)?; - } - Ok(()) - } -} - -pub use hir_expand::name as __name; - -#[macro_export] -macro_rules! __known_path { - (core::iter::IntoIterator) => {}; - (core::iter::Iterator) => {}; - (core::result::Result) => {}; - (core::option::Option) => {}; - (core::ops::Range) => {}; - (core::ops::RangeFrom) => {}; - (core::ops::RangeFull) => {}; - (core::ops::RangeTo) => {}; - (core::ops::RangeToInclusive) => {}; - (core::ops::RangeInclusive) => {}; - (core::future::Future) => {}; - (core::ops::Try) => {}; - ($path:path) => { - compile_error!("Please register your known path in the path module") - }; -} - -#[macro_export] -macro_rules! __path { - ($start:ident $(:: $seg:ident)*) => ({ - $crate::__known_path!($start $(:: $seg)*); - $crate::path::ModPath::from_segments($crate::path::PathKind::Abs, vec![ - $crate::path::__name![$start], $($crate::path::__name![$seg],)* - ]) - }); -} - -pub use crate::__path as path; diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs index 3abc48d95df..49e55e03d11 100644 --- a/crates/hir_def/src/path/lower.rs +++ b/crates/hir_def/src/path/lower.rs @@ -1,7 +1,5 @@ //! Transforms syntax into `Path` objects, ideally with accounting for hygiene -mod lower_use; - use crate::intern::Interned; use either::Either; @@ -15,8 +13,6 @@ type_ref::{LifetimeRef, TypeBound, TypeRef}, }; -pub(super) use lower_use::convert_path; - /// Converts an `ast::Path` to `Path`. Works with use trees. /// It correctly handles `$crate` based path from macro call. pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx) -> Option { @@ -72,10 +68,10 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx) -> Option { Some(trait_ref) => { let Path { mod_path, generic_args: path_generic_args, .. } = Path::from_src(trait_ref.path()?, ctx)?; - let num_segments = mod_path.segments.len(); + let num_segments = mod_path.segments().len(); kind = mod_path.kind; - segments.extend(mod_path.segments.iter().cloned().rev()); + segments.extend(mod_path.segments().iter().cloned().rev()); generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); // Insert the type reference (T in the above example) as Self parameter for the trait diff --git a/crates/hir_def/src/path/lower/lower_use.rs b/crates/hir_def/src/path/lower/lower_use.rs deleted file mode 100644 index 6b777cfeff4..00000000000 --- a/crates/hir_def/src/path/lower/lower_use.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Lowers a single complex use like `use foo::{bar, baz};` into a list of paths like -//! `foo::bar`, `foo::baz`; - -use std::iter; - -use either::Either; -use hir_expand::hygiene::Hygiene; -use syntax::{ast, AstNode}; - -use crate::{ - db::DefDatabase, - path::{ModPath, PathKind}, -}; - -pub(crate) fn convert_path( - db: &dyn DefDatabase, - prefix: Option, - path: ast::Path, - hygiene: &Hygiene, -) -> Option { - let prefix = match path.qualifier() { - Some(qual) => Some(convert_path(db, prefix, qual, hygiene)?), - 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.upcast(), 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(), - )) - } - } - } - ast::PathSegmentKind::CrateKw => { - if prefix.is_some() { - return None; - } - ModPath::from_segments(PathKind::Crate, iter::empty()) - } - ast::PathSegmentKind::SelfKw => { - if prefix.is_some() { - return None; - } - ModPath::from_segments(PathKind::Super(0), iter::empty()) - } - ast::PathSegmentKind::SuperKw => { - let nested_super_count = match prefix.map(|p| p.kind) { - Some(PathKind::Super(n)) => n, - Some(_) => return None, - None => 0, - }; - - ModPath::from_segments(PathKind::Super(nested_super_count + 1), iter::empty()) - } - ast::PathSegmentKind::Type { .. } => { - // not allowed in imports - return None; - } - }; - - // handle local_inner_macros : - // Basically, even in rustc it is quite hacky: - // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456 - // 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.upcast(), path) { - mod_path.kind = PathKind::DollarCrate(crate_id); - } - } - } - - Some(mod_path) -} diff --git a/crates/hir_def/src/visibility.rs b/crates/hir_def/src/visibility.rs index 7d335582ed0..80009010c52 100644 --- a/crates/hir_def/src/visibility.rs +++ b/crates/hir_def/src/visibility.rs @@ -56,7 +56,7 @@ pub(crate) fn from_ast_with_hygiene_and_default( }; match node.kind() { ast::VisibilityKind::In(path) => { - let path = ModPath::from_src(db, path, hygiene); + let path = ModPath::from_src(db.upcast(), path, hygiene); let path = match path { None => return RawVisibility::private(), Some(path) => path, diff --git a/crates/hir_expand/src/eager.rs b/crates/hir_expand/src/eager.rs index f2586fa9093..faeb6bebabb 100644 --- a/crates/hir_expand/src/eager.rs +++ b/crates/hir_expand/src/eager.rs @@ -27,8 +27,10 @@ use crate::{ ast::{self, AstNode}, db::AstDatabase, + hygiene::Hygiene, + mod_path::ModPath, EagerCallInfo, ExpandTo, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, - MacroDefKind, + MacroDefKind, UnresolvedMacro, }; #[derive(Debug)] @@ -94,18 +96,14 @@ fn emit(&mut self, err: mbe::ExpandError) { } } -fn err(msg: impl Into) -> mbe::ExpandError { - mbe::ExpandError::Other(msg.into()) -} - pub fn expand_eager_macro( db: &dyn AstDatabase, krate: CrateId, macro_call: InFile, def: MacroDefId, - resolver: &dyn Fn(ast::Path) -> Option, + resolver: &dyn Fn(ModPath) -> Option, diagnostic_sink: &mut dyn FnMut(mbe::ExpandError), -) -> Result { +) -> Result, UnresolvedMacro> { let parsed_args = macro_call .value .token_tree() @@ -129,16 +127,19 @@ pub fn expand_eager_macro( }), kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr }, }); - let arg_file_id = arg_id; let parsed_args = mbe::token_tree_to_syntax_node(&parsed_args, mbe::TopEntryPoint::Expr).0; - let result = eager_macro_recur( + let result = match eager_macro_recur( db, - InFile::new(arg_file_id.as_file(), parsed_args.syntax_node()), + InFile::new(arg_id.as_file(), parsed_args.syntax_node()), krate, resolver, diagnostic_sink, - )?; + ) { + Ok(Ok(it)) => it, + Ok(Err(err)) => return Ok(Err(err)), + Err(err) => return Err(err), + }; let subtree = to_subtree(&result); if let MacroDefKind::BuiltInEager(eager, _) = def.kind { @@ -157,7 +158,7 @@ pub fn expand_eager_macro( kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, }; - Ok(db.intern_macro_call(loc)) + Ok(Ok(db.intern_macro_call(loc))) } else { panic!("called `expand_eager_macro` on non-eager macro def {:?}", def); } @@ -194,9 +195,10 @@ fn eager_macro_recur( db: &dyn AstDatabase, curr: InFile, krate: CrateId, - macro_resolver: &dyn Fn(ast::Path) -> Option, + macro_resolver: &dyn Fn(ModPath) -> Option, mut diagnostic_sink: &mut dyn FnMut(mbe::ExpandError), -) -> Result { +) -> Result, UnresolvedMacro> { + let hygiene = Hygiene::new(db, curr.file_id); let original = curr.value.clone_for_update(); let children = original.descendants().filter_map(ast::MacroCall::cast); @@ -204,23 +206,27 @@ fn eager_macro_recur( // Collect replacement for child in children { - let def = diagnostic_sink.option_with( - || macro_resolver(child.path()?), - || { - let path = child.path().map(|path| format!(" `{}!`", path)).unwrap_or_default(); - err(format!("failed to resolve macro{}", path)) - }, - )?; + let def = match child.path().and_then(|path| ModPath::from_src(db, path, &hygiene)) { + Some(path) => macro_resolver(path.clone()).ok_or_else(|| UnresolvedMacro { path })?, + None => { + diagnostic_sink(mbe::ExpandError::Other("malformed macro invocation".into())); + continue; + } + }; let insert = match def.kind { MacroDefKind::BuiltInEager(..) => { - let id = expand_eager_macro( + let id = match expand_eager_macro( db, krate, curr.with_value(child.clone()), def, macro_resolver, diagnostic_sink, - )?; + ) { + Ok(Ok(it)) => it, + Ok(Err(err)) => return Ok(Err(err)), + Err(err) => return Err(err), + }; db.parse_or_expand(id.as_file()) .expect("successful macro expansion should be parseable") .clone_for_update() @@ -231,21 +237,28 @@ fn eager_macro_recur( | MacroDefKind::BuiltInDerive(..) | MacroDefKind::ProcMacro(..) => { let res = lazy_expand(db, &def, curr.with_value(child.clone()), krate); - let val = diagnostic_sink.expand_result_option(res)?; + let val = match diagnostic_sink.expand_result_option(res) { + Ok(it) => it, + Err(err) => return Ok(Err(err)), + }; // replace macro inside - eager_macro_recur(db, val, krate, macro_resolver, diagnostic_sink)? + match eager_macro_recur(db, val, krate, macro_resolver, diagnostic_sink) { + Ok(Ok(it)) => it, + Ok(Err(err)) => return Ok(Err(err)), + Err(err) => return Err(err), + } } }; // check if the whole original syntax is replaced if child.syntax() == &original { - return Ok(insert); + return Ok(Ok(insert)); } replacements.push((child, insert)); } replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new)); - Ok(original) + Ok(Ok(original)) } diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index b455fd59172..a1b1e943f12 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -14,11 +14,13 @@ pub mod proc_macro; pub mod quote; pub mod eager; +pub mod mod_path; use base_db::ProcMacroKind; use either::Either; pub use mbe::{ExpandError, ExpandResult, Origin}; +use mod_path::ModPath; use std::{hash::Hash, iter, sync::Arc}; @@ -835,3 +837,8 @@ pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo { } } } + +#[derive(Debug)] +pub struct UnresolvedMacro { + pub path: ModPath, +} diff --git a/crates/hir_expand/src/mod_path.rs b/crates/hir_expand/src/mod_path.rs new file mode 100644 index 00000000000..9c06a3f892a --- /dev/null +++ b/crates/hir_expand/src/mod_path.rs @@ -0,0 +1,238 @@ +//! A lowering for `use`-paths (more generally, paths without angle-bracketed segments). + +use std::{ + fmt::{self, Display}, + iter, +}; + +use crate::{db::AstDatabase, hygiene::Hygiene, name::Name}; +use base_db::CrateId; +use either::Either; +use syntax::{ast, AstNode}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ModPath { + pub kind: PathKind, + segments: Vec, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PathKind { + Plain, + /// `self::` is `Super(0)` + Super(u8), + Crate, + /// Absolute path (::foo) + Abs, + /// `$crate` from macro expansion + DollarCrate(CrateId), +} + +impl ModPath { + pub fn from_src(db: &dyn AstDatabase, path: ast::Path, hygiene: &Hygiene) -> Option { + convert_path(db, None, path, hygiene) + } + + pub fn from_segments(kind: PathKind, segments: impl IntoIterator) -> ModPath { + let segments = segments.into_iter().collect::>(); + ModPath { kind, segments } + } + + /// Creates a `ModPath` from a `PathKind`, with no extra path segments. + pub const fn from_kind(kind: PathKind) -> ModPath { + ModPath { kind, segments: Vec::new() } + } + + pub fn segments(&self) -> &[Name] { + &self.segments + } + + pub fn push_segment(&mut self, segment: Name) { + self.segments.push(segment); + } + + pub fn pop_segment(&mut self) -> Option { + self.segments.pop() + } + + /// Returns the number of segments in the path (counting special segments like `$crate` and + /// `super`). + pub fn len(&self) -> usize { + self.segments.len() + + match self.kind { + PathKind::Plain => 0, + PathKind::Super(i) => i as usize, + PathKind::Crate => 1, + PathKind::Abs => 0, + PathKind::DollarCrate(_) => 1, + } + } + + pub fn is_ident(&self) -> bool { + self.as_ident().is_some() + } + + pub fn is_self(&self) -> bool { + self.kind == PathKind::Super(0) && self.segments.is_empty() + } + + /// If this path is a single identifier, like `foo`, return its name. + pub fn as_ident(&self) -> Option<&Name> { + if self.kind != PathKind::Plain { + return None; + } + + match &*self.segments { + [name] => Some(name), + _ => None, + } + } +} + +impl Display for ModPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut first_segment = true; + let mut add_segment = |s| -> fmt::Result { + if !first_segment { + f.write_str("::")?; + } + first_segment = false; + f.write_str(s)?; + Ok(()) + }; + match self.kind { + PathKind::Plain => {} + PathKind::Super(0) => add_segment("self")?, + PathKind::Super(n) => { + for _ in 0..n { + add_segment("super")?; + } + } + PathKind::Crate => add_segment("crate")?, + PathKind::Abs => add_segment("")?, + PathKind::DollarCrate(_) => add_segment("$crate")?, + } + for segment in &self.segments { + if !first_segment { + f.write_str("::")?; + } + first_segment = false; + write!(f, "{}", segment)?; + } + Ok(()) + } +} + +impl From for ModPath { + fn from(name: Name) -> ModPath { + ModPath::from_segments(PathKind::Plain, iter::once(name)) + } +} + +fn convert_path( + db: &dyn AstDatabase, + prefix: Option, + path: ast::Path, + hygiene: &Hygiene, +) -> Option { + let prefix = match path.qualifier() { + Some(qual) => Some(convert_path(db, prefix, qual, hygiene)?), + 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(), + )) + } + } + } + ast::PathSegmentKind::CrateKw => { + if prefix.is_some() { + return None; + } + ModPath::from_segments(PathKind::Crate, iter::empty()) + } + ast::PathSegmentKind::SelfKw => { + if prefix.is_some() { + return None; + } + ModPath::from_segments(PathKind::Super(0), iter::empty()) + } + ast::PathSegmentKind::SuperKw => { + let nested_super_count = match prefix.map(|p| p.kind) { + Some(PathKind::Super(n)) => n, + Some(_) => return None, + None => 0, + }; + + ModPath::from_segments(PathKind::Super(nested_super_count + 1), iter::empty()) + } + ast::PathSegmentKind::Type { .. } => { + // not allowed in imports + return None; + } + }; + + // handle local_inner_macros : + // Basically, even in rustc it is quite hacky: + // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456 + // 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); + } + } + } + + Some(mod_path) +} + +pub use crate::name as __name; + +#[macro_export] +macro_rules! __known_path { + (core::iter::IntoIterator) => {}; + (core::iter::Iterator) => {}; + (core::result::Result) => {}; + (core::option::Option) => {}; + (core::ops::Range) => {}; + (core::ops::RangeFrom) => {}; + (core::ops::RangeFull) => {}; + (core::ops::RangeTo) => {}; + (core::ops::RangeToInclusive) => {}; + (core::ops::RangeInclusive) => {}; + (core::future::Future) => {}; + (core::ops::Try) => {}; + ($path:path) => { + compile_error!("Please register your known path in the path module") + }; +} + +#[macro_export] +macro_rules! __path { + ($start:ident $(:: $seg:ident)*) => ({ + $crate::__known_path!($start $(:: $seg)*); + $crate::mod_path::ModPath::from_segments($crate::mod_path::PathKind::Abs, vec![ + $crate::mod_path::__name![$start], $($crate::mod_path::__name![$seg],)* + ]) + }); +} + +pub use crate::__path as path;