Skip to content

Commit 032abd1

Browse files
committed
Universal quantification semantics for Length and Angle
Signed-off-by: Nick Cameron <[email protected]>
1 parent f7c7823 commit 032abd1

File tree

62 files changed

+2194
-3326
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2194
-3326
lines changed

docs/kcl-std/functions/std-vector-cross.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Find the cross product of two 3D points or vectors.
1111
vector::cross(
1212
@u: Point3d,
1313
v: Point3d,
14-
)
14+
): Point3d
1515
```
1616

1717

@@ -23,6 +23,10 @@ vector::cross(
2323
| `u` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | A point in three dimensional space. | Yes |
2424
| `v` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | A point in three dimensional space. | Yes |
2525

26+
### Returns
27+
28+
[`Point3d`](/docs/kcl-std/types/std-types-Point3d) - A point in three dimensional space.
29+
2630

2731
### Examples
2832

rust/kcl-lib/src/execution/fn_call.rs

Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
cad_op::{Group, OpArg, OpKclValue, Operation},
1111
kcl_value::FunctionSource,
1212
memory,
13-
types::RuntimeType,
13+
types::{DisplayType, RuntimeType, UnitSubsts},
1414
},
1515
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
1616
std::StdFn,
@@ -215,7 +215,7 @@ impl Node<CallExpressionKw> {
215215

216216
// Clone the function so that we can use a mutable reference to
217217
// exec_state.
218-
let func: KclValue = fn_name.get_result(exec_state, ctx).await?.clone();
218+
let func = fn_name.get_result(exec_state, ctx).await?.clone();
219219

220220
let Some(fn_src) = func.as_function() else {
221221
return Err(KclError::new_semantic(KclErrorDetails::new(
@@ -261,7 +261,7 @@ impl Node<CallExpressionKw> {
261261
let args = Args::new(fn_args, unlabeled, callsite, exec_state, ctx.clone());
262262

263263
let return_value = fn_src
264-
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
264+
.call(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
265265
.await
266266
.map_err(|e| {
267267
// Add the call expression to the source ranges.
@@ -291,7 +291,7 @@ impl Node<CallExpressionKw> {
291291
}
292292

293293
impl FunctionDefinition<'_> {
294-
pub async fn call_kw(
294+
pub async fn call(
295295
&self,
296296
fn_name: Option<String>,
297297
exec_state: &mut ExecState,
@@ -324,7 +324,7 @@ impl FunctionDefinition<'_> {
324324
);
325325
}
326326

327-
let args = type_check_params_kw(fn_name.as_deref(), self, args, exec_state)?;
327+
let (args, unit_substs) = type_check_params(fn_name.as_deref(), self, args, exec_state)?;
328328

329329
// Don't early return until the stack frame is popped!
330330
self.body.prep_mem(exec_state);
@@ -367,11 +367,13 @@ impl FunctionDefinition<'_> {
367367
None
368368
};
369369

370+
exec_state.mut_unit_stack().push(unit_substs);
370371
let mut result = match &self.body {
371372
FunctionBody::Rust(f) => f(exec_state, args).await.map(Some),
372373
FunctionBody::Kcl(f, _) => {
373-
if let Err(e) = assign_args_to_params_kw(self, args, exec_state) {
374+
if let Err(e) = assign_args_to_params(self, args, exec_state) {
374375
exec_state.mut_stack().pop_env();
376+
exec_state.mut_unit_stack().pop();
375377
return Err(e);
376378
}
377379

@@ -386,6 +388,7 @@ impl FunctionDefinition<'_> {
386388
};
387389

388390
exec_state.mut_stack().pop_env();
391+
exec_state.mut_unit_stack().pop();
389392

390393
if let Some(mut op) = op {
391394
op.set_std_lib_call_is_error(result.is_err());
@@ -405,7 +408,21 @@ impl FunctionDefinition<'_> {
405408
update_memory_for_tags_of_geometry(result, exec_state)?;
406409
}
407410

408-
coerce_result_type(result, self, exec_state)
411+
let ret_ty = self
412+
.return_type
413+
.as_ref()
414+
.map(|ret_ty| {
415+
let mut ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
416+
.map_err(|e| KclError::new_semantic(e.into()))?;
417+
ty.subst_units(unit_substs);
418+
Ok::<_, KclError>(ty)
419+
})
420+
.transpose()?;
421+
if let Some(ret_ty) = &ret_ty {
422+
coerce_result_type(result, ret_ty, exec_state)
423+
} else {
424+
result
425+
}
409426
}
410427

411428
// Postcondition: result.is_some() if function is not in the standard library.
@@ -427,7 +444,7 @@ impl FunctionBody<'_> {
427444
}
428445

429446
impl FunctionSource {
430-
pub async fn call_kw(
447+
pub async fn call(
431448
&self,
432449
fn_name: Option<String>,
433450
exec_state: &mut ExecState,
@@ -436,7 +453,7 @@ impl FunctionSource {
436453
callsite: SourceRange,
437454
) -> Result<Option<KclValue>, KclError> {
438455
let def: FunctionDefinition = self.into();
439-
def.call_kw(fn_name, exec_state, ctx, args, callsite).await
456+
def.call(fn_name, exec_state, ctx, args, callsite).await
440457
}
441458
}
442459

@@ -551,7 +568,12 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
551568
Ok(())
552569
}
553570

554-
fn type_err_str(expected: &Type, found: &KclValue, source_range: &SourceRange, exec_state: &mut ExecState) -> String {
571+
fn type_err_str(
572+
expected: &impl DisplayType,
573+
found: &KclValue,
574+
source_range: &SourceRange,
575+
exec_state: &mut ExecState,
576+
) -> String {
555577
fn strip_backticks(s: &str) -> &str {
556578
let mut result = s;
557579
if s.starts_with('`') {
@@ -563,8 +585,8 @@ fn type_err_str(expected: &Type, found: &KclValue, source_range: &SourceRange, e
563585
result
564586
}
565587

