Type check and coerce arguments to user functions and return values from std Rust functions (#6958)

* Shuffle around function call code

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

* Refactor function calls to share more code

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

* Hack to leave the result of revolve as a singleton rather than array

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-05-19 16:50:15 +12:00
committed by GitHub
parent f3e9d110c0
commit b19acd550d
197 changed files with 13837 additions and 14317 deletions

View File

@ -1,7 +1,6 @@
use std::num::NonZeroU32;
use anyhow::Result;
use indexmap::IndexMap;
use kcmc::{
websocket::{ModelingCmdReq, OkWebSocketResponseData},
ModelingCmd,
@ -15,8 +14,8 @@ use crate::{
execution::{
kcl_value::FunctionSource,
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitLen, UnitType},
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PlaneInfo, Sketch,
SketchSurface, Solid, TagIdentifier,
ExecState, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PlaneInfo, Sketch, SketchSurface, Solid,
TagIdentifier,
},
parsing::ast::types::TagNode,
source_range::SourceRange,
@ -28,56 +27,11 @@ use crate::{
ModuleId,
};
pub use crate::execution::fn_call::Args;
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
#[derive(Debug, Clone)]
pub struct Arg {
/// The evaluated argument.
pub value: KclValue,
/// The source range of the unevaluated argument.
pub source_range: SourceRange,
}
impl Arg {
pub fn new(value: KclValue, source_range: SourceRange) -> Self {
Self { value, source_range }
}
pub fn synthetic(value: KclValue) -> Self {
Self {
value,
source_range: SourceRange::synthetic(),
}
}
pub fn source_ranges(&self) -> Vec<SourceRange> {
vec![self.source_range]
}
}
#[derive(Debug, Clone, Default)]
pub struct KwArgs {
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
/// If the argument was a local variable, then the first element of the tuple is its name
/// which may be used to treat this arg as a labelled arg.
pub unlabeled: Option<(Option<String>, Arg)>,
/// Labeled args.
pub labeled: IndexMap<String, Arg>,
pub errors: Vec<Arg>,
}
impl KwArgs {
/// How many arguments are there?
pub fn len(&self) -> usize {
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
}
/// Are there no arguments?
pub fn is_empty(&self) -> bool {
self.labeled.len() == 0 && self.unlabeled.is_none()
}
}
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
@ -153,41 +107,7 @@ impl JsonSchema for TyF64 {
}
}
#[derive(Debug, Clone)]
pub struct Args {
/// Positional args.
pub args: Vec<Arg>,
/// Keyword arguments
pub kw_args: KwArgs,
pub source_range: SourceRange,
pub ctx: ExecutorContext,
/// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
/// Otherwise it's None.
pub pipe_value: Option<Arg>,
}
impl Args {
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args,
kw_args: Default::default(),
source_range,
ctx,
pipe_value,
}
}
/// Collect the given keyword arguments.
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args: Default::default(),
kw_args,
source_range,
ctx,
pipe_value,
}
}
/// Get a keyword argument. If not set, returns None.
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
where
@ -339,16 +259,6 @@ impl Args {
.collect::<Result<Vec<_>, _>>()
}
/// Get the unlabeled keyword argument. If not set, returns None.
pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
self.kw_args
.unlabeled
.as_ref()
.map(|(_, a)| a)
.or(self.args.first())
.or(self.pipe_value.as_ref())
}
/// Get the unlabeled keyword argument. If not set, returns Err. If it
/// can't be converted to the given type, returns Err.
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>

View File

@ -1,12 +1,9 @@
use indexmap::IndexMap;
use super::{
args::{Arg, KwArgs},
Args,
};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
fn_call::{Arg, Args, KwArgs},
kcl_value::{FunctionSource, KclValue},
types::RuntimeType,
ExecState,

View File

