#[cfg(feature = "artifact-graph")]
use std::collections::BTreeMap;
use indexmap::IndexMap;
pub use kcl_error::CompilationIssue;
pub use kcl_error::Severity;
pub use kcl_error::Suggestion;
pub use kcl_error::Tag;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use tower_lsp::lsp_types::Diagnostic;
use tower_lsp::lsp_types::DiagnosticSeverity;
use crate::ExecOutcome;
use crate::ModuleId;
use crate::SourceRange;
use crate::exec::KclValue;
#[cfg(feature = "artifact-graph")]
use crate::execution::ArtifactCommand;
#[cfg(feature = "artifact-graph")]
use crate::execution::ArtifactGraph;
use crate::execution::DefaultPlanes;
#[cfg(feature = "artifact-graph")]
use crate::execution::Operation;
#[cfg(feature = "artifact-graph")]
use crate::front::Number;
#[cfg(feature = "artifact-graph")]
use crate::front::Object;
#[cfg(feature = "artifact-graph")]
use crate::front::ObjectId;
use crate::lsp::IntoDiagnostic;
use crate::lsp::ToLspRange;
use crate::modules::ModulePath;
use crate::modules::ModuleSource;
pub trait IsRetryable {
fn is_retryable(&self) -> bool;
}
#[derive(thiserror::Error, Debug)]
pub enum ExecError {
#[error("{0}")]
Kcl(#[from] Box<crate::KclErrorWithOutputs>),
#[error("Could not connect to engine: {0}")]
Connection(#[from] ConnectionError),
#[error("PNG snapshot could not be decoded: {0}")]
BadPng(String),
#[error("Bad export: {0}")]
BadExport(String),
}
impl From<KclErrorWithOutputs> for ExecError {
fn from(error: KclErrorWithOutputs) -> Self {
ExecError::Kcl(Box::new(error))
}
}
#[derive(Debug, thiserror::Error)]
#[error("{error}")]
pub struct ExecErrorWithState {
pub error: ExecError,
pub exec_state: Option<crate::execution::ExecState>,
}
impl ExecErrorWithState {
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
pub fn new(error: ExecError, exec_state: crate::execution::ExecState) -> Self {
Self {
error,
exec_state: Some(exec_state),
}
}
}
impl IsRetryable for ExecErrorWithState {
fn is_retryable(&self) -> bool {
self.error.is_retryable()
}
}
impl ExecError {
pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
let ExecError::Kcl(k) = &self else {
return None;
};
Some(&k.error)
}
}
impl IsRetryable for ExecError {
fn is_retryable(&self) -> bool {
matches!(self, ExecError::Kcl(kcl_error) if kcl_error.is_retryable())
}
}
impl From<ExecError> for ExecErrorWithState {
fn from(error: ExecError) -> Self {
Self {
error,
exec_state: None,
}
}
}
impl From<ConnectionError> for ExecErrorWithState {
fn from(error: ConnectionError) -> Self {
Self {
error: error.into(),
exec_state: None,
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum ConnectionError {
#[error("Could not create a Zoo client: {0}")]
CouldNotMakeClient(anyhow::Error),
#[error("Could not establish connection to engine: {0}")]
Establishing(anyhow::Error),
}
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
#[ts(export)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum KclError {
#[error("lexical: {details:?}")]
Lexical { details: KclErrorDetails },
#[error("syntax: {details:?}")]
Syntax { details: KclErrorDetails },
#[error("semantic: {details:?}")]
Semantic { details: KclErrorDetails },
#[error("import cycle: {details:?}")]
ImportCycle { details: KclErrorDetails },
#[error("argument: {details:?}")]
Argument { details: KclErrorDetails },
#[error("type: {details:?}")]
Type { details: KclErrorDetails },
#[error("i/o: {details:?}")]
Io { details: KclErrorDetails },
#[error("unexpected: {details:?}")]
Unexpected { details: KclErrorDetails },
#[error("value already defined: {details:?}")]
ValueAlreadyDefined { details: KclErrorDetails },
#[error("undefined value: {details:?}")]
UndefinedValue {
details: KclErrorDetails,
name: Option<String>,
},
#[error("invalid expression: {details:?}")]
InvalidExpression { details: KclErrorDetails },
#[error("max call stack size exceeded: {details:?}")]
MaxCallStack { details: KclErrorDetails },
#[error("refactor: {details:?}")]
Refactor { details: KclErrorDetails },
#[error("engine: {details:?}")]
Engine { details: KclErrorDetails },
#[error("engine hangup: {details:?}")]
EngineHangup { details: KclErrorDetails },
#[error("engine internal: {details:?}")]
EngineInternal { details: KclErrorDetails },
#[error("internal error, please report to KittyCAD team: {details:?}")]
Internal { details: KclErrorDetails },
}
impl From<KclErrorWithOutputs> for KclError {
fn from(error: KclErrorWithOutputs) -> Self {
error.error
}
}
impl IsRetryable for KclError {
fn is_retryable(&self) -> bool {
matches!(self, KclError::EngineHangup { .. } | KclError::EngineInternal { .. })
}
}
#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
#[error("{error}")]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct KclErrorWithOutputs {
pub error: KclError,
pub non_fatal: Vec<CompilationIssue>,
pub variables: IndexMap<String, KclValue>,
#[cfg(feature = "artifact-graph")]
pub operations: Vec<Operation>,
#[cfg(feature = "artifact-graph")]
pub _artifact_commands: Vec<ArtifactCommand>,
#[cfg(feature = "artifact-graph")]
pub artifact_graph: ArtifactGraph,
#[cfg(feature = "artifact-graph")]
#[serde(skip)]
pub scene_objects: Vec<Object>,
#[cfg(feature = "artifact-graph")]
#[serde(skip)]
pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
#[cfg(feature = "artifact-graph")]
#[serde(skip)]
pub var_solutions: Vec<(SourceRange, Number)>,
pub scene_graph: Option<crate::front::SceneGraph>,
pub filenames: IndexMap<ModuleId, ModulePath>,
pub source_files: IndexMap<ModuleId, ModuleSource>,
pub default_planes: Option<DefaultPlanes>,
}
impl KclErrorWithOutputs {
#[allow(clippy::too_many_arguments)]
pub fn new(
error: KclError,
non_fatal: Vec<CompilationIssue>,
variables: IndexMap<String, KclValue>,
#[cfg(feature = "artifact-graph")] operations: Vec<Operation>,
#[cfg(feature = "artifact-graph")] artifact_commands: Vec<ArtifactCommand>,
#[cfg(feature = "artifact-graph")] artifact_graph: ArtifactGraph,
#[cfg(feature = "artifact-graph")] scene_objects: Vec<Object>,
#[cfg(feature = "artifact-graph")] source_range_to_object: BTreeMap<SourceRange, ObjectId>,
#[cfg(feature = "artifact-graph")] var_solutions: Vec<(SourceRange, Number)>,
filenames: IndexMap<ModuleId, ModulePath>,
source_files: IndexMap<ModuleId, ModuleSource>,
default_planes: Option<DefaultPlanes>,
) -> Self {
Self {
error,
non_fatal,
variables,
#[cfg(feature = "artifact-graph")]
operations,
#[cfg(feature = "artifact-graph")]
_artifact_commands: artifact_commands,
#[cfg(feature = "artifact-graph")]
artifact_graph,
#[cfg(feature = "artifact-graph")]
scene_objects,
#[cfg(feature = "artifact-graph")]
source_range_to_object,
#[cfg(feature = "artifact-graph")]
var_solutions,
scene_graph: Default::default(),
filenames,
source_files,
default_planes,
}
}
pub fn no_outputs(error: KclError) -> Self {
Self {
error,
non_fatal: Default::default(),
variables: Default::default(),
#[cfg(feature = "artifact-graph")]
operations: Default::default(),
#[cfg(feature = "artifact-graph")]
_artifact_commands: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_graph: Default::default(),
#[cfg(feature = "artifact-graph")]
scene_objects: Default::default(),
#[cfg(feature = "artifact-graph")]
source_range_to_object: Default::default(),
#[cfg(feature = "artifact-graph")]
var_solutions: Default::default(),
scene_graph: Default::default(),
filenames: Default::default(),
source_files: Default::default(),
default_planes: Default::default(),
}
}
pub fn from_error_outcome(error: KclError, outcome: ExecOutcome) -> Self {
KclErrorWithOutputs {
error,
non_fatal: outcome.issues,
variables: outcome.variables,
#[cfg(feature = "artifact-graph")]
operations: outcome.operations,
#[cfg(feature = "artifact-graph")]
_artifact_commands: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_graph: outcome.artifact_graph,
#[cfg(feature = "artifact-graph")]
scene_objects: outcome.scene_objects,
#[cfg(feature = "artifact-graph")]
source_range_to_object: outcome.source_range_to_object,
#[cfg(feature = "artifact-graph")]
var_solutions: outcome.var_solutions,
scene_graph: Default::default(),
filenames: outcome.filenames,
source_files: Default::default(),
default_planes: outcome.default_planes,
}
}
pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
let mut source_ranges = self.error.source_ranges();
let first_source_range = source_ranges
.pop()
.ok_or_else(|| anyhow::anyhow!("No source ranges found"))?;
let source = self
.source_files
.get(&first_source_range.module_id())
.cloned()
.unwrap_or(ModuleSource {
source: code.to_string(),
path: self
.filenames
.get(&first_source_range.module_id())
.cloned()
.unwrap_or(ModulePath::Main),
});
let filename = source.path.to_string();
let kcl_source = source.source;
let mut related = Vec::new();
for source_range in source_ranges {
let module_id = source_range.module_id();
let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
source: code.to_string(),
path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
});
let error = self.error.override_source_ranges(vec![source_range]);
let report = Report {
error,
kcl_source: source.source.to_string(),
filename: source.path.to_string(),
};
related.push(report);
}
Ok(ReportWithOutputs {
error: self,
kcl_source,
filename,
related,
})
}
}
impl IsRetryable for KclErrorWithOutputs {
fn is_retryable(&self) -> bool {
matches!(
self.error,
KclError::EngineHangup { .. } | KclError::EngineInternal { .. }
)
}
}
impl IntoDiagnostic for KclErrorWithOutputs {
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
let message = self.error.get_message();
let source_ranges = self.error.source_ranges();
source_ranges
.into_iter()
.map(|source_range| {
let source = self.source_files.get(&source_range.module_id()).cloned().or_else(|| {
self.filenames
.get(&source_range.module_id())
.cloned()
.map(|path| ModuleSource {
source: code.to_string(),
path,
})
});
let related_information = source.and_then(|source| {
let mut filename = source.path.to_string();
if !filename.starts_with("file://") {
filename = format!("file:///{}", filename.trim_start_matches("/"));
}
url::Url::parse(&filename).ok().map(|uri| {
vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
location: tower_lsp::lsp_types::Location {
uri,
range: source_range.to_lsp_range(&source.source),
},
message: message.to_string(),
}]
})
});
Diagnostic {
range: source_range.to_lsp_range(code),
severity: Some(self.severity()),
code: None,
code_description: None,
source: Some("kcl".to_string()),
related_information,
message: message.clone(),
tags: None,
data: None,
}
})
.collect()
}
fn severity(&self) -> DiagnosticSeverity {
DiagnosticSeverity::ERROR
}
}
#[derive(thiserror::Error, Debug)]
#[error("{}", self.error.error.get_message())]
pub struct ReportWithOutputs {
pub error: KclErrorWithOutputs,
pub kcl_source: String,
pub filename: String,
pub related: Vec<Report>,
}
impl miette::Diagnostic for ReportWithOutputs {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
let family = match self.error.error {
KclError::Lexical { .. } => "Lexical",
KclError::Syntax { .. } => "Syntax",
KclError::Semantic { .. } => "Semantic",
KclError::ImportCycle { .. } => "ImportCycle",
KclError::Argument { .. } => "Argument",
KclError::Type { .. } => "Type",
KclError::Io { .. } => "I/O",
KclError::Unexpected { .. } => "Unexpected",
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
KclError::UndefinedValue { .. } => "UndefinedValue",
KclError::InvalidExpression { .. } => "InvalidExpression",
KclError::MaxCallStack { .. } => "MaxCallStack",
KclError::Refactor { .. } => "Refactor",
KclError::Engine { .. } => "Engine",
KclError::EngineHangup { .. } => "EngineHangup",
KclError::EngineInternal { .. } => "EngineInternal",
KclError::Internal { .. } => "Internal",
};
let error_string = format!("KCL {family} error");
Some(Box::new(error_string))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.kcl_source)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let iter = self
.error
.error
.source_ranges()
.into_iter()
.map(miette::SourceSpan::from)
.map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
Some(Box::new(iter))
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
let iter = self.related.iter().map(|r| r as &dyn miette::Diagnostic);
Some(Box::new(iter))
}
}
#[derive(thiserror::Error, Debug)]
#[error("{}", self.error.get_message())]
pub struct Report {
pub error: KclError,
pub kcl_source: String,
pub filename: String,
}
impl miette::Diagnostic for Report {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
let family = match self.error {
KclError::Lexical { .. } => "Lexical",
KclError::Syntax { .. } => "Syntax",
KclError::Semantic { .. } => "Semantic",
KclError::ImportCycle { .. } => "ImportCycle",
KclError::Argument { .. } => "Argument",
KclError::Type { .. } => "Type",
KclError::Io { .. } => "I/O",
KclError::Unexpected { .. } => "Unexpected",
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
KclError::UndefinedValue { .. } => "UndefinedValue",
KclError::InvalidExpression { .. } => "InvalidExpression",
KclError::MaxCallStack { .. } => "MaxCallStack",
KclError::Refactor { .. } => "Refactor",
KclError::Engine { .. } => "Engine",
KclError::EngineHangup { .. } => "EngineHangup",
KclError::EngineInternal { .. } => "EngineInternal",
KclError::Internal { .. } => "Internal",
};
let error_string = format!("KCL {family} error");
Some(Box::new(error_string))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.kcl_source)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let iter = self
.error
.source_ranges()
.into_iter()
.map(miette::SourceSpan::from)
.map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
Some(Box::new(iter))
}
}
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[error("{message}")]
#[ts(export)]
pub struct KclErrorDetails {
#[label(collection, "Errors")]
pub source_ranges: Vec<SourceRange>,
pub backtrace: Vec<super::BacktraceItem>,
#[serde(rename = "msg")]
pub message: String,
}
impl KclErrorDetails {
pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
let backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
KclErrorDetails {
source_ranges,
backtrace,
message,
}
}
}
impl KclError {
pub fn internal(message: String) -> KclError {
KclError::Internal {
details: KclErrorDetails {
source_ranges: Default::default(),
backtrace: Default::default(),
message,
},
}
}
pub fn new_internal(details: KclErrorDetails) -> KclError {
KclError::Internal { details }
}
pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
KclError::ImportCycle { details }
}
pub fn new_argument(details: KclErrorDetails) -> KclError {
KclError::Argument { details }
}
pub fn new_semantic(details: KclErrorDetails) -> KclError {
KclError::Semantic { details }
}
pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
KclError::ValueAlreadyDefined { details }
}
pub fn new_syntax(details: KclErrorDetails) -> KclError {
KclError::Syntax { details }
}
pub fn new_io(details: KclErrorDetails) -> KclError {
KclError::Io { details }
}
pub fn new_invalid_expression(details: KclErrorDetails) -> KclError {
KclError::InvalidExpression { details }
}
pub fn refactor(message: String) -> KclError {
KclError::Refactor {
details: KclErrorDetails {
source_ranges: Default::default(),
backtrace: Default::default(),
message,
},
}
}
pub fn new_engine(details: KclErrorDetails) -> KclError {
if details.message.eq_ignore_ascii_case("internal error") {
KclError::EngineInternal { details }
} else {
KclError::Engine { details }
}
}
pub fn new_engine_hangup(details: KclErrorDetails) -> KclError {
KclError::EngineHangup { details }
}
pub fn new_lexical(details: KclErrorDetails) -> KclError {
KclError::Lexical { details }
}
pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
KclError::UndefinedValue { details, name }
}
pub fn new_type(details: KclErrorDetails) -> KclError {
KclError::Type { details }
}
pub fn get_message(&self) -> String {
format!("{}: {}", self.error_type(), self.message())
}
pub fn error_type(&self) -> &'static str {
match self {
KclError::Lexical { .. } => "lexical",
KclError::Syntax { .. } => "syntax",
KclError::Semantic { .. } => "semantic",
KclError::ImportCycle { .. } => "import cycle",
KclError::Argument { .. } => "argument",
KclError::Type { .. } => "type",
KclError::Io { .. } => "i/o",
KclError::Unexpected { .. } => "unexpected",
KclError::ValueAlreadyDefined { .. } => "value already defined",
KclError::UndefinedValue { .. } => "undefined value",
KclError::InvalidExpression { .. } => "invalid expression",
KclError::MaxCallStack { .. } => "max call stack",
KclError::Refactor { .. } => "refactor",
KclError::Engine { .. } => "engine",
KclError::EngineHangup { .. } => "engine hangup",
KclError::EngineInternal { .. } => "engine internal",
KclError::Internal { .. } => "internal",
}
}
pub fn source_ranges(&self) -> Vec<SourceRange> {
match &self {
KclError::Lexical { details: e } => e.source_ranges.clone(),
KclError::Syntax { details: e } => e.source_ranges.clone(),
KclError::Semantic { details: e } => e.source_ranges.clone(),
KclError::ImportCycle { details: e } => e.source_ranges.clone(),
KclError::Argument { details: e } => e.source_ranges.clone(),
KclError::Type { details: e } => e.source_ranges.clone(),
KclError::Io { details: e } => e.source_ranges.clone(),
KclError::Unexpected { details: e } => e.source_ranges.clone(),
KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
KclError::Refactor { details: e } => e.source_ranges.clone(),
KclError::Engine { details: e } => e.source_ranges.clone(),
KclError::EngineHangup { details: e } => e.source_ranges.clone(),
KclError::EngineInternal { details: e } => e.source_ranges.clone(),
KclError::Internal { details: e } => e.source_ranges.clone(),
}
}
pub fn message(&self) -> &str {
match &self {
KclError::Lexical { details: e } => &e.message,
KclError::Syntax { details: e } => &e.message,
KclError::Semantic { details: e } => &e.message,
KclError::ImportCycle { details: e } => &e.message,
KclError::Argument { details: e } => &e.message,
KclError::Type { details: e } => &e.message,
KclError::Io { details: e } => &e.message,
KclError::Unexpected { details: e } => &e.message,
KclError::ValueAlreadyDefined { details: e } => &e.message,
KclError::UndefinedValue { details: e, .. } => &e.message,
KclError::InvalidExpression { details: e } => &e.message,
KclError::MaxCallStack { details: e } => &e.message,
KclError::Refactor { details: e } => &e.message,
KclError::Engine { details: e } => &e.message,
KclError::EngineHangup { details: e } => &e.message,
KclError::EngineInternal { details: e } => &e.message,
KclError::Internal { details: e } => &e.message,
}
}
pub fn backtrace(&self) -> Vec<BacktraceItem> {
match self {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Argument { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::MaxCallStack { details: e }
| KclError::Refactor { details: e }
| KclError::Engine { details: e }
| KclError::EngineHangup { details: e }
| KclError::EngineInternal { details: e }
| KclError::Internal { details: e } => e.backtrace.clone(),
}
}
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Argument { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::MaxCallStack { details: e }
| KclError::Refactor { details: e }
| KclError::Engine { details: e }
| KclError::EngineHangup { details: e }
| KclError::EngineInternal { details: e }
| KclError::Internal { details: e } => {
e.backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
e.source_ranges = source_ranges;
}
}
new
}
pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Argument { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::MaxCallStack { details: e }
| KclError::Refactor { details: e }
| KclError::Engine { details: e }
| KclError::EngineHangup { details: e }
| KclError::EngineInternal { details: e }
| KclError::Internal { details: e } => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
e.backtrace.push(BacktraceItem {
source_range,
fn_name: None,
});
e.source_ranges.push(source_range);
}
}
new
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BacktraceItem {
pub source_range: SourceRange,
pub fn_name: Option<String>,
}
impl std::fmt::Display for BacktraceItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(fn_name) = &self.fn_name {
write!(f, "{fn_name}: {:?}", self.source_range)
} else {
write!(f, "(fn): {:?}", self.source_range)
}
}
}
impl IntoDiagnostic for KclError {
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
let message = self.get_message();
let source_ranges = self.source_ranges();
let module_id = ModuleId::default();
let source_ranges = source_ranges
.iter()
.filter(|r| r.module_id() == module_id)
.collect::<Vec<_>>();
let mut diagnostics = Vec::new();
for source_range in &source_ranges {
diagnostics.push(Diagnostic {
range: source_range.to_lsp_range(code),
severity: Some(self.severity()),
code: None,
code_description: None,
source: Some("kcl".to_string()),
related_information: None,
message: message.clone(),
tags: None,
data: None,
});
}
diagnostics
}
fn severity(&self) -> DiagnosticSeverity {
DiagnosticSeverity::ERROR
}
}
impl From<KclError> for String {
fn from(error: KclError) -> Self {
serde_json::to_string(&error).unwrap()
}
}
impl From<String> for KclError {
fn from(error: String) -> Self {
serde_json::from_str(&error).unwrap()
}
}
#[cfg(feature = "pyo3")]
impl From<pyo3::PyErr> for KclError {
fn from(error: pyo3::PyErr) -> Self {
KclError::new_internal(KclErrorDetails {
source_ranges: vec![],
backtrace: Default::default(),
message: error.to_string(),
})
}
}
#[cfg(feature = "pyo3")]
impl From<KclError> for pyo3::PyErr {
fn from(error: KclError) -> Self {
pyo3::exceptions::PyException::new_err(error.to_string())
}
}
impl From<CompilationIssue> for KclErrorDetails {
fn from(err: CompilationIssue) -> Self {
let backtrace = vec![BacktraceItem {
source_range: err.source_range,
fn_name: None,
}];
KclErrorDetails {
source_ranges: vec![err.source_range],
backtrace,
message: err.message,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_filename_mapping_does_not_panic_when_building_diagnostics() {
let error = KclErrorWithOutputs::no_outputs(KclError::new_semantic(KclErrorDetails::new(
"boom".to_owned(),
vec![SourceRange::new(0, 1, ModuleId::from_usize(9))],
)));
let diagnostics = error.to_lsp_diagnostics("x");
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].message, "semantic: boom");
assert_eq!(diagnostics[0].related_information, None);
}
}