566-
let expected_human = expected.human_friendly_type();
567-
let expected_ty = expected.to_string();
588+
let expected_human = expected.human_friendly_string();
589+
let expected_ty = expected.src_string();
568590
let expected_str =
569591
if expected_human == expected_ty || expected_human == format!("a value with type `{expected_ty}`") {
570592
format!("a value with type `{expected_ty}`")
@@ -581,20 +603,20 @@ fn type_err_str(expected: &Type, found: &KclValue, source_range: &SourceRange, e
581603

582604
let mut result = format!("{expected_str}, but found {found_str}.");
583605

584-
if found.is_unknown_number() {
606+
if found.is_unknown_number(exec_state) {
585607
exec_state.clear_units_warnings(source_range);
586608
result.push_str("\nThe found value is a number but has incomplete units information. You can probably fix this error by specifying the units using type ascription, e.g., `len: mm` or `(a * b): deg`.");
587609
}
588610

589611
result
590612
}
591613

592-
fn type_check_params_kw(
614+
fn type_check_params(
593615
fn_name: Option<&str>,
594616
fn_def: &FunctionDefinition<'_>,
595617
mut args: Args<Sugary>,
596618
exec_state: &mut ExecState,
597-
) -> Result<Args<Desugared>, KclError> {
619+
) -> Result<(Args<Desugared>, UnitSubsts), KclError> {
598620
let mut result = Args::new_no_args(args.source_range, args.ctx);
599621

600622
// If it's possible the input arg was meant to be labelled and we probably don't want to use
@@ -609,6 +631,10 @@ fn type_check_params_kw(
609631
args.labeled.insert(label.unwrap(), arg);
610632
}
611633

634+
// Collect substitutions for `number(Length)` or `number(Angle)`. See docs on execution::types::UnitSubsts
635+
// for details.
636+
let mut unit_substs = UnitSubsts::default();
637+
612638
// Apply the `a == a: a` shorthand by desugaring unlabeled args into labeled ones.
613639
let (labeled_unlabeled, unlabeled_unlabeled) = args.unlabeled.into_iter().partition(|(l, _)| {
614640
if let Some(l) = l
@@ -657,20 +683,28 @@ fn type_check_params_kw(
657683
} else if args.unlabeled.len() == 1 {
658684
let mut arg = args.unlabeled.pop().unwrap().1;
659685
if let Some(ty) = ty {
660-
let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range)
686+
let mut rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range)
661687
.map_err(|e| KclError::new_semantic(e.into()))?;
662-
arg.value = arg.value.coerce(&rty, true, exec_state).map_err(|_| {
663-
KclError::new_argument(KclErrorDetails::new(
664-
format!(
665-
"The input argument of {} requires {}",
666-
fn_name
667-
.map(|n| format!("`{n}`"))
668-
.unwrap_or_else(|| "this function".to_owned()),
669-
type_err_str(ty, &arg.value, &arg.source_range, exec_state),
670-
),
671-
vec![arg.source_range],
672-
))
673-
})?;
688+
rty.subst_units(unit_substs);
689+
690+
let (value, substs) = arg
691+
.value
692+
.coerce_and_find_unit_substs(&rty, true, exec_state)
693+
.map_err(|_| {
694+
KclError::new_argument(KclErrorDetails::new(
695+
format!(
696+
"The input argument of {} requires {}",
697+
fn_name
698+
.map(|n| format!("`{n}`"))
699+
.unwrap_or_else(|| "this function".to_owned()),
700+
type_err_str(ty, &arg.value, &arg.source_range, exec_state),
701+
),
702+
vec![arg.source_range],
703+
))
704+
})?;
705+
706+
arg.value = value;
707+
unit_substs = unit_substs.or(substs);
674708
}
675709
result.unlabeled = vec![(None, arg)]
676710
} else {
@@ -748,11 +782,13 @@ fn type_check_params_kw(
748782
// For optional args, passing None should be the same as not passing an arg.
749783
if !(def.is_some() && matches!(arg.value, KclValue::KclNone { .. })) {
750784
if let Some(ty) = ty {
751-
let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range)
785+
let mut rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range)
752786
.map_err(|e| KclError::new_semantic(e.into()))?;
753-
arg.value = arg
787+
rty.subst_units(unit_substs);
788+
789+
let (value, substs) = arg
754790
.value
755-
.coerce(
791+
.coerce_and_find_unit_substs(
756792
&rty,
757793
true,
758794
exec_state,
@@ -771,6 +807,9 @@ fn type_check_params_kw(
771807
vec![arg.source_range],
772808
))
773809
})?;
810+
811+
arg.value = value;
812+
unit_substs = unit_substs.or(substs);
774813
}
775814
result.labeled.insert(label, arg);
776815
}
@@ -789,10 +828,10 @@ fn type_check_params_kw(
789828
}
790829
}
791830

792-
Ok(result)
831+
Ok((result, unit_substs))
793832
}
794833

795-
fn assign_args_to_params_kw(
834+
fn assign_args_to_params(
796835
fn_def: &FunctionDefinition<'_>,
797836
args: Args<Desugared>,
798837
exec_state: &mut ExecState,
@@ -848,26 +887,20 @@ fn assign_args_to_params_kw(
848887

849888
fn coerce_result_type(
850889
result: Result<Option<KclValue>, KclError>,
851-
fn_def: &FunctionDefinition<'_>,
890+
return_ty: &RuntimeType,
852891
exec_state: &mut ExecState,
853892
) -> Result<Option<KclValue>, KclError> {
854893
if let Ok(Some(val)) = result {
855-
if let Some(ret_ty) = &fn_def.return_type {
856-
let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
857-
.map_err(|e| KclError::new_semantic(e.into()))?;
858-
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
859-
KclError::new_type(KclErrorDetails::new(
860-
format!(
861-
"This function requires its result to be {}",
862-
type_err_str(ret_ty, &val, &(&val).into(), exec_state)
863-
),
864-
ret_ty.as_source_ranges(),
865-
))
866-
})?;
867-
Ok(Some(val))
868-
} else {
869-
Ok(Some(val))
870-
}
894+
let val = val.coerce(return_ty, true, exec_state).map_err(|_| {
895+
KclError::new_type(KclErrorDetails::new(
896+
format!(
897+
"This function requires its result to be {}",
898+
type_err_str(return_ty, &val, &(&val).into(), exec_state)
899+
),
900+
val.into(),
901+
))
902+
})?;
903+
Ok(Some(val))
871904
} else {
872905
result
873906
}
@@ -1016,9 +1049,8 @@ mod test {
10161049
pipe_value: None,
10171050
_status: std::marker::PhantomData,
10181051
};
1019-
1020-
let actual = assign_args_to_params_kw(&(&func_src).into(), args, &mut exec_state)
1021-
.map(|_| exec_state.mod_local.stack);
1052+
let actual =
1053+
assign_args_to_params(&(&func_src).into(), args, &mut exec_state).map(|_| exec_state.mod_local.stack);
10221054
assert_eq!(
10231055
actual, expected,
10241056
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"

rust/kcl-lib/src/execution/kcl_value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,9 @@ impl KclValue {
660660
})
661661
}
662662

663-
pub fn is_unknown_number(&self) -> bool {
663+
pub fn is_unknown_number(&self, exec_state: &ExecState) -> bool {
664664
match self {
665-
KclValue::Number { ty, .. } => !ty.is_fully_specified(),
665+
KclValue::Number { ty, .. } => !ty.is_fully_specified(exec_state),
666666
_ => false,
667667
}
668668
}

rust/kcl-lib/src/execution/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,4 +2571,17 @@ startSketchOn(XY)
25712571
"#;
25722572
parse_execute(code).await.unwrap_err();
25732573
}
2574+
2575+
#[tokio::test(flavor = "multi_thread")]
2576+
async fn vector_module() {
2577+
let ast = r#"
2578+
cylinder = startSketchOn(XY)
2579+
|> circle(center = [0.67, 0.47], radius = 1.06)
2580+
|> extrude(length = 5)
2581+
p = planeOf(cylinder, face = END)
2582+
q = vector::cross(p.xAxis, v = p.yAxis)
2583+
r = vector::cross(p.xAxis, v = q)
2584+
"#;
2585+
parse_execute(ast).await.unwrap();
2586+
}
25742587
}

rust/kcl-lib/src/execution/state.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
cad_op::Operation,
1717
id_generator::IdGenerator,
1818
memory::{ProgramMemory, Stack},
19-
types::{self, NumericType},
19+
types::{self, NumericType, UnitSubsts},
2020
},
2121
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
2222
parsing::ast::types::{Annotation, NodeRef},
@@ -103,6 +103,10 @@ pub(super) struct ModuleState {
103103
pub settings: MetaSettings,
104104
pub(super) explicit_length_units: bool,
105105
pub(super) path: ModulePath,
106+
/// The unit substitutions for the current stack frame (see docs on execution::types::UnitSubsts
107+
/// for more detail). Each stack frame has a possible substitution, and the stack is never empty. The
108+
/// root stack frame always has `(None, None)`.
109+
pub(super) unit_stack: Vec<UnitSubsts>,
106110
/// Artifacts for only this module.
107111
pub artifacts: ModuleArtifactState,
108112

@@ -205,6 +209,19 @@ impl ExecState {
205209
&mut self.mod_local.stack
206210
}
207211

212+
pub(crate) fn mut_unit_stack(&mut self) -> &mut Vec<UnitSubsts> {
213+
&mut self.mod_local.unit_stack
214+
}
215+
216+
pub(crate) fn current_unit_substs(&self) -> UnitSubsts {
217+
debug_assert!(self.mod_local.unit_stack.last().is_some());
218+
self.mod_local
219+
.unit_stack
220+
.last()
221+
.cloned()
222+
.unwrap_or(UnitSubsts::default())
223+
}
224+
208225
pub fn next_uuid(&mut self) -> Uuid {
209226
self.mod_local.id_generator.next_uuid()
210227
}
@@ -512,6 +529,7 @@ impl ModuleState {
512529
being_declared: Default::default(),
513530
module_exports: Default::default(),
514531
explicit_length_units: false,
532+
unit_stack: vec![UnitSubsts::default()],
515533
path,
516534
settings: Default::default(),
517535
artifacts: Default::default(),

0 commit comments

Comments
 (0)