More types stuff (#5901)

* parse union and fancy array types

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

* type aliases

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

* Treat Helix and Face as primitive types

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

* code motion: factor our execution::types module

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

* Tests for type coercion and subtyping

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

* Add Point2D/3D to std

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

* Rebasing and fixes

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-03-21 10:56:55 +13:00
committed by GitHub
parent 9da8574103
commit 1d550da40b
47 changed files with 1773 additions and 1050 deletions

View File

@ -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)

View File

@ -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 |

View File

@ -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 |

View File

@ -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 |
----

17
docs/kcl/types/Point2d.md Normal file
View File

@ -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]`.

View File

@ -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 |

View File

@ -22,7 +22,6 @@ A path to sweep along.
----
A helix.
[`Helix`](/docs/kcl/types/Helix)

View File

@ -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<ObjectProperty { PropertyName ":" type }> "}" }
}
@ -137,7 +137,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
"(" ")"
"{" "}"
"[" "]"
"," "?" ":" "." ".."
"," "?" ":" "." ".." ";"
}
@external propSource kclHighlight from "./highlight"

View File

@ -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<handlebars::Handlebars<'static>> {
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,

View File

@ -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<String>,
/// The summary of the function.
pub summary: Option<String>,
@ -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();

View File

@ -18,7 +18,7 @@ use tower_lsp::lsp_types::{
};
use crate::{
execution::{kcl_value::NumericType, Sketch},
execution::{types::NumericType, Sketch},
std::Primitive,
};

View File

@ -10,6 +10,12 @@ layout: manual
{{/if}}
{{{summary}}}
{{#if definition}}
```kcl
{{{definition}}}
```
{{/if}}
{{{description}}}

View File

@ -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,
};

View File

@ -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

View File

@ -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,
@ -304,8 +307,9 @@ impl ExecutorContext {
}));
}
};
let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
let value = KclValue::Type {
value: Some(crate::std::std_ty(std_path, &ty.name.name)),
value: TypeDef::RustRepr(t, props),
meta: vec![metadata],
};
exec_state
@ -324,12 +328,40 @@ impl ExecutorContext {
}
// Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
annotations::Impl::Primitive => {}
annotations::Impl::Kcl => {
return Err(KclError::Semantic(KclErrorDetails {
message: "User-defined types are not yet supported.".to_owned(),
source_ranges: vec![metadata.source_range],
}));
}
annotations::Impl::Kcl => match &ty.alias {
Some(alias) => {
let value = KclValue::Type {
value: TypeDef::Alias(
RuntimeType::from_parsed(
alias.inner.clone(),
exec_state,
metadata.source_range,
)
.map_err(|e| KclError::Semantic(e.into()))?,
),
meta: vec![metadata],
};
exec_state
.mut_stack()
.add(
format!("{}{}", memory::TYPE_PREFIX, ty.name.name),
value,
metadata.source_range,
)
.map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!("Redefinition of type {}.", ty.name.name),
source_ranges: vec![metadata.source_range],
})
})?;
}
None => {
return Err(KclError::Semantic(KclErrorDetails {
message: "User-defined types are not yet supported.".to_owned(),
source_ranges: vec![metadata.source_range],
}))
}
},
}
last_expr = None;
@ -646,30 +678,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<Type>, exec_state: &mut ExecState) -> Option<KclValue> {
fn coerce(
value: &KclValue,
ty: &Node<Type>,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<KclValue, KclError> {
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 {

View File

@ -104,7 +104,7 @@ impl From<SolidOrSketchOrImportedGeometry> 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<SolidOrSketchOrImportedGeometry> 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(),
}
}
}

View File

@ -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,

View File

@ -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<KclValue>,
// 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<Metadata>,
},
@ -142,6 +133,12 @@ impl JsonSchema for FunctionSource {
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeDef {
RustRepr(PrimitiveType, StdFnProps),
Alias(RuntimeType),
}
impl From<Vec<Sketch>> for KclValue {
fn from(mut eg: Vec<Sketch>) -> Self {
if eg.len() == 1 {
@ -154,7 +151,7 @@ impl From<Vec<Sketch>> 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<Vec<Solid>> 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<KclValue> {
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<KclValue> {
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<KclValue> {
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::<Option<Vec<_>>>()?;
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<KclValue> {
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<KclValue> {
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<KclValue> {
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<RuntimeType> {
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::<Option<Vec<_>>>()?;
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::<Option<Vec<_>>>()?,
)),
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<RuntimeType>),
Tuple(Vec<PrimitiveType>),
Object(Vec<(String, RuntimeType)>),
}
impl RuntimeType {
pub fn from_parsed(
value: Type,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<Option<Self>, 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::<Result<Option<Vec<_>>, 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::<Vec<_>>()
.join(" or "),
RuntimeType::Tuple(tys) => format!(
"an array with values of types ({})",
tys.iter().map(PrimitiveType::to_string).collect::<Vec<_>>().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<PrimitiveType> {
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::<Vec<_>>().join(", ")
),
RuntimeType::Union(ts) => write!(
f,
"{}",
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
),
RuntimeType::Object(items) => write!(
f,
"{{ {} }}",
items
.iter()
.map(|(n, t)| format!("{n}: {t}"))
.collect::<Vec<_>>()
.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<Option<Self>, 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<UnitLen> for NumericType {
fn from(value: UnitLen) -> Self {
NumericType::Known(UnitType::Length(value))
}
}
impl From<UnitAngle> 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<NumericSuffix> for UnitLen {
type Error = ();
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
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<crate::UnitLength> 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<UnitLen> 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<UnitLen> 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<NumericSuffix> for UnitAngle {
type Error = ();
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
match suffix {
NumericSuffix::Deg => Ok(Self::Degrees),
NumericSuffix::Rad => Ok(Self::Radians),
_ => Err(()),
}
}
}

View File

@ -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()

View File

@ -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},
@ -55,6 +56,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)]
@ -1397,6 +1399,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#"

View File

@ -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,
@ -310,8 +309,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<String>,
}
@ -326,12 +325,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 => {

File diff suppressed because it is too large Load Diff

View File

@ -344,8 +344,8 @@ impl Node<Type> {
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(

View File

@ -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");

View File

@ -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());
}
});
}

View File

@ -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<T> Node<T> {
self.start <= pos && pos <= self.end
}
pub fn map<U>(self, f: fn(T) -> U) -> Node<U> {
pub fn map<U>(self, f: impl Fn(T) -> U) -> Node<U> {
Node {
inner: f(self.inner),
start: self.start,
@ -1895,6 +1895,7 @@ pub struct TypeDeclaration {
pub args: Option<NodeList<Identifier>>,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility,
pub alias: Option<Node<Type>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@ -3024,7 +3025,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<PrimitiveType>,
},
// An object type.
Object {
properties: Vec<Parameter>,
@ -3035,7 +3043,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::<Vec<_>>().join(" | ")
)
}
Type::Object { properties } => {
write!(f, "{{")?;
let mut first = true;
@ -3624,11 +3647,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,
@ -3656,7 +3685,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,
@ -3692,7 +3724,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
@ -3773,7 +3813,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
@ -3946,7 +3994,7 @@ startSketchOn('XY')"#;
assert_eq!(
meta_settings.default_length_units,
crate::execution::kcl_value::UnitLen::Inches
crate::execution::types::UnitLen::Inches
);
}
@ -3962,13 +4010,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();
@ -3977,10 +4025,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);
@ -4003,7 +4048,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();
@ -4012,10 +4057,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);

View File

@ -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<BoxNode<TypeDeclaration>> {
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<BoxNode<TypeDeclaration>> {
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<Token> for Node<TagDeclarator> {
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<Token> {
TokenType::Colon.parse_from(i)
}
fn semi_colon(i: &mut TokenSlice) -> PResult<Token> {
TokenType::SemiColon.parse_from(i)
}
fn plus(i: &mut TokenSlice) -> PResult<Token> {
one_of((TokenType::Operator, "+")).parse_next(i)
}
fn equals(i: &mut TokenSlice) -> PResult<Token> {
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<Vec<Expr>> {
separated(0.., expression, comma_sep)
@ -2685,7 +2703,15 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
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 &params {
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<Node<Type>> {
))
}),
// 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<Node<PrimitiveType>> {
Ok(result)
}
fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
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<NumericSuffix> {
any.try_map(|t: Token| t.value.parse()).parse_next(i)
}

View File

@ -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<TokenType> 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<u32> {
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;

View File

@ -88,6 +88,7 @@ pub(super) fn token(i: &mut Input<'_>) -> PResult<Token> {
'@' => at,
'0'..='9' => number,
':' => colon,
';' => semi_colon,
'.' => alt((number, double_period, period)),
'#' => hash,
'$' => dollar,
@ -282,6 +283,16 @@ fn colon(i: &mut Input<'_>) -> PResult<Token> {
))
}
fn semi_colon(i: &mut Input<'_>) -> PResult<Token> {
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<Token> {
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());

View File

@ -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<KclValue, KclError> {
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<f64> = args.get_kw_arg_opt("metalness")?;

View File

@ -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 {}",

View File

@ -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},

View File

@ -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<KclValue, KclError> {
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
exec_state,
)?;
let solids: Vec<Solid> =
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<Solid>, 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<KclValue, KclError> {
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
exec_state,
)?;
let solids: Vec<Solid> = 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<Solid>, 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<KclValue, KclError> {
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
exec_state,
)?;
let tools: Vec<Solid> = args.get_kw_arg_typed(
"tools",
&RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
exec_state,
)?;
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let tools: Vec<Solid> = args.get_kw_arg_typed("tools", &RuntimeType::solids(), exec_state)?;
let solids = inner_subtract(solids, tools, exec_state, args).await?;
Ok(solids.into())

View File

@ -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<KclValue, KclError> {
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")?;

View File

@ -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,

View File

@ -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<KclValue, KclError> {
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());

View File

@ -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!(),
}
}

View File

@ -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<KclValue, KclError> {
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<bool> = 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<KclValue, KclError> {
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<bool> = 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<KclValue, KclError> {
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<KclValue, KclError> {
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<KclValue, KclError> {
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<KclValue, KclError> {
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.

View File

@ -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<KclValue, KclError> {
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")?;

View File

@ -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},
};

View File

@ -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<KclValue, KclError> {
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")?;

View File

@ -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::{

View File

@ -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<KclValue, KclError> {
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")?;

View File

@ -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<KclValue, K
let objects = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
RuntimeType::sketches(),
RuntimeType::solids(),
RuntimeType::imported(),
]),
exec_state,
)?;
@ -187,9 +184,9 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
let objects = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
RuntimeType::sketches(),
RuntimeType::solids(),
RuntimeType::imported(),
]),
exec_state,
)?;
@ -390,9 +387,9 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let objects = args.get_unlabeled_kw_arg_typed(
"objects",
&RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
RuntimeType::sketches(),
RuntimeType::solids(),
RuntimeType::imported(),
]),
exec_state,
)?;

View File

@ -5,7 +5,7 @@ use kcl_derive_docs::stdlib;
use crate::{
errors::KclError,
execution::{ExecState, KclValue, UnitLen},
execution::{types::UnitLen, ExecState, KclValue},
std::Args,
};

View File

@ -6,8 +6,7 @@ use crate::parsing::{
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression,
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
PipeExpression, Program, TagDeclarator, Type, TypeDeclaration, UnaryExpression, VariableDeclaration,
VariableKind,
PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
},
token::NumericSuffix,
PIPE_OPERATOR,
@ -308,7 +307,7 @@ impl Expr {
Expr::AscribedExpression(e) => {
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);

View File

@ -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