1use crate::cli_options::{Backend, BackendName, MessageFormat};
2use crate::diagnostics::report::ReportCtx;
3use crate::prelude::*;
4
5#[derive_group(Serializers)]
6#[derive(Debug, Clone, JsonSchema, Hash, Eq, PartialEq)]
7#[repr(u8)]
8pub enum HaxMessage {
9 Diagnostic {
10 diagnostic: super::Diagnostics,
11 working_dir: Option<PathBuf>,
12 } = 254,
13 BinaryNotFound {
14 binary_name: String,
15 env_var: String,
16 hint: Option<String>,
17 } = 0,
18 ProducedFile {
19 path: PathBuf,
20 wrote: bool,
21 } = 1,
22 HaxEngineFailure {
23 exit_code: i32,
24 } = 2,
25 CargoBuildFailure = 3,
26 WarnExperimentalBackend {
27 backend: Backend,
28 } = 4,
29 ProfilingData(crate::engine_api::ProfilingData) = 5,
30 Stats {
31 errors_per_item: Vec<(hax_frontend_exporter::DefId, usize)>,
32 } = 6,
33 GenericError {
34 message: String,
35 } = 7,
36 GenericWarning {
37 message: String,
38 } = 8,
39 RunningStep {
40 step: String,
41 } = 9,
42 SubprocessOutput {
43 prefix: String,
44 line: String,
45 } = 10,
46 OutputTruncated {
47 prefix: String,
48 remaining: usize,
49 log_path: PathBuf,
50 } = 11,
51 UnsupportedOption {
52 option: String,
53 backend: BackendName,
54 } = 12,
55}
56
57impl HaxMessage {
58 pub fn discriminant(&self) -> u16 {
60 unsafe { *(self as *const Self as *const u16) }
61 }
62
63 pub fn code(&self) -> String {
64 match self {
65 HaxMessage::Diagnostic { diagnostic, .. } => diagnostic.kind.code(),
66 _ => format!("CARGOHAX{:0>4}", self.discriminant()),
67 }
68 }
69}
70
71const ENGINE_BINARY_NAME: &str = "hax-engine";
72
73use annotate_snippets::{Level, Renderer};
74
75impl HaxMessage {
76 pub fn report(self, message_format: MessageFormat, rctx: Option<&mut ReportCtx>) {
77 if let Some(rendered) = self.render(message_format, rctx) {
78 println!("{rendered}")
79 }
80 }
81 pub fn report_styled(self, rctx: Option<&mut ReportCtx>) {
82 println!("{}", self.render_styled(rctx))
83 }
84
85 pub fn render(
86 self,
87 message_format: MessageFormat,
88 mut rctx: Option<&mut ReportCtx>,
89 ) -> Option<String> {
90 if let (Some(r), HaxMessage::Diagnostic { diagnostic, .. }) = (rctx.as_mut(), &self)
91 && r.seen_already(diagnostic.clone())
92 {
93 return None;
94 }
95 Some(match message_format {
96 MessageFormat::Json => serde_json::to_string(&self).unwrap(),
97 MessageFormat::Human => self.render_styled(rctx),
98 })
99 }
100 pub fn render_styled(self, rctx: Option<&mut ReportCtx>) -> String {
101 let renderer = Renderer::styled();
102 match self {
103 Self::Diagnostic {
104 diagnostic,
105 working_dir,
106 } => {
107 let mut _rctx = None;
108 let rctx = rctx.unwrap_or_else(|| _rctx.get_or_insert(ReportCtx::default()));
109 diagnostic.with_message(
110 rctx,
111 working_dir.as_ref().map(PathBuf::as_path),
112 Level::Error,
113 |msg| format!("{}", renderer.render(msg)),
114 )
115 }
116 Self::BinaryNotFound {
117 binary_name,
118 env_var,
119 hint,
120 } => {
121 use colored::Colorize;
122 let mut message = format!(
123 "hax: The binary [{}] was not found in your [PATH].\n\
124 Please make sure it is installed and is in PATH!\n\
125 Hint: set the [{}] environment variable to provide its path explicitly.",
126 binary_name, env_var
127 );
128 if let Some(hint) = hint {
129 message.push_str(&format!("\n{}", hint.bright_black()));
130 }
131 format!("{}", renderer.render(Level::Error.title(&message)))
132 }
133 Self::ProducedFile { mut path, wrote } => {
134 if let Ok(current_dir) = std::env::current_dir() {
136 if let Ok(relative) = path.strip_prefix(current_dir) {
137 path = PathBuf::from(".").join(relative).to_path_buf();
138 }
139 }
140 let title = if wrote {
141 format!("hax: wrote file {}", path.display())
142 } else {
143 format!("hax: unchanged file {}", path.display())
144 };
145 format!("{}", renderer.render(Level::Info.title(&title)))
146 }
147 Self::HaxEngineFailure { exit_code } => {
148 let title = format!(
149 "hax: {} exited with non-zero code {}",
150 ENGINE_BINARY_NAME, exit_code,
151 );
152 format!("{}", renderer.render(Level::Error.title(&title)))
153 }
154 Self::ProfilingData(data) => {
155 fn format_with_dot(shift: u32, n: u64) -> String {
156 let factor = 10u64.pow(shift);
157 format!("{}.{}", n / factor, n % factor)
158 }
159 let title = format!(
160 "hax[profiling]: {}: {}ms, memory={}, {} item{}{}",
161 data.context,
162 format_with_dot(6, data.time_ns),
163 data.memory,
164 data.quantity,
165 if data.quantity > 1 { "s" } else { "" },
166 if data.errored {
167 " (note: this failed!)"
168 } else {
169 ""
170 }
171 );
172 format!("{}", renderer.render(Level::Info.title(&title)))
173 }
174 Self::Stats { errors_per_item } => {
175 let success_items = errors_per_item.iter().filter(|(_, n)| *n == 0).count();
176 let total = errors_per_item.len();
177 let title = format!(
178 "hax: {}/{} items were successfully translated ({}% success rate)",
179 success_items,
180 total,
181 (success_items * 100) / total
182 );
183 format!("{}", renderer.render(Level::Info.title(&title)))
184 }
185 Self::CargoBuildFailure => {
186 let title =
187 "hax: running `cargo build` was not successful, continuing anyway.".to_string();
188 format!("{}", renderer.render(Level::Warning.title(&title)))
189 }
190 Self::WarnExperimentalBackend { backend } => {
191 let title = format!(
192 "hax: Experimental backend \"{}\" is work in progress.",
193 backend
194 );
195 format!("{}", renderer.render(Level::Warning.title(&title)))
196 }
197 Self::GenericError { message } => {
198 let title = format!("hax: {}", message);
199 format!("{}", renderer.render(Level::Error.title(&title)))
200 }
201 Self::GenericWarning { message } => {
202 let title = format!("hax: {}", message);
203 format!("{}", renderer.render(Level::Warning.title(&title)))
204 }
205 Self::RunningStep { step } => {
206 use colored::Colorize;
207 format!("{:>12} {}", "Running".bold().green(), step)
208 }
209 Self::SubprocessOutput { prefix, line } => {
210 format!("{:>12} > {}", prefix, line)
211 }
212 Self::OutputTruncated {
213 prefix,
214 remaining,
215 log_path,
216 } => {
217 format!(
218 "{:>12} > ... ({} more lines, full output in {})",
219 prefix,
220 remaining,
221 log_path.display()
222 )
223 }
224 Self::UnsupportedOption { option, backend } => {
225 let title = format!(
226 "hax: option {} is not supported by the {} backend and will be ignored",
227 option, backend
228 );
229 format!("{}", renderer.render(Level::Warning.title(&title)))
230 }
231 }
232 }
233}