1use celox_sir::verify::SirVerifyError;
2use celox_slt::{SLTNodeFactsError, scheduler::SchedulerError};
3use thiserror::Error;
4use veryl_analyzer::multi_sources::{MultiSources, Source};
5use veryl_parser::token_range::TokenRange;
6
7#[derive(Debug, Clone)]
9pub struct SourceLocation {
10 pub source: MultiSources,
11 pub span: miette::SourceSpan,
12}
13
14impl SourceLocation {
15 pub fn from_token(token: &TokenRange) -> Self {
16 let path = token.beg.source.to_string();
17 let text = token.beg.source.get_text();
18 Self {
19 source: MultiSources {
20 sources: vec![Source::new(path, text)],
21 },
22 span: token.into(),
23 }
24 }
25
26 fn path(&self) -> Option<&str> {
27 self.source
28 .sources
29 .first()
30 .map(|source| source.path.as_str())
31 }
32}
33
34impl From<celox_frontend_core::SourceLocation> for SourceLocation {
35 fn from(location: celox_frontend_core::SourceLocation) -> Self {
36 Self {
37 source: MultiSources {
38 sources: vec![Source::new(location.path, location.text.to_string())],
39 },
40 span: location.span,
41 }
42 }
43}
44
45impl From<celox_frontend_core::LoweringPhase> for LoweringPhase {
46 fn from(phase: celox_frontend_core::LoweringPhase) -> Self {
47 match phase {
48 celox_frontend_core::LoweringPhase::FfLowering => Self::FfLowering,
49 celox_frontend_core::LoweringPhase::CombLowering => Self::CombLowering,
50 celox_frontend_core::LoweringPhase::SimulatorParser => Self::SimulatorParser,
51 }
52 }
53}
54
55#[derive(Error, Debug)]
58pub enum FrontendDiagnostic {
59 #[error("Function output copy-out order is unspecified: {detail}")]
60 UnspecifiedOutputCopyOrder {
61 detail: String,
62 source_location: SourceLocation,
63 },
64
65 #[error("Loop continuation bound is not stable: {detail}")]
66 MutableForBound {
67 detail: String,
68 source_location: SourceLocation,
69 },
70
71 #[error("Loop continuation bound may change while simulation time advances: {detail}")]
72 TimeAdvancingForBound {
73 detail: String,
74 source_location: SourceLocation,
75 },
76
77 #[error("Unable to prove that the loop continuation bound remains unchanged: {detail}")]
78 UnknownForBoundEffect {
79 detail: String,
80 source_location: SourceLocation,
81 },
82}
83
84impl FrontendDiagnostic {
85 pub fn unspecified_output_copy_order(token: &TokenRange, detail: impl Into<String>) -> Self {
86 Self::UnspecifiedOutputCopyOrder {
87 detail: detail.into(),
88 source_location: SourceLocation::from_token(token),
89 }
90 }
91
92 pub fn mutable_for_bound(token: &TokenRange, detail: impl Into<String>) -> Self {
93 Self::MutableForBound {
94 detail: detail.into(),
95 source_location: SourceLocation::from_token(token),
96 }
97 }
98
99 pub fn unknown_for_bound_effect(token: &TokenRange, detail: impl Into<String>) -> Self {
100 Self::UnknownForBoundEffect {
101 detail: detail.into(),
102 source_location: SourceLocation::from_token(token),
103 }
104 }
105
106 pub fn time_advancing_for_bound(token: &TokenRange, detail: impl Into<String>) -> Self {
107 Self::TimeAdvancingForBound {
108 detail: detail.into(),
109 source_location: SourceLocation::from_token(token),
110 }
111 }
112
113 pub fn is_error(&self) -> bool {
114 matches!(self, Self::MutableForBound { .. })
115 }
116
117 fn source_location(&self) -> &SourceLocation {
118 match self {
119 Self::MutableForBound {
120 source_location, ..
121 }
122 | Self::TimeAdvancingForBound {
123 source_location, ..
124 }
125 | Self::UnknownForBoundEffect {
126 source_location, ..
127 }
128 | Self::UnspecifiedOutputCopyOrder {
129 source_location, ..
130 } => source_location,
131 }
132 }
133}
134
135impl miette::Diagnostic for FrontendDiagnostic {
136 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
137 Some(Box::new(match self {
138 Self::UnspecifiedOutputCopyOrder { .. } => "unspecified_output_copy_order",
139 Self::MutableForBound { .. } => "mutable_for_bound",
140 Self::TimeAdvancingForBound { .. } => "time_advancing_for_bound",
141 Self::UnknownForBoundEffect { .. } => "unknown_for_bound_effect",
142 }))
143 }
144
145 fn severity(&self) -> Option<miette::Severity> {
146 Some(if self.is_error() {
147 miette::Severity::Error
148 } else {
149 miette::Severity::Warning
150 })
151 }
152
153 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
154 Some(Box::new(match self {
155 Self::UnspecifiedOutputCopyOrder { .. } => {
156 "use distinct output destinations, then assign them to shared storage in the intended order after the call"
157 }
158 Self::MutableForBound { .. } => {
159 "copy the bound to a value that is not modified by the loop body"
160 }
161 Self::TimeAdvancingForBound { .. } => {
162 "copy the bound to a procedural let before entering the loop"
163 }
164 Self::UnknownForBoundEffect { .. } => {
165 "avoid opaque or time-advancing calls in the loop, or make the bound independent of mutable state"
166 }
167 }))
168 }
169
170 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
171 Some(&self.source_location().source)
172 }
173
174 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
175 let location = self.source_location();
176 let label = match self {
177 Self::UnspecifiedOutputCopyOrder { .. } => "overlapping output arguments in this call",
178 _ => "loop with an unstable continuation bound",
179 };
180 Some(Box::new(std::iter::once(
181 miette::LabeledSpan::new_with_span(Some(label.to_string()), location.span),
182 )))
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum LoweringPhase {
189 FfLowering,
190 CombLowering,
191 SimulatorParser,
192}
193
194impl std::fmt::Display for LoweringPhase {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 LoweringPhase::FfLowering => write!(f, "FF lowering"),
198 LoweringPhase::CombLowering => write!(f, "comb lowering"),
199 LoweringPhase::SimulatorParser => write!(f, "simulator parser"),
200 }
201 }
202}
203
204#[derive(Error, Debug)]
205pub enum ParserError {
206 #[error(transparent)]
207 Scheduler(SchedulerError<String>),
208
209 #[error("{error}")]
210 SchedulerWithLocation {
211 error: SchedulerError<String>,
212 source_locations: Vec<SourceLocation>,
213 },
214
215 #[error("Unsupported in {phase}: {feature} [tracking issue #{issue}] ({detail})")]
216 Unsupported {
217 issue: u32,
218 phase: LoweringPhase,
219 feature: &'static str,
220 detail: String,
221 source_location: Option<SourceLocation>,
222 },
223
224 #[error("Illegal in current context: {feature} ({detail})")]
225 IllegalContext {
226 feature: &'static str,
227 detail: String,
228 source_location: Option<SourceLocation>,
229 },
230
231 #[error("Invalid argument binding for `{argument}` in call to function `{function}`: {detail}")]
232 InvalidFunctionArgumentBinding {
233 function: String,
234 argument: String,
235 detail: String,
236 source_location: Option<SourceLocation>,
237 },
238
239 #[error(
240 "Unresolved type width for variable `{variable}` in module `{module}`: \
241 width cannot be determined at compile time (type: {typ})"
242 )]
243 UnresolvedWidth {
244 module: String,
245 variable: String,
246 typ: String,
247 source_location: Option<SourceLocation>,
248 },
249
250 #[error("Top module `{name}` not found in IR")]
251 TopNotFound { name: String },
252
253 #[error("Top module `{name}` is generic and cannot be used as a top-level module")]
254 GenericTop { name: String },
255
256 #[error("SIR verification failed {phase} in {group} unit {unit}: {error}")]
257 SirVerify {
258 phase: &'static str,
259 group: &'static str,
260 unit: usize,
261 #[source]
262 error: SirVerifyError,
263 },
264
265 #[error("SLT verification failed {phase}: {error}")]
266 SltVerify {
267 phase: &'static str,
268 #[source]
269 error: SLTNodeFactsError,
270 },
271
272 #[error("SLT construction failed: {0}")]
273 SltConstruction(#[from] SLTNodeFactsError),
274}
275
276impl From<celox_frontend_core::ParserError> for ParserError {
277 fn from(error: celox_frontend_core::ParserError) -> Self {
278 use celox_frontend_core::ParserError as CoreError;
279 match error {
280 CoreError::Scheduler(error) => Self::Scheduler(error),
281 CoreError::SchedulerWithLocation {
282 error,
283 source_locations,
284 } => Self::SchedulerWithLocation {
285 error,
286 source_locations: source_locations.into_iter().map(Into::into).collect(),
287 },
288 CoreError::Unsupported {
289 issue,
290 phase,
291 feature,
292 detail,
293 source_location,
294 } => Self::Unsupported {
295 issue,
296 phase: phase.into(),
297 feature,
298 detail,
299 source_location: source_location.map(Into::into),
300 },
301 CoreError::IllegalContext {
302 feature,
303 detail,
304 source_location,
305 } => Self::IllegalContext {
306 feature,
307 detail,
308 source_location: source_location.map(Into::into),
309 },
310 CoreError::TopNotFound { name } => Self::TopNotFound { name },
311 CoreError::GenericTop { name } => Self::GenericTop { name },
312 CoreError::SirVerify {
313 phase,
314 group,
315 unit,
316 error,
317 } => Self::SirVerify {
318 phase,
319 group,
320 unit,
321 error,
322 },
323 CoreError::SltVerify { phase, error } => Self::SltVerify { phase, error },
324 CoreError::SltConstruction(error) => Self::SltConstruction(error),
325 }
326 }
327}
328
329impl ParserError {
330 pub fn unsupported(
331 issue: u32,
332 phase: LoweringPhase,
333 feature: &'static str,
334 detail: impl Into<String>,
335 token: Option<&TokenRange>,
336 ) -> Self {
337 ParserError::Unsupported {
338 issue,
339 phase,
340 feature,
341 detail: detail.into(),
342 source_location: token.map(SourceLocation::from_token),
343 }
344 }
345
346 pub fn illegal_context(
347 feature: &'static str,
348 detail: impl Into<String>,
349 token: Option<&TokenRange>,
350 ) -> Self {
351 ParserError::IllegalContext {
352 feature,
353 detail: detail.into(),
354 source_location: token.map(SourceLocation::from_token),
355 }
356 }
357
358 pub fn unresolved_width(
359 module: &veryl_analyzer::ir::Module,
360 var: &veryl_analyzer::ir::Variable,
361 typ: impl Into<String>,
362 ) -> Self {
363 ParserError::UnresolvedWidth {
364 module: module.name.to_string(),
365 variable: var.path.to_string(),
366 typ: typ.into(),
367 source_location: Some(SourceLocation::from_token(&var.token)),
368 }
369 }
370
371 pub fn invalid_function_argument_binding(
372 function: impl Into<String>,
373 argument: impl Into<String>,
374 detail: impl Into<String>,
375 token: Option<&TokenRange>,
376 ) -> Self {
377 ParserError::InvalidFunctionArgumentBinding {
378 function: function.into(),
379 argument: argument.into(),
380 detail: detail.into(),
381 source_location: token.map(SourceLocation::from_token),
382 }
383 }
384}
385
386impl miette::Diagnostic for ParserError {
387 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
388 match self {
389 ParserError::Unsupported { phase, .. } => Some(Box::new(format!(
390 "unsupported_{}",
391 match phase {
392 LoweringPhase::FfLowering => "ff_lowering",
393 LoweringPhase::CombLowering => "comb_lowering",
394 LoweringPhase::SimulatorParser => "simulator_parser",
395 }
396 ))),
397 ParserError::IllegalContext { .. } => Some(Box::new("illegal_context")),
398 ParserError::InvalidFunctionArgumentBinding { .. } => {
399 Some(Box::new("invalid_function_argument_binding"))
400 }
401 ParserError::UnresolvedWidth { .. } => Some(Box::new("unresolved_width")),
402 ParserError::Scheduler(_) | ParserError::SchedulerWithLocation { .. } => {
403 Some(Box::new("scheduler"))
404 }
405 ParserError::TopNotFound { .. } => Some(Box::new("top_not_found")),
406 ParserError::GenericTop { .. } => Some(Box::new("generic_top")),
407 ParserError::SirVerify { .. } => Some(Box::new("sir_verify")),
408 ParserError::SltVerify { .. } | ParserError::SltConstruction(_) => {
409 Some(Box::new("slt_verify"))
410 }
411 }
412 }
413
414 fn severity(&self) -> Option<miette::Severity> {
415 Some(miette::Severity::Error)
416 }
417
418 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
419 let location = match self {
420 ParserError::Unsupported {
421 source_location, ..
422 }
423 | ParserError::IllegalContext {
424 source_location, ..
425 }
426 | ParserError::InvalidFunctionArgumentBinding {
427 source_location, ..
428 }
429 | ParserError::UnresolvedWidth {
430 source_location, ..
431 } => source_location.as_ref(),
432 ParserError::SchedulerWithLocation {
433 source_locations, ..
434 } => source_locations.first(),
435 _ => None,
436 };
437 location.map(|location| &location.source as &dyn miette::SourceCode)
438 }
439
440 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
441 let location = match self {
442 ParserError::Unsupported {
443 source_location, ..
444 }
445 | ParserError::IllegalContext {
446 source_location, ..
447 }
448 | ParserError::InvalidFunctionArgumentBinding {
449 source_location, ..
450 }
451 | ParserError::UnresolvedWidth {
452 source_location, ..
453 } => source_location.as_ref(),
454 _ => None,
455 };
456 if let Some(location) = location {
457 return Some(Box::new(std::iter::once(
458 miette::LabeledSpan::new_with_span(
459 Some("Error location".to_string()),
460 location.span,
461 ),
462 )));
463 }
464
465 match self {
466 ParserError::SchedulerWithLocation {
467 source_locations, ..
468 } => {
469 let first_path = source_locations.first().and_then(SourceLocation::path)?;
470 let labels = source_locations
471 .iter()
472 .filter(move |location| location.path() == Some(first_path))
473 .map(|location| {
474 miette::LabeledSpan::new_with_span(
475 Some("loop participant".to_string()),
476 location.span,
477 )
478 })
479 .collect::<Vec<_>>();
480 if labels.is_empty() {
481 None
482 } else {
483 Some(Box::new(labels.into_iter()))
484 }
485 }
486 _ => None,
487 }
488 }
489}