better errors from rust to lsp for execution errors (#5526)
* better errors start Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * conversions Signed-off-by: Jess Frazelle <github@jessfraz.com> * miette update Signed-off-by: Jess Frazelle <github@jessfraz.com> * related errrors test Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * a bit better Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * regenerate other errors Signed-off-by: Jess Frazelle <github@jessfraz.com> * add diagnostics test Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@ -29,5 +29,5 @@
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "kcl_version": "0.2.39"
 | 
			
		||||
  "kcl_version": "0.2.40"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -730,7 +730,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
version = "0.1.39"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "Inflector",
 | 
			
		||||
 "anyhow",
 | 
			
		||||
@ -1724,7 +1724,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.39"
 | 
			
		||||
version = "0.2.40"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
@ -1791,7 +1791,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
version = "0.1.39"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "hyper 0.14.32",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
description = "A tool for generating documentation from Rust derive macros"
 | 
			
		||||
version = "0.1.39"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
description = "A test server for KCL"
 | 
			
		||||
version = "0.1.39"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
description = "KittyCAD Language implementation and tools"
 | 
			
		||||
version = "0.2.39"
 | 
			
		||||
version = "0.2.40"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
@ -22,7 +22,7 @@ clap = { version = "4.5.27", default-features = false, optional = true, features
 | 
			
		||||
] }
 | 
			
		||||
convert_case = "0.6.0"
 | 
			
		||||
dashmap = "6.1.0"
 | 
			
		||||
derive-docs = { version = "0.1.38", path = "../derive-docs" }
 | 
			
		||||
derive-docs = { version = "0.1.40", path = "../derive-docs" }
 | 
			
		||||
dhat = { version = "0.3", optional = true }
 | 
			
		||||
fnv = "1.0.7"
 | 
			
		||||
form_urlencoded = "1.2.1"
 | 
			
		||||
 | 
			
		||||
@ -13,14 +13,13 @@ use itertools::Itertools;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use tokio::task::JoinSet;
 | 
			
		||||
 | 
			
		||||
use super::kcl_doc::{ConstData, DocData, FnData};
 | 
			
		||||
use crate::{
 | 
			
		||||
    docs::{is_primitive, StdLibFn},
 | 
			
		||||
    std::StdLib,
 | 
			
		||||
    ExecutorContext,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::kcl_doc::{ConstData, DocData, FnData};
 | 
			
		||||
 | 
			
		||||
const TYPES_DIR: &str = "../../../docs/kcl/types";
 | 
			
		||||
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,12 @@
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
 | 
			
		||||
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    execution::{ArtifactCommand, ArtifactGraph, Operation},
 | 
			
		||||
    lsp::IntoDiagnostic,
 | 
			
		||||
    modules::ModulePath,
 | 
			
		||||
    modules::{ModulePath, ModuleSource},
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
    ModuleId,
 | 
			
		||||
};
 | 
			
		||||
