Change KCL error messages to display principal type of values (#6906)

This commit is contained in:
Jonathan Tran
2025-05-14 10:04:51 -04:00
committed by GitHub
parent edb424988d
commit 696222a070
11 changed files with 71 additions and 50 deletions

View File

@ -1249,7 +1249,7 @@ secondSketch = startSketchOn(part001, face = '')
let err = err.as_kcl_error().unwrap(); let err = err.as_kcl_error().unwrap();
assert_eq!( assert_eq!(
err.message(), err.message(),
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string (text)" "The arg face was given, but it was the wrong type. It should be type FaceTag but it was string"
); );
} }
@ -1882,7 +1882,7 @@ someFunction('INVALID')
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.err().unwrap().to_string(), result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string (text)" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
); );
} }

View File

@ -913,11 +913,9 @@ impl Node<MemberExpression> {
}), }),
(being_indexed, _, _) => { (being_indexed, _, _) => {
let t = being_indexed.human_friendly_type(); let t = being_indexed.human_friendly_type();
let article = article_for(t); let article = article_for(&t);
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
),
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
@ -1698,8 +1696,9 @@ impl Node<ObjectExpression> {
} }
} }
fn article_for(s: &str) -> &'static str { fn article_for<S: AsRef<str>>(s: S) -> &'static str {
if s.starts_with(['a', 'e', 'i', 'o', 'u']) { // '[' is included since it's an array.
if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
"an" "an"
} else { } else {
"a" "a"
@ -1709,10 +1708,9 @@ fn article_for(s: &str) -> &'static str {
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> { fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
v.as_ty_f64().ok_or_else(|| { v.as_ty_f64().ok_or_else(|| {
let actual_type = v.human_friendly_type(); let actual_type = v.human_friendly_type();
let article = article_for(actual_type);
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range], source_ranges: vec![source_range],
message: format!("Expected a number, but found {article} {actual_type}",), message: format!("Expected a number, but found {actual_type}",),
}) })
}) })
} }
@ -2446,19 +2444,23 @@ arr1 = [42]: [number(cm)]
a = 42: string a = 42: string
"#; "#;
let result = parse_execute(program).await; let result = parse_execute(program).await;
assert!(result let err = result.unwrap_err();
.unwrap_err() assert!(
.to_string() err.to_string()
.contains("could not coerce number value to type string")); .contains("could not coerce number(default units) value to type string"),
"Expected error but found {err:?}"
);
let program = r#" let program = r#"
a = 42: Plane a = 42: Plane
"#; "#;
let result = parse_execute(program).await; let result = parse_execute(program).await;
assert!(result let err = result.unwrap_err();
.unwrap_err() assert!(
.to_string() err.to_string()
.contains("could not coerce number value to type Plane")); .contains("could not coerce number(default units) value to type Plane"),
"Expected error but found {err:?}"
);
let program = r#" let program = r#"
arr = [0]: [string] arr = [0]: [string]
@ -2467,7 +2469,7 @@ arr = [0]: [string]
let err = result.unwrap_err(); let err = result.unwrap_err();
assert!( assert!(
err.to_string() err.to_string()
.contains("could not coerce array (list) value to type [string]"), .contains("could not coerce [any; 1] value to type [string]"),
"Expected error but found {err:?}" "Expected error but found {err:?}"
); );
@ -2478,7 +2480,7 @@ mixedArr = [0, "a"]: [number(mm)]
let err = result.unwrap_err(); let err = result.unwrap_err();
assert!( assert!(
err.to_string() err.to_string()
.contains("could not coerce array (list) value to type [number(mm)]"), .contains("could not coerce [any; 2] value to type [number(mm)]"),
"Expected error but found {err:?}" "Expected error but found {err:?}"
); );
} }

View File

