kcl_lib/
errors.rs

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