1#[cfg(feature = "artifact-graph")]
2use std::collections::BTreeMap;
3
4use indexmap::IndexMap;
5pub use kcl_error::CompilationError;
6pub use kcl_error::Severity;
7pub use kcl_error::Suggestion;
8pub use kcl_error::Tag;
9use serde::Deserialize;
10use serde::Serialize;
11use thiserror::Error;
12use tower_lsp::lsp_types::Diagnostic;
13use tower_lsp::lsp_types::DiagnosticSeverity;
14
15use crate::ModuleId;
16use crate::SourceRange;
17use crate::exec::KclValue;
18#[cfg(feature = "artifact-graph")]
19use crate::execution::ArtifactCommand;
20#[cfg(feature = "artifact-graph")]
21use crate::execution::ArtifactGraph;
22use crate::execution::DefaultPlanes;
23#[cfg(feature = "artifact-graph")]
24use crate::execution::Operation;
25#[cfg(feature = "artifact-graph")]
26use crate::front::Number;
27#[cfg(feature = "artifact-graph")]
28use crate::front::Object;
29#[cfg(feature = "artifact-graph")]
30use crate::front::ObjectId;
31use crate::lsp::IntoDiagnostic;
32use crate::lsp::ToLspRange;
33use crate::modules::ModulePath;
34use crate::modules::ModuleSource;
35
36pub trait IsRetryable {
37 fn is_retryable(&self) -> bool;
40}
41
42#[derive(thiserror::Error, Debug)]
44pub enum ExecError {
45 #[error("{0}")]
46 Kcl(#[from] Box<crate::KclErrorWithOutputs>),
47 #[error("Could not connect to engine: {0}")]
48 Connection(#[from] ConnectionError),
49 #[error("PNG snapshot could not be decoded: {0}")]
50 BadPng(String),
51 #[error("Bad export: {0}")]
52 BadExport(String),
53}
54
55impl From<KclErrorWithOutputs> for ExecError {
56 fn from(error: KclErrorWithOutputs) -> Self {
57 ExecError::Kcl(Box::new(error))
58 }
59}
60
61#[derive(Debug, thiserror::Error)]
63#[error("{error}")]
64pub struct ExecErrorWithState {
65 pub error: ExecError,
66 pub exec_state: Option<crate::execution::ExecState>,
67}
68
69impl ExecErrorWithState {
70 #[cfg_attr(target_arch = "wasm32", expect(dead_code))]
71 pub fn new(error: ExecError, exec_state: crate::execution::ExecState) -> Self {
72 Self {
73 error,
74 exec_state: Some(exec_state),
75 }
76 }
77}
78
79impl IsRetryable for ExecErrorWithState {
80 fn is_retryable(&self) -> bool {
81 self.error.is_retryable()
82 }
83}
84
85impl ExecError {
86 pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
87 let ExecError::Kcl(k) = &self else {
88 return None;
89 };
90 Some(&k.error)
91 }
92}
93
94impl IsRetryable for ExecError {
95 fn is_retryable(&self) -> bool {
96 matches!(self, ExecError::Kcl(kcl_error) if kcl_error.is_retryable())
97 }
98}
99
100impl From<ExecError> for ExecErrorWithState {
101 fn from(error: ExecError) -> Self {
102 Self {
103 error,
104 exec_state: None,
105 }
106 }
107}
108
109impl From<ConnectionError> for ExecErrorWithState {
110 fn from(error: ConnectionError) -> Self {
111 Self {
112 error: error.into(),
113 exec_state: None,
114 }
115 }
116}
117
118#[derive(thiserror::Error, Debug)]
120pub enum ConnectionError {
121 #[error("Could not create a Zoo client: {0}")]
122 CouldNotMakeClient(anyhow::Error),
123 #[error("Could not establish connection to engine: {0}")]
124 Establishing(anyhow::Error),
125}
126
127#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
128#[ts(export)]
129#[serde(tag = "kind", rename_all = "snake_case")]
130pub enum KclError {
131 #[error("lexical: {details:?}")]
132 Lexical { details: KclErrorDetails },
133 #[error("syntax: {details:?}")]
134 Syntax { details: KclErrorDetails },
135 #[error("semantic: {details:?}")]
136 Semantic { details: KclErrorDetails },
137 #[error("import cycle: {details:?}")]
138 ImportCycle { details: KclErrorDetails },
139 #[error("argument: {details:?}")]
140 Argument { details: KclErrorDetails },
141 #[error("type: {details:?}")]
142 Type { details: KclErrorDetails },
143 #[error("i/o: {details:?}")]
144 Io { details: KclErrorDetails },
145 #[error("unexpected: {details:?}")]
146 Unexpected { details: KclErrorDetails },
147 #[error("value already defined: {details:?}")]
148 ValueAlreadyDefined { details: KclErrorDetails },
149 #[error("undefined value: {details:?}")]
150 UndefinedValue {
151 details: KclErrorDetails,
152 name: Option<String>,
153 },
154 #[error("invalid expression: {details:?}")]
155 InvalidExpression { details: KclErrorDetails },
156 #[error("max call stack size exceeded: {details:?}")]
157 MaxCallStack { details: KclErrorDetails },
158 #[error("engine: {details:?}")]
159 Engine { details: KclErrorDetails },
160 #[error("engine hangup: {details:?}")]
161 EngineHangup { details: KclErrorDetails },
162 #[error("engine internal: {details:?}")]
163 EngineInternal { details: KclErrorDetails },
164 #[error("internal error, please report to KittyCAD team: {details:?}")]
165 Internal { details: KclErrorDetails },
166}
167
168impl From<KclErrorWithOutputs> for KclError {
169 fn from(error: KclErrorWithOutputs) -> Self {
170 error.error
171 }
172}
173
174impl IsRetryable for KclError {
175 fn is_retryable(&self) -> bool {
176 matches!(self, KclError::EngineHangup { .. } | KclError::EngineInternal { .. })
177 }
178}
179
180#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
181#[error("{error}")]
182#[ts(export)]
183#[serde(rename_all = "camelCase")]
184pub struct KclErrorWithOutputs {
185 pub error: KclError,
186 pub non_fatal: Vec<CompilationError>,
187 pub variables: IndexMap<String, KclValue>,
190 #[cfg(feature = "artifact-graph")]
191 pub operations: Vec<Operation>,
192 #[cfg(feature = "artifact-graph")]
195 pub _artifact_commands: Vec<ArtifactCommand>,
196 #[cfg(feature = "artifact-graph")]
197 pub artifact_graph: ArtifactGraph,
198 #[cfg(feature = "artifact-graph")]
199 #[serde(skip)]
200 pub scene_objects: Vec<Object>,
201 #[cfg(feature = "artifact-graph")]
202 #[serde(skip)]
203 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
204 #[cfg(feature = "artifact-graph")]
205 #[serde(skip)]
206 pub var_solutions: Vec<(SourceRange, Number)>,
207 pub scene_graph: Option<crate::front::SceneGraph>,
208 pub filenames: IndexMap<ModuleId, ModulePath>,
209 pub source_files: IndexMap<ModuleId, ModuleSource>,
210 pub default_planes: Option<DefaultPlanes>,
211}
212
213impl KclErrorWithOutputs {
214 #[allow(clippy::too_many_arguments)]
215 pub fn new(
216 error: KclError,
217 non_fatal: Vec<CompilationError>,
218 variables: IndexMap<String, KclValue>,
219 #[cfg(feature = "artifact-graph")] operations: Vec<Operation>,
220 #[cfg(feature = "artifact-graph")] artifact_commands: Vec<ArtifactCommand>,
221 #[cfg(feature = "artifact-graph")] artifact_graph: ArtifactGraph,
222 #[cfg(feature = "artifact-graph")] scene_objects: Vec<Object>,
223 #[cfg(feature = "artifact-graph")] source_range_to_object: BTreeMap<SourceRange, ObjectId>,
224 #[cfg(feature = "artifact-graph")] var_solutions: Vec<(SourceRange, Number)>,
225 filenames: IndexMap<ModuleId, ModulePath>,
226 source_files: IndexMap<ModuleId, ModuleSource>,
227 default_planes: Option<DefaultPlanes>,
228 ) -> Self {
229 Self {
230 error,
231 non_fatal,
232 variables,
233 #[cfg(feature = "artifact-graph")]
234 operations,
235 #[cfg(feature = "artifact-graph")]
236 _artifact_commands: artifact_commands,
237 #[cfg(feature = "artifact-graph")]
238 artifact_graph,
239 #[cfg(feature = "artifact-graph")]
240 scene_objects,
241 #[cfg(feature = "artifact-graph")]
242 source_range_to_object,
243 #[cfg(feature = "artifact-graph")]
244 var_solutions,
245 scene_graph: Default::default(),
246 filenames,
247 source_files,
248 default_planes,
249 }
250 }
251 pub fn no_outputs(error: KclError) -> Self {
252 Self {
253 error,
254 non_fatal: Default::default(),
255 variables: Default::default(),
256 #[cfg(feature = "artifact-graph")]
257 operations: Default::default(),
258 #[cfg(feature = "artifact-graph")]
259 _artifact_commands: Default::default(),
260 #[cfg(feature = "artifact-graph")]
261 artifact_graph: Default::default(),
262 #[cfg(feature = "artifact-graph")]
263 scene_objects: Default::default(),
264 #[cfg(feature = "artifact-graph")]
265 source_range_to_object: Default::default(),
266 #[cfg(feature = "artifact-graph")]
267 var_solutions: Default::default(),
268 scene_graph: Default::default(),
269 filenames: Default::default(),
270 source_files: Default::default(),
271 default_planes: Default::default(),
272 }
273 }
274 pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
275 let mut source_ranges = self.error.source_ranges();
276
277 let first_source_range = source_ranges
279 .pop()
280 .ok_or_else(|| anyhow::anyhow!("No source ranges found"))?;
281
282 let source = self
283 .source_files
284 .get(&first_source_range.module_id())
285 .cloned()
286 .unwrap_or(ModuleSource {
287 source: code.to_string(),
288 path: self
289 .filenames
290 .get(&first_source_range.module_id())
291 .cloned()
292 .unwrap_or(ModulePath::Main),
293 });
294 let filename = source.path.to_string();
295 let kcl_source = source.source;
296
297 let mut related = Vec::new();
298 for source_range in source_ranges {
299 let module_id = source_range.module_id();
300 let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
301 source: code.to_string(),
302 path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
303 });
304 let error = self.error.override_source_ranges(vec![source_range]);
305 let report = Report {
306 error,
307 kcl_source: source.source.to_string(),
308 filename: source.path.to_string(),
309 };
310 related.push(report);
311 }
312
313 Ok(ReportWithOutputs {
314 error: self,
315 kcl_source,
316 filename,
317 related,
318 })
319 }
320}
321
322impl IsRetryable for KclErrorWithOutputs {
323 fn is_retryable(&self) -> bool {
324 matches!(
325 self.error,
326 KclError::EngineHangup { .. } | KclError::EngineInternal { .. }
327 )
328 }
329}
330
331impl IntoDiagnostic for KclErrorWithOutputs {
332 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
333 let message = self.error.get_message();
334 let source_ranges = self.error.source_ranges();
335
336 source_ranges
337 .into_iter()
338 .map(|source_range| {
339 let source = self
340 .source_files
341 .get(&source_range.module_id())
342 .cloned()
343 .unwrap_or(ModuleSource {
344 source: code.to_string(),
345 path: self.filenames.get(&source_range.module_id()).unwrap().clone(),
346 });
347 let mut filename = source.path.to_string();
348 if !filename.starts_with("file://") {
349 filename = format!("file:///{}", filename.trim_start_matches("/"));
350 }
351
352 let related_information = if let Ok(uri) = url::Url::parse(&filename) {
353 Some(vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
354 location: tower_lsp::lsp_types::Location {
355 uri,
356 range: source_range.to_lsp_range(&source.source),
357 },
358 message: message.to_string(),
359 }])
360 } else {
361 None
362 };
363
364 Diagnostic {
365 range: source_range.to_lsp_range(code),
366 severity: Some(self.severity()),
367 code: None,
368 code_description: None,
370 source: Some("kcl".to_string()),
371 related_information,
372 message: message.clone(),
373 tags: None,
374 data: None,
375 }
376 })
377 .collect()
378 }
379
380 fn severity(&self) -> DiagnosticSeverity {
381 DiagnosticSeverity::ERROR
382 }
383}
384
385#[derive(thiserror::Error, Debug)]
386#[error("{}", self.error.error.get_message())]
387pub struct ReportWithOutputs {
388 pub error: KclErrorWithOutputs,
389 pub kcl_source: String,
390 pub filename: String,
391 pub related: Vec<Report>,
392}
393
394impl miette::Diagnostic for ReportWithOutputs {
395 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
396 let family = match self.error.error {
397 KclError::Lexical { .. } => "Lexical",
398 KclError::Syntax { .. } => "Syntax",
399 KclError::Semantic { .. } => "Semantic",
400 KclError::ImportCycle { .. } => "ImportCycle",
401 KclError::Argument { .. } => "Argument",
402 KclError::Type { .. } => "Type",
403 KclError::Io { .. } => "I/O",
404 KclError::Unexpected { .. } => "Unexpected",
405 KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
406 KclError::UndefinedValue { .. } => "UndefinedValue",
407 KclError::InvalidExpression { .. } => "InvalidExpression",
408 KclError::MaxCallStack { .. } => "MaxCallStack",
409 KclError::Engine { .. } => "Engine",
410 KclError::EngineHangup { .. } => "EngineHangup",
411 KclError::EngineInternal { .. } => "EngineInternal",
412 KclError::Internal { .. } => "Internal",
413 };
414 let error_string = format!("KCL {family} error");
415 Some(Box::new(error_string))
416 }
417
418 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
419 Some(&self.kcl_source)
420 }
421
422 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
423 let iter = self
424 .error
425 .error
426 .source_ranges()
427 .into_iter()
428 .map(miette::SourceSpan::from)
429 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
430 Some(Box::new(iter))
431 }
432
433 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
434 let iter = self.related.iter().map(|r| r as &dyn miette::Diagnostic);
435 Some(Box::new(iter))
436 }
437}
438
439#[derive(thiserror::Error, Debug)]
440#[error("{}", self.error.get_message())]
441pub struct Report {
442 pub error: KclError,
443 pub kcl_source: String,
444 pub filename: String,
445}
446
447impl miette::Diagnostic for Report {
448 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
449 let family = match self.error {
450 KclError::Lexical { .. } => "Lexical",
451 KclError::Syntax { .. } => "Syntax",
452 KclError::Semantic { .. } => "Semantic",
453 KclError::ImportCycle { .. } => "ImportCycle",
454 KclError::Argument { .. } => "Argument",
455 KclError::Type { .. } => "Type",
456 KclError::Io { .. } => "I/O",
457 KclError::Unexpected { .. } => "Unexpected",
458 KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
459 KclError::UndefinedValue { .. } => "UndefinedValue",
460 KclError::InvalidExpression { .. } => "InvalidExpression",
461 KclError::MaxCallStack { .. } => "MaxCallStack",
462 KclError::Engine { .. } => "Engine",
463 KclError::EngineHangup { .. } => "EngineHangup",
464 KclError::EngineInternal { .. } => "EngineInternal",
465 KclError::Internal { .. } => "Internal",
466 };
467 let error_string = format!("KCL {family} error");
468 Some(Box::new(error_string))
469 }
470
471 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
472 Some(&self.kcl_source)
473 }
474
475 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
476 let iter = self
477 .error
478 .source_ranges()
479 .into_iter()
480 .map(miette::SourceSpan::from)
481 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
482 Some(Box::new(iter))
483 }
484}
485
486#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
487#[serde(rename_all = "camelCase")]
488#[error("{message}")]
489#[ts(export)]
490pub struct KclErrorDetails {
491 #[label(collection, "Errors")]
492 pub source_ranges: Vec<SourceRange>,
493 pub backtrace: Vec<super::BacktraceItem>,
494 #[serde(rename = "msg")]
495 pub message: String,
496}
497
498impl KclErrorDetails {
499 pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
500 let backtrace = source_ranges
501 .iter()
502 .map(|s| BacktraceItem {
503 source_range: *s,
504 fn_name: None,
505 })
506 .collect();
507 KclErrorDetails {
508 source_ranges,
509 backtrace,
510 message,
511 }
512 }
513}
514
515impl KclError {
516 pub fn internal(message: String) -> KclError {
517 KclError::Internal {
518 details: KclErrorDetails {
519 source_ranges: Default::default(),
520 backtrace: Default::default(),
521 message,
522 },
523 }
524 }
525
526 pub fn new_internal(details: KclErrorDetails) -> KclError {
527 KclError::Internal { details }
528 }
529
530 pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
531 KclError::ImportCycle { details }
532 }
533
534 pub fn new_argument(details: KclErrorDetails) -> KclError {
535 KclError::Argument { details }
536 }
537
538 pub fn new_semantic(details: KclErrorDetails) -> KclError {
539 KclError::Semantic { details }
540 }
541
542 pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
543 KclError::ValueAlreadyDefined { details }
544 }
545
546 pub fn new_syntax(details: KclErrorDetails) -> KclError {
547 KclError::Syntax { details }
548 }
549
550 pub fn new_io(details: KclErrorDetails) -> KclError {
551 KclError::Io { details }
552 }
553
554 pub fn new_invalid_expression(details: KclErrorDetails) -> KclError {
555 KclError::InvalidExpression { details }
556 }
557
558 pub fn new_engine(details: KclErrorDetails) -> KclError {
559 if details.message.eq_ignore_ascii_case("internal error") {
560 KclError::EngineInternal { details }
561 } else {
562 KclError::Engine { details }
563 }
564 }
565
566 pub fn new_engine_hangup(details: KclErrorDetails) -> KclError {
567 KclError::EngineHangup { details }
568 }
569
570 pub fn new_lexical(details: KclErrorDetails) -> KclError {
571 KclError::Lexical { details }
572 }
573
574 pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
575 KclError::UndefinedValue { details, name }
576 }
577
578 pub fn new_type(details: KclErrorDetails) -> KclError {
579 KclError::Type { details }
580 }
581
582 pub fn get_message(&self) -> String {
584 format!("{}: {}", self.error_type(), self.message())
585 }
586
587 pub fn error_type(&self) -> &'static str {
588 match self {
589 KclError::Lexical { .. } => "lexical",
590 KclError::Syntax { .. } => "syntax",
591 KclError::Semantic { .. } => "semantic",
592 KclError::ImportCycle { .. } => "import cycle",
593 KclError::Argument { .. } => "argument",
594 KclError::Type { .. } => "type",
595 KclError::Io { .. } => "i/o",
596 KclError::Unexpected { .. } => "unexpected",
597 KclError::ValueAlreadyDefined { .. } => "value already defined",
598 KclError::UndefinedValue { .. } => "undefined value",
599 KclError::InvalidExpression { .. } => "invalid expression",
600 KclError::MaxCallStack { .. } => "max call stack",
601 KclError::Engine { .. } => "engine",
602 KclError::EngineHangup { .. } => "engine hangup",
603 KclError::EngineInternal { .. } => "engine internal",
604 KclError::Internal { .. } => "internal",
605 }
606 }
607
608 pub fn source_ranges(&self) -> Vec<SourceRange> {
609 match &self {
610 KclError::Lexical { details: e } => e.source_ranges.clone(),
611 KclError::Syntax { details: e } => e.source_ranges.clone(),
612 KclError::Semantic { details: e } => e.source_ranges.clone(),
613 KclError::ImportCycle { details: e } => e.source_ranges.clone(),
614 KclError::Argument { details: e } => e.source_ranges.clone(),
615 KclError::Type { details: e } => e.source_ranges.clone(),
616 KclError::Io { details: e } => e.source_ranges.clone(),
617 KclError::Unexpected { details: e } => e.source_ranges.clone(),
618 KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
619 KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
620 KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
621 KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
622 KclError::Engine { details: e } => e.source_ranges.clone(),
623 KclError::EngineHangup { details: e } => e.source_ranges.clone(),
624 KclError::EngineInternal { details: e } => e.source_ranges.clone(),
625 KclError::Internal { details: e } => e.source_ranges.clone(),
626 }
627 }
628
629 pub fn message(&self) -> &str {
631 match &self {
632 KclError::Lexical { details: e } => &e.message,
633 KclError::Syntax { details: e } => &e.message,
634 KclError::Semantic { details: e } => &e.message,
635 KclError::ImportCycle { details: e } => &e.message,
636 KclError::Argument { details: e } => &e.message,
637 KclError::Type { details: e } => &e.message,
638 KclError::Io { details: e } => &e.message,
639 KclError::Unexpected { details: e } => &e.message,
640 KclError::ValueAlreadyDefined { details: e } => &e.message,
641 KclError::UndefinedValue { details: e, .. } => &e.message,
642 KclError::InvalidExpression { details: e } => &e.message,
643 KclError::MaxCallStack { details: e } => &e.message,
644 KclError::Engine { details: e } => &e.message,
645 KclError::EngineHangup { details: e } => &e.message,
646 KclError::EngineInternal { details: e } => &e.message,
647 KclError::Internal { details: e } => &e.message,
648 }
649 }
650
651 pub fn backtrace(&self) -> Vec<BacktraceItem> {
652 match self {
653 KclError::Lexical { details: e }
654 | KclError::Syntax { details: e }
655 | KclError::Semantic { details: e }
656 | KclError::ImportCycle { details: e }
657 | KclError::Argument { details: e }
658 | KclError::Type { details: e }
659 | KclError::Io { details: e }
660 | KclError::Unexpected { details: e }
661 | KclError::ValueAlreadyDefined { details: e }
662 | KclError::UndefinedValue { details: e, .. }
663 | KclError::InvalidExpression { details: e }
664 | KclError::MaxCallStack { details: e }
665 | KclError::Engine { details: e }
666 | KclError::EngineHangup { details: e }
667 | KclError::EngineInternal { details: e }
668 | KclError::Internal { details: e } => e.backtrace.clone(),
669 }
670 }
671
672 pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
673 let mut new = self.clone();
674 match &mut new {
675 KclError::Lexical { details: e }
676 | KclError::Syntax { details: e }
677 | KclError::Semantic { details: e }
678 | KclError::ImportCycle { details: e }
679 | KclError::Argument { details: e }
680 | KclError::Type { details: e }
681 | KclError::Io { details: e }
682 | KclError::Unexpected { details: e }
683 | KclError::ValueAlreadyDefined { details: e }
684 | KclError::UndefinedValue { details: e, .. }
685 | KclError::InvalidExpression { details: e }
686 | KclError::MaxCallStack { details: e }
687 | KclError::Engine { details: e }
688 | KclError::EngineHangup { details: e }
689 | KclError::EngineInternal { details: e }
690 | KclError::Internal { details: e } => {
691 e.backtrace = source_ranges
692 .iter()
693 .map(|s| BacktraceItem {
694 source_range: *s,
695 fn_name: None,
696 })
697 .collect();
698 e.source_ranges = source_ranges;
699 }
700 }
701
702 new
703 }
704
705 pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
706 let mut new = self.clone();
707 match &mut new {
708 KclError::Lexical { details: e }
709 | KclError::Syntax { details: e }
710 | KclError::Semantic { details: e }
711 | KclError::ImportCycle { details: e }
712 | KclError::Argument { details: e }
713 | KclError::Type { details: e }
714 | KclError::Io { details: e }
715 | KclError::Unexpected { details: e }
716 | KclError::ValueAlreadyDefined { details: e }
717 | KclError::UndefinedValue { details: e, .. }
718 | KclError::InvalidExpression { details: e }
719 | KclError::MaxCallStack { details: e }
720 | KclError::Engine { details: e }
721 | KclError::EngineHangup { details: e }
722 | KclError::EngineInternal { details: e }
723 | KclError::Internal { details: e } => {
724 if let Some(item) = e.backtrace.last_mut() {
725 item.fn_name = last_fn_name;
726 }
727 e.backtrace.push(BacktraceItem {
728 source_range,
729 fn_name: None,
730 });
731 e.source_ranges.push(source_range);
732 }
733 }
734
735 new
736 }
737}
738
739#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
740#[serde(rename_all = "camelCase")]
741#[ts(export)]
742pub struct BacktraceItem {
743 pub source_range: SourceRange,
744 pub fn_name: Option<String>,
745}
746
747impl std::fmt::Display for BacktraceItem {
748 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749 if let Some(fn_name) = &self.fn_name {
750 write!(f, "{fn_name}: {:?}", self.source_range)
751 } else {
752 write!(f, "(fn): {:?}", self.source_range)
753 }
754 }
755}
756
757impl IntoDiagnostic for KclError {
758 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
759 let message = self.get_message();
760 let source_ranges = self.source_ranges();
761
762 let module_id = ModuleId::default();
764 let source_ranges = source_ranges
765 .iter()
766 .filter(|r| r.module_id() == module_id)
767 .collect::<Vec<_>>();
768
769 let mut diagnostics = Vec::new();
770 for source_range in &source_ranges {
771 diagnostics.push(Diagnostic {
772 range: source_range.to_lsp_range(code),
773 severity: Some(self.severity()),
774 code: None,
775 code_description: None,
777 source: Some("kcl".to_string()),
778 related_information: None,
779 message: message.clone(),
780 tags: None,
781 data: None,
782 });
783 }
784
785 diagnostics
786 }
787
788 fn severity(&self) -> DiagnosticSeverity {
789 DiagnosticSeverity::ERROR
790 }
791}
792
793impl From<KclError> for String {
796 fn from(error: KclError) -> Self {
797 serde_json::to_string(&error).unwrap()
798 }
799}
800
801impl From<String> for KclError {
802 fn from(error: String) -> Self {
803 serde_json::from_str(&error).unwrap()
804 }
805}
806
807#[cfg(feature = "pyo3")]
808impl From<pyo3::PyErr> for KclError {
809 fn from(error: pyo3::PyErr) -> Self {
810 KclError::new_internal(KclErrorDetails {
811 source_ranges: vec![],
812 backtrace: Default::default(),
813 message: error.to_string(),
814 })
815 }
816}
817
818#[cfg(feature = "pyo3")]
819impl From<KclError> for pyo3::PyErr {
820 fn from(error: KclError) -> Self {
821 pyo3::exceptions::PyException::new_err(error.to_string())
822 }
823}
824
825impl From<CompilationError> for KclErrorDetails {
826 fn from(err: CompilationError) -> Self {
827 let backtrace = vec![BacktraceItem {
828 source_range: err.source_range,
829 fn_name: None,
830 }];
831 KclErrorDetails {
832 source_ranges: vec![err.source_range],
833 backtrace,
834 message: err.message,
835 }
836 }
837}