@ -280,7 +280,10 @@ impl KclValue {
/// Human readable type name used in error messages. Should not be relied /// Human readable type name used in error messages. Should not be relied
/// on for program logic. /// on for program logic.
pub(crate) fn human_friendly_type(&self) -> &'static str { pub(crate) fn human_friendly_type(&self) -> String {
if let Some(t) = self.principal_type() {
return t.to_string();
}
match self { match self {
KclValue::Uuid { .. } => "Unique ID (uuid)", KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator", KclValue::TagDeclarator(_) => "TagDeclarator",
@ -314,6 +317,7 @@ impl KclValue {
KclValue::Type { .. } => "type", KclValue::Type { .. } => "type",
KclValue::KclNone { .. } => "None", KclValue::KclNone { .. } => "None",
} }
.to_owned()
} }
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self { pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {

View File

@ -1910,13 +1910,13 @@ notNull = !myNull
"#; "#;
assert_eq!( assert_eq!(
parse_execute(code1).await.unwrap_err().message(), parse_execute(code1).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number", "Cannot apply unary operator ! to non-boolean value: number(default units)",
); );
let code2 = "notZero = !0"; let code2 = "notZero = !0";
assert_eq!( assert_eq!(
parse_execute(code2).await.unwrap_err().message(), parse_execute(code2).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number", "Cannot apply unary operator ! to non-boolean value: number(default units)",
); );
let code3 = r#" let code3 = r#"
@ -1924,7 +1924,7 @@ notEmptyString = !""
"#; "#;
assert_eq!( assert_eq!(
parse_execute(code3).await.unwrap_err().message(), parse_execute(code3).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: string (text)", "Cannot apply unary operator ! to non-boolean value: string",
); );
let code4 = r#" let code4 = r#"
@ -1933,7 +1933,7 @@ notMember = !obj.a
"#; "#;
assert_eq!( assert_eq!(
parse_execute(code4).await.unwrap_err().message(), parse_execute(code4).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number", "Cannot apply unary operator ! to non-boolean value: number(default units)",
); );
let code5 = " let code5 = "
@ -1941,7 +1941,7 @@ a = []
notArray = !a"; notArray = !a";
assert_eq!( assert_eq!(
parse_execute(code5).await.unwrap_err().message(), parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: array (list)", "Cannot apply unary operator ! to non-boolean value: [any; 0]",
); );
let code6 = " let code6 = "
@ -1949,7 +1949,7 @@ x = {}
notObject = !x"; notObject = !x";
assert_eq!( assert_eq!(
parse_execute(code6).await.unwrap_err().message(), parse_execute(code6).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: object", "Cannot apply unary operator ! to non-boolean value: { }",
); );
let code7 = " let code7 = "
@ -1975,7 +1975,7 @@ notTagDeclarator = !myTagDeclarator";
assert!( assert!(
tag_declarator_err tag_declarator_err
.message() .message()
.starts_with("Cannot apply unary operator ! to non-boolean value: TagDeclarator"), .starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
"Actual error: {:?}", "Actual error: {:?}",
tag_declarator_err tag_declarator_err
); );
@ -1989,7 +1989,7 @@ notTagIdentifier = !myTag";
assert!( assert!(
tag_identifier_err tag_identifier_err
.message() .message()
.starts_with("Cannot apply unary operator ! to non-boolean value: TagIdentifier"), .starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
"Actual error: {:?}", "Actual error: {:?}",
tag_identifier_err tag_identifier_err
); );

View File

@ -257,14 +257,22 @@ impl Args {
}; };
let arg = arg.value.coerce(ty, exec_state).map_err(|_| { let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type_name = arg.value.human_friendly_type(); let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let msg_base = format!( let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}", "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(), ty.human_friendly_type(),
); );
let suggestion = match (ty, actual_type_name) { let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER), (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => { Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER) Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
} }
_ => None, _ => None,
@ -381,14 +389,22 @@ impl Args {
}))?; }))?;
let arg = arg.value.coerce(ty, exec_state).map_err(|_| { let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type_name = arg.value.human_friendly_type(); let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let msg_base = format!( let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}", "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(), ty.human_friendly_type(),
); );
let suggestion = match (ty, actual_type_name) { let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER), (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => { Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER) Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
} }
_ => None, _ => None,

View File

