Auto merge of #14584 - Veykril:macro-def-err, r=Veykril

internal: Report item-level macro expansion syntax errors
This commit is contained in:
bors 2023-04-16 17:40:20 +00:00
commit 697b335fda
22 changed files with 327 additions and 325 deletions

View file

@ -21,7 +21,7 @@ use limit::Limit;
use once_cell::unsync::OnceCell;
use profile::Count;
use rustc_hash::FxHashMap;
use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr};
use syntax::{ast, AstPtr, Parse, SyntaxNode, SyntaxNodePtr};
use crate::{
attr::Attrs,
@ -137,7 +137,8 @@ impl Expander {
&mut self,
db: &dyn DefDatabase,
macro_call: ast::MacroCall,
) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro> {
// FIXME: within_limit should support this, instead of us having to extract the error
let mut unresolved_macro_err = None;
let result = self.within_limit(db, |this| {
@ -146,22 +147,13 @@ impl Expander {
let resolver =
|path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it));
let mut err = None;
let call_id = match macro_call.as_call_id_with_errors(
db,
this.def_map.krate(),
resolver,
&mut |e| {
err.get_or_insert(e);
},
) {
match macro_call.as_call_id_with_errors(db, this.def_map.krate(), resolver) {
Ok(call_id) => call_id,
Err(resolve_err) => {
unresolved_macro_err = Some(resolve_err);
return ExpandResult { value: None, err: None };
ExpandResult { value: None, err: None }
}
}
};
ExpandResult { value: call_id.ok(), err }
});
if let Some(err) = unresolved_macro_err {
@ -175,37 +167,37 @@ impl Expander {
&mut self,
db: &dyn DefDatabase,
call_id: MacroCallId,
) -> ExpandResult<Option<(Mark, T)>> {
) -> ExpandResult<Option<(Mark, Parse<T>)>> {
self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
}
fn enter_expand_inner(
db: &dyn DefDatabase,
call_id: MacroCallId,
mut err: Option<ExpandError>,
) -> ExpandResult<Option<(HirFileId, SyntaxNode)>> {
if err.is_none() {
err = db.macro_expand_error(call_id);
mut error: Option<ExpandError>,
) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>> {
let file_id = call_id.as_file();
let ExpandResult { value, err } = db.parse_or_expand_with_err(file_id);
if error.is_none() {
error = err;
}
let file_id = call_id.as_file();
let raw_node = match db.parse_or_expand_with_err(file_id) {
// FIXME: report parse errors
Some(it) => it.syntax_node(),
let parse = match value {
Some(it) => it,
None => {
// Only `None` if the macro expansion produced no usable AST.
if err.is_none() {
if error.is_none() {
tracing::warn!("no error despite `parse_or_expand` failing");
}
return ExpandResult::only_err(err.unwrap_or_else(|| {
return ExpandResult::only_err(error.unwrap_or_else(|| {
ExpandError::Other("failed to parse macro invocation".into())
}));
}
};
ExpandResult { value: Some((file_id, raw_node)), err }
ExpandResult { value: Some(InFile::new(file_id, parse)), err: error }
}
pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
@ -267,7 +259,7 @@ impl Expander {
&mut self,
db: &dyn DefDatabase,
op: F,
) -> ExpandResult<Option<(Mark, T)>>
) -> ExpandResult<Option<(Mark, Parse<T>)>>
where
F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
{
@ -294,15 +286,15 @@ impl Expander {
};
Self::enter_expand_inner(db, call_id, err).map(|value| {
value.and_then(|(new_file_id, node)| {
let node = T::cast(node)?;
value.and_then(|InFile { file_id, value }| {
let parse = value.cast::<T>()?;
self.recursion_depth += 1;
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id);
let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id);
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
let mark =
Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
Some((mark, node))
Some((mark, parse))
})
})
}

View file

@ -824,7 +824,11 @@ impl ExprCollector<'_> {
self.db.ast_id_map(self.expander.current_file_id),
);
let id = collector(self, Some(expansion));
if record_diagnostics {
// FIXME: Report parse errors here
}
let id = collector(self, Some(expansion.tree()));
self.ast_id_map = prev_ast_id_map;
self.expander.exit(self.db, mark);
id

View file

@ -7,7 +7,7 @@ use std::sync::Arc;
use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroDefKind};
use intern::Interned;
use smallvec::SmallVec;
use syntax::ast;
use syntax::{ast, Parse};
use crate::{
attr::Attrs,
@ -604,14 +604,11 @@ impl<'a> AssocItemCollector<'a> {
continue 'attrs;
}
}
match self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id) {
ExpandResult { value: Some((mark, _)), .. } => {
self.collect_macro_items(mark);
let res = self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id);
self.collect_macro_items(res, &|| loc.kind.clone());
continue 'items;
}
ExpandResult { .. } => {}
}
}
}
match item {
@ -641,22 +638,23 @@ impl<'a> AssocItemCollector<'a> {
self.items.push((item.name.clone(), def.into()));
}
AssocItem::MacroCall(call) => {
if let Some(root) =
self.db.parse_or_expand_with_err(self.expander.current_file_id())
{
// FIXME: report parse errors
let root = root.syntax_node();
let file_id = self.expander.current_file_id();
let root = self.db.parse_or_expand(file_id);
if let Some(root) = root {
let call = &item_tree[call];
let ast_id_map = self.db.ast_id_map(self.expander.current_file_id());
let call = ast_id_map.get(call.ast_id).to_node(&root);
let _cx =
stdx::panic_context::enter(format!("collect_items MacroCall: {call}"));
let res = self.expander.enter_expand::<ast::MacroItems>(self.db, call);
if let Ok(ExpandResult { value: Some((mark, _)), .. }) = res {
self.collect_macro_items(mark);
let ast_id_map = self.db.ast_id_map(file_id);
let macro_call = ast_id_map.get(call.ast_id).to_node(&root);
let _cx = stdx::panic_context::enter(format!(
"collect_items MacroCall: {macro_call}"
));
if let Ok(res) =
self.expander.enter_expand::<ast::MacroItems>(self.db, macro_call)
{
self.collect_macro_items(res, &|| hir_expand::MacroCallKind::FnLike {
ast_id: InFile::new(file_id, call.ast_id),
expand_to: hir_expand::ExpandTo::Items,
});
}
}
}
@ -664,7 +662,28 @@ impl<'a> AssocItemCollector<'a> {
}
}
fn collect_macro_items(&mut self, mark: Mark) {
fn collect_macro_items(
&mut self,
ExpandResult { value, err }: ExpandResult<Option<(Mark, Parse<ast::MacroItems>)>>,
error_call_kind: &dyn Fn() -> hir_expand::MacroCallKind,
) {
let Some((mark, parse)) = value else { return };
if let Some(err) = err {
self.inactive_diagnostics.push(DefDiagnostic::macro_error(
self.module_id.local_id,
error_call_kind(),
err.to_string(),
));
}
if let errors @ [_, ..] = parse.errors() {
self.inactive_diagnostics.push(DefDiagnostic::macro_expansion_parse_error(
self.module_id.local_id,
error_call_kind(),
errors.into(),
));
}
let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None);
let item_tree = tree_id.item_tree(self.db);
let iter: SmallVec<[_; 2]> =

View file

@ -350,7 +350,7 @@ impl GenericParams {
match expander.enter_expand::<ast::Type>(db, macro_call) {
Ok(ExpandResult { value: Some((mark, expanded)), .. }) => {
let ctx = expander.ctx(db);
let type_ref = TypeRef::from_ast(&ctx, expanded);
let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
self.fill_implicit_impl_trait_args(db, expander, &type_ref);
expander.exit(db, mark);
}

View file

@ -101,6 +101,7 @@ pub struct ItemTree {
top_level: SmallVec<[ModItem; 1]>,
attrs: FxHashMap<AttrOwner, RawAttrs>,
// FIXME: Remove this indirection, an item tree is almost always non-empty?
data: Option<Box<ItemTreeData>>,
}

View file

@ -65,11 +65,11 @@ use hir_expand::{
builtin_attr_macro::BuiltinAttrExpander,
builtin_derive_macro::BuiltinDeriveExpander,
builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
eager::{expand_eager_macro, ErrorEmitted, ErrorSink},
eager::expand_eager_macro,
hygiene::Hygiene,
proc_macro::ProcMacroExpander,
AstId, ExpandError, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId,
MacroDefKind, UnresolvedMacro,
AstId, ExpandError, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind,
MacroDefId, MacroDefKind, UnresolvedMacro,
};
use item_tree::ExternBlock;
use la_arena::Idx;
@ -795,7 +795,7 @@ pub trait AsMacroCall {
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
) -> Option<MacroCallId> {
self.as_call_id_with_errors(db, krate, resolver, &mut |_| ()).ok()?.ok()
self.as_call_id_with_errors(db, krate, resolver).ok()?.value
}
fn as_call_id_with_errors(
@ -803,8 +803,7 @@ pub trait AsMacroCall {
db: &dyn db::DefDatabase,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
error_sink: &mut dyn FnMut(ExpandError),
) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro>;
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro>;
}
impl AsMacroCall for InFile<&ast::MacroCall> {
@ -813,30 +812,23 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
db: &dyn db::DefDatabase,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
mut error_sink: &mut dyn FnMut(ExpandError),
) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
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.upcast(), path, &h));
let path = match error_sink
.option(path, || ExpandError::Other("malformed macro invocation".into()))
{
Ok(path) => path,
Err(error) => {
return Ok(Err(error));
}
let Some(path) = path else {
return Ok(ExpandResult::only_err(ExpandError::Other("malformed macro invocation".into())));
};
macro_call_as_call_id(
macro_call_as_call_id_(
db,
&AstIdWithPath::new(ast_id.file_id, ast_id.value, path),
expands_to,
krate,
resolver,
error_sink,
)
}
}
@ -860,21 +852,33 @@ fn macro_call_as_call_id(
expand_to: ExpandTo,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
error_sink: &mut dyn FnMut(ExpandError),
) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
) -> Result<Option<MacroCallId>, UnresolvedMacro> {
macro_call_as_call_id_(db, call, expand_to, krate, resolver).map(|res| res.value)
}
fn macro_call_as_call_id_(
db: &dyn db::DefDatabase,
call: &AstIdWithPath<ast::MacroCall>,
expand_to: ExpandTo,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
let def =
resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
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()));
expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver, error_sink)?
expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver)?
} else {
Ok(def.as_lazy_macro(
ExpandResult {
value: Some(def.as_lazy_macro(
db.upcast(),
krate,
MacroCallKind::FnLike { ast_id: call.ast_id, expand_to },
))
)),
err: None,
}
};
Ok(res)
}

