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