type aliases

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-03-17 09:06:55 +13:00
parent d7369d8a95
commit cd59691663
17 changed files with 254 additions and 198 deletions

View File

@ -20,7 +20,7 @@ statement[@isGroup=Statement] {
ImportStatement { kw<"import"> ImportItems ImportFrom String } | ImportStatement { kw<"import"> ImportItems ImportFrom String } |
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } | FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } |
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } | 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 } | ReturnStatement { kw<"return"> expression } |
ExpressionStatement { expression } | ExpressionStatement { expression } |
Annotation { AnnotationName AnnotationList? } Annotation { AnnotationName AnnotationList? }
@ -79,7 +79,7 @@ type[@isGroup=Type] {
identifier, identifier,
"bool" | "number" | "string" | "tag" | "Sketch" | "SketchSurface" | "Solid" | "Plane" "bool" | "number" | "string" | "tag" | "Sketch" | "SketchSurface" | "Solid" | "Plane"
> | > |
ArrayType { type !member "[" "]" } | ArrayType { "[" type !member (";" Number "+"?)? "]" } |
ObjectType { "{" commaSep<ObjectProperty { PropertyName ":" type }> "}" } ObjectType { "{" commaSep<ObjectProperty { PropertyName ":" type }> "}" }
} }
@ -137,7 +137,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
"(" ")" "(" ")"
"{" "}" "{" "}"
"[" "]" "[" "]"
"," "?" ":" "." ".." "," "?" ":" "." ".." ";"
} }
@external propSource kclHighlight from "./highlight" @external propSource kclHighlight from "./highlight"

View File

@ -29,6 +29,8 @@ use crate::{
CompilationError, CompilationError,
}; };
use super::kcl_value::TypeDef;
enum StatementKind<'a> { enum StatementKind<'a> {
Declaration { name: &'a str }, Declaration { name: &'a str },
Expression, Expression,
@ -304,8 +306,9 @@ impl ExecutorContext {
})); }));
} }
}; };
let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
let value = KclValue::Type { let value = KclValue::Type {
value: Some(crate::std::std_ty(std_path, &ty.name.name)), value: TypeDef::RustRepr(t, props),
meta: vec![metadata], meta: vec![metadata],
}; };
exec_state exec_state
@ -324,12 +327,40 @@ impl ExecutorContext {
} }
// Do nothing for primitive types, they get special treatment and their declarations are just for documentation. // Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
annotations::Impl::Primitive => {} annotations::Impl::Primitive => {}
annotations::Impl::Kcl => { annotations::Impl::Kcl => match &ty.alias {
return Err(KclError::Semantic(KclErrorDetails { Some(alias) => {
message: "User-defined types are not yet supported.".to_owned(), let value = KclValue::Type {
source_ranges: vec![metadata.source_range], 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; last_expr = None;
@ -646,30 +677,28 @@ impl ExecutorContext {
let result = self let result = self
.execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind) .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
.await?; .await?;
coerce(&result, &expr.ty, exec_state).ok_or_else(|| { coerce(&result, &expr.ty, exec_state, expr.into())?
KclError::Semantic(KclErrorDetails {
message: format!(
"could not coerce {} value to type {}",
result.human_friendly_type(),
expr.ty
),
source_ranges: vec![expr.into()],
})
})?
} }
}; };
Ok(item) 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()) let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
.map_err(|e| { .map_err(|e| KclError::Semantic(e.into()))?;
exec_state.err(e);
})
.ok()??;
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 { impl BinaryPart {

View File

@ -104,7 +104,7 @@ impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
.into_iter() .into_iter()
.map(|s| crate::execution::KclValue::Solid { value: Box::new(s) }) .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
.collect(), .collect(),
ty: crate::execution::PrimitiveType::Solid, ty: crate::execution::kcl_value::RuntimeType::solid(),
} }
} }
} }

View File

