2025-02-26 19:29:59 -08:00
|
|
|
use indexmap::IndexMap;
|
2023-08-18 19:37:52 +10:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use thiserror::Error;
|
2023-09-05 16:02:27 -07:00
|
|
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
|
|
|
|
2024-12-03 16:39:51 +13:00
|
|
|
use crate::{
|
2025-03-15 10:08:39 -07:00
|
|
|
execution::{ArtifactCommand, ArtifactGraph, DefaultPlanes, Operation},
|
2024-12-03 16:39:51 +13:00
|
|
|
lsp::IntoDiagnostic,
|
2025-02-26 19:29:59 -08:00
|
|
|
modules::{ModulePath, ModuleSource},
|
2025-02-11 13:52:46 +13:00
|
|
|
source_range::SourceRange,
|
|
|
|
ModuleId,
|
2024-12-03 16:39:51 +13:00
|
|
|
};
|
2023-08-18 19:37:52 +10:00
|
|
|
|
2024-11-25 14:06:23 -06:00
|
|
|
/// How did the KCL execution fail
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum ExecError {
|
|
|
|
#[error("{0}")]
|
2025-01-08 20:02:30 -05:00
|
|
|
Kcl(#[from] Box<crate::KclErrorWithOutputs>),
|
2024-11-25 14:06:23 -06:00
|
|
|
#[error("Could not connect to engine: {0}")]
|
|
|
|
Connection(#[from] ConnectionError),
|
|
|
|
#[error("PNG snapshot could not be decoded: {0}")]
|
|
|
|
BadPng(String),
|
2025-03-03 10:26:01 -08:00
|
|
|
#[error("Bad export: {0}")]
|
|
|
|
BadExport(String),
|
2024-11-25 14:06:23 -06:00
|
|
|
}
|
|
|
|
|
2025-01-08 20:02:30 -05:00
|
|
|
impl From<KclErrorWithOutputs> for ExecError {
|
|
|
|
fn from(error: KclErrorWithOutputs) -> Self {
|
|
|
|
ExecError::Kcl(Box::new(error))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-16 13:10:31 -05:00
|
|
|
/// How did the KCL execution fail, with extra state.
|
|
|
|
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ExecErrorWithState {
|
|
|
|
pub error: ExecError,
|
2025-02-13 11:59:57 +13:00
|
|
|
pub exec_state: Option<crate::execution::ExecState>,
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ExecErrorWithState {
|
|
|
|
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
2025-02-13 11:59:57 +13:00
|
|
|
pub fn new(error: ExecError, exec_state: crate::execution::ExecState) -> Self {
|
2025-01-17 07:55:01 +13:00
|
|
|
Self {
|
|
|
|
error,
|
|
|
|
exec_state: Some(exec_state),
|
|
|
|
}
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-12 11:24:27 -05:00
|
|
|
impl ExecError {
|
|
|
|
pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
|
|
|
|
let ExecError::Kcl(k) = &self else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
Some(&k.error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-16 13:10:31 -05:00
|
|
|
impl From<ExecError> for ExecErrorWithState {
|
|
|
|
fn from(error: ExecError) -> Self {
|
|
|
|
Self {
|
|
|
|
error,
|
2025-01-17 07:55:01 +13:00
|
|
|
exec_state: None,
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ConnectionError> for ExecErrorWithState {
|
|
|
|
fn from(error: ConnectionError) -> Self {
|
|
|
|
Self {
|
|
|
|
error: error.into(),
|
2025-01-17 07:55:01 +13:00
|
|
|
exec_state: None,
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-25 14:06:23 -06:00
|
|
|
/// How did KCL client fail to connect to the engine
|
|
|
|
#[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),
|
|
|
|
}
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
|
2023-08-19 23:18:54 -07:00
|
|
|
#[ts(export)]
|
2023-08-18 19:37:52 +10:00
|
|
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
|
|
pub enum KclError {
|
2023-11-01 17:20:49 -05:00
|
|
|
#[error("lexical: {0:?}")]
|
|
|
|
Lexical(KclErrorDetails),
|
2023-08-18 19:37:52 +10:00
|
|
|
#[error("syntax: {0:?}")]
|
|
|
|
Syntax(KclErrorDetails),
|
|
|
|
#[error("semantic: {0:?}")]
|
|
|
|
Semantic(KclErrorDetails),
|
2024-10-17 00:48:33 -04:00
|
|
|
#[error("import cycle: {0:?}")]
|
|
|
|
ImportCycle(KclErrorDetails),
|
2023-08-18 19:37:52 +10:00
|
|
|
#[error("type: {0:?}")]
|
|
|
|
Type(KclErrorDetails),
|
2025-02-28 12:08:44 -05:00
|
|
|
#[error("i/o: {0:?}")]
|
|
|
|
Io(KclErrorDetails),
|
2023-08-22 13:28:02 +10:00
|
|
|
#[error("unexpected: {0:?}")]
|
|
|
|
Unexpected(KclErrorDetails),
|
2023-08-18 19:37:52 +10:00
|
|
|
#[error("value already defined: {0:?}")]
|
|
|
|
ValueAlreadyDefined(KclErrorDetails),
|
|
|
|
#[error("undefined value: {0:?}")]
|
|
|
|
UndefinedValue(KclErrorDetails),
|
|
|
|
#[error("invalid expression: {0:?}")]
|
2023-08-29 14:12:48 -07:00
|
|
|
InvalidExpression(KclErrorDetails),
|
2023-08-24 15:34:51 -07:00
|
|
|
#[error("engine: {0:?}")]
|
|
|
|
Engine(KclErrorDetails),
|
2023-11-03 13:30:19 -05:00
|
|
|
#[error("internal error, please report to KittyCAD team: {0:?}")]
|
|
|
|
Internal(KclErrorDetails),
|
2023-08-18 19:37:52 +10:00
|
|
|
}
|
|
|
|
|
2025-01-08 20:02:30 -05:00
|
|
|
impl From<KclErrorWithOutputs> for KclError {
|
|
|
|
fn from(error: KclErrorWithOutputs) -> Self {
|
|
|
|
error.error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-07 16:13:15 +12:00
|
|
|
#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
|
2024-12-16 13:10:31 -05:00
|
|
|
#[error("{error}")]
|
|
|
|
#[ts(export)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct KclErrorWithOutputs {
|
|
|
|
pub error: KclError,
|
|
|
|
pub operations: Vec<Operation>,
|
2025-01-08 20:02:30 -05:00
|
|
|
pub artifact_commands: Vec<ArtifactCommand>,
|
2025-01-17 14:34:36 -05:00
|
|
|
pub artifact_graph: ArtifactGraph,
|
2025-02-25 11:51:54 -06:00
|
|
|
pub filenames: IndexMap<ModuleId, ModulePath>,
|
2025-02-26 19:29:59 -08:00
|
|
|
pub source_files: IndexMap<ModuleId, ModuleSource>,
|
2025-03-15 10:08:39 -07:00
|
|
|
pub default_planes: Option<DefaultPlanes>,
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl KclErrorWithOutputs {
|
2025-01-17 14:34:36 -05:00
|
|
|
pub fn new(
|
|
|
|
error: KclError,
|
|
|
|
operations: Vec<Operation>,
|
|
|
|
artifact_commands: Vec<ArtifactCommand>,
|
|
|
|
artifact_graph: ArtifactGraph,
|
2025-02-25 11:51:54 -06:00
|
|
|
filenames: IndexMap<ModuleId, ModulePath>,
|
2025-02-26 19:29:59 -08:00
|
|
|
source_files: IndexMap<ModuleId, ModuleSource>,
|
2025-03-15 10:08:39 -07:00
|
|
|
default_planes: Option<DefaultPlanes>,
|
2025-01-17 14:34:36 -05:00
|
|
|
) -> Self {
|
2025-01-08 20:02:30 -05:00
|
|
|
Self {
|
|
|
|
error,
|
|
|
|
operations,
|
|
|
|
artifact_commands,
|
2025-01-17 14:34:36 -05:00
|
|
|
artifact_graph,
|
2025-02-25 11:51:54 -06:00
|
|
|
filenames,
|
2025-02-26 19:29:59 -08:00
|
|
|
source_files,
|
2025-03-15 10:08:39 -07:00
|
|
|
default_planes,
|
2025-01-08 20:02:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn no_outputs(error: KclError) -> Self {
|
|
|
|
Self {
|
|
|
|
error,
|
|
|
|
operations: Default::default(),
|
|
|
|
artifact_commands: Default::default(),
|
2025-01-17 14:34:36 -05:00
|
|
|
artifact_graph: Default::default(),
|
2025-02-25 11:51:54 -06:00
|
|
|
filenames: Default::default(),
|
2025-02-26 19:29:59 -08:00
|
|
|
source_files: Default::default(),
|
2025-03-15 10:08:39 -07:00
|
|
|
default_planes: Default::default(),
|
2025-02-26 19:29:59 -08:00
|
|
|
}
|
|
|
|
}
|
2025-03-07 18:45:33 -08:00
|
|
|
pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
|
2025-02-26 19:29:59 -08:00
|
|
|
let mut source_ranges = self.error.source_ranges();
|
|
|
|
|
|
|
|
// Pop off the first source range to get the filename.
|
|
|
|
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()
|
2025-03-07 18:45:33 -08:00
|
|
|
.unwrap_or(ModuleSource {
|
|
|
|
source: code.to_string(),
|
|
|
|
path: self
|
|
|
|
.filenames
|
|
|
|
.get(&first_source_range.module_id())
|
2025-03-13 09:38:22 -07:00
|
|
|
.cloned()
|
|
|
|
.unwrap_or(ModulePath::Main),
|
2025-03-07 18:45:33 -08:00
|
|
|
});
|
2025-02-26 19:29:59 -08:00
|
|
|
let filename = source.path.to_string();
|
|
|
|
let kcl_source = source.source.to_string();
|
|
|
|
|
|
|
|
let mut related = Vec::new();
|
|
|
|
for source_range in source_ranges {
|
|
|
|
let module_id = source_range.module_id();
|
2025-03-13 09:38:22 -07:00
|
|
|
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),
|
|
|
|
});
|
2025-02-26 19:29:59 -08:00
|
|
|
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);
|
2025-01-08 20:02:30 -05:00
|
|
|
}
|
2025-02-26 19:29:59 -08:00
|
|
|
|
|
|
|
Ok(ReportWithOutputs {
|
|
|
|
error: self,
|
|
|
|
kcl_source,
|
|
|
|
filename,
|
|
|
|
related,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
.unwrap_or(ModuleSource {
|
|
|
|
source: code.to_string(),
|
|
|
|
path: self.filenames.get(&source_range.module_id()).unwrap().clone(),
|
|
|
|
});
|
|
|
|
let mut filename = source.path.to_string();
|
|
|
|
if !filename.starts_with("file://") {
|
|
|
|
filename = format!("file:///{}", filename.trim_start_matches("/"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let related_information = if let Ok(uri) = url::Url::parse(&filename) {
|
|
|
|
Some(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(),
|
|
|
|
}])
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Diagnostic {
|
|
|
|
range: source_range.to_lsp_range(code),
|
|
|
|
severity: Some(self.severity()),
|
|
|
|
code: None,
|
|
|
|
// TODO: this is neat we can pass a URL to a help page here for this specific error.
|
|
|
|
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::Type(_) => "Type",
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(_) => "I/O",
|
2025-02-26 19:29:59 -08:00
|
|
|
KclError::Unexpected(_) => "Unexpected",
|
|
|
|
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
|
|
|
|
KclError::UndefinedValue(_) => "UndefinedValue",
|
|
|
|
KclError::InvalidExpression(_) => "InvalidExpression",
|
|
|
|
KclError::Engine(_) => "Engine",
|
|
|
|
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()
|
|
|
|
.clone()
|
|
|
|
.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))
|
2024-12-16 13:10:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-25 17:28:57 -06:00
|
|
|
#[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::Type(_) => "Type",
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(_) => "I/O",
|
2024-11-25 17:28:57 -06:00
|
|
|
KclError::Unexpected(_) => "Unexpected",
|
|
|
|
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
|
|
|
|
KclError::UndefinedValue(_) => "UndefinedValue",
|
|
|
|
KclError::InvalidExpression(_) => "InvalidExpression",
|
|
|
|
KclError::Engine(_) => "Engine",
|
|
|
|
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()
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(miette::SourceSpan::from)
|
2025-02-26 19:29:59 -08:00
|
|
|
.map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
|
2024-11-25 17:28:57 -06:00
|
|
|
Some(Box::new(iter))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
|
|
|
|
#[error("{message}")]
|
2023-08-19 23:18:54 -07:00
|
|
|
#[ts(export)]
|
2023-08-18 19:37:52 +10:00
|
|
|
pub struct KclErrorDetails {
|
|
|
|
#[serde(rename = "sourceRanges")]
|
2024-11-25 17:28:57 -06:00
|
|
|
#[label(collection, "Errors")]
|
2023-09-05 16:02:27 -07:00
|
|
|
pub source_ranges: Vec<SourceRange>,
|
2023-08-18 19:37:52 +10:00
|
|
|
#[serde(rename = "msg")]
|
|
|
|
pub message: String,
|
|
|
|
}
|
2023-08-19 14:22:11 -07:00
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
impl KclError {
|
2024-11-22 13:25:14 +13:00
|
|
|
pub fn internal(message: String) -> KclError {
|
|
|
|
KclError::Internal(KclErrorDetails {
|
|
|
|
source_ranges: Default::default(),
|
|
|
|
message,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-11 19:23:35 -04:00
|
|
|
/// Get the error message.
|
|
|
|
pub fn get_message(&self) -> String {
|
|
|
|
format!("{}: {}", self.error_type(), self.message())
|
2023-11-03 13:30:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn error_type(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
KclError::Lexical(_) => "lexical",
|
|
|
|
KclError::Syntax(_) => "syntax",
|
|
|
|
KclError::Semantic(_) => "semantic",
|
2024-10-17 00:48:33 -04:00
|
|
|
KclError::ImportCycle(_) => "import cycle",
|
2023-11-03 13:30:19 -05:00
|
|
|
KclError::Type(_) => "type",
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(_) => "i/o",
|
2023-11-03 13:30:19 -05:00
|
|
|
KclError::Unexpected(_) => "unexpected",
|
|
|
|
KclError::ValueAlreadyDefined(_) => "value already defined",
|
|
|
|
KclError::UndefinedValue(_) => "undefined value",
|
|
|
|
KclError::InvalidExpression(_) => "invalid expression",
|
|
|
|
KclError::Engine(_) => "engine",
|
|
|
|
KclError::Internal(_) => "internal",
|
|
|
|
}
|
2023-08-29 14:12:48 -07:00
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
pub fn source_ranges(&self) -> Vec<SourceRange> {
|
|
|
|
match &self {
|
2023-11-01 17:20:49 -05:00
|
|
|
KclError::Lexical(e) => e.source_ranges.clone(),
|
2023-09-05 16:02:27 -07:00
|
|
|
KclError::Syntax(e) => e.source_ranges.clone(),
|
|
|
|
KclError::Semantic(e) => e.source_ranges.clone(),
|
2024-10-17 00:48:33 -04:00
|
|
|
KclError::ImportCycle(e) => e.source_ranges.clone(),
|
2023-09-05 16:02:27 -07:00
|
|
|
KclError::Type(e) => e.source_ranges.clone(),
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(e) => e.source_ranges.clone(),
|
2023-09-05 16:02:27 -07:00
|
|
|
KclError::Unexpected(e) => e.source_ranges.clone(),
|
|
|
|
KclError::ValueAlreadyDefined(e) => e.source_ranges.clone(),
|
|
|
|
KclError::UndefinedValue(e) => e.source_ranges.clone(),
|
|
|
|
KclError::InvalidExpression(e) => e.source_ranges.clone(),
|
|
|
|
KclError::Engine(e) => e.source_ranges.clone(),
|
2023-11-03 13:30:19 -05:00
|
|
|
KclError::Internal(e) => e.source_ranges.clone(),
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
}
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
|
|
|
|
/// Get the inner error message.
|
|
|
|
pub fn message(&self) -> &str {
|
|
|
|
match &self {
|
2023-11-01 17:20:49 -05:00
|
|
|
KclError::Lexical(e) => &e.message,
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
KclError::Syntax(e) => &e.message,
|
|
|
|
KclError::Semantic(e) => &e.message,
|
2024-10-17 00:48:33 -04:00
|
|
|
KclError::ImportCycle(e) => &e.message,
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
KclError::Type(e) => &e.message,
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(e) => &e.message,
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
KclError::Unexpected(e) => &e.message,
|
|
|
|
KclError::ValueAlreadyDefined(e) => &e.message,
|
|
|
|
KclError::UndefinedValue(e) => &e.message,
|
|
|
|
KclError::InvalidExpression(e) => &e.message,
|
|
|
|
KclError::Engine(e) => &e.message,
|
2023-11-03 13:30:19 -05:00
|
|
|
KclError::Internal(e) => &e.message,
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
2024-03-11 13:37:22 -07:00
|
|
|
let mut new = self.clone();
|
|
|
|
match &mut new {
|
|
|
|
KclError::Lexical(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::Syntax(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::Semantic(e) => e.source_ranges = source_ranges,
|
2024-10-17 00:48:33 -04:00
|
|
|
KclError::ImportCycle(e) => e.source_ranges = source_ranges,
|
2024-03-11 13:37:22 -07:00
|
|
|
KclError::Type(e) => e.source_ranges = source_ranges,
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(e) => e.source_ranges = source_ranges,
|
2024-03-11 13:37:22 -07:00
|
|
|
KclError::Unexpected(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::UndefinedValue(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::InvalidExpression(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::Engine(e) => e.source_ranges = source_ranges,
|
|
|
|
KclError::Internal(e) => e.source_ranges = source_ranges,
|
|
|
|
}
|
|
|
|
|
|
|
|
new
|
|
|
|
}
|
2024-05-21 00:49:57 -07:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
pub(crate) fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
2024-05-21 00:49:57 -07:00
|
|
|
let mut new = self.clone();
|
|
|
|
match &mut new {
|
|
|
|
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
|
2024-10-17 00:48:33 -04:00
|
|
|
KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges),
|
2024-05-21 00:49:57 -07:00
|
|
|
KclError::Type(e) => e.source_ranges.extend(source_ranges),
|
2025-02-28 12:08:44 -05:00
|
|
|
KclError::Io(e) => e.source_ranges.extend(source_ranges),
|
2024-05-21 00:49:57 -07:00
|
|
|
KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::Engine(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
KclError::Internal(e) => e.source_ranges.extend(source_ranges),
|
|
|
|
}
|
|
|
|
|
|
|
|
new
|
|
|
|
}
|
2023-08-29 14:12:48 -07:00
|
|
|
}
|
|
|
|
|
2024-06-11 19:23:35 -04:00
|
|
|
impl IntoDiagnostic for KclError {
|
2025-02-26 19:29:59 -08:00
|
|
|
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
|
2024-06-11 19:23:35 -04:00
|
|
|
let message = self.get_message();
|
|
|
|
let source_ranges = self.source_ranges();
|
|
|
|
|
2024-11-07 11:23:41 -05:00
|
|
|
// Limit to only errors in the top-level file.
|
|
|
|
let module_id = ModuleId::default();
|
|
|
|
let source_ranges = source_ranges
|
|
|
|
.iter()
|
|
|
|
.filter(|r| r.module_id() == module_id)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2025-02-26 19:29:59 -08:00
|
|
|
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,
|
|
|
|
// TODO: this is neat we can pass a URL to a help page here for this specific error.
|
|
|
|
code_description: None,
|
|
|
|
source: Some("kcl".to_string()),
|
|
|
|
related_information: None,
|
|
|
|
message: message.clone(),
|
|
|
|
tags: None,
|
|
|
|
data: None,
|
|
|
|
});
|
2024-06-11 19:23:35 -04:00
|
|
|
}
|
2025-02-26 19:29:59 -08:00
|
|
|
|
|
|
|
diagnostics
|
2024-06-11 19:23:35 -04:00
|
|
|
}
|
2024-06-27 15:43:49 -07:00
|
|
|
|
|
|
|
fn severity(&self) -> DiagnosticSeverity {
|
|
|
|
DiagnosticSeverity::ERROR
|
|
|
|
}
|
2024-06-11 19:23:35 -04:00
|
|
|
}
|
|
|
|
|
2023-08-19 14:22:11 -07:00
|
|
|
/// This is different than to_string() in that it will serialize the Error
|
|
|
|
/// the struct as JSON so we can deserialize it on the js side.
|
|
|
|
impl From<KclError> for String {
|
|
|
|
fn from(error: KclError) -> Self {
|
|
|
|
serde_json::to_string(&error).unwrap()
|
|
|
|
}
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
impl From<String> for KclError {
|
|
|
|
fn from(error: String) -> Self {
|
|
|
|
serde_json::from_str(&error).unwrap()
|
|
|
|
}
|
|
|
|
}
|
2024-06-19 17:32:08 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "pyo3")]
|
|
|
|
impl From<pyo3::PyErr> for KclError {
|
|
|
|
fn from(error: pyo3::PyErr) -> Self {
|
|
|
|
KclError::Internal(KclErrorDetails {
|
|
|
|
source_ranges: vec![],
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
2024-12-06 13:57:31 +13:00
|
|
|
|
|
|
|
/// An error which occurred during parsing, etc.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub struct CompilationError {
|
|
|
|
#[serde(rename = "sourceRange")]
|
|
|
|
pub source_range: SourceRange,
|
|
|
|
pub message: String,
|
|
|
|
pub suggestion: Option<Suggestion>,
|
|
|
|
pub severity: Severity,
|
|
|
|
pub tag: Tag,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CompilationError {
|
|
|
|
pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
|
|
|
|
CompilationError {
|
|
|
|
source_range,
|
|
|
|
message: message.to_string(),
|
|
|
|
suggestion: None,
|
|
|
|
severity: Severity::Error,
|
|
|
|
tag: Tag::None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError {
|
|
|
|
CompilationError {
|
|
|
|
source_range,
|
|
|
|
message: message.to_string(),
|
|
|
|
suggestion: None,
|
|
|
|
severity: Severity::Fatal,
|
|
|
|
tag: Tag::None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn with_suggestion(
|
2025-01-22 07:10:07 +13:00
|
|
|
self,
|
|
|
|
suggestion_title: impl ToString,
|
|
|
|
suggestion_insert: impl ToString,
|
2025-02-22 18:55:44 +13:00
|
|
|
// Will use the error source range if none is supplied
|
|
|
|
source_range: Option<SourceRange>,
|
2024-12-06 13:57:31 +13:00
|
|
|
tag: Tag,
|
|
|
|
) -> CompilationError {
|
|
|
|
CompilationError {
|
2025-01-22 07:10:07 +13:00
|
|
|
suggestion: Some(Suggestion {
|
|
|
|
title: suggestion_title.to_string(),
|
|
|
|
insert: suggestion_insert.to_string(),
|
2025-02-22 18:55:44 +13:00
|
|
|
source_range: source_range.unwrap_or(self.source_range),
|
2024-12-06 13:57:31 +13:00
|
|
|
}),
|
|
|
|
tag,
|
2025-01-22 07:10:07 +13:00
|
|
|
..self
|
2024-12-06 13:57:31 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub fn apply_suggestion(&self, src: &str) -> Option<String> {
|
|
|
|
let suggestion = self.suggestion.as_ref()?;
|
|
|
|
Some(format!(
|
|
|
|
"{}{}{}",
|
2025-02-22 18:55:44 +13:00
|
|
|
&src[0..suggestion.source_range.start()],
|
2024-12-06 13:57:31 +13:00
|
|
|
suggestion.insert,
|
2025-02-22 18:55:44 +13:00
|
|
|
&src[suggestion.source_range.end()..]
|
2024-12-06 13:57:31 +13:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<CompilationError> for KclErrorDetails {
|
|
|
|
fn from(err: CompilationError) -> Self {
|
|
|
|
KclErrorDetails {
|
|
|
|
source_ranges: vec![err.source_range],
|
|
|
|
message: err.message,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub enum Severity {
|
|
|
|
Warning,
|
|
|
|
Error,
|
|
|
|
Fatal,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Severity {
|
|
|
|
pub fn is_err(self) -> bool {
|
|
|
|
match self {
|
|
|
|
Severity::Warning => false,
|
|
|
|
Severity::Error | Severity::Fatal => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub enum Tag {
|
|
|
|
Deprecated,
|
|
|
|
Unnecessary,
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
|
|
|
|
#[ts(export)]
|
|
|
|
pub struct Suggestion {
|
|
|
|
pub title: String,
|
|
|
|
pub insert: String,
|
2025-02-22 18:55:44 +13:00
|
|
|
pub source_range: SourceRange,
|
2024-12-06 13:57:31 +13:00
|
|
|
}
|