diff --git a/libs/pavexc_cli/src/main.rs b/libs/pavexc_cli/src/main.rs index 749f89c0..d6a8aa34 100644 --- a/libs/pavexc_cli/src/main.rs +++ b/libs/pavexc_cli/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use std::process::ExitCode; @@ -320,22 +320,25 @@ fn generate( let file = fs_err::OpenOptions::new().read(true).open(blueprint)?; ron::de::from_reader(&file)? }; + let mut reporter = DiagnosticReporter::new(color_profile); // We use the path to the generated application crate as a fingerprint for the project. let project_fingerprint = output.to_string_lossy().into_owned(); - let app = match App::build(blueprint, project_fingerprint, docs_toolchain) { - Ok((a, warnings)) => { - for e in warnings { + let (app, issues) = match App::build(blueprint, project_fingerprint, docs_toolchain) { + Ok((a, issues)) => { + for e in &issues { assert_eq!(e.severity(), Some(Severity::Warning)); - print_report(&e, color_profile); } - a - } - Err(issues) => { - for e in issues { - print_report(&e, color_profile); - } - return Ok(ExitCode::FAILURE); + (Some(a), issues) } + Err(issues) => (None, issues), + }; + + for e in issues { + reporter.print_report(&e); + } + + let Some(app) = app else { + return Ok(ExitCode::FAILURE); }; if let Some(diagnostic_path) = diagnostics { app.diagnostic_representation() @@ -350,33 +353,53 @@ fn generate( generated_app.persist(&output, &mut writer)?; if let Err(errors) = writer.verify() { for e in errors { - print_report(&e, color_profile); + reporter.print_report(&e); } return Ok(ExitCode::FAILURE); } Ok(ExitCode::SUCCESS) } -fn print_report(e: &miette::Report, color_profile: Color) { - let use_color = use_color_on_stderr(color_profile); - match e.severity() { - None | Some(Severity::Error) => { - if use_color { - eprintln!("{}: {e:?}", "ERROR".bold().red()); - } else { - eprintln!("ERROR: {e:?}"); - } - } - Some(Severity::Warning) => { - if use_color { - eprintln!("{}: {e:?}", "WARNING".bold().yellow()); - } else { - eprintln!("WARNING: {e:?}"); - } +/// The compiler may emit the same diagnostic more than once +/// (for a variety of reasons). We use this helper to dedup them. +struct DiagnosticReporter { + already_emitted: HashSet, + use_color: bool, +} + +impl DiagnosticReporter { + fn new(color_profile: Color) -> Self { + let use_color = use_color_on_stderr(color_profile); + Self { + already_emitted: Default::default(), + use_color, } - _ => { - unreachable!() + } + fn print_report(&mut self, e: &miette::Report) { + let formatted = format!("{e:?}"); + if self.already_emitted.contains(&formatted) { + // Avoid printing the same diagnostic multiple times. + return; } + let prefix = match e.severity() { + None | Some(Severity::Error) => { + let mut p = "ERROR".to_string(); + if self.use_color { + p = p.bold().red().to_string(); + } + p + } + Some(Severity::Warning) => { + let mut p = "WARNING".to_string(); + if self.use_color { + p = p.bold().yellow().to_string(); + } + p + } + _ => unreachable!(), + }; + eprintln!("{prefix}: {formatted}"); + self.already_emitted.insert(formatted); } } diff --git a/libs/ui_tests/borrow_checker/across_middlewares/type_is_not_cloned_if_consumed_by_wrap_but_needed_by_post/expectations/stderr.txt b/libs/ui_tests/borrow_checker/across_middlewares/type_is_not_cloned_if_consumed_by_wrap_but_needed_by_post/expectations/stderr.txt index 7eb25087..3f12b658 100644 --- a/libs/ui_tests/borrow_checker/across_middlewares/type_is_not_cloned_if_consumed_by_wrap_but_needed_by_post/expectations/stderr.txt +++ b/libs/ui_tests/borrow_checker/across_middlewares/type_is_not_cloned_if_consumed_by_wrap_but_needed_by_post/expectations/stderr.txt @@ -1,26 +1,3 @@ -ERROR: - ร— I can't generate code that will pass the borrow checker *and* match the - โ”‚ instructions in your blueprint: - โ”‚ - One of the components in the call graph for `app::wrap` - โ”‚ consumes `app::A` by value - โ”‚ - But, later on, the same type is used in the call graph of - โ”‚ `app::post`. - โ”‚ You forbid cloning of `app::A`, therefore I can't resolve this - โ”‚ conflict. - โ”‚ - โ”‚ help: Allow me to clone `app::A` in order to satisfy the borrow - โ”‚ checker. - โ”‚ You can do so by invoking `.clone_if_necessary()` after having - โ”‚ registered your constructor. - โ”‚ โ˜ž - โ”‚ โ•ญโ”€[borrow_checker/across_middlewares/type_is_not_cloned_if_consumed_by_wrap_but_needed_by_post/src/lib.rs:30:1] - โ”‚ 30 โ”‚ let mut bp = Blueprint::new(); - โ”‚ 31 โ”‚ bp.request_scoped(f!(crate::a)).never_clone(); - โ”‚ ยท  โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€ - โ”‚ ยท โ•ฐโ”€โ”€ The constructor was registered here - โ”‚ 32 โ”‚ bp.post_process(f!(crate::post)); - โ”‚ โ•ฐโ”€โ”€โ”€โ”€ - ERROR: ร— I can't generate code that will pass the borrow checker *and* match the โ”‚ instructions in your blueprint: