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, Clone)]
58pub enum FrontendDiagnostic {
59 #[error("Array literal side-effect evaluation count is undefined: {detail}")]
60 UndefinedArrayLiteralEvaluationCount {
61 detail: String,
62 source_location: SourceLocation,
63 },
64
65 #[error("Function output copy-out order is unspecified: {detail}")]
66 UnspecifiedOutputCopyOrder {
67 detail: String,
68 source_location: SourceLocation,
69 },
70
71 #[error("Loop continuation bound is not stable: {detail}")]
72 MutableForBound {
73 detail: String,
74 source_location: SourceLocation,
75 },
76
77 #[error("Loop continuation bound may change while simulation time advances: {detail}")]
78 TimeAdvancingForBound {
79 detail: String,
80 source_location: SourceLocation,
81 },
82
83 #[error("Unable to prove that the loop continuation bound remains unchanged: {detail}")]
84 UnknownForBoundEffect {
85 detail: String,
86 source_location: SourceLocation,
87 },
88}
89
90impl FrontendDiagnostic {
91 pub fn undefined_array_literal_evaluation_count(
92 token: &TokenRange,
93 detail: impl Into<String>,
94 ) -> Self {
95 Self::UndefinedArrayLiteralEvaluationCount {
96 detail: detail.into(),
97 source_location: SourceLocation::from_token(token),
98 }
99 }
100
101 pub fn unspecified_output_copy_order(token: &TokenRange, detail: impl Into<String>) -> Self {
102 Self::UnspecifiedOutputCopyOrder {
103 detail: detail.into(),
104 source_location: SourceLocation::from_token(token),
105 }
106 }
107
108 pub fn mutable_for_bound(token: &TokenRange, detail: impl Into<String>) -> Self {
109 Self::MutableForBound {
110 detail: detail.into(),
111 source_location: SourceLocation::from_token(token),
112 }
113 }
114
115 pub fn unknown_for_bound_effect(token: &TokenRange, detail: impl Into<String>) -> Self {
116 Self::UnknownForBoundEffect {
117 detail: detail.into(),
118 source_location: SourceLocation::from_token(token),
119 }
120 }
121
122 pub fn time_advancing_for_bound(token: &TokenRange, detail: impl Into<String>) -> Self {
123 Self::TimeAdvancingForBound {
124 detail: detail.into(),
125 source_location: SourceLocation::from_token(token),
126 }
127 }
128
129 pub fn is_error(&self) -> bool {
130 matches!(self, Self::MutableForBound { .. })
131 }
132
133 fn source_location(&self) -> &SourceLocation {
134 match self {
135 Self::UndefinedArrayLiteralEvaluationCount {
136 source_location, ..
137 }
138 | Self::MutableForBound {
139 source_location, ..
140 }
141 | Self::TimeAdvancingForBound {
142 source_location, ..
143 }
144 | Self::UnknownForBoundEffect {
145 source_location, ..
146 }
147 | Self::UnspecifiedOutputCopyOrder {
148 source_location, ..
149 } => source_location,
150 }
151 }
152}
153
154impl miette::Diagnostic for FrontendDiagnostic {
155 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
156 Some(Box::new(match self {
157 Self::UndefinedArrayLiteralEvaluationCount { .. } => {
158 "undefined_array_literal_evaluation_count"
159 }
160 Self::UnspecifiedOutputCopyOrder { .. } => "unspecified_output_copy_order",
161 Self::MutableForBound { .. } => "mutable_for_bound",
162 Self::TimeAdvancingForBound { .. } => "time_advancing_for_bound",
163 Self::UnknownForBoundEffect { .. } => "unknown_for_bound_effect",
164 }))
165 }
166
167 fn severity(&self) -> Option<miette::Severity> {
168 Some(if self.is_error() {
169 miette::Severity::Error
170 } else {
171 miette::Severity::Warning
172 })
173 }
174
175 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
176 Some(Box::new(match self {
177 Self::UndefinedArrayLiteralEvaluationCount { .. } => {
178 "evaluate the expression into a temporary before the array literal, then use that value in default or repeat"
179 }
180 Self::UnspecifiedOutputCopyOrder { .. } => {
181 "use distinct output destinations, then assign them to shared storage in the intended order after the call"
182 }
183 Self::MutableForBound { .. } => {
184 "copy the bound to a value that is not modified by the loop body"
185 }
186 Self::TimeAdvancingForBound { .. } => {
187 "copy the bound to a procedural let before entering the loop"
188 }
189 Self::UnknownForBoundEffect { .. } => {
190 "avoid opaque or time-advancing calls in the loop, or make the bound independent of mutable state"
191 }
192 }))
193 }
194
195 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
196 Some(&self.source_location().source)
197 }
198
199 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
200 let location = self.source_location();
201 let label = match self {
202 Self::UndefinedArrayLiteralEvaluationCount { .. } => "effectful default or repeat item",
203 Self::UnspecifiedOutputCopyOrder { .. } => "overlapping output arguments in this call",
204 _ => "loop with an unstable continuation bound",
205 };
206 Some(Box::new(std::iter::once(
207 miette::LabeledSpan::new_with_span(Some(label.to_string()), location.span),
208 )))
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum LoweringPhase {
215 FfLowering,
216 CombLowering,
217 SimulatorParser,
218}
219
220impl std::fmt::Display for LoweringPhase {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 match self {
223 LoweringPhase::FfLowering => write!(f, "FF lowering"),
224 LoweringPhase::CombLowering => write!(f, "comb lowering"),
225 LoweringPhase::SimulatorParser => write!(f, "simulator parser"),
226 }
227 }
228}
229
230#[derive(Error, Debug)]
231pub enum ParserError {
232 #[error(transparent)]
233 Scheduler(SchedulerError<String>),
234
235 #[error("{error}")]
236 SchedulerWithLocation {
237 error: SchedulerError<String>,
238 source_locations: Vec<SourceLocation>,
239 },
240
241 #[error("Unsupported in {phase}: {feature} [tracking issue #{issue}] ({detail})")]
242 Unsupported {
243 issue: u32,
244 phase: LoweringPhase,
245 feature: &'static str,
246 detail: String,
247 source_location: Option<SourceLocation>,
248 },
249
250 #[error("Illegal in current context: {feature} ({detail})")]
251 IllegalContext {
252 feature: &'static str,
253 detail: String,
254 source_location: Option<SourceLocation>,
255 },
256
257 #[error("Internal compiler error: {feature} ({detail})")]
260 Internal {
261 feature: &'static str,
262 detail: String,
263 source_location: Option<SourceLocation>,
264 },
265
266 #[error("External module `{name}` was not supplied")]
267 MissingExternalModule {
268 name: String,
269 source_location: Option<SourceLocation>,
270 },
271
272 #[error("Invalid memory file input: {detail}")]
273 MemoryFile {
274 detail: String,
275 source_location: Option<SourceLocation>,
276 },
277
278 #[error("Invalid argument binding for `{argument}` in call to function `{function}`: {detail}")]
279 InvalidFunctionArgumentBinding {
280 function: String,
281 argument: String,
282 detail: String,
283 source_location: Option<SourceLocation>,
284 },
285
286 #[error(
287 "Unresolved type width for variable `{variable}` in module `{module}`: \
288 width cannot be determined at compile time (type: {typ})"
289 )]
290 UnresolvedWidth {
291 module: String,
292 variable: String,
293 typ: String,
294 source_location: Option<SourceLocation>,
295 },
296
297 #[error(
298 "DPI-C imports linked to `{name}` have different prototypes: `{first}` and `{second}` \
299 (IEEE 1800-2023 35.5.4)"
300 )]
301 ExternSignatureMismatch {
302 name: String,
303 first: String,
304 second: String,
305 },
306
307 #[error("Top module `{name}` not found in IR")]
308 TopNotFound { name: String },
309
310 #[error("Top module `{name}` is generic and cannot be used as a top-level module")]
311 GenericTop { name: String },
312
313 #[error("SIR verification failed {phase} in {group} unit {unit}: {error}")]
314 SirVerify {
315 phase: &'static str,
316 group: &'static str,
317 unit: usize,
318 #[source]
319 error: SirVerifyError,
320 },
321
322 #[error("SLT verification failed {phase}: {error}")]
323 SltVerify {
324 phase: &'static str,
325 #[source]
326 error: SLTNodeFactsError,
327 },
328
329 #[error("SLT construction failed: {0}")]
330 SltConstruction(#[from] SLTNodeFactsError),
331}
332
333impl From<celox_frontend_core::ParserError> for ParserError {
334 fn from(error: celox_frontend_core::ParserError) -> Self {
335 use celox_frontend_core::ParserError as CoreError;
336 match error {
337 CoreError::Scheduler(error) => Self::Scheduler(error),
338 CoreError::SchedulerWithLocation {
339 error,
340 source_locations,
341 } => Self::SchedulerWithLocation {
342 error,
343 source_locations: source_locations.into_iter().map(Into::into).collect(),
344 },
345 CoreError::Unsupported {
346 issue,
347 phase,
348 feature,
349 detail,
350 source_location,
351 } => Self::Unsupported {
352 issue,
353 phase: phase.into(),
354 feature,
355 detail,
356 source_location: source_location.map(Into::into),
357 },
358 CoreError::IllegalContext {
359 feature,
360 detail,
361 source_location,
362 } => Self::IllegalContext {
363 feature,
364 detail,
365 source_location: source_location.map(Into::into),
366 },
367 CoreError::MemoryFile {
368 detail,
369 source_location,
370 } => Self::MemoryFile {
371 detail,
372 source_location: source_location.map(Into::into),
373 },
374 CoreError::ExternSignatureMismatch {
375 name,
376 first,
377 second,
378 } => Self::ExternSignatureMismatch {
379 name,
380 first,
381 second,
382 },
383 CoreError::TopNotFound { name } => Self::TopNotFound { name },
384 CoreError::GenericTop { name } => Self::GenericTop { name },
385 CoreError::SirVerify {
386 phase,
387 group,
388 unit,
389 error,
390 } => Self::SirVerify {
391 phase,
392 group,
393 unit,
394 error,
395 },
396 CoreError::SltVerify { phase, error } => Self::SltVerify { phase, error },
397 CoreError::SltConstruction(error) => Self::SltConstruction(error),
398 }
399 }
400}
401
402impl ParserError {
403 pub fn unsupported(
404 issue: u32,
405 phase: LoweringPhase,
406 feature: &'static str,
407 detail: impl Into<String>,
408 token: Option<&TokenRange>,
409 ) -> Self {
410 ParserError::Unsupported {
411 issue,
412 phase,
413 feature,
414 detail: detail.into(),
415 source_location: token.map(SourceLocation::from_token),
416 }
417 }
418
419 pub fn illegal_context(
420 feature: &'static str,
421 detail: impl Into<String>,
422 token: Option<&TokenRange>,
423 ) -> Self {
424 ParserError::IllegalContext {
425 feature,
426 detail: detail.into(),
427 source_location: token.map(SourceLocation::from_token),
428 }
429 }
430
431 pub fn internal(
432 feature: &'static str,
433 detail: impl Into<String>,
434 token: Option<&TokenRange>,
435 ) -> Self {
436 ParserError::Internal {
437 feature,
438 detail: detail.into(),
439 source_location: token.map(SourceLocation::from_token),
440 }
441 }
442
443 pub fn memory_file(detail: impl Into<String>, token: Option<&TokenRange>) -> Self {
444 ParserError::MemoryFile {
445 detail: detail.into(),
446 source_location: token.map(SourceLocation::from_token),
447 }
448 }
449
450 pub fn unresolved_width(
451 module: &veryl_analyzer::ir::Module,
452 var: &veryl_analyzer::ir::Variable,
453 typ: impl Into<String>,
454 ) -> Self {
455 ParserError::UnresolvedWidth {
456 module: module.name.to_string(),
457 variable: var.path.to_string(),
458 typ: typ.into(),
459 source_location: Some(SourceLocation::from_token(&var.token)),
460 }
461 }
462
463 pub fn invalid_function_argument_binding(
464 function: impl Into<String>,
465 argument: impl Into<String>,
466 detail: impl Into<String>,
467 token: Option<&TokenRange>,
468 ) -> Self {
469 ParserError::InvalidFunctionArgumentBinding {
470 function: function.into(),
471 argument: argument.into(),
472 detail: detail.into(),
473 source_location: token.map(SourceLocation::from_token),
474 }
475 }
476}
477
478impl miette::Diagnostic for ParserError {
479 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
480 match self {
481 ParserError::Unsupported { phase, .. } => Some(Box::new(format!(
482 "unsupported_{}",
483 match phase {
484 LoweringPhase::FfLowering => "ff_lowering",
485 LoweringPhase::CombLowering => "comb_lowering",
486 LoweringPhase::SimulatorParser => "simulator_parser",
487 }
488 ))),
489 ParserError::IllegalContext { .. } => Some(Box::new("illegal_context")),
490 ParserError::Internal { .. } => Some(Box::new("internal")),
491 ParserError::MissingExternalModule { .. } => Some(Box::new("missing_external_module")),
492 ParserError::MemoryFile { .. } => Some(Box::new("memory_file")),
493 ParserError::InvalidFunctionArgumentBinding { .. } => {
494 Some(Box::new("invalid_function_argument_binding"))
495 }
496 ParserError::UnresolvedWidth { .. } => Some(Box::new("unresolved_width")),
497 ParserError::Scheduler(_) | ParserError::SchedulerWithLocation { .. } => {
498 Some(Box::new("scheduler"))
499 }
500 ParserError::ExternSignatureMismatch { .. } => {
501 Some(Box::new("extern_signature_mismatch"))
502 }
503 ParserError::TopNotFound { .. } => Some(Box::new("top_not_found")),
504 ParserError::GenericTop { .. } => Some(Box::new("generic_top")),
505 ParserError::SirVerify { .. } => Some(Box::new("sir_verify")),
506 ParserError::SltVerify { .. } | ParserError::SltConstruction(_) => {
507 Some(Box::new("slt_verify"))
508 }
509 }
510 }
511
512 fn severity(&self) -> Option<miette::Severity> {
513 Some(miette::Severity::Error)
514 }
515
516 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
517 let location = match self {
518 ParserError::Unsupported {
519 source_location, ..
520 }
521 | ParserError::IllegalContext {
522 source_location, ..
523 }
524 | ParserError::Internal {
525 source_location, ..
526 }
527 | ParserError::MissingExternalModule {
528 source_location, ..
529 }
530 | ParserError::MemoryFile {
531 source_location, ..
532 }
533 | ParserError::InvalidFunctionArgumentBinding {
534 source_location, ..
535 }
536 | ParserError::UnresolvedWidth {
537 source_location, ..
538 } => source_location.as_ref(),
539 ParserError::SchedulerWithLocation {
540 source_locations, ..
541 } => source_locations.first(),
542 _ => None,
543 };
544 location.map(|location| &location.source as &dyn miette::SourceCode)
545 }
546
547 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
548 let location = match self {
549 ParserError::Unsupported {
550 source_location, ..
551 }
552 | ParserError::IllegalContext {
553 source_location, ..
554 }
555 | ParserError::Internal {
556 source_location, ..
557 }
558 | ParserError::MissingExternalModule {
559 source_location, ..
560 }
561 | ParserError::MemoryFile {
562 source_location, ..
563 }
564 | ParserError::InvalidFunctionArgumentBinding {
565 source_location, ..
566 }
567 | ParserError::UnresolvedWidth {
568 source_location, ..
569 } => source_location.as_ref(),
570 _ => None,
571 };
572 if let Some(location) = location {
573 return Some(Box::new(std::iter::once(
574 miette::LabeledSpan::new_with_span(
575 Some("Error location".to_string()),
576 location.span,
577 ),
578 )));
579 }
580
581 match self {
582 ParserError::SchedulerWithLocation {
583 source_locations, ..
584 } => {
585 let first_path = source_locations.first().and_then(SourceLocation::path)?;
586 let labels = source_locations
587 .iter()
588 .filter(move |location| location.path() == Some(first_path))
589 .map(|location| {
590 miette::LabeledSpan::new_with_span(
591 Some("loop participant".to_string()),
592 location.span,
593 )
594 })
595 .collect::<Vec<_>>();
596 if labels.is_empty() {
597 None
598 } else {
599 Some(Box::new(labels.into_iter()))
600 }
601 }
602 _ => None,
603 }
604 }
605}