@ -120,6 +119,7 @@ pub struct KclErrorWithOutputs {
 | 
			
		||||
    pub artifact_commands: Vec<ArtifactCommand>,
 | 
			
		||||
    pub artifact_graph: ArtifactGraph,
 | 
			
		||||
    pub filenames: IndexMap<ModuleId, ModulePath>,
 | 
			
		||||
    pub source_files: IndexMap<ModuleId, ModuleSource>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl KclErrorWithOutputs {
 | 
			
		||||
@ -129,6 +129,7 @@ impl KclErrorWithOutputs {
 | 
			
		||||
        artifact_commands: Vec<ArtifactCommand>,
 | 
			
		||||
        artifact_graph: ArtifactGraph,
 | 
			
		||||
        filenames: IndexMap<ModuleId, ModulePath>,
 | 
			
		||||
        source_files: IndexMap<ModuleId, ModuleSource>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            error,
 | 
			
		||||
@ -136,6 +137,7 @@ impl KclErrorWithOutputs {
 | 
			
		||||
            artifact_commands,
 | 
			
		||||
            artifact_graph,
 | 
			
		||||
            filenames,
 | 
			
		||||
            source_files,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn no_outputs(error: KclError) -> Self {
 | 
			
		||||
@ -145,8 +147,169 @@ impl KclErrorWithOutputs {
 | 
			
		||||
            artifact_commands: Default::default(),
 | 
			
		||||
            artifact_graph: Default::default(),
 | 
			
		||||
            filenames: Default::default(),
 | 
			
		||||
            source_files: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
 | 
			
		||||
        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()
 | 
			
		||||
            .unwrap_or(ModuleSource {
 | 
			
		||||
                source: code.to_string(),
 | 
			
		||||
                path: self
 | 
			
		||||
                    .filenames
 | 
			
		||||
                    .get(&first_source_range.module_id())
 | 
			
		||||
                    .ok_or_else(|| {
 | 
			
		||||
                        anyhow::anyhow!(
 | 
			
		||||
                            "Could not find filename for module id: {:?}",
 | 
			
		||||
                            first_source_range.module_id()
 | 
			
		||||
                        )
 | 
			
		||||
                    })?
 | 
			
		||||
                    .clone(),
 | 
			
		||||
            });
 | 
			
		||||
        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();
 | 
			
		||||
            let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
 | 
			
		||||
                source: code.to_string(),
 | 
			
		||||
                path: self
 | 
			
		||||
                    .filenames
 | 
			
		||||
                    .get(&module_id)
 | 
			
		||||
                    .ok_or_else(|| anyhow::anyhow!("Could not find filename for module id: {:?}", module_id))?
 | 
			
		||||
                    .clone(),
 | 
			
		||||
            });
 | 
			
		||||
            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 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",
 | 
			
		||||
            KclError::Unimplemented(_) => "Unimplemented",
 | 
			
		||||
            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))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
@ -188,7 +351,7 @@ impl miette::Diagnostic for Report {
 | 
			
		||||
            .clone()
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(miette::SourceSpan::from)
 | 
			
		||||
            .map(|span| miette::LabeledSpan::new_with_span(None, span));
 | 
			
		||||
            .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
 | 
			
		||||
        Some(Box::new(iter))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -311,7 +474,7 @@ impl KclError {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoDiagnostic for KclError {
 | 
			
		||||
    fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
 | 
			
		||||
    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
 | 
			
		||||
        let message = self.get_message();
 | 
			
		||||
        let source_ranges = self.source_ranges();
 | 
			
		||||
 | 
			
		||||
@ -322,18 +485,23 @@ impl IntoDiagnostic for KclError {
 | 
			
		||||
            .filter(|r| r.module_id() == module_id)
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        Diagnostic {
 | 
			
		||||
            range: source_ranges.first().map(|r| r.to_lsp_range(code)).unwrap_or_default(),
 | 
			
		||||
        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()),
 | 
			
		||||
            message,
 | 
			
		||||
                related_information: None,
 | 
			
		||||
                message: message.clone(),
 | 
			
		||||
                tags: None,
 | 
			
		||||
                data: None,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        diagnostics
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn severity(&self) -> DiagnosticSeverity {
 | 
			
		||||
 | 
			
		||||
@ -342,8 +342,9 @@ impl ExecutorContext {
 | 
			
		||||
                // Add file path string to global state even if it fails to import
 | 
			
		||||
                exec_state.add_path_to_source_id(resolved_path.clone(), id);
 | 
			
		||||
                let source = resolved_path.source(&self.fs, source_range).await?;
 | 
			
		||||
                exec_state.add_id_to_source(id, source.clone());
 | 
			
		||||
                // TODO handle parsing errors properly
 | 
			
		||||
                let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
 | 
			
		||||
                let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
 | 
			
		||||
                exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
 | 
			
		||||
 | 
			
		||||
                Ok(id)
 | 
			
		||||
@ -371,7 +372,10 @@ impl ExecutorContext {
 | 
			
		||||
                // Add file path string to global state even if it fails to import
 | 
			
		||||
                exec_state.add_path_to_source_id(resolved_path.clone(), id);
 | 
			
		||||
                let source = resolved_path.source(&self.fs, source_range).await?;
 | 
			
		||||
                let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err().unwrap();
 | 
			
		||||
                exec_state.add_id_to_source(id, source.clone());
 | 
			
		||||
                let parsed = crate::parsing::parse_str(&source.source, id)
 | 
			
		||||
                    .parse_errs_as_err()
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
 | 
			
		||||
                Ok(id)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -138,6 +138,7 @@
 | 
			
		||||
use std::{collections::HashMap, fmt};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use env::Environment;
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@ -147,7 +148,6 @@ use crate::{
 | 
			
		||||
    execution::KclValue,
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
};
 | 
			
		||||
use env::Environment;
 | 
			
		||||
 | 
			
		||||
/// The distinguished name of the return value of a function.
 | 
			
		||||
pub(crate) const RETURN_NAME: &str = "__return";
 | 
			
		||||
@ -894,9 +894,8 @@ mod env {
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::execution::kcl_value::{FunctionSource, NumericType};
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::execution::kcl_value::{FunctionSource, NumericType};
 | 
			
		||||
 | 
			
		||||
    fn sr() -> SourceRange {
 | 
			
		||||
        SourceRange::default()
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,16 @@
 | 
			
		||||
use std::{path::PathBuf, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
 | 
			
		||||
use cache::OldAstState;
 | 
			
		||||
pub use cache::{bust_cache, clear_mem_cache};
 | 
			
		||||
pub use cad_op::Operation;
 | 
			
		||||
pub use geometry::*;
 | 
			
		||||
pub(crate) use import::{
 | 
			
		||||
    import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
 | 
			
		||||
};
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
 | 
			
		||||
use kcmc::{
 | 
			
		||||
    each_cmd as mcmd,
 | 
			
		||||
    ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
 | 
			
		||||
@ -12,8 +20,10 @@ use kcmc::{
 | 
			
		||||
    ImageFormat, ModelingCmd,
 | 
			
		||||
};
 | 
			
		||||
use kittycad_modeling_cmds as kcmc;
 | 
			
		||||
pub use memory::EnvironmentRef;
 | 
			
		||||
use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
pub use state::{ExecState, IdGenerator, MetaSettings};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    engine::EngineManager,
 | 
			
		||||
@ -31,17 +41,6 @@ use crate::{
 | 
			
		||||
    CompilationError, ExecError, ExecutionKind, KclErrorWithOutputs,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
 | 
			
		||||
pub use cache::{bust_cache, clear_mem_cache};
 | 
			
		||||
pub use cad_op::Operation;
 | 
			
		||||
pub use geometry::*;
 | 
			
		||||
pub(crate) use import::{
 | 
			
		||||
    import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
 | 
			
		||||
};
 | 
			
		||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
 | 
			
		||||
pub use memory::EnvironmentRef;
 | 
			
		||||
pub use state::{ExecState, IdGenerator, MetaSettings};
 | 
			
		||||
 | 
			
		||||
pub(crate) mod annotations;
 | 
			
		||||
mod artifact;
 | 
			
		||||
pub(crate) mod cache;
 | 
			
		||||
@ -728,6 +727,7 @@ impl ExecutorContext {
 | 
			
		||||
                    exec_state.global.artifact_commands.clone(),
 | 
			
		||||
                    exec_state.global.artifact_graph.clone(),
 | 
			
		||||
                    module_id_to_module_path,
 | 
			
		||||
                    exec_state.global.id_to_source.clone(),
 | 
			
		||||
                )
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,20 +5,19 @@ use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
use super::EnvironmentRef;
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails, Severity},
 | 
			
		||||
    execution::{
 | 
			
		||||
        annotations, kcl_value, memory::ProgramMemory, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId,
 | 
			
		||||
        ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
 | 
			
		||||
    },
 | 
			
		||||
    modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
 | 
			
		||||
    modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
 | 
			
		||||
    parsing::ast::types::Annotation,
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
    CompilationError,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::EnvironmentRef;
 | 
			
		||||
 | 
			
		||||
/// State for executing a program.
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct ExecState {
 | 
			
		||||
@ -34,6 +33,8 @@ pub(super) struct GlobalState {
 | 
			
		||||
    pub id_generator: IdGenerator,
 | 
			
		||||
    /// Map from source file absolute path to module ID.
 | 
			
		||||
    pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
 | 
			
		||||
    /// Map from module ID to source file.
 | 
			
		||||
    pub id_to_source: IndexMap<ModuleId, ModuleSource>,
 | 
			
		||||
    /// Map from module ID to module info.
 | 
			
		||||
    pub module_infos: IndexMap<ModuleId, ModuleInfo>,
 | 
			
		||||
    /// Output map of UUIDs to artifacts.
 | 
			
		||||
@ -181,6 +182,11 @@ impl ExecState {
 | 
			
		||||
        self.global.path_to_source_id.insert(path.clone(), id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
 | 
			
		||||
        debug_assert!(!self.global.id_to_source.contains_key(&id));
 | 
			
		||||
        self.global.id_to_source.insert(id, source.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
 | 
			
		||||
        debug_assert!(self.global.path_to_source_id.contains_key(&path));
 | 
			
		||||
        let module_info = ModuleInfo { id, repr, path };
 | 
			
		||||
@ -227,6 +233,7 @@ impl GlobalState {
 | 
			
		||||
            operations: Default::default(),
 | 
			
		||||
            mod_loader: Default::default(),
 | 
			
		||||
            errors: Default::default(),
 | 
			
		||||
            id_to_source: Default::default(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let root_id = ModuleId::default();
 | 
			
		||||
@ -244,6 +251,8 @@ impl GlobalState {
 | 
			
		||||
        global
 | 
			
		||||
            .path_to_source_id
 | 
			
		||||
            .insert(ModulePath::Local { value: root_path }, root_id);
 | 
			
		||||
        // Ideally we'd have a way to set the root module's source here, but
 | 
			
		||||
        // we don't have a way to get the source from the executor settings.
 | 
			
		||||
        global
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -67,8 +67,8 @@ impl Discovered {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoDiagnostic for Discovered {
 | 
			
		||||
    fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
 | 
			
		||||
        (&self).to_lsp_diagnostic(code)
 | 
			
		||||
    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
 | 
			
		||||
        (&self).to_lsp_diagnostics(code)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn severity(&self) -> DiagnosticSeverity {
 | 
			
		||||
@ -77,11 +77,11 @@ impl IntoDiagnostic for Discovered {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoDiagnostic for &Discovered {
 | 
			
		||||
    fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
 | 
			
		||||
    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
 | 
			
		||||
        let message = self.finding.title.to_owned();
 | 
			
		||||
        let source_range = self.pos;
 | 
			
		||||
 | 
			
		||||
        Diagnostic {
 | 
			
		||||
        vec![Diagnostic {
 | 
			
		||||
            range: source_range.to_lsp_range(code),
 | 
			
		||||
            severity: Some(self.severity()),
 | 
			
		||||
            code: None,
 | 
			
		||||
@ -92,7 +92,7 @@ impl IntoDiagnostic for &Discovered {
 | 
			
		||||
            related_information: None,
 | 
			
		||||
            tags: None,
 | 
			
		||||
            data: None,
 | 
			
		||||
        }
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn severity(&self) -> DiagnosticSeverity {
 | 
			
		||||
 | 
			
		||||
@ -642,12 +642,14 @@ impl Backend {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for diagnostic in diagnostics {
 | 
			
		||||
            let d = diagnostic.to_lsp_diagnostic(¶ms.text);
 | 
			
		||||
            let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
 | 
			
		||||
            // Make sure we don't duplicate diagnostics.
 | 
			
		||||
            for d in lsp_d {
 | 
			
		||||
                if !items.iter().any(|x| x == &d) {
 | 
			
		||||
                    items.push(d);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.diagnostics_map.insert(params.uri.to_string(), items.clone());
 | 
			
		||||
 | 
			
		||||
@ -673,7 +675,7 @@ impl Backend {
 | 
			
		||||
 | 
			
		||||
        match executor_ctx.run_with_caching(ast.clone()).await {
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                self.add_to_diagnostics(params, &[err.error], false).await;
 | 
			
		||||
                self.add_to_diagnostics(params, &[err], false).await;
 | 
			
		||||
 | 
			
		||||
                // Since we already published the diagnostics we don't really care about the error
 | 
			
		||||
                // string.
 | 
			
		||||
 | 
			
		||||
@ -18,13 +18,13 @@ use crate::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
impl IntoDiagnostic for CompilationError {
 | 
			
		||||
    fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
 | 
			
		||||
    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
 | 
			
		||||
        let edit = self.suggestion.as_ref().map(|s| {
 | 
			
		||||
            let range = s.source_range.to_lsp_range(code);
 | 
			
		||||
            serde_json::to_value((s, range)).unwrap()
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Diagnostic {
 | 
			
		||||
        vec![Diagnostic {
 | 
			
		||||
            range: self.source_range.to_lsp_range(code),
 | 
			
		||||
            severity: Some(self.severity()),
 | 
			
		||||
            code: None,
 | 
			
		||||
@ -34,7 +34,7 @@ impl IntoDiagnostic for CompilationError {
 | 
			
		||||
            related_information: None,
 | 
			
		||||
            tags: self.tag.to_lsp_tags(),
 | 
			
		||||
            data: edit,
 | 
			
		||||
        }
 | 
			
		||||
        }]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn severity(&self) -> DiagnosticSeverity {
 | 
			
		||||
 | 
			
		||||
@ -3473,3 +3473,68 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
 | 
			
		||||
 | 
			
		||||
    assert_eq!(completions.is_none(), true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
async fn kcl_test_kcl_lsp_multi_file_error() {
 | 
			
		||||
    let server = kcl_lsp_server(true).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    let cwd = std::env::current_dir().unwrap();
 | 
			
		||||
    let joined = cwd.join("tests/import_file_parse_error/");
 | 
			
		||||
 | 
			
		||||
    // Change the current directory.
 | 
			
		||||
    std::env::set_current_dir(joined).unwrap();
 | 
			
		||||
 | 
			
		||||
    let code = std::fs::read_to_string("input.kcl").unwrap();
 | 
			
		||||
 | 
			
		||||
    // Send open file.
 | 
			
		||||
    server
 | 
			
		||||
        .did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
 | 
			
		||||
            text_document: tower_lsp::lsp_types::TextDocumentItem {
 | 
			
		||||
                uri: "file:///input.kcl".try_into().unwrap(),
 | 
			
		||||
                language_id: "kcl".to_string(),
 | 
			
		||||
                version: 1,
 | 
			
		||||
                text: code.clone(),
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
    // Send diagnostics request.
 | 
			
		||||
    let diagnostics = server
 | 
			
		||||
        .diagnostic(tower_lsp::lsp_types::DocumentDiagnosticParams {
 | 
			
		||||
            text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
 | 
			
		||||
                uri: "file:///input.kcl".try_into().unwrap(),
 | 
			
		||||
            },
 | 
			
		||||
            partial_result_params: Default::default(),
 | 
			
		||||
            work_done_progress_params: Default::default(),
 | 
			
		||||
            identifier: None,
 | 
			
		||||
            previous_result_id: None,
 | 
			
		||||
        })
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    // Check the diagnostics.
 | 
			
		||||
    if let tower_lsp::lsp_types::DocumentDiagnosticReportResult::Report(diagnostics) = diagnostics {
 | 
			
		||||
        if let tower_lsp::lsp_types::DocumentDiagnosticReport::Full(diagnostics) = diagnostics {
 | 
			
		||||
            assert_eq!(diagnostics.full_document_diagnostic_report.items.len(), 1);
 | 
			
		||||
            let item = diagnostics.full_document_diagnostic_report.items.first().unwrap();
 | 
			
		||||
            assert_eq!(item.message, "syntax: Unexpected token: }");
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                Some(vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
 | 
			
		||||
                    location: tower_lsp::lsp_types::Location {
 | 
			
		||||
                        uri: "file:///parse-failure.kcl".try_into().unwrap(),
 | 
			
		||||
                        range: tower_lsp::lsp_types::Range {
 | 
			
		||||
                            start: tower_lsp::lsp_types::Position { line: 1, character: 9 },
 | 
			
		||||
                            end: tower_lsp::lsp_types::Position { line: 2, character: 1 },
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    message: "syntax: Unexpected token: }".to_string(),
 | 
			
		||||
                }]),
 | 
			
		||||
                item.related_information
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            panic!("Expected full diagnostics");
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        panic!("Expected diagnostics");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,8 @@ pub fn get_line_before(pos: Position, rope: &Rope) -> Option<String> {
 | 
			
		||||
/// Convert an object into a [lsp_types::Diagnostic] given the
 | 
			
		||||
/// [TextDocumentItem]'s `.text` field.
 | 
			
		||||
pub trait IntoDiagnostic {
 | 
			
		||||
    /// Convert the traited object to a [lsp_types::Diagnostic].
 | 
			
		||||
    fn to_lsp_diagnostic(&self, text: &str) -> Diagnostic;
 | 
			
		||||
    /// Convert the traited object to a vector of [lsp_types::Diagnostic].
 | 
			
		||||
    fn to_lsp_diagnostics(&self, text: &str) -> Vec<Diagnostic>;
 | 
			
		||||
 | 
			
		||||
    /// Get the severity of the diagnostic.
 | 
			
		||||
    fn severity(&self) -> tower_lsp::lsp_types::DiagnosticSeverity;
 | 
			
		||||
 | 
			
		||||
@ -143,17 +143,23 @@ impl ModulePath {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) async fn source(&self, fs: &FileManager, source_range: SourceRange) -> Result<String, KclError> {
 | 
			
		||||
    pub(crate) async fn source(&self, fs: &FileManager, source_range: SourceRange) -> Result<ModuleSource, KclError> {
 | 
			
		||||
        match self {
 | 
			
		||||
            ModulePath::Local { value: p } => fs.read_to_string(p, source_range).await,
 | 
			
		||||
            ModulePath::Std { value: name } => read_std(name)
 | 
			
		||||
            ModulePath::Local { value: p } => Ok(ModuleSource {
 | 
			
		||||
                source: fs.read_to_string(p, source_range).await?,
 | 
			
		||||
                path: self.clone(),
 | 
			
		||||
            }),
 | 
			
		||||
            ModulePath::Std { value: name } => Ok(ModuleSource {
 | 
			
		||||
                source: read_std(name)
 | 
			
		||||
                    .ok_or_else(|| {
 | 
			
		||||
                        KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                            message: format!("Cannot find standard library module to import: std::{name}."),
 | 
			
		||||
                            source_ranges: vec![source_range],
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                .map(str::to_owned),
 | 
			
		||||
                    .map(str::to_owned)?,
 | 
			
		||||
                path: self.clone(),
 | 
			
		||||
            }),
 | 
			
		||||
            ModulePath::Main => unreachable!(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -188,3 +194,9 @@ impl fmt::Display for ModulePath {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
 | 
			
		||||
pub struct ModuleSource {
 | 
			
		||||
    pub path: ModulePath,
 | 
			
		||||
    pub source: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -128,11 +128,14 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
                    // Snapshot the KCL error with a fancy graphical report.
 | 
			
		||||
                    // This looks like a Cargo compile error, with arrows pointing
 | 
			
		||||
                    // to source code, underlines, etc.
 | 
			
		||||
                    let report = crate::errors::Report {
 | 
			
		||||
                        error: error.error,
 | 
			
		||||
                        filename: format!("{test_name}.kcl"),
 | 
			
		||||
                        kcl_source: read("input.kcl", test_name),
 | 
			
		||||
                    };
 | 
			
		||||
                    miette::set_hook(Box::new(|_| {
 | 
			
		||||
                        Box::new(miette::MietteHandlerOpts::new().show_related_errors_as_nested().build())
 | 
			
		||||
                    }))
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                    let report = error
 | 
			
		||||
                        .clone()
 | 
			
		||||
                        .into_miette_report_with_outputs(&read("input.kcl", test_name))
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                    let report = miette::Report::new(report);
 | 
			
		||||
                    if previously_passed {
 | 
			
		||||
                        eprintln!("This test case failed, but it previously passed. If this is intended, and the test should actually be failing now, please delete kcl/{ok_path_str} and other associated passing artifacts");
 | 
			
		||||
@ -2027,6 +2030,28 @@ mod helix_simple {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod import_file_not_exist_error {
 | 
			
		||||
    const TEST_NAME: &str = "import_file_not_exist_error";
 | 
			
		||||
 | 
			
		||||
    /// Test parsing KCL.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse() {
 | 
			
		||||
        super::parse(TEST_NAME);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that parsing and unparsing KCL produces the original KCL input.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn unparse() {
 | 
			
		||||
        super::unparse(TEST_NAME)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that KCL is executed correctly.
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn kcl_test_execute() {
 | 
			
		||||
        super::execute(TEST_NAME, true).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod import_file_parse_error {
 | 
			
		||||
    const TEST_NAME: &str = "import_file_parse_error";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -657,9 +657,8 @@ impl GeometryTrait for Box<Solid> {
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::execution::kcl_value::NumericType;
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::execution::kcl_value::NumericType;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_array_to_point3d() {
 | 
			
		||||
 | 
			
		||||
@ -4,14 +4,13 @@ use derive_docs::stdlib;
 | 
			
		||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd};
 | 
			
		||||
use kittycad_modeling_cmds as kcmc;
 | 
			
		||||
 | 
			
		||||
use super::sketch::PlaneData;
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    execution::{ExecState, KclValue, Plane, PlaneType},
 | 
			
		||||
    std::Args,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::sketch::PlaneData;
 | 
			
		||||
 | 
			
		||||
/// Offset a plane by a distance along its normal.
 | 
			
		||||
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let std_plane = args.get_unlabeled_kw_arg("plane")?;
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL Type error
 | 
			
		||||
   ╭─[5:5]
 | 
			
		||||
 4 │ 
 | 
			
		||||
 5 │ map(f, [0, 1])
 | 
			
		||||
   ·     ─
 | 
			
		||||
   ·     ┬
 | 
			
		||||
   ·     ╰── tests/argument_error/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:8]
 | 
			
		||||
 1 │ arr = []
 | 
			
		||||
 2 │ fail = pop(arr)
 | 
			
		||||
   ·        ────────
 | 
			
		||||
   ·        ────┬───
 | 
			
		||||
   ·            ╰── tests/array_elem_pop_empty_fail/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
 | 
			
		||||
   ╭─[3:8]
 | 
			
		||||
 2 │ pushedArr = pop(arr)
 | 
			
		||||
 3 │ fail = pushedArr[2]
 | 
			
		||||
   ·        ────────────
 | 
			
		||||
   ·        ──────┬─────
 | 
			
		||||
   ·              ╰── tests/array_elem_pop_fail/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
 | 
			
		||||
   ╭─[3:8]
 | 
			
		||||
 2 │ pushedArr = push(arr, 4)
 | 
			
		||||
 3 │ fail = arr[3]
 | 
			
		||||
   ·        ──────
 | 
			
		||||
   ·        ───┬──
 | 
			
		||||
   ·           ╰── tests/array_elem_push_fail/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ arr = []
 | 
			
		||||
 2 │ x = arr[0]
 | 
			
		||||
   ·     ──────
 | 
			
		||||
   ·     ───┬──
 | 
			
		||||
   ·        ╰── tests/array_index_oob/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB  | 
@ -7,5 +7,6 @@ KCL Semantic error
 | 
			
		||||
  × semantic: Expected a number, but found a boolean (true/false value)
 | 
			
		||||
   ╭────
 | 
			
		||||
 1 │ assert(3 == 3 == 3, "this should not compile")
 | 
			
		||||
   ·        ──────
 | 
			
		||||
   ·        ───┬──
 | 
			
		||||
   ·           ╰── tests/comparisons_multiple/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
 | 
			
		||||
    ╭─[22:1]
 | 
			
		||||
 21 │ // Error, after creating meaningful output.
 | 
			
		||||
 22 │ foo
 | 
			
		||||
    · ───
 | 
			
		||||
    · ─┬─
 | 
			
		||||
    ·  ╰── tests/cube_with_error/input.kcl
 | 
			
		||||
    ╰────
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ KCL ImportCycle error
 | 
			
		||||
   ╭─[2:1]
 | 
			
		||||
 1 │ @settings(defaultLengthUnit = in)
 | 
			
		||||
 2 │ import two from "import_cycle2.kcl"
 | 
			
		||||
   · ───────────────────────────────────
 | 
			
		||||
   · ─────────────────┬─────────────────
 | 
			
		||||
   ·                  ╰── tests/import_cycle1/input.kcl
 | 
			
		||||
 3 │ 
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,284 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact commands import_file_parse_error.kcl
 | 
			
		||||
---
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.7,
 | 
			
		||||
        "g": 0.28,
 | 
			
		||||
        "b": 0.28,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.28,
 | 
			
		||||
        "g": 0.7,
 | 
			
		||||
        "b": 0.28,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.28,
 | 
			
		||||
        "g": 0.28,
 | 
			
		||||
        "b": 0.7,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": -1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": -1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": -1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "edge_lines_visible",
 | 
			
		||||
      "hidden": false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "set_scene_units",
 | 
			
		||||
      "unit": "mm"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart import_file_parse_error.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										39
									
								
								src/wasm-lib/kcl/tests/import_file_not_exist_error/ast.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,39 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Result of parsing import_file_not_exist_error.kcl
 | 
			
		||||
---
 | 
			
		||||
{
 | 
			
		||||
  "Ok": {
 | 
			
		||||
    "body": [
 | 
			
		||||
      {
 | 
			
		||||
        "end": 34,
 | 
			
		||||
        "path": {
 | 
			
		||||
          "type": "Kcl",
 | 
			
		||||
          "filename": "not-exist.kcl"
 | 
			
		||||
        },
 | 
			
		||||
        "selector": {
 | 
			
		||||
          "type": "List",
 | 
			
		||||
          "items": [
 | 
			
		||||
            {
 | 
			
		||||
              "alias": null,
 | 
			
		||||
              "end": 13,
 | 
			
		||||
              "name": {
 | 
			
		||||
                "end": 13,
 | 
			
		||||
                "name": "hotdog",
 | 
			
		||||
                "start": 7,
 | 
			
		||||
                "type": "Identifier"
 | 
			
		||||
              },
 | 
			
		||||
              "start": 7,
 | 
			
		||||
              "type": "ImportItem"
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        "start": 0,
 | 
			
		||||
        "type": "ImportStatement",
 | 
			
		||||
        "type": "ImportStatement"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "end": 35,
 | 
			
		||||
    "start": 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Error from executing import_file_not_exist_error.kcl
 | 
			
		||||
---
 | 
			
		||||
KCL Engine error
 | 
			
		||||
 | 
			
		||||
  × engine: Failed to read file `tests/import_file_not_exist_error/not-
 | 
			
		||||
  │ exist.kcl`: No such file or directory (os error 2)
 | 
			
		||||
   ╭────
 | 
			
		||||
 1 │ import hotdog from "not-exist.kcl"
 | 
			
		||||
   · ─────────────────┬────────────────
 | 
			
		||||
   ·                  ╰── tests/import_file_not_exist_error/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
import hotdog from "not-exist.kcl"
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Operations executed import_file_parse_error.kcl
 | 
			
		||||
---
 | 
			
		||||
[]
 | 
			
		||||
@ -5,7 +5,9 @@ description: Error from executing import_file_parse_error.kcl
 | 
			
		||||
KCL Syntax error
 | 
			
		||||
 | 
			
		||||
  × syntax: Unexpected token: }
 | 
			
		||||
   ╭────
 | 
			
		||||
 1 │ import hotdog from "parse-failure.kcl"
 | 
			
		||||
   ·                                 ─
 | 
			
		||||
   ╭─[3:1]
 | 
			
		||||
 2 │   return 
 | 
			
		||||
 3 │ }
 | 
			
		||||
   · ┬
 | 
			
		||||
   · ╰── tests/import_file_parse_error/parse-failure.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ arr = [1, 2, 3]
 | 
			
		||||
 2 │ x = arr[1.2]
 | 
			
		||||
   ·     ────────
 | 
			
		||||
   ·     ────┬───
 | 
			
		||||
   ·         ╰── tests/invalid_index_fractional/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[3:5]
 | 
			
		||||
 2 │ i = -1
 | 
			
		||||
 3 │ x = arr[i]
 | 
			
		||||
   ·     ──────
 | 
			
		||||
   ·     ───┬──
 | 
			
		||||
   ·        ╰── tests/invalid_index_negative/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ arr = [1, 2, 3]
 | 
			
		||||
 2 │ x = arr["s"]
 | 
			
		||||
   ·     ────────
 | 
			
		||||
   ·     ────┬───
 | 
			
		||||
   ·         ╰── tests/invalid_index_str/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ num = 999
 | 
			
		||||
 2 │ x = num[3]
 | 
			
		||||
   ·     ──────
 | 
			
		||||
   ·     ───┬──
 | 
			
		||||
   ·        ╰── tests/invalid_member_object/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ b = true
 | 
			
		||||
 2 │ x = b["property"]
 | 
			
		||||
   ·     ─────────────
 | 
			
		||||
   ·     ──────┬──────
 | 
			
		||||
   ·           ╰── tests/invalid_member_object_prop/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,21 @@ KCL Semantic error
 | 
			
		||||
   ╭─[1:7]
 | 
			
		||||
 1 │ ╭─▶ fn add(x, y) {
 | 
			
		||||
 2 │ │     return x + y
 | 
			
		||||
 3 │ ╰─▶ }
 | 
			
		||||
 3 │ ├─▶ }
 | 
			
		||||
   · ╰──── tests/kw_fn_too_few_args/input.kcl
 | 
			
		||||
 4 │     
 | 
			
		||||
 5 │     three = add(x = 1)
 | 
			
		||||
   ·             ──────────
 | 
			
		||||
   ·             ─────┬────
 | 
			
		||||
   ·                  ╰── tests/kw_fn_too_few_args/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
  ╰─▶ KCL Semantic error
 | 
			
		||||
      
 | 
			
		||||
        × semantic: This function requires a parameter y, but you haven't
 | 
			
		||||
        │ passed it one.
 | 
			
		||||
         ╭─[1:7]
 | 
			
		||||
       1 │ ╭─▶ fn add(x, y) {
 | 
			
		||||
       2 │ │     return x + y
 | 
			
		||||
       3 │ ├─▶ }
 | 
			
		||||
         · ╰──── tests/kw_fn_too_few_args/input.kcl
 | 
			
		||||
       4 │
 | 
			
		||||
         ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,21 @@ KCL Semantic error
 | 
			
		||||
   ╭─[1:7]
 | 
			
		||||
 1 │ ╭─▶ fn add(@x) {
 | 
			
		||||
 2 │ │     return x + 1
 | 
			
		||||
 3 │ ╰─▶ }
 | 
			
		||||
 3 │ ├─▶ }
 | 
			
		||||
   · ╰──── tests/kw_fn_unlabeled_but_has_label/input.kcl
 | 
			
		||||
 4 │     
 | 
			
		||||
 5 │     two = add(x = 1)
 | 
			
		||||
   ·           ──────────
 | 
			
		||||
   ·           ─────┬────
 | 
			
		||||
   ·                ╰── tests/kw_fn_unlabeled_but_has_label/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
  ╰─▶ KCL Semantic error
 | 
			
		||||
      
 | 
			
		||||
        × semantic: The function does declare a parameter named 'x', but this
 | 
			
		||||
        │ parameter doesn't use a label. Try removing the `x:`
 | 
			
		||||
         ╭─[1:7]
 | 
			
		||||
       1 │ ╭─▶ fn add(@x) {
 | 
			
		||||
       2 │ │     return x + 1
 | 
			
		||||
       3 │ ├─▶ }
 | 
			
		||||
         · ╰──── tests/kw_fn_unlabeled_but_has_label/input.kcl
 | 
			
		||||
       4 │
 | 
			
		||||
         ╰────
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,6 @@ KCL Semantic error
 | 
			
		||||
   ╭─[2:7]
 | 
			
		||||
 1 │ obj = { key = 123 }
 | 
			
		||||
 2 │ num = obj[3]
 | 
			
		||||
   ·       ──────
 | 
			
		||||
   ·       ───┬──
 | 
			
		||||
   ·          ╰── tests/non_string_key_of_object/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
 | 
			
		||||
   ╭─[2:5]
 | 
			
		||||
 1 │ obj = {  }
 | 
			
		||||
 2 │ k = obj["age"]
 | 
			
		||||
   ·     ──────────
 | 
			
		||||
   ·     ─────┬────
 | 
			
		||||
   ·          ╰── tests/object_prop_not_found/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 75 KiB  | 
| 
		 Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB  | 
| 
		 Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB  | 
@ -8,6 +8,7 @@ KCL Semantic error
 | 
			
		||||
   ╭─[6:10]
 | 
			
		||||
 5 │ 
 | 
			
		||||
 6 │ answer = %
 | 
			
		||||
   ·          ─
 | 
			
		||||
   ·          ┬
 | 
			
		||||
   ·          ╰── tests/pipe_substitution_inside_function_called_from_pipeline/input.kcl
 | 
			
		||||
 7 │   |> f(%)
 | 
			
		||||
   ╰────
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB  | 
| 
		 Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 148 KiB  |