Unnamed repository; edit this file 'description' to name the repository.
Merge #11347
11347: fix: Fix resolution of eager macro contents r=jonas-schievink a=jonas-schievink Eager macros resolve and expand any contained macro invocations before they are expanded. The logic for this was previously pretty broken: any nameres failure would be reported as a generic macro expansion error, so this didn't work correctly with the fixed-point resolution loop. This manifested as spurious errors whenever a non-legacy macro was used in an eager macro (that means *any* path with more than one segment). After an intense staring contest with the abyss, this PR fixes the basic logic, but some bugs still remain (particularly around `$crate`). As a side-effect, this PR moves `ModPath` into `hir_expand`. bors r+ Co-authored-by: Jonas Schievink <[email protected]>
bors[bot] 2022-01-27
parent 1f0c20e · parent 35e5c3b · commit 28b65d3
-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 38c16503cc..4aeb714189 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 3251638c7a..22d593a7d9 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 f31fcf0d97..4547bfc55e 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 c38b498b9c..db1e65d080 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 fbb1320d6e..5d770ecab0 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 5bb8afa5cc..fc81b88db3 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 3abc48d95d..49e55e03d1 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 6b777cfeff..0000000000
--- 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 7d335582ed..80009010c5 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 f2586fa909..faeb6bebab 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 b455fd5917..a1b1e943f1 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 0000000000..9c06a3f892
--- /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;