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]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.39"
|
version = "0.1.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1724,7 +1724,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.39"
|
version = "0.2.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1791,7 +1791,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.39"
|
version = "0.1.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.32",
|
"hyper 0.14.32",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.39"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.39"
|
version = "0.1.40"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.39"
|
version = "0.2.40"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
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"
|
convert_case = "0.6.0"
|
||||||
dashmap = "6.1.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 }
|
dhat = { version = "0.3", optional = true }
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
form_urlencoded = "1.2.1"
|
form_urlencoded = "1.2.1"
|
||||||
|
|||||||
@ -13,14 +13,13 @@ use itertools::Itertools;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
use super::kcl_doc::{ConstData, DocData, FnData};
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::{is_primitive, StdLibFn},
|
docs::{is_primitive, StdLibFn},
|
||||||
std::StdLib,
|
std::StdLib,
|
||||||
ExecutorContext,
|
ExecutorContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::kcl_doc::{ConstData, DocData, FnData};
|
|
||||||
|
|
||||||
const TYPES_DIR: &str = "../../../docs/kcl/types";
|
const TYPES_DIR: &str = "../../../docs/kcl/types";
|
||||||
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
|
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
|
use indexmap::IndexMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
execution::{ArtifactCommand, ArtifactGraph, Operation},
|
execution::{ArtifactCommand, ArtifactGraph, Operation},
|
||||||
lsp::IntoDiagnostic,
|
lsp::IntoDiagnostic,
|
||||||
modules::ModulePath,
|
modules::{ModulePath, ModuleSource},
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
ModuleId,
|
ModuleId,
|
||||||
};
|
};
|
||||||
@ -120,6 +119,7 @@ pub struct KclErrorWithOutputs {
|
|||||||
pub artifact_commands: Vec<ArtifactCommand>,
|
pub artifact_commands: Vec<ArtifactCommand>,
|
||||||
pub artifact_graph: ArtifactGraph,
|
pub artifact_graph: ArtifactGraph,
|
||||||
pub filenames: IndexMap<ModuleId, ModulePath>,
|
pub filenames: IndexMap<ModuleId, ModulePath>,
|
||||||
|
pub source_files: IndexMap<ModuleId, ModuleSource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KclErrorWithOutputs {
|
impl KclErrorWithOutputs {
|
||||||
@ -129,6 +129,7 @@ impl KclErrorWithOutputs {
|
|||||||
artifact_commands: Vec<ArtifactCommand>,
|
artifact_commands: Vec<ArtifactCommand>,
|
||||||
artifact_graph: ArtifactGraph,
|
artifact_graph: ArtifactGraph,
|
||||||
filenames: IndexMap<ModuleId, ModulePath>,
|
filenames: IndexMap<ModuleId, ModulePath>,
|
||||||
|
source_files: IndexMap<ModuleId, ModuleSource>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error,
|
error,
|
||||||
@ -136,6 +137,7 @@ impl KclErrorWithOutputs {
|
|||||||
artifact_commands,
|
artifact_commands,
|
||||||
artifact_graph,
|
artifact_graph,
|
||||||
filenames,
|
filenames,
|
||||||
|
source_files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn no_outputs(error: KclError) -> Self {
|
pub fn no_outputs(error: KclError) -> Self {
|
||||||
@ -145,8 +147,169 @@ impl KclErrorWithOutputs {
|
|||||||
artifact_commands: Default::default(),
|
artifact_commands: Default::default(),
|
||||||
artifact_graph: Default::default(),
|
artifact_graph: Default::default(),
|
||||||
filenames: 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)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -188,7 +351,7 @@ impl miette::Diagnostic for Report {
|
|||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(miette::SourceSpan::from)
|
.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))
|
Some(Box::new(iter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +474,7 @@ impl KclError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDiagnostic for 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 message = self.get_message();
|
||||||
let source_ranges = self.source_ranges();
|
let source_ranges = self.source_ranges();
|
||||||
|
|
||||||
@ -322,18 +485,23 @@ impl IntoDiagnostic for KclError {
|
|||||||
.filter(|r| r.module_id() == module_id)
|
.filter(|r| r.module_id() == module_id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Diagnostic {
|
let mut diagnostics = Vec::new();
|
||||||
range: source_ranges.first().map(|r| r.to_lsp_range(code)).unwrap_or_default(),
|
for source_range in &source_ranges {
|
||||||
severity: Some(self.severity()),
|
diagnostics.push(Diagnostic {
|
||||||
code: None,
|
range: source_range.to_lsp_range(code),
|
||||||
// TODO: this is neat we can pass a URL to a help page here for this specific error.
|
severity: Some(self.severity()),
|
||||||
code_description: None,
|
code: None,
|
||||||
source: Some("kcl".to_string()),
|
// TODO: this is neat we can pass a URL to a help page here for this specific error.
|
||||||
message,
|
code_description: None,
|
||||||
related_information: None,
|
source: Some("kcl".to_string()),
|
||||||
tags: None,
|
related_information: None,
|
||||||
data: None,
|
message: message.clone(),
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn severity(&self) -> DiagnosticSeverity {
|
fn severity(&self) -> DiagnosticSeverity {
|
||||||
|
|||||||
@ -342,8 +342,9 @@ impl ExecutorContext {
|
|||||||
// Add file path string to global state even if it fails to import
|
// Add file path string to global state even if it fails to import
|
||||||
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
||||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
let source = resolved_path.source(&self.fs, source_range).await?;
|
||||||
|
exec_state.add_id_to_source(id, source.clone());
|
||||||
// TODO handle parsing errors properly
|
// 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));
|
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
@ -371,7 +372,10 @@ impl ExecutorContext {
|
|||||||
// Add file path string to global state even if it fails to import
|
// Add file path string to global state even if it fails to import
|
||||||
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
||||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
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));
|
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,6 +138,7 @@
|
|||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use env::Environment;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -147,7 +148,6 @@ use crate::{
|
|||||||
execution::KclValue,
|
execution::KclValue,
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
};
|
};
|
||||||
use env::Environment;
|
|
||||||
|
|
||||||
/// The distinguished name of the return value of a function.
|
/// The distinguished name of the return value of a function.
|
||||||
pub(crate) const RETURN_NAME: &str = "__return";
|
pub(crate) const RETURN_NAME: &str = "__return";
|
||||||
@ -894,9 +894,8 @@ mod env {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::execution::kcl_value::{FunctionSource, NumericType};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::execution::kcl_value::{FunctionSource, NumericType};
|
||||||
|
|
||||||
fn sr() -> SourceRange {
|
fn sr() -> SourceRange {
|
||||||
SourceRange::default()
|
SourceRange::default()
|
||||||
|
|||||||
@ -3,8 +3,16 @@
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||||
use cache::OldAstState;
|
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;
|
use indexmap::IndexMap;
|
||||||
|
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||||
use kcmc::{
|
use kcmc::{
|
||||||
each_cmd as mcmd,
|
each_cmd as mcmd,
|
||||||
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
|
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
|
||||||
@ -12,8 +20,10 @@ use kcmc::{
|
|||||||
ImageFormat, ModelingCmd,
|
ImageFormat, ModelingCmd,
|
||||||
};
|
};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
pub use memory::EnvironmentRef;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
pub use state::{ExecState, IdGenerator, MetaSettings};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::EngineManager,
|
engine::EngineManager,
|
||||||
@ -31,17 +41,6 @@ use crate::{
|
|||||||
CompilationError, ExecError, ExecutionKind, KclErrorWithOutputs,
|
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;
|
pub(crate) mod annotations;
|
||||||
mod artifact;
|
mod artifact;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
@ -728,6 +727,7 @@ impl ExecutorContext {
|
|||||||
exec_state.global.artifact_commands.clone(),
|
exec_state.global.artifact_commands.clone(),
|
||||||
exec_state.global.artifact_graph.clone(),
|
exec_state.global.artifact_graph.clone(),
|
||||||
module_id_to_module_path,
|
module_id_to_module_path,
|
||||||
|
exec_state.global.id_to_source.clone(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@ -5,20 +5,19 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::EnvironmentRef;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails, Severity},
|
errors::{KclError, KclErrorDetails, Severity},
|
||||||
execution::{
|
execution::{
|
||||||
annotations, kcl_value, memory::ProgramMemory, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId,
|
annotations, kcl_value, memory::ProgramMemory, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId,
|
||||||
ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
|
ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
|
||||||
},
|
},
|
||||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
|
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
|
||||||
parsing::ast::types::Annotation,
|
parsing::ast::types::Annotation,
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
CompilationError,
|
CompilationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::EnvironmentRef;
|
|
||||||
|
|
||||||
/// State for executing a program.
|
/// State for executing a program.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ExecState {
|
pub struct ExecState {
|
||||||
@ -34,6 +33,8 @@ pub(super) struct GlobalState {
|
|||||||
pub id_generator: IdGenerator,
|
pub id_generator: IdGenerator,
|
||||||
/// Map from source file absolute path to module ID.
|
/// Map from source file absolute path to module ID.
|
||||||
pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
|
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.
|
/// Map from module ID to module info.
|
||||||
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
|
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
|
||||||
/// Output map of UUIDs to artifacts.
|
/// Output map of UUIDs to artifacts.
|
||||||
@ -181,6 +182,11 @@ impl ExecState {
|
|||||||
self.global.path_to_source_id.insert(path.clone(), id);
|
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) {
|
pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
|
||||||
debug_assert!(self.global.path_to_source_id.contains_key(&path));
|
debug_assert!(self.global.path_to_source_id.contains_key(&path));
|
||||||
let module_info = ModuleInfo { id, repr, path };
|
let module_info = ModuleInfo { id, repr, path };
|
||||||
@ -227,6 +233,7 @@ impl GlobalState {
|
|||||||
operations: Default::default(),
|
operations: Default::default(),
|
||||||
mod_loader: Default::default(),
|
mod_loader: Default::default(),
|
||||||
errors: Default::default(),
|
errors: Default::default(),
|
||||||
|
id_to_source: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let root_id = ModuleId::default();
|
let root_id = ModuleId::default();
|
||||||
@ -244,6 +251,8 @@ impl GlobalState {
|
|||||||
global
|
global
|
||||||
.path_to_source_id
|
.path_to_source_id
|
||||||
.insert(ModulePath::Local { value: root_path }, root_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
|
global
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,8 +67,8 @@ impl Discovered {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
(&self).to_lsp_diagnostic(code)
|
(&self).to_lsp_diagnostics(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn severity(&self) -> DiagnosticSeverity {
|
fn severity(&self) -> DiagnosticSeverity {
|
||||||
@ -77,11 +77,11 @@ impl IntoDiagnostic for Discovered {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 message = self.finding.title.to_owned();
|
||||||
let source_range = self.pos;
|
let source_range = self.pos;
|
||||||
|
|
||||||
Diagnostic {
|
vec![Diagnostic {
|
||||||
range: source_range.to_lsp_range(code),
|
range: source_range.to_lsp_range(code),
|
||||||
severity: Some(self.severity()),
|
severity: Some(self.severity()),
|
||||||
code: None,
|
code: None,
|
||||||
@ -92,7 +92,7 @@ impl IntoDiagnostic for &Discovered {
|
|||||||
related_information: None,
|
related_information: None,
|
||||||
tags: None,
|
tags: None,
|
||||||
data: None,
|
data: None,
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn severity(&self) -> DiagnosticSeverity {
|
fn severity(&self) -> DiagnosticSeverity {
|
||||||
|
|||||||
@ -642,10 +642,12 @@ impl Backend {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for diagnostic in diagnostics {
|
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.
|
// Make sure we don't duplicate diagnostics.
|
||||||
if !items.iter().any(|x| x == &d) {
|
for d in lsp_d {
|
||||||
items.push(d);
|
if !items.iter().any(|x| x == &d) {
|
||||||
|
items.push(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +675,7 @@ impl Backend {
|
|||||||
|
|
||||||
match executor_ctx.run_with_caching(ast.clone()).await {
|
match executor_ctx.run_with_caching(ast.clone()).await {
|
||||||
Err(err) => {
|
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
|
// Since we already published the diagnostics we don't really care about the error
|
||||||
// string.
|
// string.
|
||||||
|
|||||||
@ -18,13 +18,13 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl IntoDiagnostic for CompilationError {
|
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 edit = self.suggestion.as_ref().map(|s| {
|
||||||
let range = s.source_range.to_lsp_range(code);
|
let range = s.source_range.to_lsp_range(code);
|
||||||
serde_json::to_value((s, range)).unwrap()
|
serde_json::to_value((s, range)).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
Diagnostic {
|
vec![Diagnostic {
|
||||||
range: self.source_range.to_lsp_range(code),
|
range: self.source_range.to_lsp_range(code),
|
||||||
severity: Some(self.severity()),
|
severity: Some(self.severity()),
|
||||||
code: None,
|
code: None,
|
||||||
@ -34,7 +34,7 @@ impl IntoDiagnostic for CompilationError {
|
|||||||
related_information: None,
|
related_information: None,
|
||||||
tags: self.tag.to_lsp_tags(),
|
tags: self.tag.to_lsp_tags(),
|
||||||
data: edit,
|
data: edit,
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn severity(&self) -> DiagnosticSeverity {
|
fn severity(&self) -> DiagnosticSeverity {
|
||||||
|
|||||||
@ -3473,3 +3473,68 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
|
|||||||
|
|
||||||
assert_eq!(completions.is_none(), true);
|
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
|
/// Convert an object into a [lsp_types::Diagnostic] given the
|
||||||
/// [TextDocumentItem]'s `.text` field.
|
/// [TextDocumentItem]'s `.text` field.
|
||||||
pub trait IntoDiagnostic {
|
pub trait IntoDiagnostic {
|
||||||
/// Convert the traited object to a [lsp_types::Diagnostic].
|
/// Convert the traited object to a vector of [lsp_types::Diagnostic].
|
||||||
fn to_lsp_diagnostic(&self, text: &str) -> Diagnostic;
|
fn to_lsp_diagnostics(&self, text: &str) -> Vec<Diagnostic>;
|
||||||
|
|
||||||
/// Get the severity of the diagnostic.
|
/// Get the severity of the diagnostic.
|
||||||
fn severity(&self) -> tower_lsp::lsp_types::DiagnosticSeverity;
|
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 {
|
match self {
|
||||||
ModulePath::Local { value: p } => fs.read_to_string(p, source_range).await,
|
ModulePath::Local { value: p } => Ok(ModuleSource {
|
||||||
ModulePath::Std { value: name } => read_std(name)
|
source: fs.read_to_string(p, source_range).await?,
|
||||||
.ok_or_else(|| {
|
path: self.clone(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
}),
|
||||||
message: format!("Cannot find standard library module to import: std::{name}."),
|
ModulePath::Std { value: name } => Ok(ModuleSource {
|
||||||
source_ranges: vec![source_range],
|
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!(),
|
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.
|
// Snapshot the KCL error with a fancy graphical report.
|
||||||
// This looks like a Cargo compile error, with arrows pointing
|
// This looks like a Cargo compile error, with arrows pointing
|
||||||
// to source code, underlines, etc.
|
// to source code, underlines, etc.
|
||||||
let report = crate::errors::Report {
|
miette::set_hook(Box::new(|_| {
|
||||||
error: error.error,
|
Box::new(miette::MietteHandlerOpts::new().show_related_errors_as_nested().build())
|
||||||
filename: format!("{test_name}.kcl"),
|
}))
|
||||||
kcl_source: read("input.kcl", test_name),
|
.unwrap();
|
||||||
};
|
let report = error
|
||||||
|
.clone()
|
||||||
|
.into_miette_report_with_outputs(&read("input.kcl", test_name))
|
||||||
|
.unwrap();
|
||||||
let report = miette::Report::new(report);
|
let report = miette::Report::new(report);
|
||||||
if previously_passed {
|
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");
|
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 {
|
mod import_file_parse_error {
|
||||||
const TEST_NAME: &str = "import_file_parse_error";
|
const TEST_NAME: &str = "import_file_parse_error";
|
||||||
|
|
||||||
|
|||||||
@ -657,9 +657,8 @@ impl GeometryTrait for Box<Solid> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::execution::kcl_value::NumericType;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::execution::kcl_value::NumericType;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_to_point3d() {
|
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 kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
|
||||||
|
use super::sketch::PlaneData;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
execution::{ExecState, KclValue, Plane, PlaneType},
|
execution::{ExecState, KclValue, Plane, PlaneType},
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::sketch::PlaneData;
|
|
||||||
|
|
||||||
/// Offset a plane by a distance along its normal.
|
/// Offset a plane by a distance along its normal.
|
||||||
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let std_plane = args.get_unlabeled_kw_arg("plane")?;
|
let std_plane = args.get_unlabeled_kw_arg("plane")?;
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL Type error
|
|||||||
╭─[5:5]
|
╭─[5:5]
|
||||||
4 │
|
4 │
|
||||||
5 │ map(f, [0, 1])
|
5 │ map(f, [0, 1])
|
||||||
· ─
|
· ┬
|
||||||
|
· ╰── tests/argument_error/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL Semantic error
|
|||||||
╭─[2:8]
|
╭─[2:8]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ fail = pop(arr)
|
2 │ fail = pop(arr)
|
||||||
· ────────
|
· ────┬───
|
||||||
|
· ╰── tests/array_elem_pop_empty_fail/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
|
|||||||
╭─[3:8]
|
╭─[3:8]
|
||||||
2 │ pushedArr = pop(arr)
|
2 │ pushedArr = pop(arr)
|
||||||
3 │ fail = pushedArr[2]
|
3 │ fail = pushedArr[2]
|
||||||
· ────────────
|
· ──────┬─────
|
||||||
|
· ╰── tests/array_elem_pop_fail/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
|
|||||||
╭─[3:8]
|
╭─[3:8]
|
||||||
2 │ pushedArr = push(arr, 4)
|
2 │ pushedArr = push(arr, 4)
|
||||||
3 │ fail = arr[3]
|
3 │ fail = arr[3]
|
||||||
· ──────
|
· ───┬──
|
||||||
|
· ╰── tests/array_elem_push_fail/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ x = arr[0]
|
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)
|
× semantic: Expected a number, but found a boolean (true/false value)
|
||||||
╭────
|
╭────
|
||||||
1 │ assert(3 == 3 == 3, "this should not compile")
|
1 │ assert(3 == 3 == 3, "this should not compile")
|
||||||
· ──────
|
· ───┬──
|
||||||
|
· ╰── tests/comparisons_multiple/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
|
|||||||
╭─[22:1]
|
╭─[22:1]
|
||||||
21 │ // Error, after creating meaningful output.
|
21 │ // Error, after creating meaningful output.
|
||||||
22 │ foo
|
22 │ foo
|
||||||
· ───
|
· ─┬─
|
||||||
|
· ╰── tests/cube_with_error/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -10,6 +10,7 @@ KCL ImportCycle error
|
|||||||
╭─[2:1]
|
╭─[2:1]
|
||||||
1 │ @settings(defaultLengthUnit = in)
|
1 │ @settings(defaultLengthUnit = in)
|
||||||
2 │ import two from "import_cycle2.kcl"
|
2 │ import two from "import_cycle2.kcl"
|
||||||
· ───────────────────────────────────
|
· ─────────────────┬─────────────────
|
||||||
|
· ╰── tests/import_cycle1/input.kcl
|
||||||
3 │
|
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
|
KCL Syntax error
|
||||||
|
|
||||||
× syntax: Unexpected token: }
|
× syntax: Unexpected token: }
|
||||||
╭────
|
╭─[3:1]
|
||||||
1 │ import hotdog from "parse-failure.kcl"
|
2 │ return
|
||||||
· ─
|
3 │ }
|
||||||
|
· ┬
|
||||||
|
· ╰── tests/import_file_parse_error/parse-failure.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL Semantic error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ arr = [1, 2, 3]
|
1 │ arr = [1, 2, 3]
|
||||||
2 │ x = arr[1.2]
|
2 │ x = arr[1.2]
|
||||||
· ────────
|
· ────┬───
|
||||||
|
· ╰── tests/invalid_index_fractional/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL Semantic error
|
|||||||
╭─[3:5]
|
╭─[3:5]
|
||||||
2 │ i = -1
|
2 │ i = -1
|
||||||
3 │ x = arr[i]
|
3 │ x = arr[i]
|
||||||
· ──────
|
· ───┬──
|
||||||
|
· ╰── tests/invalid_index_negative/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -9,5 +9,6 @@ KCL Semantic error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ arr = [1, 2, 3]
|
1 │ arr = [1, 2, 3]
|
||||||
2 │ x = arr["s"]
|
2 │ x = arr["s"]
|
||||||
· ────────
|
· ────┬───
|
||||||
|
· ╰── tests/invalid_index_str/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -9,5 +9,6 @@ KCL Semantic error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ num = 999
|
1 │ num = 999
|
||||||
2 │ x = num[3]
|
2 │ x = num[3]
|
||||||
· ──────
|
· ───┬──
|
||||||
|
· ╰── tests/invalid_member_object/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -9,5 +9,6 @@ KCL Semantic error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ b = true
|
1 │ b = true
|
||||||
2 │ x = b["property"]
|
2 │ x = b["property"]
|
||||||
· ─────────────
|
· ──────┬──────
|
||||||
|
· ╰── tests/invalid_member_object_prop/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -9,8 +9,21 @@ KCL Semantic error
|
|||||||
╭─[1:7]
|
╭─[1:7]
|
||||||
1 │ ╭─▶ fn add(x, y) {
|
1 │ ╭─▶ fn add(x, y) {
|
||||||
2 │ │ return x + y
|
2 │ │ return x + y
|
||||||
3 │ ╰─▶ }
|
3 │ ├─▶ }
|
||||||
|
· ╰──── tests/kw_fn_too_few_args/input.kcl
|
||||||
4 │
|
4 │
|
||||||
5 │ three = add(x = 1)
|
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:7]
|
||||||
1 │ ╭─▶ fn add(@x) {
|
1 │ ╭─▶ fn add(@x) {
|
||||||
2 │ │ return x + 1
|
2 │ │ return x + 1
|
||||||
3 │ ╰─▶ }
|
3 │ ├─▶ }
|
||||||
|
· ╰──── tests/kw_fn_unlabeled_but_has_label/input.kcl
|
||||||
4 │
|
4 │
|
||||||
5 │ two = add(x = 1)
|
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]
|
╭─[2:7]
|
||||||
1 │ obj = { key = 123 }
|
1 │ obj = { key = 123 }
|
||||||
2 │ num = obj[3]
|
2 │ num = obj[3]
|
||||||
· ──────
|
· ───┬──
|
||||||
|
· ╰── tests/non_string_key_of_object/input.kcl
|
||||||
╰────
|
╰────
|
||||||
|
|||||||
@ -8,5 +8,6 @@ KCL UndefinedValue error
|
|||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ obj = { }
|
1 │ obj = { }
|
||||||
2 │ k = obj["age"]
|
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]
|
╭─[6:10]
|
||||||
5 │
|
5 │
|
||||||
6 │ answer = %
|
6 │ answer = %
|
||||||
· ─
|
· ┬
|
||||||
|
· ╰── tests/pipe_substitution_inside_function_called_from_pipeline/input.kcl
|
||||||
7 │ |> f(%)
|
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 |