From 0746dc577339e2e04c5b158895032cbe1946c1a4 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Thu, 1 Oct 2026 14:35:04 +0200 Subject: [PATCH] add line information to error messages in `generate!()` invocations --- crates/guest-rust/macro/src/lib.rs | 105 ++++++++++++++++++----------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 7c7cc214b..68b9b6871 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -19,10 +19,31 @@ pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { .into() } -fn anyhow_to_syn(span: Span, err: anyhow::Error) -> Error { +fn render_error( + err: &(dyn std::error::Error + 'static), + sourcemap: &Option<&wit_bindgen_core::wit_parser::SourceMap>, +) -> String { + if let Some(sourcemap) = sourcemap { + if let Some(e) = err.downcast_ref::() { + return e.render(sourcemap); + } + if let Some(e) = err.downcast_ref::() { + return e.render(sourcemap); + } + } + + err.to_string() +} + +fn anyhow_to_syn( + span: Span, + err: anyhow::Error, + sourcemap: Option<&wit_bindgen_core::wit_parser::SourceMap>, +) -> Error { let err = attach_with_context(err); - let mut msg = err.to_string(); + let mut msg = render_error(err.as_ref(), &sourcemap); for cause in err.chain().skip(1) { + let cause = render_error(cause, &sourcemap); msg.push_str(&format!("\n\nCaused by:\n {cause}")); } Error::new(span, msg) @@ -193,11 +214,10 @@ impl Parse for Config { )])); } } - let (resolve, main_packages, files) = - parse_source(&source, &features).map_err(|err| anyhow_to_syn(call_site, err))?; + let (resolve, main_packages, files) = parse_source(&source, &features)?; let world = resolve .select_world(&main_packages, world.as_deref()) - .map_err(|e| anyhow_to_syn(call_site, e))?; + .map_err(|e| anyhow_to_syn(call_site, e, None))?; Ok(Config { opts, resolve, @@ -212,50 +232,57 @@ impl Parse for Config { fn parse_source( source: &Option, features: &[String], -) -> anyhow::Result<(Resolve, Vec, Vec)> { +) -> syn::Result<(Resolve, Vec, Vec)> { let mut resolve = Resolve::default(); resolve.features.extend(features.iter().cloned()); let mut files = Vec::new(); let mut pkgs = Vec::new(); let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); - let mut parse = |paths: &[PathBuf]| -> anyhow::Result<()> { - for path in paths { - let p = root.join(path); - // Try to normalize the path to make the error message more understandable when - // the path is not correct. Fallback to the original path if normalization fails - // (probably return an error somewhere else). - let normalized_path = match std::fs::canonicalize(&p) { - Ok(p) => p, - Err(_) => p.to_path_buf(), - }; - let (pkg, sources) = resolve.push_path(normalized_path)?; - pkgs.push(pkg); - files.extend(sources.paths().map(|p| p.to_owned())); - } - Ok(()) - }; - let default = root.join("wit"); - match source { - Some(Source::Inline(s, path)) => { - match path { - Some(p) => parse(p)?, - // If no `path` is explicitly specified still parse the default - // `wit` directory if it exists. Don't require its existence, - // however, as `inline` can be used in lieu of a folder. Test - // whether it exists and only if there is it parsed. - None => { - if default.exists() { - parse(&[default])?; + + let mut try_parse = || -> anyhow::Result<()> { + let mut parse = |paths: &[PathBuf]| -> anyhow::Result<()> { + for path in paths { + let p = root.join(path); + // Try to normalize the path to make the error message more understandable when + // the path is not correct. Fallback to the original path if normalization fails + // (probably return an error somewhere else). + let normalized_path = match std::fs::canonicalize(&p) { + Ok(p) => p, + Err(_) => p.to_path_buf(), + }; + let (pkg, sources) = resolve.push_path(normalized_path)?; + pkgs.push(pkg); + files.extend(sources.paths().map(|p| p.to_owned())); + } + Ok(()) + }; + let default = root.join("wit"); + + match source { + Some(Source::Inline(s, path)) => { + match path { + Some(p) => parse(p)?, + // If no `path` is explicitly specified still parse the default + // `wit` directory if it exists. Don't require its existence, + // however, as `inline` can be used in lieu of a folder. Test + // whether it exists and only if there is it parsed. + None => { + if default.exists() { + parse(&[default])?; + } } } + pkgs.clear(); + pkgs.push(resolve.push_str("macro-input", s)?); + Ok(()) } - pkgs.clear(); - pkgs.push(resolve.push_str("macro-input", s)?); + Some(Source::Paths(p)) => parse(p), + None => parse(&[default]), } - Some(Source::Paths(p)) => parse(p)?, - None => parse(&[default])?, }; + try_parse().map_err(|e| anyhow_to_syn(Span::call_site(), e, Some(&resolve.source_map)))?; + Ok((resolve, pkgs, files)) } @@ -265,7 +292,7 @@ impl Config { let mut generator = self.opts.build(); generator .generate(&mut self.resolve, self.world, &mut files) - .map_err(|e| anyhow_to_syn(Span::call_site(), e))?; + .map_err(|e| anyhow_to_syn(Span::call_site(), e, None))?; let (_, src) = files.iter().next().unwrap(); let mut src = std::str::from_utf8(src).unwrap().to_string();