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