Improve error messages around PI and other numbers with unknown units (#7457)
* Improve docs around PI Signed-off-by: Nick Cameron <nrc@ncameron.org> * Refactor and polish type error messages Signed-off-by: Nick Cameron <nrc@ncameron.org> * Add suggestion to fix unknown numbers error Signed-off-by: Nick Cameron <nrc@ncameron.org> * Don't warn so often about unknown units Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -4,7 +4,6 @@ use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::types::UnitType;
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
@ -281,69 +280,57 @@ impl KclValue {
|
||||
/// Human readable type name used in error messages. Should not be relied
|
||||
/// on for program logic.
|
||||
pub(crate) fn human_friendly_type(&self) -> String {
|
||||
self.inner_human_friendly_type(1)
|
||||
}
|
||||
|
||||
fn inner_human_friendly_type(&self, max_depth: usize) -> String {
|
||||
if let Some(pt) = self.principal_type() {
|
||||
if max_depth > 0 {
|
||||
// The principal type of an array uses the array's element type,
|
||||
// which is oftentimes `any`, and that's not a helpful message. So
|
||||
// we show the actual elements.
|
||||
if let KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } = self {
|
||||
// If it's empty, we want to show the type of the array.
|
||||
if !value.is_empty() {
|
||||
// A max of 3 is good because it's common to use 3D points.
|
||||
let max = 3;
|
||||
let len = value.len();
|
||||
let ellipsis = if len > max { ", ..." } else { "" };
|
||||
let element_label = if len == 1 { "value" } else { "values" };
|
||||
let element_tys = value
|
||||
.iter()
|
||||
.take(max)
|
||||
.map(|elem| elem.inner_human_friendly_type(max_depth - 1))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
return format!("array of {element_tys}{ellipsis} with {len} {element_label}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return pt.to_string();
|
||||
}
|
||||
match self {
|
||||
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||
KclValue::TagIdentifier(_) => "TagIdentifier",
|
||||
KclValue::Solid { .. } => "Solid",
|
||||
KclValue::Sketch { .. } => "Sketch",
|
||||
KclValue::Helix { .. } => "Helix",
|
||||
KclValue::ImportedGeometry(_) => "ImportedGeometry",
|
||||
KclValue::Function { .. } => "Function",
|
||||
KclValue::Plane { .. } => "Plane",
|
||||
KclValue::Face { .. } => "Face",
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
|
||||
KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
|
||||
KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
|
||||
KclValue::Solid { .. } => "a solid".to_owned(),
|
||||
KclValue::Sketch { .. } => "a sketch".to_owned(),
|
||||
KclValue::Helix { .. } => "a helix".to_owned(),
|
||||
KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
|
||||
KclValue::Function { .. } => "a function".to_owned(),
|
||||
KclValue::Plane { .. } => "a plane".to_owned(),
|
||||
KclValue::Face { .. } => "a face".to_owned(),
|
||||
KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
|
||||
KclValue::Number {
|
||||
ty: NumericType::Unknown,
|
||||
..
|
||||
} => "number(unknown units)",
|
||||
} => "a number with unknown units".to_owned(),
|
||||
KclValue::Number {
|
||||
ty: NumericType::Known(UnitType::Length(_)),
|
||||
ty: NumericType::Known(units),
|
||||
..
|
||||
} => "number(Length)",
|
||||
KclValue::Number {
|
||||
ty: NumericType::Known(UnitType::Angle(_)),
|
||||
..
|
||||
} => "number(Angle)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Tuple { .. } => "tuple (list)",
|
||||
KclValue::HomArray { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::Type { .. } => "type",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
} => format!("a number ({units})"),
|
||||
KclValue::Number { .. } => "a number".to_owned(),
|
||||
KclValue::String { .. } => "a string".to_owned(),
|
||||
KclValue::Object { .. } => "an object".to_owned(),
|
||||
KclValue::Module { .. } => "a module".to_owned(),
|
||||
KclValue::Type { .. } => "a type".to_owned(),
|
||||
KclValue::KclNone { .. } => "none".to_owned(),
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
if value.is_empty() {
|
||||
"an empty array".to_owned()
|
||||
} else {
|
||||
// A max of 3 is good because it's common to use 3D points.
|
||||
const MAX: usize = 3;
|
||||
|
||||
let len = value.len();
|
||||
let element_tys = value
|
||||
.iter()
|
||||
.take(MAX)
|
||||
.map(|elem| elem.principal_type_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let mut result = format!("an array of {element_tys}");
|
||||
if len > MAX {
|
||||
result.push_str(&format!(", ... with {len} values"));
|
||||
}
|
||||
if len == 1 {
|
||||
result.push_str(" with 1 value");
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
|
||||
@ -602,6 +589,13 @@ impl KclValue {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_unknown_number(&self) -> bool {
|
||||
match self {
|
||||
KclValue::Number { ty, .. } => !ty.is_fully_specified(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_str(&self) -> Option<String> {
|
||||
match self {
|
||||
KclValue::Bool { value, .. } => Some(format!("{value}")),
|
||||
@ -649,6 +643,8 @@ impl From<GeometryWithImportedGeometry> for KclValue {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::exec::UnitType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@ -658,21 +654,21 @@ mod tests {
|
||||
ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(len.human_friendly_type(), "number(Length)".to_string());
|
||||
assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
|
||||
|
||||
let unknown = KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::Unknown,
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(unknown.human_friendly_type(), "number(unknown units)".to_string());
|
||||
assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
|
||||
|
||||
let mm = KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
|
||||
meta: vec![],
|
||||
};
|
||||
assert_eq!(mm.human_friendly_type(), "number(mm)".to_string());
|
||||
assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
|
||||
|
||||
let array1_mm = KclValue::HomArray {
|
||||
value: vec![mm.clone()],
|
||||
@ -680,7 +676,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array1_mm.human_friendly_type(),
|
||||
"array of number(mm) with 1 value".to_string()
|
||||
"an array of `number(mm)` with 1 value".to_string()
|
||||
);
|
||||
|
||||
let array2_mm = KclValue::HomArray {
|
||||
@ -689,7 +685,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array2_mm.human_friendly_type(),
|
||||
"array of number(mm), number(mm) with 2 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`".to_string()
|
||||
);
|
||||
|
||||
let array3_mm = KclValue::HomArray {
|
||||
@ -698,7 +694,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array3_mm.human_friendly_type(),
|
||||
"array of number(mm), number(mm), number(mm) with 3 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
|
||||
);
|
||||
|
||||
let inches = KclValue::Number {
|
||||
@ -712,14 +708,14 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array4.human_friendly_type(),
|
||||
"array of number(mm), number(mm), number(in), ... with 4 values".to_string()
|
||||
"an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
|
||||
);
|
||||
|
||||
let empty_array = KclValue::HomArray {
|
||||
value: vec![],
|
||||
ty: RuntimeType::any(),
|
||||
};
|
||||
assert_eq!(empty_array.human_friendly_type(), "[any; 0]".to_string());
|
||||
assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
|
||||
|
||||
let array_nested = KclValue::HomArray {
|
||||
value: vec![array2_mm.clone()],
|
||||
@ -727,7 +723,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
array_nested.human_friendly_type(),
|
||||
"array of [any; 2] with 1 value".to_string()
|
||||
"an array of `[any; 2]` with 1 value".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user