@ -65,7 +65,7 @@ pub enum KclValue {
value: Vec<KclValue>, value: Vec<KclValue>,
// The type of values, not the array type. // The type of values, not the array type.
#[serde(skip)] #[serde(skip)]
ty: PrimitiveType, ty: RuntimeType,
}, },
Object { Object {
value: KclObjectFields, value: KclObjectFields,
@ -105,7 +105,7 @@ pub enum KclValue {
#[ts(skip)] #[ts(skip)]
Type { Type {
#[serde(skip)] #[serde(skip)]
value: Option<(PrimitiveType, StdFnProps)>, value: TypeDef,
#[serde(skip)] #[serde(skip)]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
@ -142,6 +142,12 @@ impl JsonSchema for FunctionSource {
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub enum TypeDef {
RustRepr(PrimitiveType, StdFnProps),
Alias(RuntimeType),
}
impl From<Vec<Sketch>> for KclValue { impl From<Vec<Sketch>> for KclValue {
fn from(mut eg: Vec<Sketch>) -> Self { fn from(mut eg: Vec<Sketch>) -> Self {
if eg.len() == 1 { if eg.len() == 1 {
@ -154,7 +160,7 @@ impl From<Vec<Sketch>> for KclValue {
.into_iter() .into_iter()
.map(|s| KclValue::Sketch { value: Box::new(s) }) .map(|s| KclValue::Sketch { value: Box::new(s) })
.collect(), .collect(),
ty: crate::execution::PrimitiveType::Sketch, ty: RuntimeType::Primitive(PrimitiveType::Sketch),
} }
} }
} }
@ -169,7 +175,7 @@ impl From<Vec<Solid>> for KclValue {
} else { } else {
KclValue::HomArray { KclValue::HomArray {
value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(), value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
ty: crate::execution::PrimitiveType::Solid, ty: RuntimeType::Primitive(PrimitiveType::Solid),
} }
} }
} }
@ -635,10 +641,15 @@ impl KclValue {
KclValue::ImportedGeometry { .. } => Some(value.clone()), KclValue::ImportedGeometry { .. } => Some(value.clone()),
_ => None, _ => None,
}, },
PrimitiveType::Tag => match value {
KclValue::TagDeclarator { .. } => Some(value.clone()),
KclValue::TagIdentifier { .. } => Some(value.clone()),
_ => None,
},
} }
} }
fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> { fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
match self { match self {
KclValue::HomArray { value, ty: aty } => { KclValue::HomArray { value, ty: aty } => {
// TODO could check types of values individually // TODO could check types of values individually
@ -685,10 +696,9 @@ impl KclValue {
} }
}; };
let rt = RuntimeType::Primitive(ty.clone());
let value = value let value = value
.iter() .iter()
.map(|v| v.coerce(&rt, exec_state)) .map(|v| v.coerce(ty, exec_state))
.collect::<Option<Vec<_>>>()?; .collect::<Option<Vec<_>>>()?;
Some(KclValue::HomArray { value, ty: ty.clone() }) Some(KclValue::HomArray { value, ty: ty.clone() })
@ -698,7 +708,7 @@ impl KclValue {
ty: ty.clone(), ty: ty.clone(),
}), }),
value if len.satisfied(1) => { value if len.satisfied(1) => {
if value.has_type(&RuntimeType::Primitive(ty.clone())) { if value.has_type(ty) {
Some(KclValue::HomArray { Some(KclValue::HomArray {
value: vec![value.clone()], value: vec![value.clone()],
ty: ty.clone(), ty: ty.clone(),
@ -711,7 +721,7 @@ impl KclValue {
} }
} }
fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option<KclValue> { fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
match self { match self {
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
if value.len() < tys.len() { if value.len() < tys.len() {
@ -719,7 +729,7 @@ impl KclValue {
} }
let mut result = Vec::new(); let mut result = Vec::new();
for (i, t) in tys.iter().enumerate() { for (i, t) in tys.iter().enumerate() {
result.push(value[i].coerce_to_primitive_type(t, exec_state)?); result.push(value[i].coerce(t, exec_state)?);
} }
Some(KclValue::MixedArray { Some(KclValue::MixedArray {
@ -732,7 +742,7 @@ impl KclValue {
meta: meta.clone(), meta: meta.clone(),
}), }),
value if tys.len() == 1 => { value if tys.len() == 1 => {
if value.has_type(&RuntimeType::Primitive(tys[0].clone())) { if value.has_type(&tys[0]) {
Some(KclValue::MixedArray { Some(KclValue::MixedArray {
value: vec![value.clone()], value: vec![value.clone()],
meta: Vec::new(), meta: Vec::new(),
@ -788,18 +798,15 @@ impl KclValue {
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)), KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple( KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
value value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
.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::HomArray { ty, value, .. } => {
KclValue::Face { .. } => None, Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
KclValue::Helix { .. } }
| KclValue::Function { .. } KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
KclValue::Face { .. } | KclValue::Helix { .. } => None,
KclValue::Function { .. }
| KclValue::Module { .. } | KclValue::Module { .. }
| KclValue::TagIdentifier(_)
| KclValue::TagDeclarator(_)
| KclValue::KclNone { .. } | KclValue::KclNone { .. }
| KclValue::Type { .. } | KclValue::Type { .. }
| KclValue::Uuid { .. } => None, | KclValue::Uuid { .. } => None,
@ -923,42 +930,89 @@ impl KclValue {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType { pub enum RuntimeType {
Primitive(PrimitiveType), Primitive(PrimitiveType),
Array(PrimitiveType, ArrayLen), Array(Box<RuntimeType>, ArrayLen),
Union(Vec<RuntimeType>), Union(Vec<RuntimeType>),
Tuple(Vec<PrimitiveType>), Tuple(Vec<RuntimeType>),
Object(Vec<(String, RuntimeType)>), Object(Vec<(String, RuntimeType)>),
} }
impl RuntimeType { impl RuntimeType {
/// `[Sketch; 1+]`
pub fn sketches() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
ArrayLen::NonEmpty,
)
}
/// `[Solid; 1+]`
pub fn solids() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
ArrayLen::NonEmpty,
)
}
pub fn solid() -> Self {
RuntimeType::Primitive(PrimitiveType::Solid)
}
pub fn imported() -> Self {
RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
}
pub fn from_parsed( pub fn from_parsed(
value: Type, value: Type,
exec_state: &mut ExecState, exec_state: &mut ExecState,
source_range: SourceRange, source_range: SourceRange,
) -> Result<Option<Self>, CompilationError> { ) -> Result<Self, CompilationError> {
Ok(match value { match value {
Type::Primitive(pt) => { Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive)
}
Type::Array { ty, len } => { Type::Array { ty, len } => {
PrimitiveType::from_parsed(ty, exec_state, source_range)?.map(|t| RuntimeType::Array(t, len)) Self::from_parsed_primitive(ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len))
} }
Type::Union { tys } => tys Type::Union { tys } => tys
.into_iter() .into_iter()
.map(|t| PrimitiveType::from_parsed(t.inner, exec_state, source_range)) .map(|t| Self::from_parsed_primitive(t.inner, exec_state, source_range))
.collect::<Result<Option<Vec<_>>, CompilationError>>()? .collect::<Result<Vec<_>, CompilationError>>()
.map(RuntimeType::Union), .map(RuntimeType::Union),
Type::Object { properties } => properties Type::Object { properties } => properties
.into_iter() .into_iter()
.map(|p| { .map(|p| {
let pt = match p.type_ { RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
Some(t) => t, .map(|ty| (p.identifier.inner.name, ty))
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>>()? .collect::<Result<Vec<_>, CompilationError>>()
.map(RuntimeType::Object), .map(RuntimeType::Object),
}
}
fn from_parsed_primitive(
value: AstPrimitiveType,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<Self, CompilationError> {
Ok(match value {
AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
)),
AstPrimitiveType::Named(name) => {
let ty_val = exec_state
.stack()
.get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
.map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
match ty_val {
KclValue::Type { value, .. } => match value {
TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
TypeDef::Alias(ty) => ty.clone(),
},
_ => unreachable!(),
}
}
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
}) })
} }
@ -975,7 +1029,7 @@ impl RuntimeType {
.join(" or "), .join(" or "),
RuntimeType::Tuple(tys) => format!( RuntimeType::Tuple(tys) => format!(
"an array with values of types ({})", "an array with values of types ({})",
tys.iter().map(PrimitiveType::to_string).collect::<Vec<_>>().join(", ") tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
), ),
RuntimeType::Object(_) => format!("an object with fields {}", self), RuntimeType::Object(_) => format!("an object with fields {}", self),
} }
@ -990,7 +1044,7 @@ impl RuntimeType {
// TODO arrays could be covariant // TODO arrays could be covariant
(Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2), (Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2),
(Tuple(t1), Tuple(t2)) => t1 == t2, (Tuple(t1), Tuple(t2)) => t1 == t2,
(Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == t2), (Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == &**t2),
(Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)), (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
(t1, Union(ts2)) => ts2.contains(t1), (t1, Union(ts2)) => ts2.contains(t1),
// TODO record subtyping - subtype can be larger, fields can be covariant. // TODO record subtyping - subtype can be larger, fields can be covariant.
@ -999,10 +1053,17 @@ impl RuntimeType {
} }
} }
fn primitive(self) -> Option<PrimitiveType> { fn display_multiple(&self) -> String {
match self { match self {
RuntimeType::Primitive(t) => Some(t), RuntimeType::Primitive(ty) => ty.display_multiple(),
_ => None, RuntimeType::Array(..) => "arrays".to_owned(),
RuntimeType::Union(tys) => tys
.iter()
.map(|t| t.display_multiple())
.collect::<Vec<_>>()
.join(" or "),
RuntimeType::Tuple(_) => "arrays".to_owned(),
RuntimeType::Object(_) => format!("objects with fields {self}"),
} }
} }
} }
@ -1072,6 +1133,7 @@ pub enum PrimitiveType {
Number(NumericType), Number(NumericType),
String, String,
Boolean, Boolean,
Tag,
Sketch, Sketch,
Solid, Solid,
Plane, Plane,
@ -1079,35 +1141,6 @@ pub enum PrimitiveType {
} }
impl PrimitiveType { 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 { fn display_multiple(&self) -> String {
match self { match self {
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
@ -1118,6 +1151,7 @@ impl PrimitiveType {
PrimitiveType::Solid => "Solids".to_owned(), PrimitiveType::Solid => "Solids".to_owned(),
PrimitiveType::Plane => "Planes".to_owned(), PrimitiveType::Plane => "Planes".to_owned(),
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
PrimitiveType::Tag => "tags".to_owned(),
} }
} }
} }
@ -1129,6 +1163,7 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::Number(_) => write!(f, "number"), PrimitiveType::Number(_) => write!(f, "number"),
PrimitiveType::String => write!(f, "string"), PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"), PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::Tag => write!(f, "tag"),
PrimitiveType::Sketch => write!(f, "Sketch"), PrimitiveType::Sketch => write!(f, "Sketch"),
PrimitiveType::Solid => write!(f, "Solid"), PrimitiveType::Solid => write!(f, "Solid"),
PrimitiveType::Plane => write!(f, "Plane"), PrimitiveType::Plane => write!(f, "Plane"),

View File

@ -1397,6 +1397,22 @@ const answer = returnX()"#;
assert!(errs.is_empty()); assert!(errs.is_empty());
} }
#[tokio::test(flavor = "multi_thread")]
async fn type_aliases() {
let text = r#"type MyTy = [number; 2]
fn foo(x: MyTy) {
return x[0]
}
foo([0, 1])
type Other = MyTy | string
"#;
let result = parse_execute(text).await.unwrap();
let errs = result.exec_state.errors();
assert!(errs.is_empty());
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_cannot_shebang_in_fn() { async fn test_cannot_shebang_in_fn() {
let ast = r#" let ast = r#"

View File

@ -312,6 +312,9 @@ impl TypeDeclaration {
hasher.update(a.compute_digest()); hasher.update(a.compute_digest());
} }
} }
if let Some(alias) = &mut slf.alias {
hasher.update(alias.compute_digest());
}
}); });
} }

View File

@ -1895,6 +1895,7 @@ pub struct TypeDeclaration {
pub args: Option<NodeList<Identifier>>, pub args: Option<NodeList<Identifier>>,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")] #[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility, pub visibility: ItemVisibility,
pub alias: Option<Node<Type>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)] #[ts(optional)]

View File

@ -2216,7 +2216,7 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
let name = identifier(i)?; let name = identifier(i)?;
let mut end = name.end; 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); ignore_whitespace(i);
open_paren(i)?; open_paren(i)?;
ignore_whitespace(i); ignore_whitespace(i);
@ -2229,11 +2229,28 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
None 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 module_id = name.module_id;
let result = Node::boxed( let result = Node::boxed(
TypeDeclaration { TypeDeclaration {
name, name,
args, args,
alias,
visibility, visibility,
digest: None, digest: None,
}, },
@ -2686,13 +2703,21 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
let type_ = alt(( let type_ = alt((
// Object types // Object types
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`. // 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)| {
Node::new( 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 }, Type::Object { properties: params },
open.start, open.start,
close.end, close.end,
open.module_id, open.module_id,
) ))
}), }),
// Array types // Array types
array_type, array_type,

View File

@ -12,10 +12,7 @@ use validator::Validate;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{kcl_value::RuntimeType, ExecState, KclValue, Solid},
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, Solid,
},
std::Args, std::Args,
}; };
@ -42,11 +39,7 @@ struct AppearanceData {
/// Set the appearance of a solid. This only works on solids, not sketches or individual paths. /// 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> { pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
"solids",
&RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
exec_state,
)?;
let color: String = args.get_kw_arg("color")?; let color: String = args.get_kw_arg("color")?;
let metalness: Option<f64> = args.get_kw_arg_opt("metalness")?; let metalness: Option<f64> = args.get_kw_arg_opt("metalness")?;

View File

@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, kcl_value::{FunctionSource, NumericType, RuntimeType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch, ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch,
SketchSurface, Solid, TagIdentifier, SketchSurface, Solid, TagIdentifier,
}, },
@ -309,8 +309,10 @@ impl Args {
ty.human_friendly_type(), ty.human_friendly_type(),
); );
let suggestion = match (ty, actual_type_name) { let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
| (RuntimeType::Array(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`", "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
), ),
_ => None, _ => None,
@ -597,7 +599,7 @@ impl Args {
}; };
let sarg = arg0 let sarg = arg0
.value .value
.coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) .coerce(&RuntimeType::sketches(), exec_state)
.ok_or(KclError::Type(KclErrorDetails { .ok_or(KclError::Type(KclErrorDetails {
message: format!( message: format!(
"Expected an array of sketches, found {}", "Expected an array of sketches, found {}",
@ -685,7 +687,7 @@ impl Args {
}; };
let sarg = arg1 let sarg = arg1
.value .value
.coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) .coerce(&RuntimeType::sketches(), exec_state)
.ok_or(KclError::Type(KclErrorDetails { .ok_or(KclError::Type(KclErrorDetails {
message: format!( message: format!(
"Expected one or more sketches for second argument, found {}", "Expected one or more sketches for second argument, found {}",

View File

@ -19,8 +19,8 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
kcl_value::{ArrayLen, RuntimeType}, kcl_value::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface,
ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, PrimitiveType, Sketch, SketchSurface, Solid, Solid,
}, },
parsing::ast::types::TagNode, parsing::ast::types::TagNode,
std::Args, std::Args,
@ -28,11 +28,7 @@ use crate::{
/// Extrudes by a given amount. /// Extrudes by a given amount.
pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let length = args.get_kw_arg("length")?; let length = args.get_kw_arg("length")?;
let tag_start = args.get_kw_arg_opt("tagStart")?; let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?; let tag_end = args.get_kw_arg_opt("tagEnd")?;

View File

@ -9,11 +9,7 @@ use kittycad_modeling_cmds as kcmc;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{kcl_value::RuntimeType, ExecState, KclValue, Sketch, Solid},
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
}; };
@ -21,11 +17,7 @@ const DEFAULT_V_DEGREE: u32 = 2;
/// Create a 3D surface or solid by interpolating between two or more sketches. /// 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> { pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let v_degree: NonZeroU32 = args let v_degree: NonZeroU32 = args
.get_kw_arg_opt("vDegree")? .get_kw_arg_opt("vDegree")?
.unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()); .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());

View File

@ -20,8 +20,8 @@ use super::args::Arg;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, kcl_value::{FunctionSource, NumericType, RuntimeType},
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, PrimitiveType, Sketch, Solid, ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, Sketch, Solid,
}, },
std::Args, std::Args,
ExecutorContext, SourceRange, ExecutorContext, SourceRange,
@ -47,11 +47,7 @@ pub struct LinearPattern3dData {
/// Repeat some 3D solid, changing each repetition slightly. /// Repeat some 3D solid, changing each repetition slightly.
pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
"solids",
&RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
exec_state,
)?;
let instances: u32 = args.get_kw_arg("instances")?; let instances: u32 = args.get_kw_arg("instances")?;
let transform: &FunctionSource = args.get_kw_arg("transform")?; let transform: &FunctionSource = args.get_kw_arg("transform")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
@ -62,11 +58,7 @@ pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result
/// Repeat some 2D sketch, changing each repetition slightly. /// Repeat some 2D sketch, changing each repetition slightly.
pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let instances: u32 = args.get_kw_arg("instances")?; let instances: u32 = args.get_kw_arg("instances")?;
let transform: &FunctionSource = args.get_kw_arg("transform")?; let transform: &FunctionSource = args.get_kw_arg("transform")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
@ -696,11 +688,7 @@ mod tests {
/// A linear pattern on a 2D sketch. /// A linear pattern on a 2D sketch.
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let instances: u32 = args.get_kw_arg("instances")?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?; let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 2] = args.get_kw_arg("axis")?; let axis: [f64; 2] = args.get_kw_arg("axis")?;
@ -779,11 +767,7 @@ async fn inner_pattern_linear_2d(
/// A linear pattern on a 3D model. /// A linear pattern on a 3D model.
pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
"solids",
&RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
exec_state,
)?;
let instances: u32 = args.get_kw_arg("instances")?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?; let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 3] = args.get_kw_arg("axis")?; let axis: [f64; 3] = args.get_kw_arg("axis")?;
@ -1028,11 +1012,7 @@ impl CircularPattern {
/// A circular pattern on a 2D sketch. /// A circular pattern on a 2D sketch.
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let instances: u32 = args.get_kw_arg("instances")?; let instances: u32 = args.get_kw_arg("instances")?;
let center: [f64; 2] = args.get_kw_arg("center")?; let center: [f64; 2] = args.get_kw_arg("center")?;
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?; let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
@ -1136,11 +1116,7 @@ async fn inner_pattern_circular_2d(
/// A circular pattern on a 3D model. /// A circular pattern on a 3D model.
pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
"solids",
&RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
exec_state,
)?;
// The number of total instances. Must be greater than or equal to 1. // The number of total instances. Must be greater than or equal to 1.
// This includes the original entity. For example, if instances is 2, // This includes the original entity. For example, if instances is 2,
// there will be two copies -- the original, and one new copy. // there will be two copies -- the original, and one new copy.

View File

@ -7,20 +7,13 @@ use kittycad_modeling_cmds as kcmc;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{kcl_value::RuntimeType, ExecState, KclValue, Solid},
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, Solid,
},
std::{sketch::FaceTag, Args}, std::{sketch::FaceTag, Args},
}; };
/// Create a shell. /// Create a shell.
pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
"solids",
&RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty),
exec_state,
)?;
let thickness = args.get_kw_arg("thickness")?; let thickness = args.get_kw_arg("thickness")?;
let faces = args.get_kw_arg("faces")?; let faces = args.get_kw_arg("faces")?;

View File

@ -9,10 +9,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclError, errors::KclError,
execution::{ execution::{kcl_value::RuntimeType, ExecState, Helix, KclValue, Sketch, Solid},
kcl_value::{ArrayLen, RuntimeType},
ExecState, Helix, KclValue, PrimitiveType, Sketch, Solid,
},
parsing::ast::types::TagNode, parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
}; };
@ -28,11 +25,7 @@ pub enum SweepPath {
/// Extrude a sketch along a path. /// Extrude a sketch along a path.
pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed( let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
"sketches",
&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty),
exec_state,
)?;
let path: SweepPath = args.get_kw_arg("path")?; let path: SweepPath = args.get_kw_arg("path")?;
let sectional = args.get_kw_arg_opt("sectional")?; let sectional = args.get_kw_arg_opt("sectional")?;
let tolerance = args.get_kw_arg_opt("tolerance")?; let tolerance = args.get_kw_arg_opt("tolerance")?;

View File

@ -13,10 +13,7 @@ use kittycad_modeling_cmds as kcmc;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{kcl_value::RuntimeType, ExecState, KclValue, SolidOrSketchOrImportedGeometry},
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, SolidOrSketchOrImportedGeometry,
},
std::Args, 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( let objects = args.get_unlabeled_kw_arg_typed(
"objects", "objects",
&RuntimeType::Union(vec![ &RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), RuntimeType::sketches(),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), RuntimeType::solids(),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry), RuntimeType::imported(),
]), ]),
exec_state, 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( let objects = args.get_unlabeled_kw_arg_typed(
"objects", "objects",
&RuntimeType::Union(vec![ &RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), RuntimeType::sketches(),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), RuntimeType::solids(),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry), RuntimeType::imported(),
]), ]),
exec_state, 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( let objects = args.get_unlabeled_kw_arg_typed(
"objects", "objects",
&RuntimeType::Union(vec![ &RuntimeType::Union(vec![
RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), RuntimeType::sketches(),
RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), RuntimeType::solids(),
RuntimeType::Primitive(PrimitiveType::ImportedGeometry), RuntimeType::imported(),
]), ]),
exec_state, exec_state,
)?; )?;

View File

@ -456,6 +456,10 @@ impl TypeDeclaration {
} }
arg_str.push(')'); 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) format!("{}type {}{}", vis, self.name.name, arg_str)
} }
} }
@ -811,7 +815,7 @@ impl FunctionExpression {
let tab0 = options.get_indentation(indentation_level); let tab0 = options.get_indentation(indentation_level);
let tab1 = options.get_indentation(indentation_level + 1); let tab1 = options.get_indentation(indentation_level + 1);
let return_type = match &self.return_type { let return_type = match &self.return_type {
Some(rt) => format!(": {}", rt.to_string()), Some(rt) => format!(": {rt}"),
None => String::new(), None => String::new(),
}; };
let body = self.body.recast(&new_options, indentation_level + 1); let body = self.body.recast(&new_options, indentation_level + 1);
@ -2427,6 +2431,7 @@ thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
// A comment // A comment
@(impl = primitive) @(impl = primitive)
export type bar(unit, baz) export type bar(unit, baz)
type baz = Foo | Bar
"#; "#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap(); let program = crate::parsing::top_level_parse(some_program_string).unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);