merge main

This commit is contained in:
benjamaan476
2025-07-03 09:59:53 +01:00
141 changed files with 7267 additions and 1030 deletions

View File

@ -226,10 +226,7 @@ impl From<&KclValue> for OpKclValue {
match value {
KclValue::Uuid { value, .. } => Self::Uuid { value: *value },
KclValue::Bool { value, .. } => Self::Bool { value: *value },
KclValue::Number { value, ty, .. } => Self::Number {
value: *value,
ty: ty.clone(),
},
KclValue::Number { value, ty, .. } => Self::Number { value: *value, ty: *ty },
KclValue::String { value, .. } => Self::String { value: value.clone() },
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
let value = value.iter().map(Self::from).collect();

View File

@ -864,6 +864,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
}
@ -1046,6 +1049,16 @@ impl Node<MemberExpression> {
(KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(value.sketch),
}),
(geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
// This is a common mistake.
Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
geometry.human_friendly_type()
),
vec![self.clone().into()],
)))
}
(KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
meta: vec![Metadata {
source_range: SourceRange::from(self.clone()),
@ -1056,6 +1069,12 @@ impl Node<MemberExpression> {
.map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
.collect(),
}),
(geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
vec![self.clone().into()],
)))
}
(being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Only arrays can be indexed, but you're trying to index {}",
@ -1297,7 +1316,7 @@ impl Node<UnaryExpression> {
Ok(KclValue::Number {
value: -value,
meta,
ty: ty.clone(),
ty: *ty,
})
}
KclValue::Plane { value } => {
@ -1329,7 +1348,7 @@ impl Node<UnaryExpression> {
.map(|v| match v {
KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
value: *value * -1.0,
ty: ty.clone(),
ty: *ty,
meta: meta.clone(),
}),
_ => Err(err()),
@ -1350,7 +1369,7 @@ impl Node<UnaryExpression> {
.map(|v| match v {
KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
value: *value * -1.0,
ty: ty.clone(),
ty: *ty,
meta: meta.clone(),
}),
_ => Err(err()),
@ -1544,7 +1563,7 @@ impl Node<ArrayRangeExpression> {
.into_iter()
.map(|num| KclValue::Number {
value: num as f64,
ty: start_ty.clone(),
ty: start_ty,
meta: meta.clone(),
})
.collect(),

View File

@ -401,7 +401,7 @@ impl FunctionDefinition<'_> {
impl FunctionBody<'_> {
fn prep_mem(&self, exec_state: &mut ExecState) {
match self {
FunctionBody::Rust(_) => exec_state.mut_stack().push_new_env_for_rust_call(),
FunctionBody::Rust(_) => exec_state.mut_stack().push_new_root_env(true),
FunctionBody::Kcl(_, memory) => exec_state.mut_stack().push_new_env_for_call(*memory),
}
}

View File

@ -958,6 +958,7 @@ impl From<Point3d> for Point3D {
Self { x: p.x, y: p.y, z: p.z }
}
}
impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
fn from(p: Point3d) -> Self {
Self {
@ -1023,12 +1024,12 @@ pub struct BasePath {
impl BasePath {
pub fn get_to(&self) -> [TyF64; 2] {
let ty: NumericType = self.units.into();
[TyF64::new(self.to[0], ty.clone()), TyF64::new(self.to[1], ty)]
[TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
}
pub fn get_from(&self) -> [TyF64; 2] {
let ty: NumericType = self.units.into();
[TyF64::new(self.from[0], ty.clone()), TyF64::new(self.from[1], ty)]
[TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
}
}
@ -1049,7 +1050,7 @@ pub struct GeoMeta {
#[ts(export)]
#[serde(tag = "type")]
pub enum Path {
/// A path that goes to a point.
/// A straight line which ends at the given point.
ToPoint {
#[serde(flatten)]
base: BasePath,
@ -1269,14 +1270,14 @@ impl Path {
pub fn get_from(&self) -> [TyF64; 2] {
let p = &self.get_base().from;
let ty: NumericType = self.get_base().units.into();
[TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
[TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
}
/// Where does this path segment end?
pub fn get_to(&self) -> [TyF64; 2] {
let p = &self.get_base().to;
let ty: NumericType = self.get_base().units.into();
[TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
[TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
}
/// The path segment start point and its type.

View File

@ -415,15 +415,41 @@ impl KclValue {
/// Put the point into a KCL value.
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
let [x, y] = p;
Self::Tuple {
value: vec![
Self::Number {
value: p[0],
value: x,
meta: meta.clone(),
ty: ty.clone(),
ty,
},
Self::Number {
value: p[1],
value: y,
meta: meta.clone(),
ty,
},
],
meta,
}
}
/// Put the point into a KCL value.
pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
let [x, y, z] = p;
Self::Tuple {
value: vec![
Self::Number {
value: x,
meta: meta.clone(),
ty,
},
Self::Number {
value: y,
meta: meta.clone(),
ty,
},
Self::Number {
value: z,
meta: meta.clone(),
ty,
},
@ -448,7 +474,7 @@ impl KclValue {
pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
match self {
KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, ty.clone())),
KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
_ => None,
}
}
@ -562,7 +588,7 @@ impl KclValue {
pub fn as_ty_f64(&self) -> Option<TyF64> {
match self {
KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
_ => None,
}
}

View File

@ -541,22 +541,6 @@ impl Stack {
self.push_new_env_for_call(snapshot);
}
/// Push a new stack frame on to the call stack for callees which should not read or write
/// from memory.
///
/// This is suitable for calling standard library functions or other functions written in Rust
/// which will use 'Rust memory' rather than KCL's memory and cannot reach into the wider
/// environment.
///
/// Trying to read or write from this environment will panic with an index out of bounds.
pub fn push_new_env_for_rust_call(&mut self) {
self.call_stack.push(self.current_env);
// Rust functions shouldn't try to set or access anything in their environment, so don't
// waste time and space on a new env. Using usize::MAX means we'll get an overflow if we
// try to access anything rather than a silent error.
self.current_env = EnvironmentRef(usize::MAX, 0);
}
/// Push a new stack frame on to the call stack with no connection to a parent environment.
///
/// Suitable for executing a separate module.
@ -683,7 +667,7 @@ impl Stack {
env.contains_key(var)
}
/// Get a key from the first KCL (i.e., non-Rust) stack frame on the call stack.
/// Get a key from the first stack frame on the call stack.
pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
if !self.current_env.skip_env() {
return Ok((self.current_env.1, self.get(key, source_range)?));
@ -695,7 +679,7 @@ impl Stack {
}
}
unreachable!("It can't be Rust frames all the way down");
unreachable!("No frames on the stack?");
}
/// Iterate over all keys in the current environment which satisfy the provided predicate.
@ -1217,24 +1201,6 @@ mod test {
assert_get_from(mem, "c", 5, callee);
}
#[test]
fn rust_env() {
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_rust_call();
mem.push_new_env_for_call(sn);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(4), sr()).unwrap();
assert_get(mem, "b", 4);
mem.pop_env();
mem.pop_env();
assert_get(mem, "b", 3);
}
#[test]
fn deep_call_env() {
let mem = &mut Stack::new_for_tests();

View File

@ -1920,6 +1920,22 @@ shape = layer() |> patternTransform(instances = 10, transform = transform)
);
}
#[tokio::test(flavor = "multi_thread")]
async fn pass_std_to_std() {
let ast = r#"sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 2)
extrude001 = extrude(profile001, length = 5)
extrudes = patternLinear3d(
extrude001,
instances = 3,
distance = 5,
axis = [1, 1, 0],
)
clone001 = map(extrudes, f = clone)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() {
let ast = r#"sigmaAllow = 35000 // psi

View File

@ -460,7 +460,7 @@ impl fmt::Display for PrimitiveType {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum NumericType {
@ -575,7 +575,7 @@ impl NumericType {
match (&ty, &i.ty) {
(Any, Default { .. }) if i.n == 0.0 => {}
(Any, t) => {
ty = t.clone();
ty = *t;
}
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
@ -598,7 +598,7 @@ impl NumericType {
}
if ty == Any && !input.is_empty() {
ty = input[0].ty.clone();
ty = input[0].ty;
}
(result, ty)
@ -722,7 +722,7 @@ impl NumericType {
if ty.subtype(self) {
return Ok(KclValue::Number {
value: *value,
ty: ty.clone(),
ty: *ty,
meta: meta.clone(),
});
}
@ -736,7 +736,7 @@ impl NumericType {
(Any, _) => Ok(KclValue::Number {
value: *value,
ty: self.clone(),
ty: *self,
meta: meta.clone(),
}),
@ -744,7 +744,7 @@ impl NumericType {
// means accept any number rather than force the current default.
(_, Default { .. }) => Ok(KclValue::Number {
value: *value,
ty: ty.clone(),
ty: *ty,
meta: meta.clone(),
}),
@ -840,6 +840,18 @@ pub enum UnitType {
Angle(UnitAngle),
}
impl UnitType {
pub(crate) fn to_suffix(self) -> Option<String> {
match self {
UnitType::Count => Some("_".to_owned()),
UnitType::Length(UnitLen::Unknown) => None,
UnitType::Angle(UnitAngle::Unknown) => None,
UnitType::Length(l) => Some(l.to_string()),
UnitType::Angle(a) => Some(a.to_string()),
}
}
}
impl std::fmt::Display for UnitType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -1479,7 +1491,7 @@ impl KclValue {
pub fn principal_type(&self) -> Option<RuntimeType> {
match self {
KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
KclValue::Object { value, .. } => {
let properties = value