Fix argument error to point to the arg at the call site (#4533)

* Fix argument error to point to the arg at the call site

* Change MemoryFunction to accept Vec<Arg> instead of Vec<KclValue>

* Rename variable to be clearer

* Fix one more argument error message

* Clean up from PR feedback
This commit is contained in:
Jonathan Tran
2024-11-21 13:10:03 -05:00
committed by GitHub
parent 0092922565
commit 676ac201bb
13 changed files with 465 additions and 40 deletions

View File

@ -11,7 +11,7 @@ use crate::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo, BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier, TagIdentifier,
}, },
std::FunctionKind, std::{args::Arg, FunctionKind},
}; };
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -361,16 +361,17 @@ impl Node<CallExpression> {
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let fn_name = &self.callee.name; let fn_name = &self.callee.name;
let mut fn_args: Vec<KclValue> = Vec::with_capacity(self.arguments.len()); let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
for arg in &self.arguments { for arg_expr in &self.arguments {
let metadata = Metadata { let metadata = Metadata {
source_range: SourceRange::from(arg), source_range: SourceRange::from(arg_expr),
}; };
let result = ctx let value = ctx
.execute_expr(arg, exec_state, &metadata, StatementKind::Expression) .execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
.await?; .await?;
fn_args.push(result); let arg = Arg::new(value, SourceRange::from(arg_expr));
fn_args.push(arg);
} }
match ctx.stdlib.get_either(&self.callee.name) { match ctx.stdlib.get_either(&self.callee.name) {
@ -470,14 +471,18 @@ impl Node<CallExpression> {
for (index, param) in required_params.iter().enumerate() { for (index, param) in required_params.iter().enumerate() {
fn_memory.add( fn_memory.add(
&param.identifier.name, &param.identifier.name,
fn_args.get(index).unwrap().clone(), fn_args.get(index).unwrap().value.clone(),
param.identifier.clone().into(), param.identifier.clone().into(),
)?; )?;
} }
// Add the optional arguments to the memory. // Add the optional arguments to the memory.
for (index, param) in optional_params.iter().enumerate() { for (index, param) in optional_params.iter().enumerate() {
if let Some(arg) = fn_args.get(index + required_params.len()) { if let Some(arg) = fn_args.get(index + required_params.len()) {
fn_memory.add(&param.identifier.name, arg.clone(), param.identifier.clone().into())?; fn_memory.add(
&param.identifier.name,
arg.value.clone(),
param.identifier.clone().into(),
)?;
} else { } else {
fn_memory.add( fn_memory.add(
&param.identifier.name, &param.identifier.name,

View File

@ -33,7 +33,7 @@ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem}, fs::{FileManager, FileSystem},
settings::types::UnitLength, settings::types::UnitLength,
std::StdLib, std::{args::Arg, StdLib},
Program, Program,
}; };
@ -740,7 +740,7 @@ impl std::hash::Hash for TagIdentifier {
pub type MemoryFunction = pub type MemoryFunction =
fn( fn(
s: Vec<KclValue>, s: Vec<Arg>,
memory: ProgramMemory, memory: ProgramMemory,
expression: crate::ast::types::BoxNode<FunctionExpression>, expression: crate::ast::types::BoxNode<FunctionExpression>,
metadata: Vec<Metadata>, metadata: Vec<Metadata>,
@ -1032,6 +1032,11 @@ impl SourceRange {
Self([start, end, module_id.as_usize()]) Self([start, end, module_id.as_usize()])
} }
/// A source range that doesn't correspond to any source code.
pub fn synthetic() -> Self {
Self::default()
}
/// Get the start of the range. /// Get the start of the range.
pub fn start(&self) -> usize { pub fn start(&self) -> usize {
self.0[0] self.0[0]
@ -2203,7 +2208,7 @@ impl ExecutorContext {
/// Returns Err if too few/too many arguments were given for the function. /// Returns Err if too few/too many arguments were given for the function.
fn assign_args_to_params( fn assign_args_to_params(
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
args: Vec<KclValue>, args: Vec<Arg>,
mut fn_memory: ProgramMemory, mut fn_memory: ProgramMemory,
) -> Result<ProgramMemory, KclError> { ) -> Result<ProgramMemory, KclError> {
let num_args = function_expression.number_of_args(); let num_args = function_expression.number_of_args();
@ -2229,7 +2234,7 @@ fn assign_args_to_params(
for (index, param) in function_expression.params.iter().enumerate() { for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) { if let Some(arg) = args.get(index) {
// Argument was provided. // Argument was provided.
fn_memory.add(&param.identifier.name, arg.clone(), (&param.identifier).into())?; fn_memory.add(&param.identifier.name, arg.value.clone(), (&param.identifier).into())?;
} else { } else {
// Argument was not provided. // Argument was not provided.
if param.optional { if param.optional {
@ -2257,7 +2262,7 @@ fn assign_args_to_params(
} }
pub(crate) async fn call_user_defined_function( pub(crate) async fn call_user_defined_function(
args: Vec<KclValue>, args: Vec<Arg>,
memory: &ProgramMemory, memory: &ProgramMemory,
function_expression: NodeRef<'_, FunctionExpression>, function_expression: NodeRef<'_, FunctionExpression>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
@ -3156,6 +3161,7 @@ let w = f() + f()
return_type: None, return_type: None,
digest: None, digest: None,
}); });
let args = args.into_iter().map(Arg::synthetic).collect();
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new()); let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
assert_eq!( assert_eq!(
actual, expected, actual, expected,

View File

@ -6,6 +6,7 @@ use crate::{
executor::{ executor::{
call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
}, },
std::args::Arg,
}; };
/// A function being used as a parameter into a stdlib function. This is a /// A function being used as a parameter into a stdlib function. This is a
@ -19,7 +20,7 @@ pub struct FunctionParam<'a> {
} }
impl<'a> FunctionParam<'a> { impl<'a> FunctionParam<'a> {
pub async fn call(&self, exec_state: &mut ExecState, args: Vec<KclValue>) -> Result<Option<KclValue>, KclError> { pub async fn call(&self, exec_state: &mut ExecState, args: Vec<Arg>) -> Result<Option<KclValue>, KclError> {
if let Some(inner) = self.inner { if let Some(inner) = self.inner {
inner( inner(
args, args,

View File

@ -9,7 +9,7 @@ use crate::{
errors::KclErrorDetails, errors::KclErrorDetails,
exec::{ProgramMemory, Sketch}, exec::{ProgramMemory, Sketch},
executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier}, executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
std::FnAsArg, std::{args::Arg, FnAsArg},
ExecState, ExecutorContext, KclError, SourceRange, ExecState, ExecutorContext, KclError, SourceRange,
}; };
@ -452,7 +452,7 @@ impl KclValue {
/// If it's not a function, return Err. /// If it's not a function, return Err.
pub async fn call_fn( pub async fn call_fn(
&self, &self,
args: Vec<KclValue>, args: Vec<Arg>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
ctx: ExecutorContext, ctx: ExecutorContext,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {

View File

@ -413,6 +413,36 @@ mod add_lots {
super::execute(TEST_NAME, false).await super::execute(TEST_NAME, false).await
} }
} }
mod argument_error {
//! The argument error points to the problematic argument in the call site,
//! not the function definition that the variable points to.
const TEST_NAME: &str = "argument_error";
/// Test tokenizing KCL.
#[test]
fn tokenize() {
super::tokenize(TEST_NAME)
}
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, false).await
}
}
mod array_elem_push { mod array_elem_push {
const TEST_NAME: &str = "array_elem_push"; const TEST_NAME: &str = "array_elem_push";

View File

@ -16,15 +16,40 @@ use crate::{
use super::shapes::PolygonType; use super::shapes::PolygonType;
#[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)] #[derive(Debug, Clone)]
pub struct Args { pub struct Args {
pub args: Vec<KclValue>, pub args: Vec<Arg>,
pub source_range: SourceRange, pub source_range: SourceRange,
pub ctx: ExecutorContext, pub ctx: ExecutorContext,
} }
impl Args { impl Args {
pub fn new(args: Vec<KclValue>, source_range: SourceRange, ctx: ExecutorContext) -> Self { pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
Self { Self {
args, args,
source_range, source_range,
@ -244,10 +269,10 @@ impl Args {
.args .args
.iter() .iter()
.map(|arg| { .map(|arg| {
let Some(num) = f64::from_kcl_val(arg) else { let Some(num) = f64::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
source_ranges: arg.metadata().iter().map(|x| x.source_range).collect(), source_ranges: arg.source_ranges(),
message: format!("Expected a number but found {}", arg.human_friendly_type()), message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
})); }));
}; };
Ok(num) Ok(num)
@ -509,10 +534,10 @@ impl<'a> FromArgs<'a> for Vec<KclValue> {
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
})); }));
}; };
let KclValue::Array { value: array, meta: _ } = arg else { let KclValue::Array { value: array, meta: _ } = &arg.value else {
let message = format!("Expected an array but found {}", arg.human_friendly_type()); let message = format!("Expected an array but found {}", arg.value.human_friendly_type());
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {
source_ranges: arg.metadata().into_iter().map(|m| m.source_range).collect(), source_ranges: arg.source_ranges(),
message, message,
})); }));
}; };
@ -531,14 +556,14 @@ where
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
})); }));
}; };
let Some(val) = T::from_kcl_val(arg) else { let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Argument at index {i} was supposed to be type {} but found {}", "Argument at index {i} was supposed to be type {} but found {}",
type_name::<T>(), type_name::<T>(),
arg.human_friendly_type() arg.value.human_friendly_type()
), ),
source_ranges: vec![args.source_range], source_ranges: arg.source_ranges(),
})); }));
}; };
Ok(val) Ok(val)
@ -551,17 +576,17 @@ where
{ {
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> { fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else { return Ok(None) }; let Some(arg) = args.args.get(i) else { return Ok(None) };
if crate::ast::types::KclNone::from_kcl_val(arg).is_some() { if crate::ast::types::KclNone::from_kcl_val(&arg.value).is_some() {
return Ok(None); return Ok(None);
} }
let Some(val) = T::from_kcl_val(arg) else { let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Argument at index {i} was supposed to be type Option<{}> but found {}", "Argument at index {i} was supposed to be type Option<{}> but found {}",
type_name::<T>(), type_name::<T>(),
arg.human_friendly_type() arg.value.human_friendly_type()
), ),
source_ranges: vec![args.source_range], source_ranges: arg.source_ranges(),
})); }));
}; };
Ok(Some(val)) Ok(Some(val))