@ -4,8 +4,7 @@ description: Error from executing argument_error.kcl
--- ---
KCL Semantic error KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found array × semantic: f requires a value with type `fn(any): any`, but found [any; 2]
│ (list)
╭─[5:1] ╭─[5:1]
4 │ 4 │
5 │ map(f, f = [0, 1]) 5 │ map(f, f = [0, 1])
@ -16,7 +15,7 @@ KCL Semantic error
╰─▶ KCL Semantic error ╰─▶ KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found × semantic: f requires a value with type `fn(any): any`, but found
array (list) [any; 2]
╭─[5:12] ╭─[5:12]
4 │ 4 │
5 │ map(f, f = [0, 1]) 5 │ map(f, f = [0, 1])

View File

@ -5,7 +5,7 @@ description: Error from executing array_elem_pop_empty_fail.kcl
KCL Semantic error KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value with × semantic: The input argument of `std::array::pop` requires a value with
│ type `[any; 1+]`, but found array (list) │ type `[any; 1+]`, but found [any; 0]
╭─[2:8] ╭─[2:8]
1 │ arr = [] 1 │ arr = []
2 │ fail = pop(arr) 2 │ fail = pop(arr)
@ -16,7 +16,7 @@ KCL Semantic error
╰─▶ KCL Semantic error ╰─▶ KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value × semantic: The input argument of `std::array::pop` requires a value
│ with type `[any; 1+]`, but found array (list) │ with type `[any; 1+]`, but found [any; 0]
╭─[2:12] ╭─[2:12]
1 │ arr = [] 1 │ arr = []
2 │ fail = pop(arr) 2 │ fail = pop(arr)

View File

@ -4,7 +4,7 @@ description: Error from executing comparisons_multiple.kcl
--- ---
KCL Semantic error KCL Semantic error
× semantic: Expected a number, but found a boolean (true/false value) × semantic: Expected a number, but found bool
╭──── ╭────
1 │ assert(3 == 3 == 3, error = "this should not compile") 1 │ assert(3 == 3 == 3, error = "this should not compile")
· ───┬── · ───┬──

View File

@ -5,7 +5,7 @@ description: Error from executing error_inside_fn_also_has_source_range_of_call_
KCL Semantic error KCL Semantic error
× semantic: This function expected the input argument to be Solid or Plane × semantic: This function expected the input argument to be Solid or Plane
│ but it's actually of type string (text) │ but it's actually of type string
╭─[3:23] ╭─[3:23]
2 │ fn someNestedFunction(@something2) { 2 │ fn someNestedFunction(@something2) {
3 │ startSketchOn(something2) 3 │ startSketchOn(something2)
@ -25,7 +25,7 @@ KCL Semantic error
├─▶ KCL Semantic error ├─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or × semantic: This function expected the input argument to be Solid or
│ │ Plane but it's actually of type string (text) │ │ Plane but it's actually of type string
│ ╭─[3:23] │ ╭─[3:23]
│ 2 │ fn someNestedFunction(@something2) { │ 2 │ fn someNestedFunction(@something2) {
│ 3 │ startSketchOn(something2) │ 3 │ startSketchOn(something2)
@ -37,7 +37,7 @@ KCL Semantic error
╰─▶ KCL Semantic error ╰─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or × semantic: This function expected the input argument to be Solid or
│ Plane but it's actually of type string (text) │ Plane but it's actually of type string
╭─[6:5] ╭─[6:5]
5 │ 5 │
6 │ someNestedFunction(something) 6 │ someNestedFunction(something)

View File

@ -1,11 +1,11 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl-lib/src/simulation_tests.rs
description: Error from executing invalid_member_object.kcl description: Error from executing invalid_member_object.kcl
--- ---
KCL Semantic error KCL Semantic error
× semantic: Only arrays and objects can be indexed, but you're trying to × semantic: Only arrays can be indexed, but you're trying to index a
index a number number(default units)
╭─[2:5] ╭─[2:5]
1 │ num = 999 1 │ num = 999
2 │ x = num[3] 2 │ x = num[3]

View File

@ -5,7 +5,7 @@ description: Error from executing panic_repro_cube.kcl
KCL Semantic error KCL Semantic error
× semantic: This function expected the input argument to be tag identifier × semantic: This function expected the input argument to be tag identifier
│ but it's actually of type Unique ID (uuid) │ but it's actually of type tag
╭─[43:25] ╭─[43:25]
42 │ // these double wrapped functions are the point of this test 42 │ // these double wrapped functions are the point of this test
43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)), 43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)),