@ -59,10 +59,7 @@ async fn inner_clone(
let mut new_sketch = sketch.clone();
new_sketch.id = new_id;
new_sketch.original_id = new_id;
#[cfg(feature = "artifact-graph")]
{
new_sketch.artifact_id = new_id.into();
}
new_sketch.artifact_id = new_id.into();
GeometryWithImportedGeometry::Sketch(new_sketch)
}
GeometryWithImportedGeometry::Solid(solid) => {
@ -72,10 +69,7 @@ async fn inner_clone(
let mut new_solid = solid.clone();
new_solid.id = new_id;
new_solid.sketch.original_id = new_id;
#[cfg(feature = "artifact-graph")]
{
new_solid.artifact_id = new_id.into();
}
new_solid.artifact_id = new_id.into();
GeometryWithImportedGeometry::Solid(new_solid)
}
};
@ -118,10 +112,7 @@ async fn fix_tags_and_references(
// Make the sketch id the new geometry id.
solid.sketch.id = new_geometry_id;
solid.sketch.original_id = new_geometry_id;
#[cfg(feature = "artifact-graph")]
{
solid.sketch.artifact_id = new_geometry_id.into();
}
solid.sketch.artifact_id = new_geometry_id.into();
fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state).await?;
@ -148,7 +139,6 @@ async fn fix_tags_and_references(
// information.
let new_solid = do_post_extrude(
&solid.sketch,
#[cfg(feature = "artifact-graph")]
new_geometry_id.into(),
crate::std::args::TyF64::new(
solid.height,
@ -332,10 +322,8 @@ clonedCube = clone(cube)
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.original_id, cloned_cube.original_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
assert_eq!(cloned_cube.original_id, cloned_cube.id);
@ -384,12 +372,9 @@ clonedCube = clone(cube)
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
@ -501,12 +486,9 @@ clonedCube = clone(cube)
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
@ -576,12 +558,9 @@ clonedCube = clone(cube)
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
@ -679,12 +658,9 @@ clonedCube = clone(cube)
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
#[cfg(feature = "artifact-graph")]
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {

View File

@ -17,11 +17,12 @@ use kittycad_modeling_cmds::{self as kcmc};
use uuid::Uuid;
use super::args::TyF64;
#[cfg(feature = "artifact-graph")]
use crate::execution::ArtifactId;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface, Solid},
execution::{
types::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface,
Solid,
},
parsing::ast::types::TagNode,
std::Args,
};
@ -210,7 +211,6 @@ async fn inner_extrude(
solids.push(
do_post_extrude(
sketch,
#[cfg(feature = "artifact-graph")]
id.into(),
length.clone(),
false,
@ -238,7 +238,7 @@ pub(crate) struct NamedCapTags<'a> {
#[allow(clippy::too_many_arguments)]
pub(crate) async fn do_post_extrude<'a>(
sketch: &Sketch,
#[cfg(feature = "artifact-graph")] solid_id: ArtifactId,
solid_id: ArtifactId,
length: TyF64,
sectional: bool,
named_cap_tags: &'a NamedCapTags<'a>,
@ -431,7 +431,6 @@ pub(crate) async fn do_post_extrude<'a>(
// that we passed in to the function, but it's actually the id of the
// sketch.
id: sketch.id,
#[cfg(feature = "artifact-graph")]
artifact_id: solid_id,
value: new_value,
meta: sketch.meta.clone(),

View File

@ -110,7 +110,6 @@ async fn inner_helix(
let helix_result = Box::new(HelixValue {
value: id,
#[cfg(feature = "artifact-graph")]
artifact_id: id.into(),
revolutions,
angle_start,

View File

@ -177,7 +177,6 @@ async fn inner_loft(
Ok(Box::new(
do_post_extrude(
&sketch,
#[cfg(feature = "artifact-graph")]
id.into(),
TyF64::new(0.0, NumericType::mm()),
false,

View File

@ -328,14 +328,14 @@ impl StdLib {
self.fns.get(name).cloned()
}
pub fn get_either(&self, name: &Name) -> FunctionKind {
pub fn get_rust_function(&self, name: &Name) -> Option<Box<dyn StdLibFn>> {
if let Some(name) = name.local_ident() {
if let Some(f) = self.get(name.inner) {
return FunctionKind::Core(f);
return Some(f);
}
}
FunctionKind::UserDefined
None
}
pub fn contains_key(&self, key: &str) -> bool {
@ -349,11 +349,5 @@ impl Default for StdLib {
}
}
#[derive(Debug)]
pub enum FunctionKind {
Core(Box<dyn StdLibFn>),
UserDefined,
}
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
const DEFAULT_TOLERANCE: f64 = 0.0000001;

View File

@ -18,15 +18,15 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
fn_call::{Arg, Args, KwArgs},
kcl_value::FunctionSource,
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Sketch, Solid,
},
std::{
args::{Arg, KwArgs, TyF64},
args::TyF64,
axis_or_reference::Axis2dOrPoint2d,
utils::{point_3d_to_mm, point_to_mm},
Args,
},
ExecutorContext, SourceRange,
};

View File

@ -196,7 +196,6 @@ async fn inner_revolve(
solids.push(
do_post_extrude(
sketch,
#[cfg(feature = "artifact-graph")]
id.into(),
TyF64::new(0.0, NumericType::mm()),
false,

View File

@ -1233,7 +1233,6 @@ async fn start_sketch_on_face(
Ok(Box::new(Face {
id: extrude_plane_id,
#[cfg(feature = "artifact-graph")]
artifact_id: extrude_plane_id.into(),
value: tag.to_string(),
// TODO: get this from the extrude plane data.
@ -1414,7 +1413,6 @@ pub(crate) async fn inner_start_profile(
let sketch = Sketch {
id: path_id,
original_id: path_id,
#[cfg(feature = "artifact-graph")]
artifact_id: path_id.into(),
on: sketch_surface.clone(),
paths: vec![],

View File

@ -218,7 +218,6 @@ async fn inner_sweep(
solids.push(
do_post_extrude(
sketch,
#[cfg(feature = "artifact-graph")]
id.into(),
TyF64::new(0.0, NumericType::mm()),
sectional.unwrap_or(false),