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("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("Invalid argument binding for `{argument}` in call to function `{function}`: {detail}")]
258 InvalidFunctionArgumentBinding {
259 function: String,
260 argument: String,
261 detail: String,
262 source_location: Option<SourceLocation>,
263 },
264
265 #[error(
266 "Unresolved type width for variable `{variable}` in module `{module}`: \
267 width cannot be determined at compile time (type: {typ})"
268 )]
269 UnresolvedWidth {
270 module: String,
271 variable: String,
272 typ: String,
273 source_location: Option<SourceLocation>,
274 },
275
276 #[error("Top module `{name}` not found in IR")]
277 TopNotFound { name: String },
278
279 #[error("Top module `{name}` is generic and cannot be used as a top-level module")]
280 GenericTop { name: String },
281
282 #[error("SIR verification failed {phase} in {group} unit {unit}: {error}")]
283 SirVerify {
284 phase: &'static str,
285 group: &'static str,
286 unit: usize,
287 #[source]
288 error: SirVerifyError,
289 },
290
291 #[error("SLT verification failed {phase}: {error}")]
292 SltVerify {
293 phase: &'static str,
294 #[source]
295 error: SLTNodeFactsError,
296 },
297
298 #[error("SLT construction failed: {0}")]
299 SltConstruction(#[from] SLTNodeFactsError),
300}
301
302impl From<celox_frontend_core::ParserError> for ParserError {
303 fn from(error: celox_frontend_core::ParserError) -> Self {
304 use celox_frontend_core::ParserError as CoreError;
305 match error {
306 CoreError::Scheduler(error) => Self::Scheduler(error),
307 CoreError::SchedulerWithLocation {
308 error,
309 source_locations,
310 } => Self::SchedulerWithLocation {
311 error,
312 source_locations: source_locations.into_iter().map(Into::into).collect(),
313 },
314 CoreError::Unsupported {
315 issue,
316 phase,
317 feature,
318 detail,
319 source_location,
320 } => Self::Unsupported {
321 issue,
322 phase: phase.into(),
323 feature,
324 detail,
325 source_location: source_location.map(Into::into),
326 },
327 CoreError::IllegalContext {
328 feature,
329 detail,
330 source_location,
331 } => Self::IllegalContext {
332 feature,
333 detail,
334 source_location: source_location.map(Into::into),
335 },
336 CoreError::TopNotFound { name } => Self::TopNotFound { name },
337 CoreError::GenericTop { name } => Self::GenericTop { name },
338 CoreError::SirVerify {
339 phase,
340 group,
341 unit,
342 error,
343 } => Self::SirVerify {
344 phase,
345 group,
346 unit,
347 error,
348 },
349 CoreError::SltVerify { phase, error } => Self::SltVerify { phase, error },
350 CoreError::SltConstruction(error) => Self::SltConstruction(error),
351 }
352 }
353}
354
355impl ParserError {
356 pub fn unsupported(
357 issue: u32,
358 phase: LoweringPhase,
359 feature: &'static str,
360 detail: impl Into<String>,
361 token: Option<&TokenRange>,
362 ) -> Self {
363 ParserError::Unsupported {
364 issue,
365 phase,
366 feature,
367 detail: detail.into(),
368 source_location: token.map(SourceLocation::from_token),
369 }
370 }
371
372 pub fn illegal_context(
373 feature: &'static str,
374 detail: impl Into<String>,
375 token: Option<&TokenRange>,
376 ) -> Self {
377 ParserError::IllegalContext {
378 feature,
379 detail: detail.into(),
380 source_location: token.map(SourceLocation::from_token),
381 }
382 }
383
384 pub fn unresolved_width(
385 module: &veryl_analyzer::ir::Module,
386 var: &veryl_analyzer::ir::Variable,
387 typ: impl Into<String>,
388 ) -> Self {
389 ParserError::UnresolvedWidth {
390 module: module.name.to_string(),
391 variable: var.path.to_string(),
392 typ: typ.into(),
393 source_location: Some(SourceLocation::from_token(&var.token)),
394 }
395 }
396
397 pub fn invalid_function_argument_binding(
398 function: impl Into<String>,
399 argument: impl Into<String>,
400 detail: impl Into<String>,
401 token: Option<&TokenRange>,
402 ) -> Self {
403 ParserError::InvalidFunctionArgumentBinding {
404 function: function.into(),
405 argument: argument.into(),
406 detail: detail.into(),
407 source_location: token.map(SourceLocation::from_token),
408 }
409 }
410}
411
412impl miette::Diagnostic for ParserError {
413 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
414 match self {
415 ParserError::Unsupported { phase, .. } => Some(Box::new(format!(
416 "unsupported_{}",
417 match phase {
418 LoweringPhase::FfLowering => "ff_lowering",
419 LoweringPhase::CombLowering => "comb_lowering",
420 LoweringPhase::SimulatorParser => "simulator_parser",
421 }
422 ))),
423 ParserError::IllegalContext { .. } => Some(Box::new("illegal_context")),
424 ParserError::InvalidFunctionArgumentBinding { .. } => {
425 Some(Box::new("invalid_function_argument_binding"))
426 }
427 ParserError::UnresolvedWidth { .. } => Some(Box::new("unresolved_width")),
428 ParserError::Scheduler(_) | ParserError::SchedulerWithLocation { .. } => {
429 Some(Box::new("scheduler"))
430 }
431 ParserError::TopNotFound { .. } => Some(Box::new("top_not_found")),
432 ParserError::GenericTop { .. } => Some(Box::new("generic_top")),
433 ParserError::SirVerify { .. } => Some(Box::new("sir_verify")),
434 ParserError::SltVerify { .. } | ParserError::SltConstruction(_) => {
435 Some(Box::new("slt_verify"))
436 }
437 }
438 }
439
440 fn severity(&self) -> Option<miette::Severity> {
441 Some(miette::Severity::Error)
442 }
443
444 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
445 let location = match self {
446 ParserError::Unsupported {
447 source_location, ..
448 }
449 | ParserError::IllegalContext {
450 source_location, ..
451 }
452 | ParserError::InvalidFunctionArgumentBinding {
453 source_location, ..
454 }
455 | ParserError::UnresolvedWidth {
456 source_location, ..
457 } => source_location.as_ref(),
458 ParserError::SchedulerWithLocation {
459 source_locations, ..
460 } => source_locations.first(),
461 _ => None,
462 };
463 location.map(|location| &location.source as &dyn miette::SourceCode)
464 }
465
466 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
467 let location = match self {
468 ParserError::Unsupported {
469 source_location, ..
470 }
471 | ParserError::IllegalContext {
472 source_location, ..
473 }
474 | ParserError::InvalidFunctionArgumentBinding {
475 source_location, ..
476 }
477 | ParserError::UnresolvedWidth {
478 source_location, ..
479 } => source_location.as_ref(),
480 _ => None,
481 };
482 if let Some(location) = location {
483 return Some(Box::new(std::iter::once(
484 miette::LabeledSpan::new_with_span(
485 Some("Error location".to_string()),
486 location.span,
487 ),
488 )));
489 }
490
491 match self {
492 ParserError::SchedulerWithLocation {
493 source_locations, ..
494 } => {
495 let first_path = source_locations.first().and_then(SourceLocation::path)?;
496 let labels = source_locations
497 .iter()
498 .filter(move |location| location.path() == Some(first_path))
499 .map(|location| {
500 miette::LabeledSpan::new_with_span(
501 Some("loop participant".to_string()),
502 location.span,
503 )
504 })
505 .collect::<Vec<_>>();
506 if labels.is_empty() {
507 None
508 } else {
509 Some(Box::new(labels.into_iter()))
510 }
511 }
512 _ => None,
513 }
514 }
515}