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:
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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#"
|
||||
|
@ -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 => {
|
||||
|
1254
rust/kcl-lib/src/execution/types.rs
Normal file
1254
rust/kcl-lib/src/execution/types.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user