Misc tidying up around KclValue, etc. (#5653)

* Make some things a little less public

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Rename KclValue::Array to MixedArray

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add settings to KclValue::Function

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove a bunch of dead code, simplifying mock exec and meaning KclValue no longer needs to deserialize

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-03-08 04:04:57 +13:00
committed by GitHub
parent 5d25f4a0e5
commit 6cce3cda36
39 changed files with 163 additions and 470 deletions

View File

@ -234,7 +234,7 @@ impl From<&KclValue> for OpKclValue {
ty: ty.clone(),
},
KclValue::String { value, .. } => Self::String { value: value.clone() },
KclValue::Array { value, .. } => {
KclValue::MixedArray { value, .. } => {
let value = value.iter().map(Self::from).collect();
Self::Array { value }
}

View File

@ -588,6 +588,7 @@ impl ExecutorContext {
KclValue::Function {
value: FunctionSource::User {
ast: function_expression.clone(),
settings: exec_state.mod_local.settings.clone(),
memory: exec_state.mut_stack().snapshot(),
},
meta: vec![metadata.to_owned()],
@ -754,7 +755,7 @@ impl Node<MemberExpression> {
}
};
let KclValue::Array { value: array, meta: _ } = array else {
let KclValue::MixedArray { value: array, meta: _ } = array else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("MemberExpression array is not an array: {:?}", array),
source_ranges: vec![self.clone().into()],
@ -804,7 +805,7 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()],
}))
}
(KclValue::Array { value: arr, meta: _ }, Property::UInt(index)) => {
(KclValue::MixedArray { value: arr, meta: _ }, Property::UInt(index)) => {
let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr {
Ok(value.to_owned())
@ -815,7 +816,7 @@ impl Node<MemberExpression> {
}))
}
}
(KclValue::Array { .. }, p) => {
(KclValue::MixedArray { .. }, p) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
@ -1489,7 +1490,7 @@ impl Node<ArrayExpression> {
results.push(value);
}
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: results,
meta: vec![self.into()],
})
@ -1538,7 +1539,7 @@ impl Node<ArrayRangeExpression> {
let meta = vec![Metadata {
source_range: self.into(),
}];
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: range
.into_iter()
.map(|num| KclValue::Number {
@ -1952,7 +1953,7 @@ impl FunctionSource {
func(exec_state, args).await.map(Some)
}
FunctionSource::User { ast, memory } => {
FunctionSource::User { ast, memory, .. } => {
call_user_defined_function(args, *memory, ast, exec_state, ctx).await
}
FunctionSource::None => unreachable!(),

View File

@ -28,7 +28,7 @@ use crate::{
pub type KclObjectFields = HashMap<String, KclValue>;
/// Any KCL value.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum KclValue {
@ -53,7 +53,7 @@ pub enum KclValue {
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Array {
MixedArray {
value: Vec<KclValue>,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
@ -129,6 +129,7 @@ pub enum FunctionSource {
},
User {
ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
settings: MetaSettings,
memory: EnvironmentRef,
},
}
@ -194,7 +195,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::Array { meta, .. } => to_vec_sr(&meta),
KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
KclValue::Object { meta, .. } => to_vec_sr(&meta),
KclValue::Module { meta, .. } => to_vec_sr(&meta),
KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
@ -227,7 +228,7 @@ impl From<&KclValue> for Vec<SourceRange> {
KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::String { meta, .. } => to_vec_sr(meta),
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::Array { meta, .. } => to_vec_sr(meta),
KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
KclValue::Object { meta, .. } => to_vec_sr(meta),
KclValue::Module { meta, .. } => to_vec_sr(meta),
KclValue::KclNone { meta, .. } => to_vec_sr(meta),
@ -251,7 +252,7 @@ impl KclValue {
KclValue::Bool { value: _, meta } => meta.clone(),
KclValue::Number { meta, .. } => meta.clone(),
KclValue::String { value: _, meta } => meta.clone(),
KclValue::Array { value: _, meta } => meta.clone(),
KclValue::MixedArray { value: _, meta } => meta.clone(),
KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(),
KclValue::TagDeclarator(x) => vec![x.metadata()],
@ -288,7 +289,7 @@ impl KclValue {
match self {
KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
KclValue::Array { value, .. } => {
KclValue::MixedArray { value, .. } => {
let solids: Vec<_> = value
.iter()
.enumerate()
@ -334,7 +335,7 @@ impl KclValue {
KclValue::Bool { .. } => "boolean (true/false value)",
KclValue::Number { .. } => "number",
KclValue::String { .. } => "string (text)",
KclValue::Array { .. } => "array (list)",
KclValue::MixedArray { .. } => "array (list)",
KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module",
KclValue::Type { .. } => "type",
@ -395,7 +396,7 @@ impl KclValue {
/// Put the point into a KCL value.
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
Self::Array {
Self::MixedArray {
value: vec![
Self::Number {
value: p[0],
@ -451,7 +452,7 @@ impl KclValue {
}
pub fn as_array(&self) -> Option<&[KclValue]> {
if let KclValue::Array { value, meta: _ } = &self {
if let KclValue::MixedArray { value, meta: _ } = &self {
Some(value)
} else {
None
@ -610,7 +611,7 @@ impl KclValue {
KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)),
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)),
KclValue::Array { value, .. } => Some(RuntimeType::Tuple(
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
value
.iter()
.map(|v| v.principal_type().and_then(RuntimeType::primitive))
@ -665,7 +666,7 @@ impl KclValue {
result
}
KclValue::Function {
value: FunctionSource::User { ast, memory },
value: FunctionSource::User { ast, memory, .. },
..
} => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
_ => Err(KclError::Semantic(KclErrorDetails {
@ -701,7 +702,7 @@ impl KclValue {
todo!("Implement KCL stdlib fns with keyword args");
}
KclValue::Function {
value: FunctionSource::User { ast, memory },
value: FunctionSource::User { ast, memory, .. },
..
} => {
crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
@ -723,7 +724,7 @@ impl KclValue {
KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
// TODO better Array and Object stringification
KclValue::Array { .. } => Some("[...]".to_owned()),
KclValue::MixedArray { .. } => Some("[...]".to_owned()),
KclValue::Object { .. } => Some("{ ... }".to_owned()),
KclValue::Module { .. }
| KclValue::Solid { .. }

View File

@ -687,14 +687,6 @@ impl Stack {
.insert_or_update(key, value, self.id);
}
/// Delete an item from memory.
///
/// Item will be preserved in any snapshots.
pub fn clear(&mut self, key: String) {
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
self.memory.get_env(self.current_env.index()).clear(key, self.id);
}
/// Get a value from the program memory.
/// Return Err if not found.
pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
@ -1167,19 +1159,6 @@ mod env {
self.get_mut_bindings(owner).insert(key, value);
}
/// Delete a key/value.
///
/// We want to preserve the snapshot, so we can't just remove the element. We copy the deleted
/// value to the snapshot and replace the current value with a tombstone.
pub(super) fn clear(&self, key: String, owner: usize) {
if self.get_bindings().contains_key(&key) {
let old = self.get_mut_bindings(owner).insert(key.clone(), tombstone()).unwrap();
if let Some(s) = self.cur_snapshot(owner) {
s.data.insert(key, old);
}
}
}
/// Was the key contained in this environment at the specified point in time.
fn snapshot_contains_key(&self, key: &str, snapshot: SnapshotRef) -> bool {
for i in snapshot.index()..self.snapshots_len() {
@ -1573,61 +1552,6 @@ mod test {
);
}
#[test]
fn snap_env_clear() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
let sn = mem.snapshot();
mem.push_new_env_for_call(sn);
mem.snapshot();
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.snapshot();
mem.clear("b".to_owned());
mem.clear("a".to_owned());
assert_get(mem, "b", 3);
assert_get(mem, "a", 1);
mem.pop_env();
assert_get(mem, "b", 3);
assert_get(mem, "a", 1);
}
#[test]
fn snap_env_clear2() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
let sn1 = mem.snapshot();
mem.clear("b".to_owned());
mem.clear("a".to_owned());
mem.get("b", SourceRange::default()).unwrap_err();
mem.get("a", SourceRange::default()).unwrap_err();
let sn = mem.snapshot();
mem.push_new_env_for_call(sn);
mem.add("b".to_owned(), val(4), sr()).unwrap();
let sn2 = mem.snapshot();
mem.clear("b".to_owned());
mem.clear("a".to_owned());
mem.get("b", SourceRange::default()).unwrap_err();
mem.get("a", SourceRange::default()).unwrap_err();
mem.pop_env();
mem.get("b", SourceRange::default()).unwrap_err();
mem.get("a", SourceRange::default()).unwrap_err();
assert_get_from(mem, "a", 1, sn1);
assert_get_from(mem, "b", 3, sn1);
mem.memory
.get_from_unchecked("a", sn2, SourceRange::default())
.unwrap_err();
assert_get_from(mem, "b", 4, sn2);
}
#[test]
fn squash_env() {
let mem = &mut Stack::new_for_tests();
@ -1641,6 +1565,7 @@ mod test {
KclValue::Function {
value: FunctionSource::User {
ast: crate::parsing::ast::types::FunctionExpression::dummy(),
settings: crate::MetaSettings::default(),
memory: sn2,
},
meta: Vec::new(),

View File

@ -55,7 +55,7 @@ mod memory;
mod state;
/// Outcome of executing a program. This is used in TS.
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
#[derive(Debug, Clone, Serialize, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExecOutcome {
@ -517,7 +517,6 @@ impl ExecutorContext {
&self,
program: crate::Program,
use_prev_memory: bool,
variables: IndexMap<String, KclValue>,
) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(self.is_mock());
@ -531,22 +530,9 @@ impl ExecutorContext {
self.prepare_mem(&mut exec_state).await?
};
let mut to_restore = Vec::new();
{
let mem = exec_state.mut_stack();
// Push a scope so that old variables can be overwritten (since we might be re-executing some
// part of the scene).
mem.push_new_env_for_scope();
// Add any extra variables to memory (we want to remove these variables after execution, but
// can't do this using scopes because we want to keep the results of computation in other cases).
for (k, v) in variables {
to_restore.push((k.clone(), mem.get(&k, SourceRange::default()).ok().cloned()));
mem.add(k, v, SourceRange::synthetic())
.map_err(KclErrorWithOutputs::no_outputs)?;
}
}
// Push a scope so that old variables can be overwritten (since we might be re-executing some
// part of the scene).
exec_state.mut_stack().push_new_env_for_scope();
let result = self.inner_run(&program, &mut exec_state, true).await?;
@ -558,12 +544,6 @@ impl ExecutorContext {
let outcome = exec_state.to_mock_wasm_outcome(result.0);
mem.squash_env(result.0);
for (k, v) in to_restore {
match v {
Some(v) => mem.insert_or_update(k, v),
None => mem.clear(k),
}
}
cache::write_old_memory(mem).await;
Ok(outcome)
@ -1885,33 +1865,6 @@ let w = f() + f()
assert_eq!(settings_state, ctx.settings);
}
#[tokio::test(flavor = "multi_thread")]
async fn mock_variables() {
let ctx = ExecutorContext::new_mock().await;
let program = crate::Program::parse_no_errs("x = y").unwrap();
let mut vars = IndexMap::new();
vars.insert(
"y".to_owned(),
KclValue::Number {
value: 2.0,
ty: kcl_value::NumericType::Unknown,
meta: Vec::new(),
},
);
let result = ctx.run_mock(program, true, vars).await.unwrap();
assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0);
cache::read_old_memory()
.await
.unwrap()
.get("y", SourceRange::default())
.unwrap_err();
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
let result = ctx.run_mock(program2, true, IndexMap::new()).await.unwrap();
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
}
#[tokio::test(flavor = "multi_thread")]
async fn mock_after_not_mock() {
let ctx = ExecutorContext::new_with_default_client(UnitLength::Mm).await.unwrap();
@ -1921,7 +1874,7 @@ let w = f() + f()
let ctx2 = ExecutorContext::new_mock().await;
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
let result = ctx2.run_mock(program2, true, IndexMap::new()).await.unwrap();
let result = ctx2.run_mock(program2, true).await.unwrap();
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
}
}

View File

@ -76,7 +76,7 @@ pub mod std;
pub mod test_server;
mod thread;
mod unparser;
pub mod walk;
mod walk;
#[cfg(target_arch = "wasm32")]
mod wasm;

View File

@ -341,7 +341,7 @@ impl Args {
meta: vec![meta],
ty: NumericType::Unknown,
};
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: vec![x, y],
meta: vec![meta],
})
@ -377,7 +377,7 @@ impl Args {
ty: ty.clone(),
})
.collect::<Vec<_>>();
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: array,
meta: vec![Metadata {
source_range: self.source_range,
@ -631,7 +631,7 @@ impl<'a> FromArgs<'a> for Vec<KclValue> {
source_ranges: vec![args.source_range],
}));
};
let KclValue::Array { value: array, meta: _ } = &arg.value else {
let KclValue::MixedArray { value: array, meta: _ } = &arg.value else {
let message = format!("Expected an array but found {}", arg.value.human_friendly_type());
return Err(KclError::Type(KclErrorDetails {
source_ranges: arg.source_ranges(),
@ -733,7 +733,7 @@ where
impl<'a> FromKclValue<'a> for [f64; 2] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Array { value, meta: _ } = arg else {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
};
if value.len() != 2 {
@ -748,7 +748,7 @@ impl<'a> FromKclValue<'a> for [f64; 2] {
impl<'a> FromKclValue<'a> for [usize; 3] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Array { value, meta: _ } = arg else {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
@ -764,7 +764,7 @@ impl<'a> FromKclValue<'a> for [usize; 3] {
impl<'a> FromKclValue<'a> for [f64; 3] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Array { value, meta: _ } = arg else {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
@ -1249,7 +1249,7 @@ impl_from_kcl_for_vec!(Sketch);
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::Array { value, meta: _ } = arg else {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
@ -1517,7 +1517,7 @@ impl<'a> FromKclValue<'a> for SketchSet {
match arg {
KclValue::Sketch { value: sketch } => Some(SketchSet::from(sketch.to_owned())),
KclValue::Sketches { value } => Some(SketchSet::from(value.to_owned())),
KclValue::Array { .. } => {
KclValue::MixedArray { .. } => {
let v: Option<Vec<Sketch>> = FromKclValue::from_kcl_val(arg);
Some(SketchSet::Sketches(v?.iter().cloned().map(Box::new).collect()))
}

View File

@ -19,7 +19,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
let (array, f): (Vec<KclValue>, &FunctionSource) = FromArgs::from_args(&args, 0)?;
let meta = vec![args.source_range.into()];
let new_array = inner_map(array, f, exec_state, &args).await?;
Ok(KclValue::Array { value: new_array, meta })
Ok(KclValue::MixedArray { value: new_array, meta })
}
/// Apply a function to every element of a list.
@ -230,7 +230,7 @@ async fn call_reduce_closure(
async fn inner_push(mut array: Vec<KclValue>, elem: KclValue, args: &Args) -> Result<KclValue, KclError> {
// Unwrap the KclValues to JValues for manipulation
array.push(elem);
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: array,
meta: vec![args.source_range.into()],
})
@ -241,7 +241,7 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let (val, elem): (KclValue, KclValue) = FromArgs::from_args(&args, 0)?;
let meta = vec![args.source_range];
let KclValue::Array { value: array, meta: _ } = val else {
let KclValue::MixedArray { value: array, meta: _ } = val else {
let actual_type = val.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
@ -281,7 +281,7 @@ async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclErr
// Create a new array with all elements except the last one
let new_array = array[..array.len() - 1].to_vec();
Ok(KclValue::Array {
Ok(KclValue::MixedArray {
value: new_array,
meta: vec![args.source_range.into()],
})
@ -292,7 +292,7 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let val = args.get_unlabeled_kw_arg("array")?;
let meta = vec![args.source_range];
let KclValue::Array { value: array, meta: _ } = val else {
let KclValue::MixedArray { value: array, meta: _ } = val else {
let actual_type = val.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,

View File

@ -441,7 +441,7 @@ async fn make_transform<T: GeometryTrait>(
})?;
let transforms = match transform_fn_return {
KclValue::Object { value, meta: _ } => vec![value],
KclValue::Array { value, meta: _ } => {
KclValue::MixedArray { value, meta: _ } => {
let transforms: Vec<_> = value
.into_iter()
.map(|val| {
@ -540,7 +540,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
}
fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
let KclValue::Array { value: arr, meta } = val else {
let KclValue::MixedArray { value: arr, meta } = val else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(),
source_ranges,
@ -572,7 +572,7 @@ fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<P
}
fn array_to_point2d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point2d, KclError> {
let KclValue::Array { value: arr, meta } = val else {
let KclValue::MixedArray { value: arr, meta } = val else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an array of 2 numbers (i.e. a 2D point)".to_string(),
source_ranges,
@ -662,7 +662,7 @@ mod tests {
#[test]
fn test_array_to_point3d() {
let input = KclValue::Array {
let input = KclValue::MixedArray {
value: vec![
KclValue::Number {
value: 1.1,

View File

@ -1,9 +1,10 @@
#![allow(dead_code)]
mod ast_node;
mod ast_visitor;
mod ast_walk;
mod import_graph;
pub use ast_node::Node;
pub use ast_visitor::{Visitable, Visitor};
pub use ast_visitor::Visitable;
pub use ast_walk::walk;
pub use import_graph::import_graph;