View File

@ -1,6 +1,9 @@
use derive_docs::stdlib; use derive_docs::stdlib;
use super::{args::FromArgs, Args, FnAsArg}; use super::{
args::{Arg, FromArgs},
Args, FnAsArg,
};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange}, executor::{ExecState, KclValue, SourceRange},
@ -75,7 +78,7 @@ async fn call_map_closure<'a>(
source_range: SourceRange, source_range: SourceRange,
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<KclValue, KclError> { ) -> Result<KclValue, KclError> {
let output = map_fn.call(exec_state, vec![input]).await?; let output = map_fn.call(exec_state, vec![Arg::synthetic(input)]).await?;
let source_ranges = vec![source_range]; let source_ranges = vec![source_range];
let output = output.ok_or_else(|| { let output = output.ok_or_else(|| {
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
@ -202,7 +205,7 @@ async fn call_reduce_closure<'a>(
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<KclValue, KclError> { ) -> Result<KclValue, KclError> {
// Call the reduce fn for this repetition. // Call the reduce fn for this repetition.
let reduce_fn_args = vec![elem, start]; let reduce_fn_args = vec![Arg::synthetic(elem), Arg::synthetic(start)];
let transform_fn_return = reduce_fn.call(exec_state, reduce_fn_args).await?; let transform_fn_return = reduce_fn.call(exec_state, reduce_fn_args).await?;
// Unpack the returned transform object. // Unpack the returned transform object.

View File

@ -22,6 +22,8 @@ use crate::{
std::{types::Uint, Args}, std::{types::Uint, Args},
}; };
use super::args::Arg;
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry"; const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 2D sketch. /// Data for a linear pattern on a 2D sketch.
@ -381,7 +383,7 @@ async fn make_transform<'a>(
value: i.into(), value: i.into(),
meta: vec![source_range.into()], meta: vec![source_range.into()],
}; };
let transform_fn_args = vec![repetition_num]; let transform_fn_args = vec![Arg::synthetic(repetition_num)];
let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?; let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?;
// Unpack the returned transform object. // Unpack the returned transform object.

View File

@ -0,0 +1,137 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing argument_error.kcl
---
{
"Ok": {
"body": [
{
"declarations": [
{
"end": 28,
"id": {
"end": 4,
"name": "f",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 26,
"raw": "5",
"start": 25,
"type": "Literal",
"type": "Literal",
"value": 5
},
"end": 26,
"start": 18,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 28,
"start": 14
},
"end": 28,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 9,
"name": "i",
"start": 8,
"type": "Identifier"
},
"optional": false
}
],
"start": 7,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 3,
"type": "VariableDeclarator"
}
],
"end": 28,
"kind": "fn",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 44,
"expression": {
"arguments": [
{
"end": 35,
"name": "f",
"start": 34,
"type": "Identifier",
"type": "Identifier"
},
{
"elements": [
{
"end": 39,
"raw": "0",
"start": 38,
"type": "Literal",
"type": "Literal",
"value": 0
},
{
"end": 42,
"raw": "1",
"start": 41,
"type": "Literal",
"type": "Literal",
"value": 1
}
],
"end": 43,
"start": 37,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 33,
"name": "map",
"start": 30,
"type": "Identifier"
},
"end": 44,
"optional": false,
"start": 30,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 30,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 45,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 30,
"start": 28,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,5 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing argument_error.kcl
---
type: KclErrorDetails { source_ranges: [SourceRange([34, 35, 0])], message: "Expected an array but found Function" }

View File

@ -0,0 +1,5 @@
fn f = (i) => {
return 5
}
map(f, [0, 1])

View File

@ -0,0 +1,206 @@
---
source: kcl/src/simulation_tests.rs
description: Result of tokenizing argument_error.kcl
---
{
"Ok": [
{
"type": "keyword",
"start": 0,
"end": 2,
"value": "fn"
},
{
"type": "whitespace",
"start": 2,
"end": 3,
"value": " "
},
{
"type": "word",
"start": 3,
"end": 4,
"value": "f"
},
{
"type": "whitespace",
"start": 4,
"end": 5,
"value": " "
},
{
"type": "operator",
"start": 5,
"end": 6,
"value": "="
},
{
"type": "whitespace",
"start": 6,
"end": 7,
"value": " "
},
{
"type": "brace",
"start": 7,
"end": 8,
"value": "("
},
{
"type": "word",
"start": 8,
"end": 9,
"value": "i"
},
{
"type": "brace",
"start": 9,
"end": 10,
"value": ")"
},
{
"type": "whitespace",
"start": 10,
"end": 11,
"value": " "
},
{
"type": "operator",
"start": 11,
"end": 13,
"value": "=>"
},
{
"type": "whitespace",
"start": 13,
"end": 14,
"value": " "
},
{
"type": "brace",
"start": 14,
"end": 15,
"value": "{"
},
{
"type": "whitespace",
"start": 15,
"end": 18,
"value": "\n "
},
{
"type": "keyword",
"start": 18,
"end": 24,
"value": "return"
},
{
"type": "whitespace",
"start": 24,
"end": 25,
"value": " "
},
{
"type": "number",
"start": 25,
"end": 26,
"value": "5"
},
{
"type": "whitespace",
"start": 26,
"end": 27,
"value": "\n"
},
{
"type": "brace",
"start": 27,
"end": 28,
"value": "}"
},
{
"type": "whitespace",
"start": 28,
"end": 30,
"value": "\n\n"
},
{
"type": "word",
"start": 30,
"end": 33,
"value": "map"
},
{
"type": "brace",
"start": 33,
"end": 34,
"value": "("
},
{
"type": "word",
"start": 34,
"end": 35,
"value": "f"
},
{
"type": "comma",
"start": 35,
"end": 36,
"value": ","
},
{
"type": "whitespace",
"start": 36,
"end": 37,
"value": " "
},
{
"type": "brace",
"start": 37,
"end": 38,
"value": "["
},
{
"type": "number",
"start": 38,
"end": 39,
"value": "0"
},
{
"type": "comma",
"start": 39,
"end": 40,
"value": ","
},
{
"type": "whitespace",
"start": 40,
"end": 41,
"value": " "
},
{
"type": "number",
"start": 41,
"end": 42,
"value": "1"
},
{
"type": "brace",
"start": 42,
"end": 43,
"value": "]"
},
{
"type": "brace",
"start": 43,
"end": 44,
"value": ")"
},
{
"type": "whitespace",
"start": 44,
"end": 45,
"value": "\n"
}
]
}

View File

@ -866,7 +866,7 @@ part = rectShape([0, 0], 20, 20)
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([863, 912, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::shapes::CircleData but found string (text)" }"#, r#"semantic: KclErrorDetails { source_ranges: [SourceRange([870, 874, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::shapes::CircleData but found string (text)" }"#,
); );
} }
@ -1351,7 +1351,7 @@ secondSketch = startSketchOn(part001, '')
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([260, 286, 0])], message: "Argument at index 1 was supposed to be type Option<kcl_lib::std::sketch::FaceTag> but found string (text)" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([283, 285, 0])], message: "Argument at index 1 was supposed to be type Option<kcl_lib::std::sketch::FaceTag> but found string (text)" }"#
); );
} }
@ -1983,7 +1983,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([37, 61, 0]), SourceRange([65, 88, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::sketch::SketchData but found string (text)" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([51, 60, 0]), SourceRange([65, 88, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::sketch::SketchData but found string (text)" }"#
); );
} }
@ -2004,7 +2004,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([89, 114, 0]), SourceRange([126, 155, 0]), SourceRange([159, 182, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::sketch::SketchData but found string (text)" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([103, 113, 0]), SourceRange([126, 155, 0]), SourceRange([159, 182, 0])], message: "Argument at index 0 was supposed to be type kcl_lib::std::sketch::SketchData but found string (text)" }"#
); );
} }