Skip to content

Commit d46fce8

Browse files
Merge #6700
6700: More macro diagnostics improvements r=jonas-schievink a=jonas-schievink This threads macro expansion errors through `eager.rs` and the `AsMacroCall` trait, improving macro diagnostics emitted during body lowering. Co-authored-by: Jonas Schievink <[email protected]>
2 parents 74de29b + bca1e5f commit d46fce8

File tree

6 files changed

+223
-67
lines changed

6 files changed

+223
-67
lines changed

crates/hir_def/src/body.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,18 +120,24 @@ impl Expander {
120120
self.resolve_path_as_macro(db, &path)
121121
};
122122

123-
let call_id = match macro_call.as_call_id(db, self.crate_def_map.krate, resolver) {
123+
let mut err = None;
124+
let call_id =
125+
macro_call.as_call_id_with_errors(db, self.crate_def_map.krate, resolver, &mut |e| {
126+
err.get_or_insert(e);
127+
});
128+
let call_id = match call_id {
124129
Some(it) => it,
125130
None => {
126-
// FIXME: this can mean other things too, but `as_call_id` doesn't provide enough
127-
// info.
128-
return ExpandResult::only_err(mbe::ExpandError::Other(
129-
"failed to parse or resolve macro invocation".into(),
130-
));
131+
if err.is_none() {
132+
eprintln!("no error despite `as_call_id_with_errors` returning `None`");
133+
}
134+
return ExpandResult { value: None, err };
131135
}
132136
};
133137

134-
let err = db.macro_expand_error(call_id);
138+
if err.is_none() {
139+
err = db.macro_expand_error(call_id);
140+
}
135141

136142
let file_id = call_id.as_file();
137143

crates/hir_def/src/body/tests.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,41 @@ fn f() {
7878
fn macro_diag_builtin() {
7979
check_diagnostics(
8080
r#"
81+
#[rustc_builtin_macro]
82+
macro_rules! env {}
83+
84+
#[rustc_builtin_macro]
85+
macro_rules! include {}
86+
87+
#[rustc_builtin_macro]
88+
macro_rules! compile_error {}
89+
90+
#[rustc_builtin_macro]
91+
macro_rules! format_args {
92+
() => {}
93+
}
94+
8195
fn f() {
8296
// Test a handful of built-in (eager) macros:
8397
8498
include!(invalid);
85-
//^^^^^^^^^^^^^^^^^ failed to parse or resolve macro invocation
99+
//^^^^^^^^^^^^^^^^^ could not convert tokens
86100
include!("does not exist");
87-
//^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to parse or resolve macro invocation
101+
//^^^^^^^^^^^^^^^^^^^^^^^^^^ could not convert tokens
88102
89103
env!(invalid);
90-
//^^^^^^^^^^^^^ failed to parse or resolve macro invocation
104+
//^^^^^^^^^^^^^ could not convert tokens
105+
106+
env!("OUT_DIR");
107+
//^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "load out dirs from check" to fix
108+
109+
compile_error!("compile_error works");
110+
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `compile_error!` called: compile_error works
91111
92112
// Lazy:
93113
94114
format_args!();
95-
//^^^^^^^^^^^^^^ failed to parse or resolve macro invocation
115+
//^^^^^^^^^^^^^^ no rule matches input tokens
96116
}
97117
"#,
98118
);

crates/hir_def/src/lib.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,21 +465,37 @@ pub trait AsMacroCall {
465465
db: &dyn db::DefDatabase,
466466
krate: CrateId,
467467
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
468+
) -> Option<MacroCallId> {
469+
self.as_call_id_with_errors(db, krate, resolver, &mut |_| ())
470+
}
471+
472+
fn as_call_id_with_errors(
473+
&self,
474+
db: &dyn db::DefDatabase,
475+
krate: CrateId,
476+
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
477+
error_sink: &mut dyn FnMut(mbe::ExpandError),
468478
) -> Option<MacroCallId>;
469479
}
470480

471481
impl AsMacroCall for InFile<&ast::MacroCall> {
472-
fn as_call_id(
482+
fn as_call_id_with_errors(
473483
&self,
474484
db: &dyn db::DefDatabase,
475485
krate: CrateId,
476486
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
487+
error_sink: &mut dyn FnMut(mbe::ExpandError),
477488
) -> Option<MacroCallId> {
478489
let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
479490
let h = Hygiene::new(db.upcast(), self.file_id);
480-
let path = path::ModPath::from_src(self.value.path()?, &h)?;
491+
let path = self.value.path().and_then(|path| path::ModPath::from_src(path, &h));
492+
493+
if path.is_none() {
494+
error_sink(mbe::ExpandError::Other("malformed macro invocation".into()));
495+
}
481496

482-
AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, krate, resolver)
497+
AstIdWithPath::new(ast_id.file_id, ast_id.value, path?)
498+
.as_call_id_with_errors(db, krate, resolver, error_sink)
483499
}
484500
}
485501

@@ -497,22 +513,32 @@ impl<T: ast::AstNode> AstIdWithPath<T> {
497513
}
498514

499515
impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
500-
fn as_call_id(
516+
fn as_call_id_with_errors(
501517
&self,
502518
db: &dyn db::DefDatabase,
503519
krate: CrateId,
504520
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
521+
error_sink: &mut dyn FnMut(mbe::ExpandError),
505522
) -> Option<MacroCallId> {
506-
let def: MacroDefId = resolver(self.path.clone())?;
523+
let def: MacroDefId = resolver(self.path.clone()).or_else(|| {
524+
error_sink(mbe::ExpandError::Other("could not resolve macro".into()));
525+
None
526+
})?;
507527

508528
if let MacroDefKind::BuiltInEager(_) = def.kind {
509529
let macro_call = InFile::new(self.ast_id.file_id, self.ast_id.to_node(db.upcast()));
510530
let hygiene = Hygiene::new(db.upcast(), self.ast_id.file_id);
511531

512532
Some(
513-
expand_eager_macro(db.upcast(), krate, macro_call, def, &|path: ast::Path| {
514-
resolver(path::ModPath::from_src(path, &hygiene)?)
515-
})?
533+
expand_eager_macro(
534+
db.upcast(),
535+
krate,
536+
macro_call,
537+
def,
538+
&|path: ast::Path| resolver(path::ModPath::from_src(path, &hygiene)?),
539+
error_sink,
540+
)
541+
.ok()?
516542
.into(),
517543
)
518544
} else {
@@ -522,13 +548,18 @@ impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
522548
}
523549

524550
impl AsMacroCall for AstIdWithPath<ast::Item> {
525-
fn as_call_id(
551+
fn as_call_id_with_errors(
526552
&self,
527553
db: &dyn db::DefDatabase,
528554
krate: CrateId,
529555
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
556+
error_sink: &mut dyn FnMut(mbe::ExpandError),
530557
) -> Option<MacroCallId> {
531-
let def = resolver(self.path.clone())?;
558+
let def: MacroDefId = resolver(self.path.clone()).or_else(|| {
559+
error_sink(mbe::ExpandError::Other("could not resolve macro".into()));
560+
None
561+
})?;
562+
532563
Some(
533564
def.as_lazy_macro(
534565
db.upcast(),

crates/hir_expand/src/builtin_macro.rs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ pub fn find_builtin_macro(
8686
register_builtin! {
8787
LAZY:
8888
(column, Column) => column_expand,
89-
(compile_error, CompileError) => compile_error_expand,
9089
(file, File) => file_expand,
9190
(line, Line) => line_expand,
9291
(assert, Assert) => assert_expand,
@@ -97,6 +96,7 @@ register_builtin! {
9796
(format_args_nl, FormatArgsNl) => format_args_expand,
9897

9998
EAGER:
99+
(compile_error, CompileError) => compile_error_expand,
100100
(concat, Concat) => concat_expand,
101101
(include, Include) => include_expand,
102102
(include_bytes, IncludeBytes) => include_bytes_expand,
@@ -213,25 +213,6 @@ fn file_expand(
213213
ExpandResult::ok(expanded)
214214
}
215215

216-
fn compile_error_expand(
217-
_db: &dyn AstDatabase,
218-
_id: LazyMacroId,
219-
tt: &tt::Subtree,
220-
) -> ExpandResult<tt::Subtree> {
221-
if tt.count() == 1 {
222-
if let tt::TokenTree::Leaf(tt::Leaf::Literal(it)) = &tt.token_trees[0] {
223-
let s = it.text.as_str();
224-
if s.contains('"') {
225-
return ExpandResult::ok(quote! { loop { #it }});
226-
}
227-
};
228-
}
229-
230-
ExpandResult::only_err(mbe::ExpandError::BindingError(
231-
"`compile_error!` argument be a string".into(),
232-
))
233-
}
234-
235216
fn format_args_expand(
236217
_db: &dyn AstDatabase,
237218
_id: LazyMacroId,
@@ -280,6 +261,30 @@ fn unquote_str(lit: &tt::Literal) -> Option<String> {
280261
token.value().map(|it| it.into_owned())
281262
}
282263

264+
fn compile_error_expand(
265+
_db: &dyn AstDatabase,
266+
_id: EagerMacroId,
267+
tt: &tt::Subtree,
268+
) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> {
269+
let err = match &*tt.token_trees {
270+
[tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => {
271+
let text = it.text.as_str();
272+
if text.starts_with('"') && text.ends_with('"') {
273+
// FIXME: does not handle raw strings
274+
mbe::ExpandError::Other(format!(
275+
"`compile_error!` called: {}",
276+
&text[1..text.len() - 1]
277+
))
278+
} else {
279+
mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into())
280+
}
281+
}
282+
_ => mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()),
283+
};
284+
285+
ExpandResult { value: Some((quote! {}, FragmentKind::Items)), err: Some(err) }
286+
}
287+
283288
fn concat_expand(
284289
_db: &dyn AstDatabase,
285290
_arg_id: EagerMacroId,
@@ -417,17 +422,25 @@ fn env_expand(
417422
Err(e) => return ExpandResult::only_err(e),
418423
};
419424

420-
// FIXME:
421-
// If the environment variable is not defined int rustc, then a compilation error will be emitted.
422-
// We might do the same if we fully support all other stuffs.
423-
// But for now on, we should return some dummy string for better type infer purpose.
424-
// However, we cannot use an empty string here, because for
425-
// `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
426-
// `include!("foo.rs"), which might go to infinite loop
427-
let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string());
425+
let mut err = None;
426+
let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| {
427+
// The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid
428+
// unnecessary diagnostics for eg. `CARGO_PKG_NAME`.
429+
if key == "OUT_DIR" {
430+
err = Some(mbe::ExpandError::Other(
431+
r#"`OUT_DIR` not set, enable "load out dirs from check" to fix"#.into(),
432+
));
433+
}
434+
435+
// If the variable is unset, still return a dummy string to help type inference along.
436+
// We cannot use an empty string here, because for
437+
// `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
438+
// `include!("foo.rs"), which might go to infinite loop
439+
"__RA_UNIMPLEMENTED__".to_string()
440+
});
428441
let expanded = quote! { #s };
429442

430-
ExpandResult::ok(Some((expanded, FragmentKind::Expr)))
443+
ExpandResult { value: Some((expanded, FragmentKind::Expr)), err }
431444
}
432445

433446
fn option_env_expand(
@@ -638,7 +651,8 @@ mod tests {
638651
"#,
639652
);
640653

641-
assert_eq!(expanded, r#"loop{"error!"}"#);
654+
// This expands to nothing (since it's in item position), but emits an error.
655+
assert_eq!(expanded, "");
642656
}
643657

644658
#[test]

crates/hir_expand/src/db.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ fn macro_expand_with_arg(
207207
} else {
208208
return ExpandResult {
209209
value: Some(db.lookup_intern_eager_expansion(id).subtree),
210+
// FIXME: There could be errors here!
210211
err: None,
211212
};
212213
}

0 commit comments

Comments
 (0)