View file

@ -125,21 +125,15 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) {
let macro_call = InFile::new(source.file_id, &macro_call);
let mut error = None;
let macro_call_id = macro_call
.as_call_id_with_errors(
&db,
krate,
|path| {
let res = macro_call
.as_call_id_with_errors(&db, krate, |path| {
resolver.resolve_path_as_macro(&db, &path).map(|it| macro_id_to_def_id(&db, it))
},
&mut |err| error = Some(err),
)
.unwrap()
})
.unwrap();
let macro_call_id = res.value.unwrap();
let macro_file = MacroFile { macro_call_id };
let mut expansion_result = db.parse_macro_expansion(macro_file);
expansion_result.err = expansion_result.err.or(error);
expansion_result.err = expansion_result.err.or(res.err);
expansions.push((macro_call.value.clone(), expansion_result, db.macro_arg(macro_call_id)));
}

View file

@ -16,8 +16,8 @@ use hir_expand::{
builtin_fn_macro::find_builtin_macro,
name::{name, AsName, Name},
proc_macro::ProcMacroExpander,
ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId,
MacroDefKind,
ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc,
MacroDefId, MacroDefKind,
};
use itertools::{izip, Itertools};
use la_arena::Idx;
@ -1116,10 +1116,10 @@ impl DefCollector<'_> {
*expand_to,
self.def_map.krate,
resolver_def_id,
&mut |_err| (),
);
if let Ok(Ok(call_id)) = call_id {
if let Ok(Some(call_id)) = call_id {
push_resolved(directive, call_id);
res = ReachedFixedPoint::No;
return false;
}
@ -1355,26 +1355,30 @@ impl DefCollector<'_> {
let file_id = macro_call_id.as_file();
// First, fetch the raw expansion result for purposes of error reporting. This goes through
// `macro_expand_error` to avoid depending on the full expansion result (to improve
// `parse_macro_expansion_error` to avoid depending on the full expansion result (to improve
// incrementality).
let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id);
let err = self.db.macro_expand_error(macro_call_id);
let ExpandResult { value, err } = self.db.parse_macro_expansion_error(macro_call_id);
if let Some(err) = err {
let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id);
let diag = match err {
// why is this reported here?
hir_expand::ExpandError::UnresolvedProcMacro(krate) => {
always!(krate == loc.def.krate);
// Missing proc macros are non-fatal, so they are handled specially.
DefDiagnostic::unresolved_proc_macro(module_id, loc.kind.clone(), loc.def.krate)
}
_ => DefDiagnostic::macro_error(module_id, loc.kind, err.to_string()),
_ => DefDiagnostic::macro_error(module_id, loc.kind.clone(), err.to_string()),
};
self.def_map.diagnostics.push(diag);
}
if let Some(errors) = value {
let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id);
let diag = DefDiagnostic::macro_expansion_parse_error(module_id, loc.kind, &errors);
self.def_map.diagnostics.push(diag);
}
// Then, fetch and process the item tree. This will reuse the expansion result from above.
let item_tree = self.db.file_item_tree(file_id);
// FIXME: report parse errors for the macro expansion here
let mod_dir = self.mod_dirs[&module_id].clone();
ModCollector {
@ -1396,6 +1400,7 @@ impl DefCollector<'_> {
for directive in &self.unresolved_macros {
match &directive.kind {
MacroDirectiveKind::FnLike { ast_id, expand_to } => {
// FIXME: we shouldn't need to re-resolve the macro here just to get the unresolved error!
let macro_call_as_call_id = macro_call_as_call_id(
self.db,
ast_id,
@ -1414,7 +1419,6 @@ impl DefCollector<'_> {
.take_macros()
.map(|it| macro_id_to_def_id(self.db, it))
},
&mut |_| (),
);
if let Err(UnresolvedMacro { path }) = macro_call_as_call_id {
self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
@ -2112,8 +2116,7 @@ impl ModCollector<'_, '_> {
let ast_id = AstIdWithPath::new(self.file_id(), mac.ast_id, ModPath::clone(&mac.path));
// Case 1: try to resolve in legacy scope and expand macro_rules
let mut error = None;
match macro_call_as_call_id(
if let Ok(res) = macro_call_as_call_id(
self.def_collector.db,
&ast_id,
mac.expand_to,
@ -2133,42 +2136,20 @@ impl ModCollector<'_, '_> {
)
})
},
&mut |err| {
error.get_or_insert(err);
},
) {
Ok(Ok(macro_call_id)) => {
// Legacy macros need to be expanded immediately, so that any macros they produce
// are in scope.
if let Some(val) = res {
self.def_collector.collect_macro_expansion(
self.module_id,
macro_call_id,
val,
self.macro_depth + 1,
container,
);
if let Some(err) = error {
self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error(
self.module_id,
MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to },
err.to_string(),
));
}
return;
}
Ok(Err(_)) => {
// Built-in macro failed eager expansion.
self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error(
self.module_id,
MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to },
error.unwrap().to_string(),
));
return;
}
Err(UnresolvedMacro { .. }) => (),
}
// Case 2: resolve in module scope, expand during name resolution.
self.def_collector.unresolved_macros.push(MacroDirective {

View file

@ -4,7 +4,10 @@ use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
use hir_expand::{attrs::AttrId, MacroCallKind};
use la_arena::Idx;
use syntax::ast::{self, AnyHasAttrs};
use syntax::{
ast::{self, AnyHasAttrs},
SyntaxError,
};
use crate::{
item_tree::{self, ItemTreeId},
@ -29,6 +32,8 @@ pub enum DefDiagnosticKind {
MacroError { ast: MacroCallKind, message: String },
MacroExpansionParseError { ast: MacroCallKind, errors: Box<[SyntaxError]> },
UnimplementedBuiltinMacro { ast: AstId<ast::Macro> },
InvalidDeriveTarget { ast: AstId<ast::Item>, id: usize },
@ -91,7 +96,7 @@ impl DefDiagnostic {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } }
}
pub(super) fn macro_error(
pub(crate) fn macro_error(
container: LocalModuleId,
ast: MacroCallKind,
message: String,
@ -99,6 +104,20 @@ impl DefDiagnostic {
Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
}
pub(crate) fn macro_expansion_parse_error(
container: LocalModuleId,
ast: MacroCallKind,
errors: &[SyntaxError],
) -> Self {
Self {
in_module: container,
kind: DefDiagnosticKind::MacroExpansionParseError {
ast,
errors: errors.to_vec().into_boxed_slice(),
},
}
}
pub(super) fn unresolved_macro_call(
container: LocalModuleId,
ast: MacroCallKind,

View file

@ -140,7 +140,7 @@ m!(Z);
let n_recalculated_item_trees = events.iter().filter(|it| it.contains("item_tree")).count();
assert_eq!(n_recalculated_item_trees, 6);
let n_reparsed_macros =
events.iter().filter(|it| it.contains("parse_macro_expansion")).count();
events.iter().filter(|it| it.contains("parse_macro_expansion(")).count();
assert_eq!(n_reparsed_macros, 3);
}

View file

@ -9,7 +9,7 @@ use mbe::syntax_node_to_token_tree;
use rustc_hash::FxHashSet;
use syntax::{
ast::{self, HasAttrs, HasDocComments},
AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken, T,
AstNode, GreenNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T,
};
use crate::{
@ -100,7 +100,10 @@ pub trait ExpandDatabase: SourceDatabase {
#[salsa::transparent]
fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode>;
#[salsa::transparent]
fn parse_or_expand_with_err(&self, file_id: HirFileId) -> Option<Parse<SyntaxNode>>;
fn parse_or_expand_with_err(
&self,
file_id: HirFileId,
) -> ExpandResult<Option<Parse<SyntaxNode>>>;
/// Implementation for the macro case.
fn parse_macro_expansion(
&self,
@ -129,15 +132,18 @@ pub trait ExpandDatabase: SourceDatabase {
/// just fetches procedural ones.
fn macro_def(&self, id: MacroDefId) -> Result<Arc<TokenExpander>, mbe::ParseError>;
/// Expand macro call to a token tree. This query is LRUed (we keep 128 or so results in memory)
/// Expand macro call to a token tree.
fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult<Option<Arc<tt::Subtree>>>;
/// Special case of the previous query for procedural macros. We can't LRU
/// proc macros, since they are not deterministic in general, and
/// non-determinism breaks salsa in a very, very, very bad way. @edwin0cheng
/// heroically debugged this once!
fn expand_proc_macro(&self, call: MacroCallId) -> ExpandResult<tt::Subtree>;
/// Firewall query that returns the error from the `macro_expand` query.
fn macro_expand_error(&self, macro_call: MacroCallId) -> Option<ExpandError>;
/// Firewall query that returns the errors from the `parse_macro_expansion` query.
fn parse_macro_expansion_error(
&self,
macro_call: MacroCallId,
) -> ExpandResult<Option<Box<[SyntaxError]>>>;
fn hygiene_frame(&self, file_id: HirFileId) -> Arc<HygieneFrame>;
}
@ -262,11 +268,11 @@ fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> Option<Syntax
fn parse_or_expand_with_err(
db: &dyn ExpandDatabase,
file_id: HirFileId,
) -> Option<Parse<SyntaxNode>> {
) -> ExpandResult<Option<Parse<SyntaxNode>>> {
match file_id.repr() {
HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).to_syntax()),
HirFileIdRepr::FileId(file_id) => ExpandResult::ok(Some(db.parse(file_id).to_syntax())),
HirFileIdRepr::MacroFile(macro_file) => {
db.parse_macro_expansion(macro_file).value.map(|(parse, _)| parse)
db.parse_macro_expansion(macro_file).map(|it| it.map(|(parse, _)| parse))
}
}
}
@ -279,6 +285,7 @@ fn parse_macro_expansion(
let mbe::ValueResult { value, err } = db.macro_expand(macro_file.macro_call_id);
if let Some(err) = &err {
if tracing::enabled!(tracing::Level::DEBUG) {
// Note:
// The final goal we would like to make all parse_macro success,
// such that the following log will not call anyway.
@ -286,8 +293,9 @@ fn parse_macro_expansion(
let node = loc.kind.to_node(db);
// collect parent information for warning log
let parents =
std::iter::successors(loc.kind.file_id().call_node(db), |it| it.file_id.call_node(db))
let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| {
it.file_id.call_node(db)
})
.map(|n| format!("{:#}", n.value))
.collect::<Vec<_>>()
.join("\n");
@ -299,6 +307,7 @@ fn parse_macro_expansion(
parents
);
}
}
let tt = match value {
Some(tt) => tt,
None => return ExpandResult { value: None, err },
@ -442,14 +451,14 @@ fn macro_def(
fn macro_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
// FIXME: Remove the OPtion if possible
) -> ExpandResult<Option<Arc<tt::Subtree>>> {
let _p = profile::span("macro_expand");
let loc: MacroCallLoc = db.lookup_intern_macro_call(id);
if let Some(eager) = &loc.eager {
return ExpandResult {
value: Some(eager.arg_or_expansion.clone()),
// FIXME: There could be errors here!
err: None,
err: eager.error.clone(),
};
}
@ -466,7 +475,8 @@ fn macro_expand(
Ok(it) => it,
// FIXME: This is weird -- we effectively report macro *definition*
// errors lazily, when we try to expand the macro. Instead, they should
// be reported at the definition site (when we construct a def map).
// be reported at the definition site when we construct a def map.
// (Note we do report them also at the definition site in the late diagnostic pass)
Err(err) => {
return ExpandResult::only_err(ExpandError::Other(
format!("invalid macro definition: {err}").into(),
@ -492,8 +502,12 @@ fn macro_expand(
ExpandResult { value: Some(Arc::new(tt)), err }
}
fn macro_expand_error(db: &dyn ExpandDatabase, macro_call: MacroCallId) -> Option<ExpandError> {
db.macro_expand(macro_call).err
fn parse_macro_expansion_error(
db: &dyn ExpandDatabase,
macro_call_id: MacroCallId,
) -> ExpandResult<Option<Box<[SyntaxError]>>> {
db.parse_macro_expansion(MacroFile { macro_call_id })
.map(|it| it.map(|(it, _)| it.errors().to_vec().into_boxed_slice()))
}
fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<tt::Subtree> {

View file

@ -21,7 +21,7 @@
use std::sync::Arc;
use base_db::CrateId;
use syntax::{ted, SyntaxNode};
use syntax::{ted, Parse, SyntaxNode};
use crate::{
ast::{self, AstNode},
@ -32,77 +32,16 @@ use crate::{
MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro,
};
#[derive(Debug)]
pub struct ErrorEmitted {
_private: (),
}
pub trait ErrorSink {
fn emit(&mut self, err: ExpandError);
fn option<T>(
&mut self,
opt: Option<T>,
error: impl FnOnce() -> ExpandError,
) -> Result<T, ErrorEmitted> {
match opt {
Some(it) => Ok(it),
None => {
self.emit(error());
Err(ErrorEmitted { _private: () })
}
}
}
fn option_with<T>(
&mut self,
opt: impl FnOnce() -> Option<T>,
error: impl FnOnce() -> ExpandError,
) -> Result<T, ErrorEmitted> {
self.option(opt(), error)
}
fn result<T>(&mut self, res: Result<T, ExpandError>) -> Result<T, ErrorEmitted> {
match res {
Ok(it) => Ok(it),
Err(e) => {
self.emit(e);
Err(ErrorEmitted { _private: () })
}
}
}
fn expand_result_option<T>(&mut self, res: ExpandResult<Option<T>>) -> Result<T, ErrorEmitted> {
match (res.value, res.err) {
(None, Some(err)) => {
self.emit(err);
Err(ErrorEmitted { _private: () })
}
(Some(value), opt_err) => {
if let Some(err) = opt_err {
self.emit(err);
}
Ok(value)
}
(None, None) => unreachable!("`ExpandResult` without value or error"),
}
}
}
impl ErrorSink for &'_ mut dyn FnMut(ExpandError) {
fn emit(&mut self, err: ExpandError) {
self(err);
}
}
pub fn expand_eager_macro(
db: &dyn ExpandDatabase,
krate: CrateId,
macro_call: InFile<ast::MacroCall>,
def: MacroDefId,
resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
diagnostic_sink: &mut dyn FnMut(ExpandError),
) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
let MacroDefKind::BuiltInEager(eager, _) = def.kind else {
panic!("called `expand_eager_macro` on non-eager macro def {def:?}")
};
let hygiene = Hygiene::new(db, macro_call.file_id);
let parsed_args = macro_call
.value
@ -121,48 +60,44 @@ pub fn expand_eager_macro(
let arg_id = db.intern_macro_call(MacroCallLoc {
def,
krate,
eager: Some(EagerCallInfo {
eager: Some(Box::new(EagerCallInfo {
arg_or_expansion: Arc::new(parsed_args.clone()),
included_file: None,
}),
error: None,
})),
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr },
});
let parsed_args = mbe::token_tree_to_syntax_node(&parsed_args, mbe::TopEntryPoint::Expr).0;
let result = match eager_macro_recur(
let ExpandResult { value, mut err } = eager_macro_recur(
db,
&hygiene,
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 Some(value ) = value else {
return Ok(ExpandResult { value: None, err })
};
let subtree = to_subtree(&result);
let subtree = to_subtree(&value);
if let MacroDefKind::BuiltInEager(eager, _) = def.kind {
let res = eager.expand(db, arg_id, &subtree);
if let Some(err) = res.err {
diagnostic_sink(err);
if err.is_none() {
err = res.err;
}
let loc = MacroCallLoc {
def,
krate,
eager: Some(EagerCallInfo {
eager: Some(Box::new(EagerCallInfo {
arg_or_expansion: Arc::new(res.value.subtree),
included_file: res.value.included_file,
}),
error: err.clone(),
})),
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to },
};
Ok(Ok(db.intern_macro_call(loc)))
} else {
panic!("called `expand_eager_macro` on non-eager macro def {def:?}");
}
Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err })
}
fn to_subtree(node: &SyntaxNode) -> crate::tt::Subtree {
@ -176,7 +111,7 @@ fn lazy_expand(
def: &MacroDefId,
macro_call: InFile<ast::MacroCall>,
krate: CrateId,
) -> ExpandResult<Option<InFile<SyntaxNode>>> {
) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>> {
let ast_id = db.ast_id_map(macro_call.file_id).ast_id(&macro_call.value);
let expand_to = ExpandTo::from_call_site(&macro_call.value);
@ -186,13 +121,8 @@ fn lazy_expand(
MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id), expand_to },
);
let err = db.macro_expand_error(id);
let value =
db.parse_or_expand_with_err(id.as_file()).map(|node| InFile::new(id.as_file(), node));
// FIXME: report parse errors
let value = value.map(|it| it.map(|it| it.syntax_node()));
ExpandResult { value, err }
db.parse_or_expand_with_err(id.as_file())
.map(|parse| parse.map(|parse| InFile::new(id.as_file(), parse)))
}
fn eager_macro_recur(
@ -201,23 +131,25 @@ fn eager_macro_recur(
curr: InFile<SyntaxNode>,
krate: CrateId,
macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
mut diagnostic_sink: &mut dyn FnMut(ExpandError),
) -> Result<Result<SyntaxNode, ErrorEmitted>, UnresolvedMacro> {
) -> Result<ExpandResult<Option<SyntaxNode>>, UnresolvedMacro> {
let original = curr.value.clone_for_update();
let children = original.descendants().filter_map(ast::MacroCall::cast);
let mut replacements = Vec::new();
// Note: We only report a single error inside of eager expansions
let mut error = None;
// Collect replacement
for child in children {
let def = match child.path().and_then(|path| ModPath::from_src(db, path, hygiene)) {
Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?,
None => {
diagnostic_sink(ExpandError::Other("malformed macro invocation".into()));
error = Some(ExpandError::Other("malformed macro invocation".into()));
continue;
}
};
let insert = match def.kind {
let ExpandResult { value, err } = match def.kind {
MacroDefKind::BuiltInEager(..) => {
let id = match expand_eager_macro(
db,
@ -225,45 +157,55 @@ fn eager_macro_recur(
curr.with_value(child.clone()),
def,
macro_resolver,
diagnostic_sink,
) {
Ok(Ok(it)) => it,
Ok(Err(err)) => return Ok(Err(err)),
Ok(it) => it,
Err(err) => return Err(err),
};
db.parse_or_expand(id.as_file())
.expect("successful macro expansion should be parseable")
.clone_for_update()
id.map(|call| {
call.and_then(|call| db.parse_or_expand(call.as_file()))
.map(|it| it.clone_for_update())
})
}
MacroDefKind::Declarative(_)
| MacroDefKind::BuiltIn(..)
| MacroDefKind::BuiltInAttr(..)
| MacroDefKind::BuiltInDerive(..)
| MacroDefKind::ProcMacro(..) => {
let res = lazy_expand(db, &def, curr.with_value(child.clone()), krate);
let val = match diagnostic_sink.expand_result_option(res) {
Ok(it) => it,
Err(err) => return Ok(Err(err)),
};
let ExpandResult { value, err } =
lazy_expand(db, &def, curr.with_value(child.clone()), krate);
match value {
Some(val) => {
// replace macro inside
let hygiene = Hygiene::new(db, val.file_id);
match eager_macro_recur(db, &hygiene, val, krate, macro_resolver, diagnostic_sink) {
Ok(Ok(it)) => it,
Ok(Err(err)) => return Ok(Err(err)),
Err(err) => return Err(err),
let ExpandResult { value, err: error } = eager_macro_recur(
db,
&hygiene,
// FIXME: We discard parse errors here
val.map(|it| it.syntax_node()),
krate,
macro_resolver,
)?;
let err = if err.is_none() { error } else { err };
ExpandResult { value, err }
}
None => ExpandResult { value: None, err },
}
}
};
if err.is_some() {
error = err;
}
// check if the whole original syntax is replaced
if child.syntax() == &original {
return Ok(Ok(insert));
return Ok(ExpandResult { value, err: error });
}
if let Some(insert) = value {
replacements.push((child, insert));
}
}
replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
Ok(Ok(original))
Ok(ExpandResult { value: Some(original), err: error })
}

View file

@ -52,7 +52,7 @@ use crate::{
pub type ExpandResult<T> = ValueResult<T, ExpandError>;
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ExpandError {
UnresolvedProcMacro(CrateId),
Mbe(mbe::ExpandError),
@ -114,7 +114,7 @@ impl_intern_key!(MacroCallId);
pub struct MacroCallLoc {
pub def: MacroDefId,
pub(crate) krate: CrateId,
eager: Option<EagerCallInfo>,
eager: Option<Box<EagerCallInfo>>,
pub kind: MacroCallKind,
}
@ -141,6 +141,7 @@ struct EagerCallInfo {
/// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro!
arg_or_expansion: Arc<tt::Subtree>,
included_file: Option<(FileId, TokenMap)>,
error: Option<ExpandError>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -206,8 +207,8 @@ impl HirFileId {
HirFileIdRepr::FileId(id) => break id,
HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => {
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_call_id);
file_id = match loc.eager {
Some(EagerCallInfo { included_file: Some((file, _)), .. }) => file.into(),
file_id = match loc.eager.as_deref() {
Some(&EagerCallInfo { included_file: Some((file, _)), .. }) => file.into(),
_ => loc.kind.file_id(),
};
}
@ -320,7 +321,7 @@ impl HirFileId {
match self.macro_file() {
Some(macro_file) => {
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
matches!(loc.eager, Some(EagerCallInfo { included_file: Some(..), .. }))
matches!(loc.eager.as_deref(), Some(EagerCallInfo { included_file: Some(..), .. }))
}
_ => false,
}

View file

@ -381,7 +381,8 @@ impl<'a> TyLoweringContext<'a> {
match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call) {
Ok(ExpandResult { value: Some((mark, expanded)), .. }) => {
let ctx = expander.ctx(self.db.upcast());
let type_ref = TypeRef::from_ast(&ctx, expanded);
// FIXME: Report syntax errors in expansion here
let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
drop(expander);
let ty = self.lower_ty(&type_ref);

View file

@ -6,8 +6,8 @@
pub use hir_def::db::*;
pub use hir_expand::db::{
AstIdMapQuery, ExpandDatabase, ExpandDatabaseStorage, ExpandProcMacroQuery, HygieneFrameQuery,
InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandErrorQuery,
MacroExpandQuery, ParseMacroExpansionQuery,
InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery,
ParseMacroExpansionQuery,
};
pub use hir_ty::db::*;

View file

@ -10,7 +10,7 @@ use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_def::path::ModPath;
use hir_expand::{name::Name, HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
use crate::{AssocItem, Field, Local, MacroKind, Type};
@ -38,8 +38,9 @@ diagnostics![
IncorrectCase,
InvalidDeriveTarget,
IncoherentImpl,
MacroError,
MacroDefError,
MacroError,
MacroExpansionParseError,
MalformedDerive,
MismatchedArgCount,
MissingFields,
@ -132,6 +133,13 @@ pub struct MacroError {
pub message: String,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroExpansionParseError {
pub node: InFile<SyntaxNodePtr>,
pub precise_location: Option<TextRange>,
pub errors: Box<[SyntaxError]>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroDefError {
pub node: InFile<AstPtr<ast::Macro>>,

View file

@ -87,12 +87,12 @@ pub use crate::{
attrs::{HasAttrs, Namespace},
diagnostics::{
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl,
IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MalformedDerive,
MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField,
PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
UndeclaredLabel, UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate,
UnresolvedField, UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall,
UnresolvedModule, UnresolvedProcMacro, UnusedMut,
IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError,
MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe,
NeedMut, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap,
TypeMismatch, UndeclaredLabel, UnimplementedBuiltinMacro, UnreachableLabel,
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut,
},
has_source::HasSource,
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@ -753,7 +753,6 @@ fn emit_def_diagnostic_(
.into(),
);
}
DefDiagnosticKind::UnresolvedProcMacro { ast, krate } => {
let (node, precise_location, macro_name, kind) = precise_macro_call_location(ast, db);
acc.push(
@ -761,7 +760,6 @@ fn emit_def_diagnostic_(
.into(),
);
}
DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
let (node, precise_location, _, _) = precise_macro_call_location(ast, db);
acc.push(
@ -774,12 +772,16 @@ fn emit_def_diagnostic_(
.into(),
);
}
DefDiagnosticKind::MacroError { ast, message } => {
let (node, precise_location, _, _) = precise_macro_call_location(ast, db);
acc.push(MacroError { node, precise_location, message: message.clone() }.into());
}
DefDiagnosticKind::MacroExpansionParseError { ast, errors } => {
let (node, precise_location, _, _) = precise_macro_call_location(ast, db);
acc.push(
MacroExpansionParseError { node, precise_location, errors: errors.clone() }.into(),
);
}
DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
let node = ast.to_node(db.upcast());
// Must have a name, otherwise we wouldn't emit it.

View file

@ -80,7 +80,6 @@ impl RootDatabase {
hir::db::MacroDefQuery
hir::db::MacroExpandQuery
hir::db::ExpandProcMacroQuery
hir::db::MacroExpandErrorQuery
hir::db::HygieneFrameQuery
// DefDatabase

View file

@ -152,7 +152,6 @@ impl RootDatabase {
let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_LRU_CAP);
base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
}
pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
@ -167,12 +166,6 @@ impl RootDatabase {
.copied()
.unwrap_or(base_db::DEFAULT_LRU_CAP),
);
hir_db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(
lru_capacities
.get(stringify!(MacroExpandQuery))
.copied()
.unwrap_or(base_db::DEFAULT_LRU_CAP),
);
macro_rules! update_lru_capacity_per_query {
($( $module:ident :: $query:ident )*) => {$(
@ -201,7 +194,6 @@ impl RootDatabase {
hir_db::MacroDefQuery
// hir_db::MacroExpandQuery
hir_db::ExpandProcMacroQuery
hir_db::MacroExpandErrorQuery
hir_db::HygieneFrameQuery
// DefDatabase

View file

@ -238,6 +238,22 @@ fn f() {
//^^^ error: invalid macro definition: expected subtree
}
"#,
)
}
#[test]
fn expansion_syntax_diagnostic() {
check_diagnostics(
r#"
macro_rules! foo {
() => { struct; };
}
fn f() {
foo!();
//^^^ error: Syntax Error in Expansion: expected a name
}
"#,
)
}

View file

@ -265,6 +265,19 @@ pub fn diagnostics(
AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
AnyDiagnostic::MacroDefError(d) => handlers::macro_error::macro_def_error(&ctx, &d),
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
AnyDiagnostic::MacroExpansionParseError(d) => {
res.extend(d.errors.iter().take(32).map(|err| {
{
Diagnostic::new(
"syntax-error",
format!("Syntax Error in Expansion: {err}"),
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
)
}
.experimental()
}));
continue;
},
AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d),

View file

@ -69,7 +69,7 @@ impl fmt::Display for ParseError {
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ExpandError {
BindingError(Box<Box<str>>),
LeftoverTokens,