diff --git a/Makefile b/Makefile index 05023bec6..23f9d0a99 100644 --- a/Makefile +++ b/Makefile @@ -20,29 +20,25 @@ $(WASM_PACK): ############################################################################### # BUILD -KCL_WASM_LIB_FILES := $(wildcard rust/**/*.rs) -TS_SRC := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts) -XSTATE_TYPEGENS := $(wildcard src/machines/*.typegen.ts) +RUST_SOURCES := $(wildcard rust/*) $(wildcard rust/**/*) +TYPESCRIPT_SOURCES := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts) .PHONY: build build: build-web build-desktop .PHONY: build-web -build-web: public/kcl_wasm_lib_bg.wasm $(XSTATE_TYPEGENS) build/index.html +build-web: public/kcl_wasm_lib_bg.wasm build/index.html .PHONY: build-desktop -build-desktop: public/kcl_wasm_lib_bg.wasm $(XSTATE_TYPEGENS) .vite/build/main.js +build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js -public/kcl_wasm_lib_bg.wasm: $(KCL_WASM_LIB_FILES) +public/kcl_wasm_lib_bg.wasm: $(RUST_SOURCES) yarn build:wasm -$(XSTATE_TYPEGENS): $(TS_SRC) - yarn xstate typegen 'src/**/*.ts?(x)' - -build/index.html: $(TS_SRC) +build/index.html: $(TYPESCRIPT_SOURCES) yarn build:local -.vite/build/main.js: $(TS_SRC) +.vite/build/main.js: $(TYPESCRIPT_SOURCES) yarn tronb:vite:dev ############################################################################### diff --git a/docs/kcl/index.md b/docs/kcl/index.md index 20e4e64ab..5cc81cb60 100644 --- a/docs/kcl/index.md +++ b/docs/kcl/index.md @@ -22,8 +22,12 @@ layout: manual * [`string`](kcl/types/string) * [`tag`](kcl/types/tag) * **std** + * [`Face`](kcl/types/Face) * [`HALF_TURN`](kcl/consts/std-HALF_TURN) + * [`Helix`](kcl/types/Helix) * [`Plane`](kcl/types/Plane) + * [`Point2d`](kcl/types/Point2d) + * [`Point3d`](kcl/types/Point3d) * [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN) * [`Sketch`](kcl/types/Sketch) * [`Solid`](kcl/types/Solid) diff --git a/docs/kcl/types/Face.md b/docs/kcl/types/Face.md index 64b166239..68f52e5dd 100644 --- a/docs/kcl/types/Face.md +++ b/docs/kcl/types/Face.md @@ -1,28 +1,12 @@ --- -title: "Face" +title: "std::Face" excerpt: "A face." layout: manual --- A face. -**Type:** `object` - -## Properties - -| Property | Type | Description | Required | -|----------|------|-------------|----------| -| `id` |[`string`](/docs/kcl/types/string)| The id of the face. | No | -| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No | -| `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No | -| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No | -| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No | -| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | -| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | -| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | - - diff --git a/docs/kcl/types/Helix.md b/docs/kcl/types/Helix.md index 45f8531df..0c96a2bf4 100644 --- a/docs/kcl/types/Helix.md +++ b/docs/kcl/types/Helix.md @@ -1,26 +1,12 @@ --- -title: "Helix" +title: "std::Helix" excerpt: "A helix." layout: manual --- A helix. -**Type:** `object` - -## Properties - -| Property | Type | Description | Required | -|----------|------|-------------|----------| -| `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No | -| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No | -| `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No | -| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | -| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | -| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | - - diff --git a/docs/kcl/types/KclValue.md b/docs/kcl/types/KclValue.md index a9f0d7eec..3cc2eea02 100644 --- a/docs/kcl/types/KclValue.md +++ b/docs/kcl/types/KclValue.md @@ -188,7 +188,7 @@ Any KCL value. | Property | Type | Description | Required | |----------|------|-------------|----------| | `type` |enum: [`Face`](/docs/kcl/types/Face)| | No | -| `value` |[`Face`](/docs/kcl/types/Face)| A face. | No | +| `value` |[`Face`](/docs/kcl/types/Face)| | No | ---- @@ -236,7 +236,7 @@ Any KCL value. | Property | Type | Description | Required | |----------|------|-------------|----------| | `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No | -| `value` |[`Helix`](/docs/kcl/types/Helix)| A helix. | No | +| `value` |[`Helix`](/docs/kcl/types/Helix)| | No | ---- diff --git a/docs/kcl/types/Point2d.md b/docs/kcl/types/Point2d.md new file mode 100644 index 000000000..8ab9a9430 --- /dev/null +++ b/docs/kcl/types/Point2d.md @@ -0,0 +1,17 @@ +--- +title: "std::Point2d" +excerpt: "A point in two dimensional space." +layout: manual +--- + +A point in two dimensional space. + +```kcl +type Point2d = [number; 2] +``` + +`Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value +with type `Point2d`, use an array, e.g., `[0, 0]` or `[5.0, 3.14]`. + + + diff --git a/docs/kcl/types/Point3d.md b/docs/kcl/types/Point3d.md index 48872c031..b9ea4fda5 100644 --- a/docs/kcl/types/Point3d.md +++ b/docs/kcl/types/Point3d.md @@ -1,22 +1,17 @@ --- -title: "Point3d" -excerpt: "" +title: "std::Point3d" +excerpt: "A point in three dimensional space." layout: manual --- +A point in three dimensional space. -**Type:** `object` +```kcl +type Point3d = [number; 3] +``` + +`Point3d` is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value +with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`. - - -## Properties - -| Property | Type | Description | Required | -|----------|------|-------------|----------| -| `x` |[`number`](/docs/kcl/types/number)| | No | -| `y` |[`number`](/docs/kcl/types/number)| | No | -| `z` |[`number`](/docs/kcl/types/number)| | No | - - diff --git a/docs/kcl/types/SweepPath.md b/docs/kcl/types/SweepPath.md index 3103b84f2..b8e7e0865 100644 --- a/docs/kcl/types/SweepPath.md +++ b/docs/kcl/types/SweepPath.md @@ -22,7 +22,6 @@ A path to sweep along. ---- -A helix. [`Helix`](/docs/kcl/types/Helix) diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-5-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-5-Google-Chrome-linux.png index f46dc4b20..61b1ac8a3 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-5-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-5-Google-Chrome-linux.png differ 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/docs/gen_std_tests.rs b/rust/kcl-lib/src/docs/gen_std_tests.rs index fa1abadcc..0b27768cf 100644 --- a/rust/kcl-lib/src/docs/gen_std_tests.rs +++ b/rust/kcl-lib/src/docs/gen_std_tests.rs @@ -23,7 +23,9 @@ use crate::{ const TYPES_DIR: &str = "../../docs/kcl/types"; const LANG_TOPICS: [&str; 5] = ["Types", "Modules", "Settings", "Known Issues", "Constants"]; // These types are declared in std. -const DECLARED_TYPES: [&str; 7] = ["number", "string", "tag", "bool", "Sketch", "Solid", "Plane"]; +const DECLARED_TYPES: [&str; 11] = [ + "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Point2d", "Point3d", +]; fn init_handlebars() -> Result> { let mut hbs = handlebars::Handlebars::new(); @@ -457,6 +459,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String) let data = json!({ "name": ty.qual_name(), + "definition": ty.alias.as_ref().map(|t| format!("type {} = {t}", ty.name)), "summary": ty.summary, "description": ty.description, "deprecated": ty.properties.deprecated, diff --git a/rust/kcl-lib/src/docs/kcl_doc.rs b/rust/kcl-lib/src/docs/kcl_doc.rs index d4c80ecff..9bc401a7a 100644 --- a/rust/kcl-lib/src/docs/kcl_doc.rs +++ b/rust/kcl-lib/src/docs/kcl_doc.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use regex::Regex; use tower_lsp::lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, InsertTextFormat, MarkupContent, MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, @@ -282,7 +283,7 @@ impl ConstData { documentation: self.short_docs().map(|s| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, - value: s, + value: remove_md_links(&s), }) }), deprecated: Some(self.properties.deprecated), @@ -335,7 +336,7 @@ impl FnData { name, qual_name, args: expr.params.iter().map(ArgData::from_ast).collect(), - return_type: expr.return_type.as_ref().map(|t| t.recast(&Default::default(), 0)), + return_type: expr.return_type.as_ref().map(|t| t.to_string()), properties: Properties { exported: !var.visibility.is_default(), deprecated: false, @@ -393,7 +394,7 @@ impl FnData { documentation: self.short_docs().map(|s| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, - value: s, + value: remove_md_links(&s), }) }), deprecated: Some(self.properties.deprecated), @@ -496,7 +497,7 @@ impl ArgData { fn from_ast(arg: &crate::parsing::ast::types::Parameter) -> Self { ArgData { name: arg.identifier.name.clone(), - ty: arg.type_.as_ref().map(|t| t.recast(&Default::default(), 0)), + ty: arg.type_.as_ref().map(|t| t.to_string()), // Doc comments are not yet supported on parameters. docs: None, kind: if arg.labeled { @@ -560,6 +561,7 @@ pub struct TyData { /// The fully qualified name. pub qual_name: String, pub properties: Properties, + pub alias: Option, /// The summary of the function. pub summary: Option, @@ -583,6 +585,7 @@ impl TyData { doc_hidden: false, impl_kind: annotations::Impl::Kcl, }, + alias: ty.alias.as_ref().map(|t| t.to_string()), summary: None, description: None, examples: Vec::new(), @@ -609,13 +612,16 @@ impl TyData { fn to_completion_item(&self) -> CompletionItem { CompletionItem { label: self.name.clone(), - label_details: None, + label_details: self.alias.as_ref().map(|t| CompletionItemLabelDetails { + detail: Some(format!("type {} = {t}", self.name)), + description: None, + }), kind: Some(CompletionItemKind::FUNCTION), detail: Some(self.qual_name().to_owned()), documentation: self.short_docs().map(|s| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, - value: s, + value: remove_md_links(&s), }) }), deprecated: Some(self.properties.deprecated), @@ -635,6 +641,11 @@ impl TyData { } } +fn remove_md_links(s: &str) -> String { + let re = Regex::new(r"\[([^\]]*)\]\([^\)]*\)").unwrap(); + re.replace_all(s, "$1").to_string() +} + trait ApplyMeta { fn apply_docs( &mut self, @@ -863,6 +874,24 @@ mod test { panic!("didn't find PI"); } + #[test] + fn test_remove_md_links() { + assert_eq!( + remove_md_links("sdf dsf sd fj sdk fasdfs. asad[sdfs] dfsdf(dsfs, dsf)"), + "sdf dsf sd fj sdk fasdfs. asad[sdfs] dfsdf(dsfs, dsf)".to_owned() + ); + assert_eq!(remove_md_links("[]()"), "".to_owned()); + assert_eq!(remove_md_links("[foo](bar)"), "foo".to_owned()); + assert_eq!( + remove_md_links("asdasda dsa[foo](http://www.bar/baz/qux.md). asdasdasdas asdas"), + "asdasda dsafoo. asdasdasdas asdas".to_owned() + ); + assert_eq!( + remove_md_links("a [foo](bar) b [2](bar) c [_](bar)"), + "a foo b 2 c _".to_owned() + ); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 5)] async fn test_examples() -> miette::Result<()> { let std = walk_prelude(); diff --git a/rust/kcl-lib/src/docs/mod.rs b/rust/kcl-lib/src/docs/mod.rs index e2f224276..44d339ff2 100644 --- a/rust/kcl-lib/src/docs/mod.rs +++ b/rust/kcl-lib/src/docs/mod.rs @@ -18,7 +18,7 @@ use tower_lsp::lsp_types::{ }; use crate::{ - execution::{kcl_value::NumericType, Sketch}, + execution::{types::NumericType, Sketch}, std::Primitive, }; diff --git a/rust/kcl-lib/src/docs/templates/kclType.hbs b/rust/kcl-lib/src/docs/templates/kclType.hbs index 71a667da6..8926707cc 100644 --- a/rust/kcl-lib/src/docs/templates/kclType.hbs +++ b/rust/kcl-lib/src/docs/templates/kclType.hbs @@ -10,6 +10,12 @@ layout: manual {{/if}} {{{summary}}} +{{#if definition}} +```kcl +{{{definition}}} +``` + +{{/if}} {{{description}}} diff --git a/rust/kcl-lib/src/execution/annotations.rs b/rust/kcl-lib/src/execution/annotations.rs index 5dd2ff844..b2622b4ae 100644 --- a/rust/kcl-lib/src/execution/annotations.rs +++ b/rust/kcl-lib/src/execution/annotations.rs @@ -6,7 +6,7 @@ use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN}; use crate::{ errors::KclErrorDetails, - execution::kcl_value::{UnitAngle, UnitLen}, + execution::types::{UnitAngle, UnitLen}, parsing::ast::types::{Annotation, Expr, Node, ObjectProperty}, KclError, SourceRange, }; diff --git a/rust/kcl-lib/src/execution/cad_op.rs b/rust/kcl-lib/src/execution/cad_op.rs index a77f13560..ec7c9783b 100644 --- a/rust/kcl-lib/src/execution/cad_op.rs +++ b/rust/kcl-lib/src/execution/cad_op.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::{kcl_value::NumericType, ArtifactId, KclValue}; +use super::{types::NumericType, ArtifactId, KclValue}; use crate::{docs::StdLibFn, std::get_stdlib_fn, SourceRange}; /// A CAD modeling operation for display in the feature tree, AKA operations diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index 652d7d8ac..a246f3f96 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -8,9 +8,10 @@ use crate::{ execution::{ annotations, cad_op::{OpArg, OpKclValue, Operation}, - kcl_value::{FunctionSource, NumericType, RuntimeType}, + kcl_value::FunctionSource, memory, state::ModuleState, + types::{NumericType, RuntimeType}, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo, TagIdentifier, }, @@ -29,6 +30,8 @@ use crate::{ CompilationError, }; +use super::kcl_value::TypeDef; + enum StatementKind<'a> { Declaration { name: &'a str }, Expression, @@ -299,8 +302,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 @@ -319,12 +323,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; @@ -641,30 +673,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..01da631ef 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::types::RuntimeType::solid(), } } } @@ -119,7 +119,7 @@ impl From for crate::execution::KclValue { .into_iter() .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) }) .collect(), - ty: crate::execution::PrimitiveType::Sketch, + ty: crate::execution::types::RuntimeType::sketch(), } } } diff --git a/rust/kcl-lib/src/execution/import.rs b/rust/kcl-lib/src/execution/import.rs index b54d720e0..98ae36570 100644 --- a/rust/kcl-lib/src/execution/import.rs +++ b/rust/kcl-lib/src/execution/import.rs @@ -17,7 +17,7 @@ use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{annotations, kcl_value::UnitLen, ExecState, ExecutorContext, ImportedGeometry}, + execution::{annotations, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry}, fs::FileSystem, parsing::ast::types::{Annotation, Node}, source_range::SourceRange, diff --git a/rust/kcl-lib/src/execution/kcl_value.rs b/rust/kcl-lib/src/execution/kcl_value.rs index d2a0a1da0..82b1c3b7b 100644 --- a/rust/kcl-lib/src/execution/kcl_value.rs +++ b/rust/kcl-lib/src/execution/kcl_value.rs @@ -1,29 +1,20 @@ -use std::{collections::HashMap, fmt}; +use std::collections::HashMap; use anyhow::Result; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::Serialize; -use super::{ - memory::{self, EnvironmentRef}, - MetaSettings, Point3d, -}; +use super::{memory::EnvironmentRef, MetaSettings}; use crate::{ errors::KclErrorDetails, execution::{ + types::{NumericType, PrimitiveType, RuntimeType}, ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, Solid, TagIdentifier, }, - parsing::{ - ast::types::{ - DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, - PrimitiveType as AstPrimitiveType, TagDeclarator, TagNode, Type, - }, - token::NumericSuffix, - }, - std::{ - args::{Arg, FromKclValue}, - StdFnProps, + parsing::ast::types::{ + DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode, }, + std::{args::Arg, StdFnProps}, CompilationError, KclError, ModuleId, SourceRange, }; @@ -65,7 +56,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 +96,7 @@ pub enum KclValue { #[ts(skip)] Type { #[serde(skip)] - value: Option<(PrimitiveType, StdFnProps)>, + value: TypeDef, #[serde(skip)] meta: Vec, }, @@ -142,6 +133,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 +151,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 +166,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), } } } @@ -553,259 +550,6 @@ impl KclValue { Ok(*b) } - /// True if `self` has a type which is a subtype of `ty` without coercion. - pub fn has_type(&self, ty: &RuntimeType) -> bool { - let Some(self_ty) = self.principal_type() else { - return false; - }; - - self_ty.subtype(ty) - } - - /// Coerce `self` to a new value which has `ty` as it's closest supertype. - /// - /// If the result is Some, then: - /// - result.principal_type().unwrap().subtype(ty) - /// - /// If self.principal_type() == ty then result == self - pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option { - match ty { - RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state), - RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state), - RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state), - RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state), - RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state), - } - } - - fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option { - let value = match self { - KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0], - _ => self, - }; - match ty { - // TODO numeric type coercions - PrimitiveType::Number(_ty) => match value { - KclValue::Number { .. } => Some(value.clone()), - _ => None, - }, - PrimitiveType::String => match value { - KclValue::String { .. } => Some(value.clone()), - _ => None, - }, - PrimitiveType::Boolean => match value { - KclValue::Bool { .. } => Some(value.clone()), - _ => None, - }, - PrimitiveType::Sketch => match value { - KclValue::Sketch { .. } => Some(value.clone()), - _ => None, - }, - PrimitiveType::Solid => match value { - KclValue::Solid { .. } => Some(value.clone()), - _ => None, - }, - PrimitiveType::Plane => match value { - KclValue::Plane { .. } => Some(value.clone()), - KclValue::Object { value, meta } => { - let origin = value.get("origin").and_then(Point3d::from_kcl_val)?; - let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?; - let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?; - let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?; - - let id = exec_state.mod_local.id_generator.next_uuid(); - let plane = Plane { - id, - artifact_id: id.into(), - origin, - x_axis, - y_axis, - z_axis, - value: super::PlaneType::Uninit, - // TODO use length unit from origin - units: exec_state.length_unit(), - meta: meta.clone(), - }; - - Some(KclValue::Plane { value: Box::new(plane) }) - } - _ => None, - }, - PrimitiveType::ImportedGeometry => match value { - KclValue::ImportedGeometry { .. } => Some(value.clone()), - _ => None, - }, - } - } - - fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option { - match self { - KclValue::HomArray { value, ty: aty } => { - // TODO could check types of values individually - if aty != ty { - return None; - } - - let value = match len { - ArrayLen::None => value.clone(), - ArrayLen::NonEmpty => { - if value.is_empty() { - return None; - } - - value.clone() - } - ArrayLen::Known(n) => { - if n != value.len() { - return None; - } - - value[..n].to_vec() - } - }; - - Some(KclValue::HomArray { value, ty: ty.clone() }) - } - KclValue::MixedArray { value, .. } => { - let value = match len { - ArrayLen::None => value.clone(), - ArrayLen::NonEmpty => { - if value.is_empty() { - return None; - } - - value.clone() - } - ArrayLen::Known(n) => { - if n != value.len() { - return None; - } - - value[..n].to_vec() - } - }; - - let rt = RuntimeType::Primitive(ty.clone()); - let value = value - .iter() - .map(|v| v.coerce(&rt, exec_state)) - .collect::>>()?; - - Some(KclValue::HomArray { value, ty: ty.clone() }) - } - KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray { - value: Vec::new(), - ty: ty.clone(), - }), - value if len.satisfied(1) => { - if value.has_type(&RuntimeType::Primitive(ty.clone())) { - Some(KclValue::HomArray { - value: vec![value.clone()], - ty: ty.clone(), - }) - } else { - None - } - } - _ => None, - } - } - - fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option { - match self { - KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { - if value.len() < tys.len() { - return None; - } - let mut result = Vec::new(); - for (i, t) in tys.iter().enumerate() { - result.push(value[i].coerce_to_primitive_type(t, exec_state)?); - } - - Some(KclValue::MixedArray { - value: result, - meta: Vec::new(), - }) - } - KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray { - value: Vec::new(), - meta: meta.clone(), - }), - value if tys.len() == 1 => { - if value.has_type(&RuntimeType::Primitive(tys[0].clone())) { - Some(KclValue::MixedArray { - value: vec![value.clone()], - meta: Vec::new(), - }) - } else { - None - } - } - _ => None, - } - } - - fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option { - for t in tys { - if let Some(v) = self.coerce(t, exec_state) { - return Some(v); - } - } - - None - } - - fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option { - match self { - KclValue::Object { value, .. } => { - for (s, t) in tys { - // TODO coerce fields - if !value.get(s)?.has_type(t) { - return None; - } - } - // TODO remove non-required fields - Some(self.clone()) - } - _ => None, - } - } - - pub fn principal_type(&self) -> Option { - match self { - KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)), - KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))), - KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)), - KclValue::Object { value, .. } => { - let properties = value - .iter() - .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t))) - .collect::>>()?; - Some(RuntimeType::Object(properties)) - } - KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)), - KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)), - 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::>>()?, - )), - KclValue::HomArray { ty, value, .. } => Some(RuntimeType::Array(ty.clone(), ArrayLen::Known(value.len()))), - KclValue::Face { .. } => None, - KclValue::Helix { .. } - | KclValue::Function { .. } - | KclValue::Module { .. } - | KclValue::TagIdentifier(_) - | KclValue::TagDeclarator(_) - | KclValue::KclNone { .. } - | KclValue::Type { .. } - | KclValue::Uuid { .. } => None, - } - } - /// If this memory item is a function, call it with the given arguments, return its val as Ok. /// If it's not a function, return Err. pub async fn call_fn( @@ -919,447 +663,3 @@ impl KclValue { } } } - -#[derive(Debug, Clone, PartialEq)] -pub enum RuntimeType { - Primitive(PrimitiveType), - Array(PrimitiveType, ArrayLen), - Union(Vec), - Tuple(Vec), - Object(Vec<(String, RuntimeType)>), -} - -impl RuntimeType { - 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) - } - Type::Array(pt) => { - PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(|t| RuntimeType::Array(t, ArrayLen::None)) - } - 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))) - }) - .collect::>, CompilationError>>()? - .map(RuntimeType::Object), - }) - } - - pub fn human_friendly_type(&self) -> String { - match self { - RuntimeType::Primitive(ty) => ty.to_string(), - RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()), - RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()), - RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()), - RuntimeType::Union(tys) => tys - .iter() - .map(Self::human_friendly_type) - .collect::>() - .join(" or "), - RuntimeType::Tuple(tys) => format!( - "an array with values of types ({})", - tys.iter().map(PrimitiveType::to_string).collect::>().join(", ") - ), - RuntimeType::Object(_) => format!("an object with fields {}", self), - } - } - - // Subtype with no coercion, including refining numeric types. - fn subtype(&self, sup: &RuntimeType) -> bool { - use RuntimeType::*; - - match (self, sup) { - (Primitive(t1), Primitive(t2)) => t1 == t2, - // 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), - (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. - (Object(t1), Object(t2)) => t1 == t2, - _ => false, - } - } - - fn primitive(self) -> Option { - match self { - RuntimeType::Primitive(t) => Some(t), - _ => None, - } - } -} - -impl fmt::Display for RuntimeType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RuntimeType::Primitive(t) => t.fmt(f), - RuntimeType::Array(t, l) => match l { - ArrayLen::None => write!(f, "[{t}]"), - ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"), - ArrayLen::Known(n) => write!(f, "[{t}; {n}]"), - }, - RuntimeType::Tuple(ts) => write!( - f, - "[{}]", - ts.iter().map(|t| t.to_string()).collect::>().join(", ") - ), - RuntimeType::Union(ts) => write!( - f, - "{}", - ts.iter().map(|t| t.to_string()).collect::>().join(" | ") - ), - RuntimeType::Object(items) => write!( - f, - "{{ {} }}", - items - .iter() - .map(|(n, t)| format!("{n}: {t}")) - .collect::>() - .join(", ") - ), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ArrayLen { - None, - NonEmpty, - Known(usize), -} - -impl ArrayLen { - pub fn subtype(self, other: ArrayLen) -> bool { - match (self, other) { - (_, ArrayLen::None) => true, - (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true, - (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true, - (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true, - _ => false, - } - } - - /// True if the length constraint is satisfied by the supplied length. - fn satisfied(self, len: usize) -> bool { - match self { - ArrayLen::None => true, - ArrayLen::NonEmpty => len > 0, - ArrayLen::Known(s) => len == s, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum PrimitiveType { - Number(NumericType), - String, - Boolean, - Sketch, - Solid, - Plane, - ImportedGeometry, -} - -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})"), - PrimitiveType::Number(_) => "numbers".to_owned(), - PrimitiveType::String => "strings".to_owned(), - PrimitiveType::Boolean => "bools".to_owned(), - PrimitiveType::Sketch => "Sketches".to_owned(), - PrimitiveType::Solid => "Solids".to_owned(), - PrimitiveType::Plane => "Planes".to_owned(), - PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), - } - } -} - -impl fmt::Display for PrimitiveType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"), - PrimitiveType::Number(_) => write!(f, "number"), - PrimitiveType::String => write!(f, "string"), - PrimitiveType::Boolean => write!(f, "bool"), - PrimitiveType::Sketch => write!(f, "Sketch"), - PrimitiveType::Solid => write!(f, "Solid"), - PrimitiveType::Plane => write!(f, "Plane"), - PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum NumericType { - // Specified by the user (directly or indirectly) - Known(UnitType), - // Unspecified, using defaults - Default { len: UnitLen, angle: UnitAngle }, - // Exceeded the ability of the type system to track. - Unknown, - // Type info has been explicitly cast away. - Any, -} - -impl NumericType { - pub fn count() -> Self { - NumericType::Known(UnitType::Count) - } - - /// Combine two types when we expect them to be equal. - pub fn combine_eq(self, other: &NumericType) -> NumericType { - if &self == other { - self - } else { - NumericType::Unknown - } - } - - /// Combine n types when we expect them to be equal. - /// - /// Precondition: tys.len() > 0 - pub fn combine_n_eq(tys: &[NumericType]) -> NumericType { - let ty0 = tys[0].clone(); - for t in &tys[1..] { - if t != &ty0 { - return NumericType::Unknown; - } - } - ty0 - } - - /// Combine two types in addition-like operations. - pub fn combine_add(a: NumericType, b: NumericType) -> NumericType { - if a == b { - return a; - } - NumericType::Unknown - } - - /// Combine two types in multiplication-like operations. - pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType { - if a == NumericType::count() { - return b; - } - if b == NumericType::count() { - return a; - } - NumericType::Unknown - } - - /// Combine two types in division-like operations. - pub fn combine_div(a: NumericType, b: NumericType) -> NumericType { - if b == NumericType::count() { - return a; - } - NumericType::Unknown - } - - pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self { - match suffix { - NumericSuffix::None => NumericType::Default { - len: settings.default_length_units, - angle: settings.default_angle_units, - }, - NumericSuffix::Count => NumericType::Known(UnitType::Count), - NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)), - NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)), - NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)), - NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)), - NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)), - NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)), - NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)), - NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)), - } - } -} - -impl From for NumericType { - fn from(value: UnitLen) -> Self { - NumericType::Known(UnitType::Length(value)) - } -} - -impl From for NumericType { - fn from(value: UnitAngle) -> Self { - NumericType::Known(UnitType::Angle(value)) - } -} - -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum UnitType { - Count, - Length(UnitLen), - Angle(UnitAngle), -} - -impl std::fmt::Display for UnitType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UnitType::Count => write!(f, "_"), - UnitType::Length(l) => l.fmt(f), - UnitType::Angle(a) => a.fmt(f), - } - } -} - -// TODO called UnitLen so as not to clash with UnitLength in settings) -/// A unit of length. -#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum UnitLen { - #[default] - Mm, - Cm, - M, - Inches, - Feet, - Yards, -} - -impl std::fmt::Display for UnitLen { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UnitLen::Mm => write!(f, "mm"), - UnitLen::Cm => write!(f, "cm"), - UnitLen::M => write!(f, "m"), - UnitLen::Inches => write!(f, "in"), - UnitLen::Feet => write!(f, "ft"), - UnitLen::Yards => write!(f, "yd"), - } - } -} - -impl TryFrom for UnitLen { - type Error = (); - - fn try_from(suffix: NumericSuffix) -> std::result::Result { - match suffix { - NumericSuffix::Mm => Ok(Self::Mm), - NumericSuffix::Cm => Ok(Self::Cm), - NumericSuffix::M => Ok(Self::M), - NumericSuffix::Inch => Ok(Self::Inches), - NumericSuffix::Ft => Ok(Self::Feet), - NumericSuffix::Yd => Ok(Self::Yards), - _ => Err(()), - } - } -} - -impl From for UnitLen { - fn from(unit: crate::UnitLength) -> Self { - match unit { - crate::UnitLength::Cm => UnitLen::Cm, - crate::UnitLength::Ft => UnitLen::Feet, - crate::UnitLength::In => UnitLen::Inches, - crate::UnitLength::M => UnitLen::M, - crate::UnitLength::Mm => UnitLen::Mm, - crate::UnitLength::Yd => UnitLen::Yards, - } - } -} - -impl From for crate::UnitLength { - fn from(unit: UnitLen) -> Self { - match unit { - UnitLen::Cm => crate::UnitLength::Cm, - UnitLen::Feet => crate::UnitLength::Ft, - UnitLen::Inches => crate::UnitLength::In, - UnitLen::M => crate::UnitLength::M, - UnitLen::Mm => crate::UnitLength::Mm, - UnitLen::Yards => crate::UnitLength::Yd, - } - } -} - -impl From for kittycad_modeling_cmds::units::UnitLength { - fn from(unit: UnitLen) -> Self { - match unit { - UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters, - UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet, - UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches, - UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters, - UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters, - UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards, - } - } -} - -/// A unit of angle. -#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum UnitAngle { - #[default] - Degrees, - Radians, -} - -impl std::fmt::Display for UnitAngle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UnitAngle::Degrees => write!(f, "deg"), - UnitAngle::Radians => write!(f, "rad"), - } - } -} - -impl TryFrom for UnitAngle { - type Error = (); - - fn try_from(suffix: NumericSuffix) -> std::result::Result { - match suffix { - NumericSuffix::Deg => Ok(Self::Degrees), - NumericSuffix::Rad => Ok(Self::Radians), - _ => Err(()), - } - } -} diff --git a/rust/kcl-lib/src/execution/memory.rs b/rust/kcl-lib/src/execution/memory.rs index b558943d1..a1d7c330a 100644 --- a/rust/kcl-lib/src/execution/memory.rs +++ b/rust/kcl-lib/src/execution/memory.rs @@ -1055,7 +1055,7 @@ mod env { #[cfg(test)] mod test { use super::*; - use crate::execution::kcl_value::{FunctionSource, NumericType}; + use crate::execution::{kcl_value::FunctionSource, types::NumericType}; fn sr() -> SourceRange { SourceRange::default() diff --git a/rust/kcl-lib/src/execution/mod.rs b/rust/kcl-lib/src/execution/mod.rs index 5ce907c74..229baf644 100644 --- a/rust/kcl-lib/src/execution/mod.rs +++ b/rust/kcl-lib/src/execution/mod.rs @@ -15,7 +15,7 @@ pub(crate) use import::{ import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM, }; use indexmap::IndexMap; -pub use kcl_value::{KclObjectFields, KclValue, PrimitiveType, UnitAngle, UnitLen}; +pub use kcl_value::{KclObjectFields, KclValue}; use kcmc::{ each_cmd as mcmd, ok_response::{output::TakeSnapshot, OkModelingCmdResponse}, @@ -34,6 +34,7 @@ use crate::{ execution::{ artifact::build_artifact_graph, cache::{CacheInformation, CacheResult}, + types::{UnitAngle, UnitLen}, }, fs::FileManager, modules::{ModuleId, ModulePath}, @@ -54,6 +55,7 @@ mod import; pub(crate) mod kcl_value; mod memory; mod state; +pub(crate) mod types; /// Outcome of executing a program. This is used in TS. #[derive(Debug, Clone, Serialize, ts_rs::TS)] @@ -1375,6 +1377,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 | Helix +"#; + 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/execution/state.rs b/rust/kcl-lib/src/execution/state.rs index d8d697d80..d8c4fbafd 100644 --- a/rust/kcl-lib/src/execution/state.rs +++ b/rust/kcl-lib/src/execution/state.rs @@ -12,10 +12,9 @@ use crate::{ execution::{ annotations, id_generator::IdGenerator, - kcl_value, memory::{ProgramMemory, Stack}, - Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, - Operation, UnitAngle, UnitLen, + types, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, + KclValue, Operation, UnitAngle, UnitLen, }, modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource}, parsing::ast::types::Annotation, @@ -305,8 +304,8 @@ impl ModuleState { #[ts(export)] #[serde(rename_all = "camelCase")] pub struct MetaSettings { - pub default_length_units: kcl_value::UnitLen, - pub default_angle_units: kcl_value::UnitAngle, + pub default_length_units: types::UnitLen, + pub default_angle_units: types::UnitAngle, pub std_path: Option, } @@ -321,12 +320,12 @@ impl MetaSettings { match &*p.inner.key.name { annotations::SETTINGS_UNIT_LENGTH => { let value = annotations::expect_ident(&p.inner.value)?; - let value = kcl_value::UnitLen::from_str(value, annotation.as_source_range())?; + let value = types::UnitLen::from_str(value, annotation.as_source_range())?; self.default_length_units = value; } annotations::SETTINGS_UNIT_ANGLE => { let value = annotations::expect_ident(&p.inner.value)?; - let value = kcl_value::UnitAngle::from_str(value, annotation.as_source_range())?; + let value = types::UnitAngle::from_str(value, annotation.as_source_range())?; self.default_angle_units = value; } name => { diff --git a/rust/kcl-lib/src/execution/types.rs b/rust/kcl-lib/src/execution/types.rs new file mode 100644 index 000000000..2728ddc83 --- /dev/null +++ b/rust/kcl-lib/src/execution/types.rs @@ -0,0 +1,1254 @@ +use std::{collections::HashMap, fmt}; + +use anyhow::Result; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + execution::{ + kcl_value::{KclValue, TypeDef}, + memory::{self}, + ExecState, Plane, Point3d, + }, + parsing::{ + ast::types::{PrimitiveType as AstPrimitiveType, Type}, + token::NumericSuffix, + }, + std::args::FromKclValue, + CompilationError, SourceRange, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum RuntimeType { + Primitive(PrimitiveType), + Array(Box, ArrayLen), + Union(Vec), + Tuple(Vec), + Object(Vec<(String, RuntimeType)>), +} + +impl RuntimeType { + pub fn sketch() -> Self { + RuntimeType::Primitive(PrimitiveType::Sketch) + } + + /// `[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 { + match value { + Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range), + Type::Array { ty, 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| Self::from_parsed_primitive(t.inner, exec_state, source_range)) + .collect::, CompilationError>>() + .map(RuntimeType::Union), + Type::Object { properties } => properties + .into_iter() + .map(|p| { + RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range) + .map(|ty| (p.identifier.inner.name, ty)) + }) + .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), + }) + } + + pub fn human_friendly_type(&self) -> String { + match self { + RuntimeType::Primitive(ty) => ty.to_string(), + RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()), + RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()), + RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()), + RuntimeType::Union(tys) => tys + .iter() + .map(Self::human_friendly_type) + .collect::>() + .join(" or "), + RuntimeType::Tuple(tys) => format!( + "an array with values of types ({})", + tys.iter().map(Self::human_friendly_type).collect::>().join(", ") + ), + RuntimeType::Object(_) => format!("an object with fields {}", self), + } + } + + // Subtype with no coercion, including refining numeric types. + fn subtype(&self, sup: &RuntimeType) -> bool { + use RuntimeType::*; + + match (self, sup) { + (Primitive(t1), Primitive(t2)) => t1.subtype(t2), + (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2), + (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)), + (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)), + (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)), + (Object(t1), Object(t2)) => t2 + .iter() + .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))), + _ => false, + } + } + + fn display_multiple(&self) -> String { + match self { + 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}"), + } + } +} + +impl fmt::Display for RuntimeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RuntimeType::Primitive(t) => t.fmt(f), + RuntimeType::Array(t, l) => match l { + ArrayLen::None => write!(f, "[{t}]"), + ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"), + ArrayLen::Known(n) => write!(f, "[{t}; {n}]"), + }, + RuntimeType::Tuple(ts) => write!( + f, + "[{}]", + ts.iter().map(|t| t.to_string()).collect::>().join(", ") + ), + RuntimeType::Union(ts) => write!( + f, + "{}", + ts.iter().map(|t| t.to_string()).collect::>().join(" | ") + ), + RuntimeType::Object(items) => write!( + f, + "{{ {} }}", + items + .iter() + .map(|(n, t)| format!("{n}: {t}")) + .collect::>() + .join(", ") + ), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)] +pub enum ArrayLen { + None, + NonEmpty, + Known(usize), +} + +impl ArrayLen { + pub fn subtype(self, other: ArrayLen) -> bool { + match (self, other) { + (_, ArrayLen::None) => true, + (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true, + (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true, + (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true, + _ => false, + } + } + + /// True if the length constraint is satisfied by the supplied length. + fn satisfied(self, len: usize) -> bool { + match self { + ArrayLen::None => true, + ArrayLen::NonEmpty => len > 0, + ArrayLen::Known(s) => len == s, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PrimitiveType { + Number(NumericType), + String, + Boolean, + Tag, + Sketch, + Solid, + Plane, + Helix, + Face, + ImportedGeometry, +} + +impl PrimitiveType { + fn display_multiple(&self) -> String { + match self { + PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), + PrimitiveType::Number(_) => "numbers".to_owned(), + PrimitiveType::String => "strings".to_owned(), + PrimitiveType::Boolean => "bools".to_owned(), + PrimitiveType::Sketch => "Sketches".to_owned(), + PrimitiveType::Solid => "Solids".to_owned(), + PrimitiveType::Plane => "Planes".to_owned(), + PrimitiveType::Helix => "Helices".to_owned(), + PrimitiveType::Face => "Faces".to_owned(), + PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), + PrimitiveType::Tag => "tags".to_owned(), + } + } + + fn subtype(&self, other: &PrimitiveType) -> bool { + match (self, other) { + (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2), + (t1, t2) => t1 == t2, + } + } +} + +impl fmt::Display for PrimitiveType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"), + 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"), + PrimitiveType::Face => write!(f, "Face"), + PrimitiveType::Helix => write!(f, "Helix"), + PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type")] +pub enum NumericType { + // Specified by the user (directly or indirectly) + Known(UnitType), + // Unspecified, using defaults + Default { len: UnitLen, angle: UnitAngle }, + // Exceeded the ability of the type system to track. + Unknown, + // Type info has been explicitly cast away. + Any, +} + +impl NumericType { + pub fn count() -> Self { + NumericType::Known(UnitType::Count) + } + + /// Combine two types when we expect them to be equal. + pub fn combine_eq(self, other: &NumericType) -> NumericType { + if &self == other { + self + } else { + NumericType::Unknown + } + } + + /// Combine n types when we expect them to be equal. + /// + /// Precondition: tys.len() > 0 + pub fn combine_n_eq(tys: &[NumericType]) -> NumericType { + let ty0 = tys[0].clone(); + for t in &tys[1..] { + if t != &ty0 { + return NumericType::Unknown; + } + } + ty0 + } + + /// Combine two types in addition-like operations. + pub fn combine_add(a: NumericType, b: NumericType) -> NumericType { + if a == b { + return a; + } + NumericType::Unknown + } + + /// Combine two types in multiplication-like operations. + pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType { + if a == NumericType::count() { + return b; + } + if b == NumericType::count() { + return a; + } + NumericType::Unknown + } + + /// Combine two types in division-like operations. + pub fn combine_div(a: NumericType, b: NumericType) -> NumericType { + if b == NumericType::count() { + return a; + } + NumericType::Unknown + } + + pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self { + match suffix { + NumericSuffix::None => NumericType::Default { + len: settings.default_length_units, + angle: settings.default_angle_units, + }, + NumericSuffix::Count => NumericType::Known(UnitType::Count), + NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)), + NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)), + NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)), + NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)), + NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)), + NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)), + NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)), + NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)), + } + } + + fn subtype(&self, other: &NumericType) -> bool { + use NumericType::*; + + match (self, other) { + (Unknown, _) | (_, Unknown) => false, + (a, b) if a == b => true, + (_, Any) => true, + (_, _) => false, + } + } +} + +impl From for NumericType { + fn from(value: UnitLen) -> Self { + NumericType::Known(UnitType::Length(value)) + } +} + +impl From for NumericType { + fn from(value: UnitAngle) -> Self { + NumericType::Known(UnitType::Angle(value)) + } +} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type")] +pub enum UnitType { + Count, + Length(UnitLen), + Angle(UnitAngle), +} + +impl std::fmt::Display for UnitType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitType::Count => write!(f, "_"), + UnitType::Length(l) => l.fmt(f), + UnitType::Angle(a) => a.fmt(f), + } + } +} + +// TODO called UnitLen so as not to clash with UnitLength in settings) +/// A unit of length. +#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] +#[ts(export)] +#[serde(tag = "type")] +pub enum UnitLen { + #[default] + Mm, + Cm, + M, + Inches, + Feet, + Yards, +} + +impl std::fmt::Display for UnitLen { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitLen::Mm => write!(f, "mm"), + UnitLen::Cm => write!(f, "cm"), + UnitLen::M => write!(f, "m"), + UnitLen::Inches => write!(f, "in"), + UnitLen::Feet => write!(f, "ft"), + UnitLen::Yards => write!(f, "yd"), + } + } +} + +impl TryFrom for UnitLen { + type Error = (); + + fn try_from(suffix: NumericSuffix) -> std::result::Result { + match suffix { + NumericSuffix::Mm => Ok(Self::Mm), + NumericSuffix::Cm => Ok(Self::Cm), + NumericSuffix::M => Ok(Self::M), + NumericSuffix::Inch => Ok(Self::Inches), + NumericSuffix::Ft => Ok(Self::Feet), + NumericSuffix::Yd => Ok(Self::Yards), + _ => Err(()), + } + } +} + +impl From for UnitLen { + fn from(unit: crate::UnitLength) -> Self { + match unit { + crate::UnitLength::Cm => UnitLen::Cm, + crate::UnitLength::Ft => UnitLen::Feet, + crate::UnitLength::In => UnitLen::Inches, + crate::UnitLength::M => UnitLen::M, + crate::UnitLength::Mm => UnitLen::Mm, + crate::UnitLength::Yd => UnitLen::Yards, + } + } +} + +impl From for crate::UnitLength { + fn from(unit: UnitLen) -> Self { + match unit { + UnitLen::Cm => crate::UnitLength::Cm, + UnitLen::Feet => crate::UnitLength::Ft, + UnitLen::Inches => crate::UnitLength::In, + UnitLen::M => crate::UnitLength::M, + UnitLen::Mm => crate::UnitLength::Mm, + UnitLen::Yards => crate::UnitLength::Yd, + } + } +} + +impl From for kittycad_modeling_cmds::units::UnitLength { + fn from(unit: UnitLen) -> Self { + match unit { + UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters, + UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet, + UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches, + UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters, + UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters, + UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards, + } + } +} + +/// A unit of angle. +#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] +#[ts(export)] +#[serde(tag = "type")] +pub enum UnitAngle { + #[default] + Degrees, + Radians, +} + +impl std::fmt::Display for UnitAngle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitAngle::Degrees => write!(f, "deg"), + UnitAngle::Radians => write!(f, "rad"), + } + } +} + +impl TryFrom for UnitAngle { + type Error = (); + + fn try_from(suffix: NumericSuffix) -> std::result::Result { + match suffix { + NumericSuffix::Deg => Ok(Self::Degrees), + NumericSuffix::Rad => Ok(Self::Radians), + _ => Err(()), + } + } +} + +impl KclValue { + /// True if `self` has a type which is a subtype of `ty` without coercion. + pub fn has_type(&self, ty: &RuntimeType) -> bool { + let Some(self_ty) = self.principal_type() else { + return false; + }; + + self_ty.subtype(ty) + } + + /// Coerce `self` to a new value which has `ty` as it's closest supertype. + /// + /// If the result is Some, then: + /// - result.principal_type().unwrap().subtype(ty) + /// + /// If self.principal_type() == ty then result == self + pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option { + match ty { + RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state), + RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state), + RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state), + RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state), + RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state), + } + } + + fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option { + let value = match self { + KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0], + _ => self, + }; + match ty { + // TODO numeric type coercions + PrimitiveType::Number(_ty) => match value { + KclValue::Number { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::String => match value { + KclValue::String { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::Boolean => match value { + KclValue::Bool { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::Sketch => match value { + KclValue::Sketch { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::Solid => match value { + KclValue::Solid { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::Plane => match value { + KclValue::Plane { .. } => Some(value.clone()), + KclValue::Object { value, meta } => { + let origin = value.get("origin").and_then(Point3d::from_kcl_val)?; + let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?; + let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?; + let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?; + + let id = exec_state.mod_local.id_generator.next_uuid(); + let plane = Plane { + id, + artifact_id: id.into(), + origin, + x_axis, + y_axis, + z_axis, + value: super::PlaneType::Uninit, + // TODO use length unit from origin + units: exec_state.length_unit(), + meta: meta.clone(), + }; + + Some(KclValue::Plane { value: Box::new(plane) }) + } + _ => None, + }, + PrimitiveType::Face => match value { + KclValue::Face { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::Helix => match value { + KclValue::Helix { .. } => Some(value.clone()), + _ => None, + }, + PrimitiveType::ImportedGeometry => match value { + 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: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option { + match self { + KclValue::HomArray { value, ty: aty } if aty == ty => { + let value = match len { + ArrayLen::None => value.clone(), + ArrayLen::NonEmpty => { + if value.is_empty() { + return None; + } + + value.clone() + } + ArrayLen::Known(n) => { + if n != value.len() { + return None; + } + + value[..n].to_vec() + } + }; + + Some(KclValue::HomArray { value, ty: ty.clone() }) + } + value if len.satisfied(1) && value.has_type(ty) => Some(KclValue::HomArray { + value: vec![value.clone()], + ty: ty.clone(), + }), + KclValue::MixedArray { value, .. } => { + let value = match len { + ArrayLen::None => value.clone(), + ArrayLen::NonEmpty => { + if value.is_empty() { + return None; + } + + value.clone() + } + ArrayLen::Known(n) => { + if n != value.len() { + return None; + } + + value[..n].to_vec() + } + }; + + let value = value + .iter() + .map(|v| v.coerce(ty, exec_state)) + .collect::>>()?; + + Some(KclValue::HomArray { value, ty: ty.clone() }) + } + KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray { + value: Vec::new(), + ty: ty.clone(), + }), + _ => None, + } + } + + 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() => { + let mut result = Vec::new(); + for (i, t) in tys.iter().enumerate() { + result.push(value[i].coerce(t, exec_state)?); + } + + Some(KclValue::MixedArray { + value: result, + meta: Vec::new(), + }) + } + KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray { + value: Vec::new(), + meta: meta.clone(), + }), + value if tys.len() == 1 && value.has_type(&tys[0]) => Some(KclValue::MixedArray { + value: vec![value.clone()], + meta: Vec::new(), + }), + _ => None, + } + } + + fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option { + for t in tys { + if let Some(v) = self.coerce(t, exec_state) { + return Some(v); + } + } + + None + } + + fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option { + match self { + KclValue::Object { value, .. } => { + for (s, t) in tys { + // TODO coerce fields + if !value.get(s)?.has_type(t) { + return None; + } + } + // TODO remove non-required fields + Some(self.clone()) + } + KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::Object { + value: HashMap::new(), + meta: meta.clone(), + }), + _ => None, + } + } + + pub fn principal_type(&self) -> Option { + match self { + KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)), + KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))), + KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)), + KclValue::Object { value, .. } => { + let properties = value + .iter() + .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t))) + .collect::>>()?; + Some(RuntimeType::Object(properties)) + } + KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)), + KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)), + KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)), + KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)), + KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)), + KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), + KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple( + value.iter().map(|v| v.principal_type()).collect::>>()?, + )), + 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::Function { .. } + | KclValue::Module { .. } + | KclValue::KclNone { .. } + | KclValue::Type { .. } + | KclValue::Uuid { .. } => None, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn values(exec_state: &mut ExecState) -> Vec { + vec![ + KclValue::Bool { + value: true, + meta: Vec::new(), + }, + KclValue::Number { + value: 1.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::String { + value: "hello".to_owned(), + meta: Vec::new(), + }, + KclValue::MixedArray { + value: Vec::new(), + meta: Vec::new(), + }, + KclValue::HomArray { + value: Vec::new(), + ty: RuntimeType::solid(), + }, + KclValue::Object { + value: crate::execution::KclObjectFields::new(), + meta: Vec::new(), + }, + KclValue::TagIdentifier(Box::new("foo".parse().unwrap())), + KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))), + KclValue::Plane { + value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state)), + }, + // No easy way to make a Face, Sketch, Solid, or Helix + KclValue::ImportedGeometry(crate::execution::ImportedGeometry { + id: uuid::Uuid::nil(), + value: Vec::new(), + meta: Vec::new(), + }), + // Other values don't have types + ] + } + + #[track_caller] + fn assert_coerce_results( + value: &KclValue, + super_type: &RuntimeType, + expected_value: &KclValue, + exec_state: &mut ExecState, + ) { + let is_subtype = value == expected_value; + assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value); + assert_eq!( + is_subtype, + value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type), + "{:?} <: {super_type:?} should be {is_subtype}", + value.principal_type().unwrap() + ); + assert!( + expected_value.principal_type().unwrap().subtype(super_type), + "{} <: {super_type}", + expected_value.principal_type().unwrap() + ) + } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_idempotent() { + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let values = values(&mut exec_state); + for v in &values { + // Identity subtype + let ty = v.principal_type().unwrap(); + assert_coerce_results(v, &ty, v, &mut exec_state); + + // Union subtype + let uty1 = RuntimeType::Union(vec![ty.clone()]); + let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]); + assert_coerce_results(v, &uty1, v, &mut exec_state); + assert_coerce_results(v, &uty2, v, &mut exec_state); + + // Array subtypes + let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None); + let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1)); + let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty); + + assert_coerce_results( + v, + &aty, + &KclValue::HomArray { + value: vec![v.clone()], + ty: ty.clone(), + }, + &mut exec_state, + ); + assert_coerce_results( + v, + &aty1, + &KclValue::HomArray { + value: vec![v.clone()], + ty: ty.clone(), + }, + &mut exec_state, + ); + assert_coerce_results( + v, + &aty0, + &KclValue::HomArray { + value: vec![v.clone()], + ty: ty.clone(), + }, + &mut exec_state, + ); + + // Tuple subtype + let tty = RuntimeType::Tuple(vec![ty.clone()]); + assert_coerce_results( + v, + &tty, + &KclValue::MixedArray { + value: vec![v.clone()], + meta: Vec::new(), + }, + &mut exec_state, + ); + } + + for v in &values[1..] { + // Not a subtype + assert!(v + .coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state) + .is_none()); + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_none() { + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let none = KclValue::KclNone { + value: crate::parsing::ast::types::KclNone::new(), + meta: Vec::new(), + }; + + let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None); + let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0)); + let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1)); + let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty); + assert_coerce_results( + &none, + &aty, + &KclValue::HomArray { + value: Vec::new(), + ty: RuntimeType::solid(), + }, + &mut exec_state, + ); + assert_coerce_results( + &none, + &aty0, + &KclValue::HomArray { + value: Vec::new(), + ty: RuntimeType::solid(), + }, + &mut exec_state, + ); + assert!(none.coerce(&aty1, &mut exec_state).is_none()); + assert!(none.coerce(&aty1p, &mut exec_state).is_none()); + + let tty = RuntimeType::Tuple(vec![]); + let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]); + assert_coerce_results( + &none, + &tty, + &KclValue::MixedArray { + value: Vec::new(), + meta: Vec::new(), + }, + &mut exec_state, + ); + assert!(none.coerce(&tty1, &mut exec_state).is_none()); + + let oty = RuntimeType::Object(vec![]); + assert_coerce_results( + &none, + &oty, + &KclValue::Object { + value: HashMap::new(), + meta: Vec::new(), + }, + &mut exec_state, + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_record() { + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + + let obj0 = KclValue::Object { + value: HashMap::new(), + meta: Vec::new(), + }; + let obj1 = KclValue::Object { + value: [( + "foo".to_owned(), + KclValue::Bool { + value: true, + meta: Vec::new(), + }, + )] + .into(), + meta: Vec::new(), + }; + let obj2 = KclValue::Object { + value: [ + ( + "foo".to_owned(), + KclValue::Bool { + value: true, + meta: Vec::new(), + }, + ), + ( + "bar".to_owned(), + KclValue::Number { + value: 0.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + ), + ( + "baz".to_owned(), + KclValue::Number { + value: 42.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + ), + ] + .into(), + meta: Vec::new(), + }; + + let ty0 = RuntimeType::Object(vec![]); + assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state); + assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state); + assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state); + + let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]); + assert!(&obj0.coerce(&ty1, &mut exec_state).is_none()); + assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state); + assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state); + + // Different ordering, (TODO - test for covariance once implemented) + let ty2 = RuntimeType::Object(vec![ + ( + "bar".to_owned(), + RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + ), + ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)), + ]); + assert!(&obj0.coerce(&ty2, &mut exec_state).is_none()); + assert!(&obj1.coerce(&ty2, &mut exec_state).is_none()); + assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state); + + // field not present + let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]); + assert!(&obj0.coerce(&tyq, &mut exec_state).is_none()); + assert!(&obj1.coerce(&tyq, &mut exec_state).is_none()); + assert!(&obj2.coerce(&tyq, &mut exec_state).is_none()); + + // field with different type + let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]); + assert!(&obj2.coerce(&ty1, &mut exec_state).is_none()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_array() { + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + + let hom_arr = KclValue::HomArray { + value: vec![ + KclValue::Number { + value: 0.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Number { + value: 1.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Number { + value: 2.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Number { + value: 3.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + ], + ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + }; + let mixed1 = KclValue::MixedArray { + value: vec![ + KclValue::Number { + value: 0.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Number { + value: 1.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + ], + meta: Vec::new(), + }; + let mixed2 = KclValue::MixedArray { + value: vec![ + KclValue::Number { + value: 0.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Bool { + value: true, + meta: Vec::new(), + }, + ], + meta: Vec::new(), + }; + + // Principal types + let tyh = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::Known(4), + ); + let tym1 = RuntimeType::Tuple(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + ]); + let tym2 = RuntimeType::Tuple(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + RuntimeType::Primitive(PrimitiveType::Boolean), + ]); + assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state); + assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state); + assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state); + assert!(&mixed1.coerce(&tym2, &mut exec_state).is_none()); + assert!(&mixed2.coerce(&tym1, &mut exec_state).is_none()); + + // Length subtyping + let tyhn = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::None, + ); + let tyh1 = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::NonEmpty, + ); + let tyh3 = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::Known(3), + ); + assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state); + assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state); + assert!(&hom_arr.coerce(&tyh3, &mut exec_state).is_none()); + + let hom_arr0 = KclValue::HomArray { + value: vec![], + ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + }; + assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state); + assert!(&hom_arr0.coerce(&tyh1, &mut exec_state).is_none()); + assert!(&hom_arr0.coerce(&tyh3, &mut exec_state).is_none()); + + // Covariance + // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4)); + let tym1 = RuntimeType::Tuple(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + ]); + let tym2 = RuntimeType::Tuple(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean), + ]); + // TODO implement covariance for homogeneous arrays + // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state); + assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state); + assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state); + + // Mixed to homogeneous + let hom_arr_2 = KclValue::HomArray { + value: vec![ + KclValue::Number { + value: 0.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + KclValue::Number { + value: 1.0, + ty: NumericType::count(), + meta: Vec::new(), + }, + ], + ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), + }; + let mixed0 = KclValue::MixedArray { + value: vec![], + meta: Vec::new(), + }; + assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state); + assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state); + assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state); + assert!(&mixed0.coerce(&tyh, &mut exec_state).is_none()); + assert!(&mixed0.coerce(&tyh1, &mut exec_state).is_none()); + + // Homogehous to mixed + assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state); + assert!(&hom_arr.coerce(&tym1, &mut exec_state).is_none()); + assert!(&hom_arr_2.coerce(&tym2, &mut exec_state).is_none()); + + assert!(&mixed0.coerce(&tym1, &mut exec_state).is_none()); + assert!(&mixed0.coerce(&tym2, &mut exec_state).is_none()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_union() { + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + + // Subtyping smaller unions + assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean) + ]))); + assert!( + RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype( + &RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean) + ]) + ) + ); + assert!(RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean) + ]) + .subtype(&RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean) + ]))); + + // Covariance + let count = KclValue::Number { + value: 1.0, + ty: NumericType::count(), + meta: Vec::new(), + }; + + let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]); + let tya2 = RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)), + RuntimeType::Primitive(PrimitiveType::Boolean), + ]); + assert_coerce_results(&count, &tya, &count, &mut exec_state); + assert_coerce_results(&count, &tya2, &count, &mut exec_state); + + // No matching type + let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]); + let tyb2 = RuntimeType::Union(vec![ + RuntimeType::Primitive(PrimitiveType::Boolean), + RuntimeType::Primitive(PrimitiveType::String), + ]); + assert!(count.coerce(&tyb, &mut exec_state).is_none()); + assert!(count.coerce(&tyb2, &mut exec_state).is_none()); + } +} diff --git a/rust/kcl-lib/src/lsp/kcl/hover.rs b/rust/kcl-lib/src/lsp/kcl/hover.rs index 2bb754459..4904419d7 100644 --- a/rust/kcl-lib/src/lsp/kcl/hover.rs +++ b/rust/kcl-lib/src/lsp/kcl/hover.rs @@ -344,8 +344,8 @@ impl Node { let range = self.as_source_range(); if range.contains(pos) { match &self.inner { - Type::Array(t) | Type::Primitive(t) => { - let mut name = t.to_string(); + Type::Array { ty, .. } | Type::Primitive(ty) => { + let mut name = ty.to_string(); if name.ends_with(')') { name.truncate(name.find('(').unwrap()); } @@ -379,7 +379,7 @@ impl FunctionExpression { if let Some(value) = self.body.get_expr_for_position(pos) { let mut vars = opts.vars.clone().unwrap_or_default(); for arg in &self.params { - let ty = arg.type_.as_ref().map(|ty| ty.recast(&FormatOptions::default(), 0)); + let ty = arg.type_.as_ref().map(|ty| ty.to_string()); vars.insert(arg.identifier.inner.name.clone(), ty); } return value.get_hover_value_for_position( diff --git a/rust/kcl-lib/src/lsp/tests.rs b/rust/kcl-lib/src/lsp/tests.rs index d7258410a..e0f2dee63 100644 --- a/rust/kcl-lib/src/lsp/tests.rs +++ b/rust/kcl-lib/src/lsp/tests.rs @@ -1900,7 +1900,7 @@ async fn test_kcl_lsp_diagnostic_has_errors() { assert_eq!(diagnostics.full_document_diagnostic_report.items.len(), 1); assert_eq!( diagnostics.full_document_diagnostic_report.items[0].message, - "lexical: found unknown token ';'" + "Unexpected token: ;" ); } else { panic!("Expected full diagnostics"); diff --git a/rust/kcl-lib/src/parsing/ast/digest.rs b/rust/kcl-lib/src/parsing/ast/digest.rs index aa7031022..1c46e5617 100644 --- a/rust/kcl-lib/src/parsing/ast/digest.rs +++ b/rust/kcl-lib/src/parsing/ast/digest.rs @@ -194,9 +194,21 @@ impl Type { hasher.update(b"FnArgType::Primitive"); hasher.update(prim.compute_digest()) } - Type::Array(prim) => { + Type::Array { ty, len } => { hasher.update(b"FnArgType::Array"); - hasher.update(prim.compute_digest()) + hasher.update(ty.compute_digest()); + match len { + crate::execution::types::ArrayLen::None => {} + crate::execution::types::ArrayLen::NonEmpty => hasher.update(usize::MAX.to_ne_bytes()), + crate::execution::types::ArrayLen::Known(n) => hasher.update(n.to_ne_bytes()), + } + } + Type::Union { tys } => { + hasher.update(b"FnArgType::Union"); + hasher.update(tys.len().to_ne_bytes()); + for t in tys.iter_mut() { + hasher.update(t.compute_digest()); + } } Type::Object { properties } => { hasher.update(b"FnArgType::Object"); @@ -300,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 b41853459..89531d78d 100644 --- a/rust/kcl-lib/src/parsing/ast/types/mod.rs +++ b/rust/kcl-lib/src/parsing/ast/types/mod.rs @@ -25,7 +25,7 @@ pub use crate::parsing::ast::types::{ use crate::{ docs::StdLibFn, errors::KclError, - execution::{annotations, KclValue, Metadata, TagIdentifier}, + execution::{annotations, types::ArrayLen, KclValue, Metadata, TagIdentifier}, parsing::{ast::digest::Digest, token::NumericSuffix, PIPE_OPERATOR}, source_range::SourceRange, ModuleId, @@ -150,7 +150,7 @@ impl Node { self.start <= pos && pos <= self.end } - pub fn map(self, f: fn(T) -> U) -> Node { + pub fn map(self, f: impl Fn(T) -> U) -> Node { Node { inner: f(self.inner), start: self.start, @@ -1914,6 +1914,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)] @@ -3043,7 +3044,14 @@ pub enum Type { /// A primitive type. Primitive(PrimitiveType), // An array of a primitive type. - Array(PrimitiveType), + Array { + ty: PrimitiveType, + len: ArrayLen, + }, + // Union/enum types + Union { + tys: NodeList, + }, // An object type. Object { properties: Vec, @@ -3054,7 +3062,22 @@ impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Type::Primitive(primitive_type) => primitive_type.fmt(f), - Type::Array(primitive_type) => write!(f, "[{primitive_type}]"), + Type::Array { ty, len } => { + write!(f, "[{ty}")?; + match len { + ArrayLen::None => {} + ArrayLen::NonEmpty => write!(f, "; 1+")?, + ArrayLen::Known(n) => write!(f, "; {n}")?, + } + write!(f, "]") + } + Type::Union { tys } => { + write!( + f, + "{}", + tys.iter().map(|t| t.to_string()).collect::>().join(" | ") + ) + } Type::Object { properties } => { write!(f, "{{")?; let mut first = true; @@ -3671,11 +3694,17 @@ const cylinder = startSketchOn('-XZ') assert_eq!(params.len(), 3); assert_eq!( params[0].type_.as_ref().unwrap().inner, - Type::Array(PrimitiveType::Number(NumericSuffix::None)) + Type::Array { + ty: PrimitiveType::Number(NumericSuffix::None), + len: ArrayLen::None + } ); assert_eq!( params[1].type_.as_ref().unwrap().inner, - Type::Array(PrimitiveType::String) + Type::Array { + ty: PrimitiveType::String, + len: ArrayLen::None + } ); assert_eq!( params[2].type_.as_ref().unwrap().inner, @@ -3703,7 +3732,10 @@ const cylinder = startSketchOn('-XZ') assert_eq!(params.len(), 3); assert_eq!( params[0].type_.as_ref().unwrap().inner, - Type::Array(PrimitiveType::Number(NumericSuffix::None)) + Type::Array { + ty: PrimitiveType::Number(NumericSuffix::None), + len: ArrayLen::None + } ); assert_eq!( params[1].type_.as_ref().unwrap().inner, @@ -3739,7 +3771,15 @@ const cylinder = startSketchOn('-XZ') 56, module_id, ), - type_: Some(Node::new(Type::Array(PrimitiveType::String), 59, 65, module_id)), + type_: Some(Node::new( + Type::Array { + ty: PrimitiveType::String, + len: ArrayLen::None + }, + 59, + 65, + module_id + )), default_value: None, labeled: true, digest: None @@ -3820,7 +3860,15 @@ const cylinder = startSketchOn('-XZ') 34, module_id, ), - type_: Some(Node::new(Type::Array(PrimitiveType::String), 37, 43, module_id)), + type_: Some(Node::new( + Type::Array { + ty: PrimitiveType::String, + len: ArrayLen::None + }, + 37, + 43, + module_id + )), default_value: None, labeled: true, digest: None @@ -3993,7 +4041,7 @@ startSketchOn('XY')"#; assert_eq!( meta_settings.default_length_units, - crate::execution::kcl_value::UnitLen::Inches + crate::execution::types::UnitLen::Inches ); } @@ -4009,13 +4057,13 @@ startSketchOn('XY')"#; assert_eq!( meta_settings.default_length_units, - crate::execution::kcl_value::UnitLen::Inches + crate::execution::types::UnitLen::Inches ); // Edit the ast. let new_program = program .change_meta_settings(crate::execution::MetaSettings { - default_length_units: crate::execution::kcl_value::UnitLen::Mm, + default_length_units: crate::execution::types::UnitLen::Mm, ..Default::default() }) .unwrap(); @@ -4024,10 +4072,7 @@ startSketchOn('XY')"#; assert!(result.is_some()); let meta_settings = result.unwrap(); - assert_eq!( - meta_settings.default_length_units, - crate::execution::kcl_value::UnitLen::Mm - ); + assert_eq!(meta_settings.default_length_units, crate::execution::types::UnitLen::Mm); let formatted = new_program.recast(&Default::default(), 0); @@ -4050,7 +4095,7 @@ startSketchOn('XY') // Edit the ast. let new_program = program .change_meta_settings(crate::execution::MetaSettings { - default_length_units: crate::execution::kcl_value::UnitLen::Mm, + default_length_units: crate::execution::types::UnitLen::Mm, ..Default::default() }) .unwrap(); @@ -4059,10 +4104,7 @@ startSketchOn('XY') assert!(result.is_some()); let meta_settings = result.unwrap(); - assert_eq!( - meta_settings.default_length_units, - crate::execution::kcl_value::UnitLen::Mm - ); + assert_eq!(meta_settings.default_length_units, crate::execution::types::UnitLen::Mm); let formatted = new_program.recast(&Default::default(), 0); diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index fdd7ec1d1..2a9098f4a 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -19,6 +19,7 @@ use super::{ use crate::{ docs::StdLibFn, errors::{CompilationError, Severity, Tag}, + execution::types::ArrayLen, parsing::{ ast::types::{ Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, @@ -2215,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); @@ -2228,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, }, @@ -2336,21 +2354,7 @@ impl TryFrom for Node { format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()), )), - TokenType::Bang - | TokenType::At - | TokenType::Hash - | TokenType::Colon - | TokenType::Period - | TokenType::Operator - | TokenType::DoublePeriod - | TokenType::QuestionMark - | TokenType::BlockComment - | TokenType::Function - | TokenType::String - | TokenType::Dollar - | TokenType::Keyword - | TokenType::Unknown - | TokenType::LineComment => Err(CompilationError::fatal( + _ => Err(CompilationError::fatal( token.as_source_range(), // this is `start with` because if most of these cases are in the middle, it ends // up hitting a different error path(e.g. including a bang) or being valid(e.g. including a comment) since it will get broken up into @@ -2617,6 +2621,14 @@ fn colon(i: &mut TokenSlice) -> PResult { TokenType::Colon.parse_from(i) } +fn semi_colon(i: &mut TokenSlice) -> PResult { + TokenType::SemiColon.parse_from(i) +} + +fn plus(i: &mut TokenSlice) -> PResult { + one_of((TokenType::Operator, "+")).parse_next(i) +} + fn equals(i: &mut TokenSlice) -> PResult { one_of((TokenType::Operator, "=")) .context(expected("the equals operator, =")) @@ -2659,6 +2671,12 @@ fn comma_sep(i: &mut TokenSlice) -> PResult<()> { Ok(()) } +/// Parse a `|`, optionally followed by some whitespace. +fn pipe_sep(i: &mut TokenSlice) -> PResult<()> { + (opt(whitespace), one_of((TokenType::Operator, "|")), opt(whitespace)).parse_next(i)?; + Ok(()) +} + /// Arguments are passed into a function. fn arguments(i: &mut TokenSlice) -> PResult> { separated(0.., expression, comma_sep) @@ -2685,7 +2703,15 @@ 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)| { + (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, @@ -2694,12 +2720,21 @@ fn argument_type(i: &mut TokenSlice) -> PResult> { )) }), // Array types - (open_bracket, primitive_type, close_bracket).map(|(_, t, _)| Ok(t.map(Type::Array))), - // Primitive types - primitive_type.map(|t| Ok(t.map(Type::Primitive))), + array_type, + // Primitive or union types + separated(1.., primitive_type, pipe_sep).map(|mut tys: Vec<_>| { + if tys.len() == 1 { + tys.pop().unwrap().map(Type::Primitive) + } else { + let start = tys[0].start; + let module_id = tys[0].module_id; + let end = tys.last().unwrap().end; + Node::new(Type::Union { tys }, start, end, module_id) + } + }), )) - .parse_next(i)? - .map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?; + .parse_next(i)?; + Ok(type_) } @@ -2721,6 +2756,55 @@ fn primitive_type(i: &mut TokenSlice) -> PResult> { Ok(result) } +fn array_type(i: &mut TokenSlice) -> PResult> { + fn opt_whitespace(i: &mut TokenSlice) -> PResult<()> { + ignore_whitespace(i); + Ok(()) + } + + open_bracket(i)?; + let ty = primitive_type(i)?; + let len = opt(( + semi_colon, + opt_whitespace, + any.try_map(|token: Token| match token.token_type { + TokenType::Number => { + let value = token.uint_value().ok_or_else(|| { + CompilationError::fatal( + token.as_source_range(), + format!("Expected unsigned integer literal, found: {}", token.value), + ) + })?; + + Ok(value as usize) + } + _ => Err(CompilationError::fatal(token.as_source_range(), "invalid array length")), + }), + opt(plus), + )) + .parse_next(i)?; + close_bracket(i)?; + + let len = if let Some((tok, _, n, plus)) = len { + if plus.is_some() { + if n != 1 { + return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal( + tok.as_source_range(), + "Non-empty arrays are specified using `1+`, for a fixed-size array use just an integer", + )))); + } else { + ArrayLen::NonEmpty + } + } else { + ArrayLen::Known(n) + } + } else { + ArrayLen::None + }; + + Ok(ty.map(|ty| Type::Array { ty, len })) +} + fn uom_for_type(i: &mut TokenSlice) -> PResult { any.try_map(|t: Token| t.value.parse()).parse_next(i) } diff --git a/rust/kcl-lib/src/parsing/token/mod.rs b/rust/kcl-lib/src/parsing/token/mod.rs index 3c26bee06..28c3f325d 100644 --- a/rust/kcl-lib/src/parsing/token/mod.rs +++ b/rust/kcl-lib/src/parsing/token/mod.rs @@ -367,6 +367,8 @@ pub enum TokenType { QuestionMark, /// The @ symbol. At, + /// `;` + SemiColon, } /// Most KCL tokens correspond to LSP semantic tokens (but not all). @@ -396,6 +398,7 @@ impl TryFrom for SemanticTokenType { | TokenType::Hash | TokenType::Dollar | TokenType::At + | TokenType::SemiColon | TokenType::Unknown => { anyhow::bail!("unsupported token type: {:?}", token_type) } @@ -488,6 +491,18 @@ impl Token { value.parse().ok() } + pub fn uint_value(&self) -> Option { + if self.token_type != TokenType::Number { + return None; + } + let value = &self.value; + let value = value + .split_once(|c: char| c == '_' || c.is_ascii_alphabetic()) + .map(|(s, _)| s) + .unwrap_or(value); + value.parse().ok() + } + pub fn numeric_suffix(&self) -> NumericSuffix { if self.token_type != TokenType::Number { return NumericSuffix::None; diff --git a/rust/kcl-lib/src/parsing/token/tokeniser.rs b/rust/kcl-lib/src/parsing/token/tokeniser.rs index 1caaf70e5..8e46014e0 100644 --- a/rust/kcl-lib/src/parsing/token/tokeniser.rs +++ b/rust/kcl-lib/src/parsing/token/tokeniser.rs @@ -88,6 +88,7 @@ pub(super) fn token(i: &mut Input<'_>) -> PResult { '@' => at, '0'..='9' => number, ':' => colon, + ';' => semi_colon, '.' => alt((number, double_period, period)), '#' => hash, '$' => dollar, @@ -282,6 +283,16 @@ fn colon(i: &mut Input<'_>) -> PResult { )) } +fn semi_colon(i: &mut Input<'_>) -> PResult { + let (value, range) = ';'.with_span().parse_next(i)?; + Ok(Token::from_range( + range, + i.state.module_id, + TokenType::SemiColon, + value.to_string(), + )) +} + fn period(i: &mut Input<'_>) -> PResult { let (value, range) = '.'.with_span().parse_next(i)?; Ok(Token::from_range( @@ -689,7 +700,7 @@ const things = "things" #[test] fn test_unrecognized_token() { let module_id = ModuleId::from_usize(1); - let actual = lex("12 ; 8", module_id).unwrap(); + let actual = lex("12 ~ 8", module_id).unwrap(); use TokenType::*; assert_tokens(&[(Number, 0, 2), (Unknown, 3, 4), (Number, 5, 6)], actual.as_slice()); diff --git a/rust/kcl-lib/src/std/appearance.rs b/rust/kcl-lib/src/std/appearance.rs index efb7e27a3..c19620bf9 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::{types::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..7c93af731 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -12,9 +12,10 @@ use serde::{Deserialize, Serialize}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, - ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch, - SketchSurface, Solid, TagIdentifier, + kcl_value::FunctionSource, + types::{NumericType, PrimitiveType, RuntimeType}, + ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface, + Solid, TagIdentifier, }, parsing::ast::types::TagNode, source_range::SourceRange, @@ -188,8 +189,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(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => Some( "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`", ), _ => None, @@ -309,8 +312,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 +602,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 +690,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/chamfer.rs b/rust/kcl-lib/src/std/chamfer.rs index 083a00bce..7d4cdf1bb 100644 --- a/rust/kcl-lib/src/std/chamfer.rs +++ b/rust/kcl-lib/src/std/chamfer.rs @@ -8,8 +8,8 @@ use kittycad_modeling_cmds as kcmc; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::RuntimeType, ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, PrimitiveType, - Solid, + types::{PrimitiveType, RuntimeType}, + ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, Solid, }, parsing::ast::types::TagNode, std::{fillet::EdgeReference, Args}, diff --git a/rust/kcl-lib/src/std/csg.rs b/rust/kcl-lib/src/std/csg.rs index 49e2b323f..238828e31 100644 --- a/rust/kcl-lib/src/std/csg.rs +++ b/rust/kcl-lib/src/std/csg.rs @@ -5,20 +5,14 @@ use kcl_derive_docs::stdlib; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Solid, - }, + execution::{types::RuntimeType, ExecState, KclValue, Solid}, std::Args, }; /// Union two or more solids into a single solid. pub async fn union(exec_state: &mut ExecState, args: Args) -> Result { - let solids: Vec = args.get_unlabeled_kw_arg_typed( - "objects", - &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]), - exec_state, - )?; + let solids: Vec = + args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::Union(vec![RuntimeType::solids()]), exec_state)?; if solids.len() < 2 { return Err(KclError::UndefinedValue(KclErrorDetails { @@ -74,11 +68,7 @@ async fn inner_union(solids: Vec, exec_state: &mut ExecState, args: Args) /// Intersect returns the shared volume between multiple solids, preserving only /// overlapping regions. pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result { - let solids: Vec = args.get_unlabeled_kw_arg_typed( - "objects", - &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]), - exec_state, - )?; + let solids: Vec = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; if solids.len() < 2 { return Err(KclError::UndefinedValue(KclErrorDetails { @@ -139,16 +129,8 @@ async fn inner_intersect(solids: Vec, exec_state: &mut ExecState, args: A /// Subtract removes tool solids from base solids, leaving the remaining material. pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result { - let solids: Vec = args.get_unlabeled_kw_arg_typed( - "objects", - &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]), - exec_state, - )?; - let tools: Vec = args.get_kw_arg_typed( - "tools", - &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]), - exec_state, - )?; + let solids: Vec = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?; + let tools: Vec = args.get_kw_arg_typed("tools", &RuntimeType::solids(), exec_state)?; let solids = inner_subtract(solids, tools, exec_state, args).await?; Ok(solids.into()) diff --git a/rust/kcl-lib/src/std/extrude.rs b/rust/kcl-lib/src/std/extrude.rs index db76cc51f..03a10e6bb 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, + types::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/fillet.rs b/rust/kcl-lib/src/std/fillet.rs index 33424a055..e210fa690 100644 --- a/rust/kcl-lib/src/std/fillet.rs +++ b/rust/kcl-lib/src/std/fillet.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::RuntimeType, EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, PrimitiveType, - Solid, TagIdentifier, + types::{PrimitiveType, RuntimeType}, + EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier, }, parsing::ast::types::TagNode, settings::types::UnitLength, diff --git a/rust/kcl-lib/src/std/loft.rs b/rust/kcl-lib/src/std/loft.rs index 22747bcc9..86ed5db35 100644 --- a/rust/kcl-lib/src/std/loft.rs +++ b/rust/kcl-lib/src/std/loft.rs @@ -9,10 +9,7 @@ use kittycad_modeling_cmds as kcmc; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Sketch, Solid, - }, + execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid}, parsing::ast::types::TagNode, std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, }; @@ -21,11 +18,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/mod.rs b/rust/kcl-lib/src/std/mod.rs index 16f03c0f1..e2af54d5e 100644 --- a/rust/kcl-lib/src/std/mod.rs +++ b/rust/kcl-lib/src/std/mod.rs @@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize}; use crate::{ docs::StdLibFn, errors::KclError, - execution::{ExecState, KclValue}, + execution::{types::PrimitiveType, ExecState, KclValue}, }; pub type StdFn = fn( @@ -207,20 +207,13 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp } } -pub(crate) fn std_ty(path: &str, fn_name: &str) -> (crate::execution::PrimitiveType, StdFnProps) { +pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) { match (path, fn_name) { - ("prelude", "Sketch") => ( - crate::execution::PrimitiveType::Sketch, - StdFnProps::default("std::Sketch"), - ), - ("prelude", "Solid") => ( - crate::execution::PrimitiveType::Solid, - StdFnProps::default("std::Solid"), - ), - ("prelude", "Plane") => ( - crate::execution::PrimitiveType::Plane, - StdFnProps::default("std::Plane"), - ), + ("prelude", "Sketch") => (PrimitiveType::Sketch, StdFnProps::default("std::Sketch")), + ("prelude", "Solid") => (PrimitiveType::Solid, StdFnProps::default("std::Solid")), + ("prelude", "Plane") => (PrimitiveType::Plane, StdFnProps::default("std::Plane")), + ("prelude", "Face") => (PrimitiveType::Face, StdFnProps::default("std::Face")), + ("prelude", "Helix") => (PrimitiveType::Helix, StdFnProps::default("std::Helix")), _ => unreachable!(), } } diff --git a/rust/kcl-lib/src/std/patterns.rs b/rust/kcl-lib/src/std/patterns.rs index 835ebba79..184bbc7a8 100644 --- a/rust/kcl-lib/src/std/patterns.rs +++ b/rust/kcl-lib/src/std/patterns.rs @@ -20,8 +20,9 @@ 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, + types::{NumericType, RuntimeType}, + ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, Sketch, Solid, }, std::Args, ExecutorContext, SourceRange, @@ -47,11 +48,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 +59,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")?; @@ -664,7 +657,7 @@ impl GeometryTrait for Solid { #[cfg(test)] mod tests { use super::*; - use crate::execution::kcl_value::NumericType; + use crate::execution::types::NumericType; #[test] fn test_array_to_point3d() { @@ -696,11 +689,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 +768,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 +1013,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 +1117,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/revolve.rs b/rust/kcl-lib/src/std/revolve.rs index ded6740a9..872edc1af 100644 --- a/rust/kcl-lib/src/std/revolve.rs +++ b/rust/kcl-lib/src/std/revolve.rs @@ -7,21 +7,14 @@ use kittycad_modeling_cmds::{self as kcmc}; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{ - kcl_value::{ArrayLen, RuntimeType}, - ExecState, KclValue, PrimitiveType, Sketch, Solid, - }, + execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid}, parsing::ast::types::TagNode, std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, fillet::default_tolerance, Args}, }; /// Revolve a sketch or set of sketches around an axis. pub async fn revolve(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 axis: Axis2dOrEdgeReference = args.get_kw_arg("axis")?; let angle = args.get_kw_arg_opt("angle")?; let tolerance = args.get_kw_arg_opt("tolerance")?; diff --git a/rust/kcl-lib/src/std/segment.rs b/rust/kcl-lib/src/std/segment.rs index 1f5be0898..7f4a3f420 100644 --- a/rust/kcl-lib/src/std/segment.rs +++ b/rust/kcl-lib/src/std/segment.rs @@ -6,7 +6,10 @@ use kittycad_modeling_cmds::shared::Angle; use crate::{ errors::{KclError, KclErrorDetails}, - execution::{kcl_value::RuntimeType, ExecState, KclValue, Point2d, PrimitiveType, Sketch, TagIdentifier}, + execution::{ + types::{PrimitiveType, RuntimeType}, + ExecState, KclValue, Point2d, Sketch, TagIdentifier, + }, std::{utils::between, Args}, }; diff --git a/rust/kcl-lib/src/std/shell.rs b/rust/kcl-lib/src/std/shell.rs index 2be881131..618696f7c 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::{types::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/sketch.rs b/rust/kcl-lib/src/std/sketch.rs index 23fe6ae3d..5c06a4b1b 100644 --- a/rust/kcl-lib/src/std/sketch.rs +++ b/rust/kcl-lib/src/std/sketch.rs @@ -14,9 +14,9 @@ use serde::{Deserialize, Serialize}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::RuntimeType, Artifact, ArtifactId, BasePath, CodeRef, ExecState, Face, GeoMeta, KclValue, Path, - Plane, Point2d, Point3d, PrimitiveType, Sketch, SketchSurface, Solid, StartSketchOnFace, StartSketchOnPlane, - TagEngineInfo, TagIdentifier, + types::{PrimitiveType, RuntimeType}, + Artifact, ArtifactId, BasePath, CodeRef, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, + Sketch, SketchSurface, Solid, StartSketchOnFace, StartSketchOnPlane, TagEngineInfo, TagIdentifier, }, parsing::ast::types::TagNode, std::{ diff --git a/rust/kcl-lib/src/std/sweep.rs b/rust/kcl-lib/src/std/sweep.rs index 5921b4666..3c1242c36 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::{types::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..3d9f69774 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::{types::RuntimeType, ExecState, KclValue, SolidOrSketchOrImportedGeometry}, std::Args, }; @@ -25,9 +22,9 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result Result Result { let mut result = e.expr.recast(options, indentation_level, ctxt); result += ": "; - result += &e.ty.recast(options, indentation_level); + result += &e.ty.to_string(); result } Expr::None(_) => { @@ -457,6 +456,10 @@ impl TypeDeclaration { } arg_str.push(')'); } + if let Some(alias) = &self.alias { + arg_str.push_str(" = "); + arg_str.push_str(&alias.to_string()); + } format!("{}type {}{}", vis, self.name.name, arg_str) } } @@ -812,7 +815,7 @@ impl FunctionExpression { let tab0 = options.get_indentation(indentation_level); let tab1 = options.get_indentation(indentation_level + 1); let return_type = match &self.return_type { - Some(rt) => format!(": {}", rt.recast(&new_options, indentation_level)), + Some(rt) => format!(": {rt}"), None => String::new(), }; let body = self.body.recast(&new_options, indentation_level + 1); @@ -822,14 +825,14 @@ impl FunctionExpression { } impl Parameter { - pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { + pub fn recast(&self, _options: &FormatOptions, _indentation_level: usize) -> String { let at_sign = if self.labeled { "" } else { "@" }; let identifier = &self.identifier.name; let question_mark = if self.default_value.is_some() { "?" } else { "" }; let mut result = format!("{at_sign}{identifier}{question_mark}"); if let Some(ty) = &self.type_ { result += ": "; - result += &ty.recast(options, indentation_level); + result += &ty.to_string(); } if let Some(DefaultParamVal::Literal(ref literal)) = self.default_value { let lit = literal.recast(); @@ -840,31 +843,6 @@ impl Parameter { } } -impl Type { - pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { - match self { - Type::Primitive(t) => t.to_string(), - Type::Array(t) => format!("[{t}]"), - Type::Object { properties } => { - let mut result = "{".to_owned(); - for p in properties { - result += " "; - result += &p.recast(options, indentation_level); - result += ","; - } - - if result.ends_with(',') { - result.pop(); - result += " "; - } - result += "}"; - - result - } - } - } -} - /// Collect all the kcl files in a directory, recursively. #[cfg(not(target_arch = "wasm32"))] #[async_recursion::async_recursion] @@ -1414,6 +1392,21 @@ thing(1) assert_eq!(recasted, some_program_string); } + #[test] + fn test_recast_typed_consts() { + let some_program_string = r#"a = 42: number +export b = 3.2: number(ft) +c = "dsfds": A | B | C +d = [1]: [number] +e = foo: [number; 3] +f = [1, 2, 3]: [number; 1+] +"#; + let program = crate::parsing::top_level_parse(some_program_string).unwrap(); + + let recasted = program.recast(&Default::default(), 0); + assert_eq!(recasted, some_program_string); + } + #[test] fn test_recast_object_fn_in_array_weird_bracket() { let some_program_string = r#"bing = { yo = 55 } @@ -2438,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); diff --git a/rust/kcl-lib/std/prelude.kcl b/rust/kcl-lib/std/prelude.kcl index bdc3bd09a..0e5e63829 100644 --- a/rust/kcl-lib/std/prelude.kcl +++ b/rust/kcl-lib/std/prelude.kcl @@ -223,6 +223,26 @@ export type Sketch @(impl = std_rust) export type Solid +/// A face. +@(impl = std_rust) +export type Face + +/// A helix. +@(impl = std_rust) +export type Helix + +/// A point in two dimensional space. +/// +/// `Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value +/// with type `Point2d`, use an array, e.g., `[0, 0]` or `[5.0, 3.14]`. +export type Point2d = [number; 2] + +/// A point in three dimensional space. +/// +/// `Point3d` is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value +/// with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`. +export type Point3d = [number; 3] + export ZERO = 0 export QUARTER_TURN = 90deg export HALF_TURN = 180deg