about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJonas Schievink <jonas.schievink@ferrous-systems.com>2022-01-26 18:31:07 +0100
committerJonas Schievink <jonas.schievink@ferrous-systems.com>2022-01-26 18:31:07 +0100
commit35e5c3b3f9075c86d950cd774db3b904be0aa478 (patch)
treed602351e47f3d436dca5447ed87139677c13ab39
parent1f0c20e8ba6e48a14f50e77ce1a472a6543182b7 (diff)
downloadrust-35e5c3b3f9075c86d950cd774db3b904be0aa478.tar.gz
rust-35e5c3b3f9075c86d950cd774db3b904be0aa478.zip
Fix resolution of eager macro contents
-rw-r--r--crates/hir_def/src/attr.rs2
-rw-r--r--crates/hir_def/src/find_path.rs7
-rw-r--r--crates/hir_def/src/item_tree/lower.rs6
-rw-r--r--crates/hir_def/src/lib.rs20
-rw-r--r--crates/hir_def/src/nameres/tests/macros.rs68
-rw-r--r--crates/hir_def/src/path.rs167
-rw-r--r--crates/hir_def/src/path/lower.rs8
-rw-r--r--crates/hir_def/src/path/lower/lower_use.rs88
-rw-r--r--crates/hir_def/src/visibility.rs2
-rw-r--r--crates/hir_expand/src/eager.rs67
-rw-r--r--crates/hir_expand/src/lib.rs7
-rw-r--r--crates/hir_expand/src/mod_path.rs238
12 files changed, 375 insertions, 305 deletions
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 @@ impl Attr {
         hygiene: &Hygiene,
         id: AttrId,
     ) -> Option<Attr> {
-        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 @@ impl<'a> Ctx<'a> {
     }
 
     fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
-        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 @@ impl UseTreeLowering<'_> {
                 // 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 @@ impl UseTreeLowering<'_> {
         } 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 @@ use hir_expand::{
     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 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
         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 @@ impl<T: ast::AstNode> AstIdWithPath<T> {
     }
 }
 
-#[derive(Debug)]
-pub struct UnresolvedMacro {
-    pub path: ModPath,
-}
-
 fn macro_call_as_call_id(
     call: &AstIdWithPath<ast::MacroCall>,
     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 @@ 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 @@ use std::{
     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<Name>,
-}
-
-#[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 @@ impl Display for ImportAlias {
     }
 }
 
-impl ModPath {
-    pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> {
-        lower::convert_path(db, None, path, hygiene)
-    }
-
-    pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath {
-        let segments = segments.into_iter().collect::<Vec<_>>();
-        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<Name> {
-        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 `<T>::foo`.
@@ -185,10 +104,7 @@ impl Path {
     }
 
     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 @@ impl Path {
             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 @@ impl From<Name> for Box<Path> {
         Box::new(Path::from(name))
     }
 }
-
-impl From<Name> 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 @@ use crate::{
     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<Path> {
@@ -72,10 +68,10 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx) -> Option<Path> {
                     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<ModPath>,
-    path: ast::Path,
-    hygiene: &Hygiene,
-) -> Option<ModPath> {
-    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 @@ impl RawVisibility {
         };
         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 syntax::{ted, SyntaxNode};
 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 @@ impl ErrorSink for &'_ mut dyn FnMut(mbe::ExpandError) {
     }
 }
 
-fn err(msg: impl Into<String>) -> mbe::ExpandError {
-    mbe::ExpandError::Other(msg.into())
-}
-
 pub fn expand_eager_macro(
     db: &dyn AstDatabase,
     krate: CrateId,
     macro_call: InFile<ast::MacroCall>,
     def: MacroDefId,
-    resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
+    resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
     diagnostic_sink: &mut dyn FnMut(mbe::ExpandError),
-) -> Result<MacroCallId, ErrorEmitted> {
+) -> Result<Result<MacroCallId, ErrorEmitted>, 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<SyntaxNode>,
     krate: CrateId,
-    macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
+    macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
     mut diagnostic_sink: &mut dyn FnMut(mbe::ExpandError),
-) -> Result<SyntaxNode, ErrorEmitted> {
+) -> Result<Result<SyntaxNode, ErrorEmitted>, 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 builtin_fn_macro;
 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 @@ impl 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<Name>,
+}
+
+#[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<ModPath> {
+        convert_path(db, None, path, hygiene)
+    }
+
+    pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath {
+        let segments = segments.into_iter().collect::<Vec<_>>();
+        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<Name> {
+        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<Name> for ModPath {
+    fn from(name: Name) -> ModPath {
+        ModPath::from_segments(PathKind::Plain, iter::once(name))
+    }
+}
+
+fn convert_path(
+    db: &dyn AstDatabase,
+    prefix: Option<ModPath>,
+    path: ast::Path,
+    hygiene: &Hygiene,
+) -> Option<ModPath> {
+    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;