diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index 372027744..d0c695d07 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -736,21 +736,35 @@ fn apply_ascription( let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into()) .map_err(|e| KclError::Semantic(e.into()))?; - if let KclValue::Number { value, meta, .. } = value { - // If the number has unknown units but the user is explicitly specifying them, treat the value as having had it's units erased, - // rather than forcing the user to explicitly erase them. - KclValue::Number { - ty: NumericType::Any, - value: *value, - meta: meta.clone(), + let mut value = value.clone(); + + // If the number has unknown units but the user is explicitly specifying them, treat the value as having had it's units erased, + // rather than forcing the user to explicitly erase them. + if let KclValue::Number { value: n, meta, .. } = &value { + if let RuntimeType::Primitive(PrimitiveType::Number(num)) = &ty { + if num.is_fully_specified() { + value = KclValue::Number { + ty: NumericType::Any, + value: *n, + meta: meta.clone(), + }; + } } - .coerce(&ty, exec_state) - } else { - value.coerce(&ty, exec_state) } - .map_err(|_| { + + value.coerce(&ty, exec_state).map_err(|_| { + let suggestion = if ty == RuntimeType::length() { + ", you might try coercing to a fully specified numeric type such as `number(mm)`" + } else if ty == RuntimeType::angle() { + ", you might try coercing to a fully specified numeric type such as `number(deg)`" + } else { + "" + }; KclError::Semantic(KclErrorDetails { - message: format!("could not coerce {} value to type {}", value.human_friendly_type(), ty), + message: format!( + "could not coerce {} value to type {ty}{suggestion}", + value.human_friendly_type() + ), source_ranges: vec![source_range], }) }) @@ -2767,4 +2781,29 @@ startSketchOn(XY) // Make sure we get a useful error message and not an engine error. assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message()); } + + #[tokio::test(flavor = "multi_thread")] + async fn coerce_unknown_to_length() { + let ast = r#"x = 2mm * 2mm +y = x: number(Length)"#; + let e = parse_execute(ast).await.unwrap_err(); + assert!( + e.message().contains("could not coerce"), + "Error message: '{}'", + e.message() + ); + + let ast = r#"x = 2mm +y = x: number(Length)"#; + let result = parse_execute(ast).await.unwrap(); + let mem = result.exec_state.stack(); + let num = mem + .memory + .get_from("y", result.mem_env, SourceRange::default(), 0) + .unwrap() + .as_ty_f64() + .unwrap(); + assert_eq!(num.n, 2.0); + assert_eq!(num.ty, NumericType::mm()); + } } diff --git a/rust/kcl-lib/src/execution/types.rs b/rust/kcl-lib/src/execution/types.rs index a90afd9ea..dca994dae 100644 --- a/rust/kcl-lib/src/execution/types.rs +++ b/rust/kcl-lib/src/execution/types.rs @@ -664,6 +664,17 @@ impl NumericType { ) } + pub fn is_fully_specified(&self) -> bool { + !matches!( + self, + NumericType::Unknown + | NumericType::Known(UnitType::Angle(UnitAngle::Unknown)) + | NumericType::Known(UnitType::Length(UnitLen::Unknown)) + | NumericType::Any + | NumericType::Default { .. } + ) + } + fn example_ty(&self) -> Option { match self { Self::Known(t) if !self.is_unknown() => Some(t.to_string()), diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 71f4e891e..d69177998 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -102,7 +102,7 @@ impl TyF64 { t => unreachable!("expected length, found {t:?}"), }; - assert_ne!(len, UnitLen::Unknown); + debug_assert_ne!(len, UnitLen::Unknown); len.adjust_to(self.n, units).0 } @@ -114,7 +114,7 @@ impl TyF64 { _ => unreachable!(), }; - assert_ne!(angle, UnitAngle::Unknown); + debug_assert_ne!(angle, UnitAngle::Unknown); angle.adjust_to(self.n, UnitAngle::Degrees).0 } @@ -126,7 +126,7 @@ impl TyF64 { _ => unreachable!(), }; - assert_ne!(angle, UnitAngle::Unknown); + debug_assert_ne!(angle, UnitAngle::Unknown); angle.adjust_to(self.n, UnitAngle::Radians).0 }