From cd59691663d22b2f6178bbeca6c9224aed4cac41 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Mon, 17 Mar 2025 09:06:55 +1300 Subject: [PATCH] type aliases Signed-off-by: Nick Cameron --- packages/codemirror-lang-kcl/src/kcl.grammar | 6 +- rust/kcl-lib/src/execution/exec_ast.rs | 75 +++++--- rust/kcl-lib/src/execution/geometry.rs | 2 +- rust/kcl-lib/src/execution/kcl_value.rs | 179 +++++++++++-------- rust/kcl-lib/src/execution/mod.rs | 16 ++ rust/kcl-lib/src/parsing/ast/digest.rs | 3 + rust/kcl-lib/src/parsing/ast/types/mod.rs | 1 + rust/kcl-lib/src/parsing/parser.rs | 33 +++- rust/kcl-lib/src/std/appearance.rs | 11 +- rust/kcl-lib/src/std/args.rs | 12 +- rust/kcl-lib/src/std/extrude.rs | 10 +- rust/kcl-lib/src/std/loft.rs | 12 +- rust/kcl-lib/src/std/patterns.rs | 40 +---- rust/kcl-lib/src/std/shell.rs | 11 +- rust/kcl-lib/src/std/sweep.rs | 11 +- rust/kcl-lib/src/std/transform.rs | 23 ++- rust/kcl-lib/src/unparser.rs | 7 +- 17 files changed, 254 insertions(+), 198 deletions(-) diff --git a/packages/codemirror-lang-kcl/src/kcl.grammar b/packages/codemirror-lang-kcl/src/kcl.grammar index 82667490f..a48100d59 100644 --- a/packages/codemirror-lang-kcl/src/kcl.grammar +++ b/packages/codemirror-lang-kcl/src/kcl.grammar @@ -20,7 +20,7 @@ statement[@isGroup=Statement] { ImportStatement { kw<"import"> ImportItems ImportFrom String } | FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } | VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } | - TypeDeclaration { kw<"export">? kw<"type"> identifier } | + TypeDeclaration { kw<"export">? kw<"type"> identifier ("=" type)? } | ReturnStatement { kw<"return"> expression } | ExpressionStatement { expression } | Annotation { AnnotationName AnnotationList? } @@ -79,7 +79,7 @@ type[@isGroup=Type] { identifier, "bool" | "number" | "string" | "tag" | "Sketch" | "SketchSurface" | "Solid" | "Plane" > | - ArrayType { type !member "[" "]" } | + ArrayType { "[" type !member (";" Number "+"?)? "]" } | ObjectType { "{" commaSep "}" } } @@ -137,7 +137,7 @@ commaSep1NoTrailingComma { term ("," term)* } "(" ")" "{" "}" "[" "]" - "," "?" ":" "." ".." + "," "?" ":" "." ".." ";" } @external propSource kclHighlight from "./highlight" diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index 55c224c0b..5ac16e04f 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -29,6 +29,8 @@ use crate::{ CompilationError, }; +use super::kcl_value::TypeDef; + enum StatementKind<'a> { Declaration { name: &'a str }, Expression, @@ -304,8 +306,9 @@ impl ExecutorContext { })); } }; + let (t, props) = crate::std::std_ty(std_path, &ty.name.name); let value = KclValue::Type { - value: Some(crate::std::std_ty(std_path, &ty.name.name)), + value: TypeDef::RustRepr(t, props), meta: vec![metadata], }; exec_state @@ -324,12 +327,40 @@ impl ExecutorContext { } // Do nothing for primitive types, they get special treatment and their declarations are just for documentation. annotations::Impl::Primitive => {} - annotations::Impl::Kcl => { - return Err(KclError::Semantic(KclErrorDetails { - message: "User-defined types are not yet supported.".to_owned(), - source_ranges: vec![metadata.source_range], - })); - } + annotations::Impl::Kcl => match &ty.alias { + Some(alias) => { + let value = KclValue::Type { + value: TypeDef::Alias( + RuntimeType::from_parsed( + alias.inner.clone(), + exec_state, + metadata.source_range, + ) + .map_err(|e| KclError::Semantic(e.into()))?, + ), + meta: vec![metadata], + }; + exec_state + .mut_stack() + .add( + format!("{}{}", memory::TYPE_PREFIX, ty.name.name), + value, + metadata.source_range, + ) + .map_err(|_| { + KclError::Semantic(KclErrorDetails { + message: format!("Redefinition of type {}.", ty.name.name), + source_ranges: vec![metadata.source_range], + }) + })?; + } + None => { + return Err(KclError::Semantic(KclErrorDetails { + message: "User-defined types are not yet supported.".to_owned(), + source_ranges: vec![metadata.source_range], + })) + } + }, } last_expr = None; @@ -646,30 +677,28 @@ impl ExecutorContext { let result = self .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind) .await?; - coerce(&result, &expr.ty, exec_state).ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!( - "could not coerce {} value to type {}", - result.human_friendly_type(), - expr.ty - ), - source_ranges: vec![expr.into()], - }) - })? + coerce(&result, &expr.ty, exec_state, expr.into())? } }; Ok(item) } } -fn coerce(value: &KclValue, ty: &Node, exec_state: &mut ExecState) -> Option { +fn coerce( + value: &KclValue, + ty: &Node, + exec_state: &mut ExecState, + source_range: SourceRange, +) -> Result { let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into()) - .map_err(|e| { - exec_state.err(e); - }) - .ok()??; + .map_err(|e| KclError::Semantic(e.into()))?; - value.coerce(&ty, exec_state) + value.coerce(&ty, exec_state).ok_or_else(|| { + KclError::Semantic(KclErrorDetails { + message: format!("could not coerce {} value to type {}", value.human_friendly_type(), ty), + source_ranges: vec![source_range], + }) + }) } impl BinaryPart { diff --git a/rust/kcl-lib/src/execution/geometry.rs b/rust/kcl-lib/src/execution/geometry.rs index 79f2a9fe5..d7d7c679d 100644 --- a/rust/kcl-lib/src/execution/geometry.rs +++ b/rust/kcl-lib/src/execution/geometry.rs @@ -104,7 +104,7 @@ impl From for crate::execution::KclValue { .into_iter() .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) }) .collect(), - ty: crate::execution::PrimitiveType::Solid, + ty: crate::execution::kcl_value::RuntimeType::solid(), } } } diff --git a/rust/kcl-lib/src/execution/kcl_value.rs b/rust/kcl-lib/src/execution/kcl_value.rs index 5b86583e1..d363e3aae 100644 --- a/rust/kcl-lib/src/execution/kcl_value.rs +++ b/rust/kcl-lib/src/execution/kcl_value.rs @@ -65,7 +65,7 @@ pub enum KclValue { value: Vec, // The type of values, not the array type. #[serde(skip)] - ty: PrimitiveType, + ty: RuntimeType, }, Object { value: KclObjectFields, @@ -105,7 +105,7 @@ pub enum KclValue { #[ts(skip)] Type { #[serde(skip)] - value: Option<(PrimitiveType, StdFnProps)>, + value: TypeDef, #[serde(skip)] meta: Vec, }, @@ -142,6 +142,12 @@ impl JsonSchema for FunctionSource { } } +#[derive(Debug, Clone, PartialEq)] +pub enum TypeDef { + RustRepr(PrimitiveType, StdFnProps), + Alias(RuntimeType), +} + impl From> for KclValue { fn from(mut eg: Vec) -> Self { if eg.len() == 1 { @@ -154,7 +160,7 @@ impl From> for KclValue { .into_iter() .map(|s| KclValue::Sketch { value: Box::new(s) }) .collect(), - ty: crate::execution::PrimitiveType::Sketch, + ty: RuntimeType::Primitive(PrimitiveType::Sketch), } } } @@ -169,7 +175,7 @@ impl From> for KclValue { } else { KclValue::HomArray { value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(), - ty: crate::execution::PrimitiveType::Solid, + ty: RuntimeType::Primitive(PrimitiveType::Solid), } } } @@ -635,10 +641,15 @@ impl KclValue { KclValue::ImportedGeometry { .. } => Some(value.clone()), _ => None, }, + PrimitiveType::Tag => match value { + KclValue::TagDeclarator { .. } => Some(value.clone()), + KclValue::TagIdentifier { .. } => Some(value.clone()), + _ => None, + }, } } - fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option { + fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option { match self { KclValue::HomArray { value, ty: aty } => { // TODO could check types of values individually @@ -685,10 +696,9 @@ impl KclValue { } }; - let rt = RuntimeType::Primitive(ty.clone()); let value = value .iter() - .map(|v| v.coerce(&rt, exec_state)) + .map(|v| v.coerce(ty, exec_state)) .collect::>>()?; Some(KclValue::HomArray { value, ty: ty.clone() }) @@ -698,7 +708,7 @@ impl KclValue { ty: ty.clone(), }), value if len.satisfied(1) => { - if value.has_type(&RuntimeType::Primitive(ty.clone())) { + if value.has_type(ty) { Some(KclValue::HomArray { value: vec![value.clone()], ty: ty.clone(), @@ -711,7 +721,7 @@ impl KclValue { } } - fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option { + fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option { match self { KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { if value.len() < tys.len() { @@ -719,7 +729,7 @@ impl KclValue { } let mut result = Vec::new(); for (i, t) in tys.iter().enumerate() { - result.push(value[i].coerce_to_primitive_type(t, exec_state)?); + result.push(value[i].coerce(t, exec_state)?); } Some(KclValue::MixedArray { @@ -732,7 +742,7 @@ impl KclValue { meta: meta.clone(), }), value if tys.len() == 1 => { - if value.has_type(&RuntimeType::Primitive(tys[0].clone())) { + if value.has_type(&tys[0]) { Some(KclValue::MixedArray { value: vec![value.clone()], meta: Vec::new(), @@ -788,18 +798,15 @@ impl KclValue { KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)), KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple( - value - .iter() - .map(|v| v.principal_type().and_then(RuntimeType::primitive)) - .collect::>>()?, + value.iter().map(|v| v.principal_type()).collect::>>()?, )), - KclValue::HomArray { ty, value, .. } => Some(RuntimeType::Array(ty.clone(), ArrayLen::Known(value.len()))), - KclValue::Face { .. } => None, - KclValue::Helix { .. } - | KclValue::Function { .. } + KclValue::HomArray { ty, value, .. } => { + Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len()))) + } + KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::Tag)), + KclValue::Face { .. } | KclValue::Helix { .. } => None, + KclValue::Function { .. } | KclValue::Module { .. } - | KclValue::TagIdentifier(_) - | KclValue::TagDeclarator(_) | KclValue::KclNone { .. } | KclValue::Type { .. } | KclValue::Uuid { .. } => None, @@ -923,42 +930,89 @@ impl KclValue { #[derive(Debug, Clone, PartialEq)] pub enum RuntimeType { Primitive(PrimitiveType), - Array(PrimitiveType, ArrayLen), + Array(Box, ArrayLen), Union(Vec), - Tuple(Vec), + Tuple(Vec), Object(Vec<(String, RuntimeType)>), } impl RuntimeType { + /// `[Sketch; 1+]` + pub fn sketches() -> Self { + RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)), + ArrayLen::NonEmpty, + ) + } + + /// `[Solid; 1+]` + pub fn solids() -> Self { + RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Solid)), + ArrayLen::NonEmpty, + ) + } + + pub fn solid() -> Self { + RuntimeType::Primitive(PrimitiveType::Solid) + } + + pub fn imported() -> Self { + RuntimeType::Primitive(PrimitiveType::ImportedGeometry) + } + pub fn from_parsed( value: Type, exec_state: &mut ExecState, source_range: SourceRange, - ) -> Result, CompilationError> { - Ok(match value { - Type::Primitive(pt) => { - PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive) - } + ) -> Result { + match value { + Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range), Type::Array { ty, len } => { - PrimitiveType::from_parsed(ty, exec_state, source_range)?.map(|t| RuntimeType::Array(t, len)) + Self::from_parsed_primitive(ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len)) } Type::Union { tys } => tys .into_iter() - .map(|t| PrimitiveType::from_parsed(t.inner, exec_state, source_range)) - .collect::>, CompilationError>>()? + .map(|t| Self::from_parsed_primitive(t.inner, exec_state, source_range)) + .collect::, CompilationError>>() .map(RuntimeType::Union), Type::Object { properties } => properties .into_iter() .map(|p| { - let pt = match p.type_ { - Some(t) => t, - None => return Ok(None), - }; - Ok(RuntimeType::from_parsed(pt.inner, exec_state, source_range)? - .map(|ty| (p.identifier.inner.name, ty))) + RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range) + .map(|ty| (p.identifier.inner.name, ty)) }) - .collect::>, CompilationError>>()? + .collect::, CompilationError>>() .map(RuntimeType::Object), + } + } + + fn from_parsed_primitive( + value: AstPrimitiveType, + exec_state: &mut ExecState, + source_range: SourceRange, + ) -> Result { + Ok(match value { + AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String), + AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean), + AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number( + NumericType::from_parsed(suffix, &exec_state.mod_local.settings), + )), + AstPrimitiveType::Named(name) => { + let ty_val = exec_state + .stack() + .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range) + .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?; + + match ty_val { + KclValue::Type { value, .. } => match value { + TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()), + TypeDef::Alias(ty) => ty.clone(), + }, + _ => unreachable!(), + } + } + AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag), }) } @@ -975,7 +1029,7 @@ impl RuntimeType { .join(" or "), RuntimeType::Tuple(tys) => format!( "an array with values of types ({})", - tys.iter().map(PrimitiveType::to_string).collect::>().join(", ") + tys.iter().map(Self::human_friendly_type).collect::>().join(", ") ), RuntimeType::Object(_) => format!("an object with fields {}", self), } @@ -990,7 +1044,7 @@ impl RuntimeType { // TODO arrays could be covariant (Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2), (Tuple(t1), Tuple(t2)) => t1 == t2, - (Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == t2), + (Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == &**t2), (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)), (t1, Union(ts2)) => ts2.contains(t1), // TODO record subtyping - subtype can be larger, fields can be covariant. @@ -999,10 +1053,17 @@ impl RuntimeType { } } - fn primitive(self) -> Option { + fn display_multiple(&self) -> String { match self { - RuntimeType::Primitive(t) => Some(t), - _ => None, + RuntimeType::Primitive(ty) => ty.display_multiple(), + RuntimeType::Array(..) => "arrays".to_owned(), + RuntimeType::Union(tys) => tys + .iter() + .map(|t| t.display_multiple()) + .collect::>() + .join(" or "), + RuntimeType::Tuple(_) => "arrays".to_owned(), + RuntimeType::Object(_) => format!("objects with fields {self}"), } } } @@ -1072,6 +1133,7 @@ pub enum PrimitiveType { Number(NumericType), String, Boolean, + Tag, Sketch, Solid, Plane, @@ -1079,35 +1141,6 @@ pub enum PrimitiveType { } impl PrimitiveType { - fn from_parsed( - value: AstPrimitiveType, - exec_state: &mut ExecState, - source_range: SourceRange, - ) -> Result, CompilationError> { - Ok(match value { - AstPrimitiveType::String => Some(PrimitiveType::String), - AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean), - AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed( - suffix, - &exec_state.mod_local.settings, - ))), - AstPrimitiveType::Named(name) => { - let ty_val = exec_state - .stack() - .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range) - .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?; - - let (ty, _) = match ty_val { - KclValue::Type { value: Some(ty), .. } => ty, - _ => unreachable!(), - }; - - Some(ty.clone()) - } - _ => None, - }) - } - fn display_multiple(&self) -> String { match self { PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), @@ -1118,6 +1151,7 @@ impl PrimitiveType { PrimitiveType::Solid => "Solids".to_owned(), PrimitiveType::Plane => "Planes".to_owned(), PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), + PrimitiveType::Tag => "tags".to_owned(), } } } @@ -1129,6 +1163,7 @@ impl fmt::Display for PrimitiveType { PrimitiveType::Number(_) => write!(f, "number"), PrimitiveType::String => write!(f, "string"), PrimitiveType::Boolean => write!(f, "bool"), + PrimitiveType::Tag => write!(f, "tag"), PrimitiveType::Sketch => write!(f, "Sketch"), PrimitiveType::Solid => write!(f, "Solid"), PrimitiveType::Plane => write!(f, "Plane"), diff --git a/rust/kcl-lib/src/execution/mod.rs b/rust/kcl-lib/src/execution/mod.rs index 2a495125f..9bcbd3775 100644 --- a/rust/kcl-lib/src/execution/mod.rs +++ b/rust/kcl-lib/src/execution/mod.rs @@ -1397,6 +1397,22 @@ const answer = returnX()"#; assert!(errs.is_empty()); } + #[tokio::test(flavor = "multi_thread")] + async fn type_aliases() { + let text = r#"type MyTy = [number; 2] +fn foo(x: MyTy) { + return x[0] +} + +foo([0, 1]) + +type Other = MyTy | string +"#; + let result = parse_execute(text).await.unwrap(); + let errs = result.exec_state.errors(); + assert!(errs.is_empty()); + } + #[tokio::test(flavor = "multi_thread")] async fn test_cannot_shebang_in_fn() { let ast = r#" diff --git a/rust/kcl-lib/src/parsing/ast/digest.rs b/rust/kcl-lib/src/parsing/ast/digest.rs index 1597096de..2b519ddd8 100644 --- a/rust/kcl-lib/src/parsing/ast/digest.rs +++ b/rust/kcl-lib/src/parsing/ast/digest.rs @@ -312,6 +312,9 @@ impl TypeDeclaration { hasher.update(a.compute_digest()); } } + if let Some(alias) = &mut slf.alias { + hasher.update(alias.compute_digest()); + } }); } diff --git a/rust/kcl-lib/src/parsing/ast/types/mod.rs b/rust/kcl-lib/src/parsing/ast/types/mod.rs index ab415e523..799f4c23f 100644 --- a/rust/kcl-lib/src/parsing/ast/types/mod.rs +++ b/rust/kcl-lib/src/parsing/ast/types/mod.rs @@ -1895,6 +1895,7 @@ pub struct TypeDeclaration { pub args: Option>, #[serde(default, skip_serializing_if = "ItemVisibility::is_default")] pub visibility: ItemVisibility, + pub alias: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index 29bf592e9..09e465b59 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -2216,7 +2216,7 @@ fn ty_decl(i: &mut TokenSlice) -> PResult> { let name = identifier(i)?; let mut end = name.end; - let args = if peek(open_paren).parse_next(i).is_ok() { + let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() { ignore_whitespace(i); open_paren(i)?; ignore_whitespace(i); @@ -2229,11 +2229,28 @@ fn ty_decl(i: &mut TokenSlice) -> PResult> { None }; + let alias = if peek((opt(whitespace), equals)).parse_next(i).is_ok() { + ignore_whitespace(i); + equals(i)?; + ignore_whitespace(i); + let ty = argument_type(i)?; + + ParseContext::warn(CompilationError::err( + ty.as_source_range(), + "Type aliases are experimental, likely to change in the future, and likely to not work properly.", + )); + + Some(ty) + } else { + None + }; + let module_id = name.module_id; let result = Node::boxed( TypeDeclaration { name, args, + alias, visibility, digest: None, }, @@ -2686,13 +2703,21 @@ fn argument_type(i: &mut TokenSlice) -> PResult> { let type_ = alt(( // Object types // TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`. - (open_brace, parameters, close_brace).map(|(open, params, close)| { - Node::new( + (open_brace, parameters, close_brace).try_map(|(open, params, close)| { + for p in ¶ms { + if p.type_.is_none() { + return Err(CompilationError::fatal( + p.identifier.as_source_range(), + "Missing type for field in record type", + )); + } + } + Ok(Node::new( Type::Object { properties: params }, open.start, close.end, open.module_id, - ) + )) }), // Array types array_type, diff --git a/rust/kcl-lib/src/std/appearance.rs b/rust/kcl-lib/src/std/appearance.rs index efb7e27a3..3d3dfdcab 100644 --- a/rust/kcl-lib/src/std/appearance.rs +++ b/rust/kcl-lib/src/std/appearance.rs @@ -12,10 +12,7 @@ use validator::Validate; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Solid, - }, + execution::{kcl_value::RuntimeType, ExecState, KclValue, Solid}, std::Args, }; @@ -42,11 +39,7 @@ struct AppearanceData { /// Set the appearance of a solid. This only works on solids, not sketches or individual paths. pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result { - let solids = args.get_unlabeled_kw_arg_typed( - "solids", - &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), - exec_state, - )?; + let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; let color: String = args.get_kw_arg("color")?; let metalness: Option = args.get_kw_arg_opt("metalness")?; diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 108a81c8b..26785262f 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, + kcl_value::{FunctionSource, NumericType, RuntimeType}, ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch, SketchSurface, Solid, TagIdentifier, }, @@ -309,8 +309,10 @@ impl Args { ty.human_friendly_type(), ); let suggestion = match (ty, actual_type_name) { - (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") - | (RuntimeType::Array(PrimitiveType::Solid, _), "Sketch") => Some( + (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some( + "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`", + ), + (RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => Some( "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`", ), _ => None, @@ -597,7 +599,7 @@ impl Args { }; let sarg = arg0 .value - .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) + .coerce(&RuntimeType::sketches(), exec_state) .ok_or(KclError::Type(KclErrorDetails { message: format!( "Expected an array of sketches, found {}", @@ -685,7 +687,7 @@ impl Args { }; let sarg = arg1 .value - .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) + .coerce(&RuntimeType::sketches(), exec_state) .ok_or(KclError::Type(KclErrorDetails { message: format!( "Expected one or more sketches for second argument, found {}", diff --git a/rust/kcl-lib/src/std/extrude.rs b/rust/kcl-lib/src/std/extrude.rs index db76cc51f..b973bd464 100644 --- a/rust/kcl-lib/src/std/extrude.rs +++ b/rust/kcl-lib/src/std/extrude.rs @@ -19,8 +19,8 @@ use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, PrimitiveType, Sketch, SketchSurface, Solid, + kcl_value::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface, + Solid, }, parsing::ast::types::TagNode, std::Args, @@ -28,11 +28,7 @@ use crate::{ /// Extrudes by a given amount. pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let length = args.get_kw_arg("length")?; let tag_start = args.get_kw_arg_opt("tagStart")?; let tag_end = args.get_kw_arg_opt("tagEnd")?; diff --git a/rust/kcl-lib/src/std/loft.rs b/rust/kcl-lib/src/std/loft.rs index 490596441..ab7c4a5c2 100644 --- a/rust/kcl-lib/src/std/loft.rs +++ b/rust/kcl-lib/src/std/loft.rs @@ -9,11 +9,7 @@ use kittycad_modeling_cmds as kcmc; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Sketch, Solid, - }, - parsing::ast::types::TagNode, + execution::{kcl_value::RuntimeType, ExecState, KclValue, Sketch, Solid}, std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, }; @@ -21,11 +17,7 @@ const DEFAULT_V_DEGREE: u32 = 2; /// Create a 3D surface or solid by interpolating between two or more sketches. pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let v_degree: NonZeroU32 = args .get_kw_arg_opt("vDegree")? .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()); diff --git a/rust/kcl-lib/src/std/patterns.rs b/rust/kcl-lib/src/std/patterns.rs index 835ebba79..eafaecb53 100644 --- a/rust/kcl-lib/src/std/patterns.rs +++ b/rust/kcl-lib/src/std/patterns.rs @@ -20,8 +20,8 @@ use super::args::Arg; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, - ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, PrimitiveType, Sketch, Solid, + kcl_value::{FunctionSource, NumericType, RuntimeType}, + ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, Sketch, Solid, }, std::Args, ExecutorContext, SourceRange, @@ -47,11 +47,7 @@ pub struct LinearPattern3dData { /// Repeat some 3D solid, changing each repetition slightly. pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result { - let solids = args.get_unlabeled_kw_arg_typed( - "solids", - &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), - exec_state, - )?; + let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; let instances: u32 = args.get_kw_arg("instances")?; let transform: &FunctionSource = args.get_kw_arg("transform")?; let use_original: Option = args.get_kw_arg_opt("useOriginal")?; @@ -62,11 +58,7 @@ pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result /// Repeat some 2D sketch, changing each repetition slightly. pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let instances: u32 = args.get_kw_arg("instances")?; let transform: &FunctionSource = args.get_kw_arg("transform")?; let use_original: Option = args.get_kw_arg_opt("useOriginal")?; @@ -696,11 +688,7 @@ mod tests { /// A linear pattern on a 2D sketch. pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let instances: u32 = args.get_kw_arg("instances")?; let distance: f64 = args.get_kw_arg("distance")?; let axis: [f64; 2] = args.get_kw_arg("axis")?; @@ -779,11 +767,7 @@ async fn inner_pattern_linear_2d( /// A linear pattern on a 3D model. pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result { - let solids = args.get_unlabeled_kw_arg_typed( - "solids", - &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), - exec_state, - )?; + let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; let instances: u32 = args.get_kw_arg("instances")?; let distance: f64 = args.get_kw_arg("distance")?; let axis: [f64; 3] = args.get_kw_arg("axis")?; @@ -1028,11 +1012,7 @@ impl CircularPattern { /// A circular pattern on a 2D sketch. pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let instances: u32 = args.get_kw_arg("instances")?; let center: [f64; 2] = args.get_kw_arg("center")?; let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?; @@ -1136,11 +1116,7 @@ async fn inner_pattern_circular_2d( /// A circular pattern on a 3D model. pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result { - let solids = args.get_unlabeled_kw_arg_typed( - "solids", - &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), - exec_state, - )?; + let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; // The number of total instances. Must be greater than or equal to 1. // This includes the original entity. For example, if instances is 2, // there will be two copies -- the original, and one new copy. diff --git a/rust/kcl-lib/src/std/shell.rs b/rust/kcl-lib/src/std/shell.rs index 2be881131..37c7c18d3 100644 --- a/rust/kcl-lib/src/std/shell.rs +++ b/rust/kcl-lib/src/std/shell.rs @@ -7,20 +7,13 @@ use kittycad_modeling_cmds as kcmc; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Solid, - }, + execution::{kcl_value::RuntimeType, ExecState, KclValue, Solid}, std::{sketch::FaceTag, Args}, }; /// Create a shell. pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result { - let solids = args.get_unlabeled_kw_arg_typed( - "solids", - &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), - exec_state, - )?; + let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; let thickness = args.get_kw_arg("thickness")?; let faces = args.get_kw_arg("faces")?; diff --git a/rust/kcl-lib/src/std/sweep.rs b/rust/kcl-lib/src/std/sweep.rs index 68df798e1..966fd8f66 100644 --- a/rust/kcl-lib/src/std/sweep.rs +++ b/rust/kcl-lib/src/std/sweep.rs @@ -9,10 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ errors::KclError, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, Helix, KclValue, PrimitiveType, Sketch, Solid, - }, + execution::{kcl_value::RuntimeType, ExecState, Helix, KclValue, Sketch, Solid}, parsing::ast::types::TagNode, std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, }; @@ -28,11 +25,7 @@ pub enum SweepPath { /// Extrude a sketch along a path. pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result { - let sketches = args.get_unlabeled_kw_arg_typed( - "sketches", - &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), - exec_state, - )?; + let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?; let path: SweepPath = args.get_kw_arg("path")?; let sectional = args.get_kw_arg_opt("sectional")?; let tolerance = args.get_kw_arg_opt("tolerance")?; diff --git a/rust/kcl-lib/src/std/transform.rs b/rust/kcl-lib/src/std/transform.rs index 674ad229d..2a73c78f9 100644 --- a/rust/kcl-lib/src/std/transform.rs +++ b/rust/kcl-lib/src/std/transform.rs @@ -13,10 +13,7 @@ use kittycad_modeling_cmds as kcmc; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, SolidOrSketchOrImportedGeometry, - }, + execution::{kcl_value::RuntimeType, ExecState, KclValue, SolidOrSketchOrImportedGeometry}, std::Args, }; @@ -25,9 +22,9 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result Result Result format!(": {}", rt.to_string()), + Some(rt) => format!(": {rt}"), None => String::new(), }; let body = self.body.recast(&new_options, indentation_level + 1); @@ -2427,6 +2431,7 @@ thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#; // A comment @(impl = primitive) export type bar(unit, baz) +type baz = Foo | Bar "#; let program = crate::parsing::top_level_parse(some_program_string).unwrap(); let recasted = program.recast(&Default::default(), 0);