diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index 38893f758..f921db2aa 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -88,6 +88,7 @@ jobs: KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} ZOO_HOST: https://api.dev.zoo.dev RUST_BACKTRACE: full + RUST_MIN_STACK: 10485760000 - name: Commit differences if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure' shell: bash @@ -119,6 +120,7 @@ jobs: # Configure nextest when it's run by insta (via just). NEXTEST_PROFILE: ci RUST_BACKTRACE: full + RUST_MIN_STACK: 10485760000 - name: Build and archive tests run: | cd rust @@ -182,6 +184,7 @@ jobs: env: KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} ZOO_HOST: https://api.dev.zoo.dev + RUST_MIN_STACK: 10485760000 - name: Upload results if: always() run: .github/ci-cd-scripts/upload-results.sh @@ -238,6 +241,7 @@ jobs: KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} ZOO_HOST: https://api.dev.zoo.dev MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}} + RUST_MIN_STACK: 10485760000 run-wasm-tests: name: Run wasm tests strategy: diff --git a/e2e/playwright/code-pane-and-errors.spec.ts b/e2e/playwright/code-pane-and-errors.spec.ts index be6ca7992..21a3c09e0 100644 --- a/e2e/playwright/code-pane-and-errors.spec.ts +++ b/e2e/playwright/code-pane-and-errors.spec.ts @@ -235,6 +235,48 @@ extrude001 = extrude(sketch001, length = 5)` .first() ).toBeVisible() }) + + test('KCL errors with functions show hints for the entire backtrace', async ({ + page, + homePage, + scene, + cmdBar, + editor, + toolbar, + }) => { + await homePage.goToModelingScene() + await scene.settled(cmdBar) + + const code = `fn check(@x) { + return assert(x, isGreaterThan = 0) +} + +fn middle(@x) { + return check(x) +} + +middle(1) +middle(0) +` + await test.step('Set the code with a KCL error', async () => { + await toolbar.openPane('code') + await editor.replaceCode('', code) + }) + // This shows all the diagnostics in a way that doesn't require the mouse + // pointer hovering over a coordinate, which would be brittle. + await test.step('Open CodeMirror diagnostics list', async () => { + // Ensure keyboard focus is in the editor. + await page.getByText('fn check(').click() + await page.keyboard.press('ControlOrMeta+Shift+M') + }) + await expect( + page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't +check() +middle()`) + ).toBeVisible() + // There should be one hint inside middle() and one at the top level. + await expect(page.getByText('Part of the error backtrace')).toHaveCount(2) + }) }) test( diff --git a/rust/kcl-lib/e2e/executor/main.rs b/rust/kcl-lib/e2e/executor/main.rs index 80ea8ad65..cb788c1fc 100644 --- a/rust/kcl-lib/e2e/executor/main.rs +++ b/rust/kcl-lib/e2e/executor/main.rs @@ -2,7 +2,7 @@ mod cache; use kcl_lib::{ test_server::{execute_and_export_step, execute_and_snapshot, execute_and_snapshot_no_auth}, - ExecError, + BacktraceItem, ExecError, ModuleId, SourceRange, }; /// The minimum permissible difference between asserted twenty-twenty images. @@ -441,10 +441,15 @@ async fn kcl_test_import_file_doesnt_exist() { model = cube"#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "File `thing.obj` does not exist."); assert_eq!( - result.err().unwrap().to_string(), - r#"semantic: KclErrorDetails { source_ranges: [SourceRange([0, 18, 0])], message: "File `thing.obj` does not exist." }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(0, 18, ModuleId::default()), + fn_name: None, + }] ); } @@ -519,10 +524,18 @@ import 'e2e/executor/inputs/cube.gltf' model = cube"#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); assert_eq!( - result.err().unwrap().to_string(), - r#"semantic: KclErrorDetails { source_ranges: [SourceRange([32, 70, 0])], message: "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" }"# + err.message(), + "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" + ); + assert_eq!( + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(32, 70, ModuleId::default()), + fn_name: None, + }] ); } @@ -1666,10 +1679,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 111, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(70, 111, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1686,10 +1704,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have an x constrained angle of 270 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(70, 112, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1706,10 +1729,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 110, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(70, 110, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1726,10 +1754,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(70, 112, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1746,10 +1779,15 @@ extrusion = extrude(sketch001, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(66, 116, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1757,7 +1795,7 @@ extrusion = extrude(sketch001, length = 10) async fn kcl_test_angled_line_of_x_length_270() { let code = r#"sketch001 = startSketchOn(XZ) |> startProfile(at = [0, 0]) - |> angledLine(angle = 90, lengthX = 90, tag = $edge1) + |> angledLine(angle = 270, lengthX = 90, tag = $edge1) |> angledLine(angle = -15, lengthX = -15, tag = $edge2) |> line(end = [0, -5]) |> close(tag = $edge3) @@ -1766,10 +1804,15 @@ extrusion = extrude(sketch001, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(66, 117, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1788,10 +1831,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 130, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(95, 130, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1810,10 +1858,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 132, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(95, 132, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1832,10 +1885,15 @@ example = extrude(exampleSketch, length = 10) "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees"); assert_eq!( - result.err().unwrap().to_string(), - r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 133, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"# + err.backtrace(), + vec![BacktraceItem { + source_range: SourceRange::new(95, 133, ModuleId::default()), + fn_name: Some("angledLine".to_owned()) + }] ); } @@ -1849,10 +1907,31 @@ someFunction('INVALID') "#; let result = execute_and_snapshot(code, None).await; - assert!(result.is_err()); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); assert_eq!( - 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" }"# + err.message(), + "This function expected the input argument to be Solid or Plane but it's actually of type string" + ); + assert_eq!( + err.source_ranges(), + vec![ + SourceRange::new(46, 55, ModuleId::default()), + SourceRange::new(60, 83, ModuleId::default()), + ] + ); + assert_eq!( + err.backtrace(), + vec![ + BacktraceItem { + source_range: SourceRange::new(46, 55, ModuleId::default()), + fn_name: Some("someFunction".to_owned()), + }, + BacktraceItem { + source_range: SourceRange::new(60, 83, ModuleId::default()), + fn_name: None, + }, + ] ); } @@ -1873,12 +1952,14 @@ async fn kcl_test_error_no_auth_websocket() { "#; let result = execute_and_snapshot_no_auth(code, None).await; - assert!(result.is_err()); - assert!(result - .err() - .unwrap() - .to_string() - .contains("Please send the following object over this websocket")); + let err = result.unwrap_err(); + let err = err.as_kcl_error().unwrap(); + assert!( + err.message() + .contains("Please send the following object over this websocket"), + "actual: {}", + err.message() + ); } #[tokio::test(flavor = "multi_thread")] diff --git a/rust/kcl-lib/src/engine/conn.rs b/rust/kcl-lib/src/engine/conn.rs index 2f4ee629b..c00b61104 100644 --- a/rust/kcl-lib/src/engine/conn.rs +++ b/rust/kcl-lib/src/engine/conn.rs @@ -439,12 +439,7 @@ impl EngineManager for EngineConnection { request_sent: tx, }) .await - .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to send debug: {}", e), - source_ranges: vec![], - }) - })?; + .map_err(|e| KclError::Engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?; let _ = rx.await; Ok(()) @@ -479,25 +474,25 @@ impl EngineManager for EngineConnection { }) .await .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to send modeling command: {}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to send modeling command: {}", e), + vec![source_range], + )) })?; // Wait for the request to be sent. rx.await .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("could not send request to the engine actor: {e}"), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("could not send request to the engine actor: {e}"), + vec![source_range], + )) })? .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("could not send request to the engine: {e}"), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("could not send request to the engine: {e}"), + vec![source_range], + )) })?; Ok(()) @@ -521,15 +516,15 @@ impl EngineManager for EngineConnection { // Check if we have any pending errors. let pe = self.pending_errors.read().await; if !pe.is_empty() { - return Err(KclError::Engine(KclErrorDetails { - message: pe.join(", ").to_string(), - source_ranges: vec![source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + pe.join(", ").to_string(), + vec![source_range], + ))); } else { - return Err(KclError::Engine(KclErrorDetails { - message: "Modeling command failed: websocket closed early".to_string(), - source_ranges: vec![source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + "Modeling command failed: websocket closed early".to_string(), + vec![source_range], + ))); } } @@ -548,10 +543,10 @@ impl EngineManager for EngineConnection { } } - Err(KclError::Engine(KclErrorDetails { - message: format!("Modeling command timed out `{}`", id), - source_ranges: vec![source_range], - })) + Err(KclError::Engine(KclErrorDetails::new( + format!("Modeling command timed out `{}`", id), + vec![source_range], + ))) } async fn get_session_data(&self) -> Option { diff --git a/rust/kcl-lib/src/engine/conn_wasm.rs b/rust/kcl-lib/src/engine/conn_wasm.rs index e77efad5f..ccbc93fa5 100644 --- a/rust/kcl-lib/src/engine/conn_wasm.rs +++ b/rust/kcl-lib/src/engine/conn_wasm.rs @@ -147,32 +147,27 @@ impl EngineConnection { id_to_source_range: HashMap, ) -> Result<(), KclError> { let source_range_str = serde_json::to_string(&source_range).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize source range: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize source range: {:?}", e), + vec![source_range], + )) })?; let cmd_str = serde_json::to_string(&cmd).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize modeling command: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize modeling command: {:?}", e), + vec![source_range], + )) })?; let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize id to source range: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize id to source range: {:?}", e), + vec![source_range], + )) })?; self.manager .fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str) - .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; Ok(()) } @@ -185,33 +180,28 @@ impl EngineConnection { id_to_source_range: HashMap, ) -> Result { let source_range_str = serde_json::to_string(&source_range).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize source range: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize source range: {:?}", e), + vec![source_range], + )) })?; let cmd_str = serde_json::to_string(&cmd).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize modeling command: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize modeling command: {:?}", e), + vec![source_range], + )) })?; let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to serialize id to source range: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to serialize id to source range: {:?}", e), + vec![source_range], + )) })?; let promise = self .manager .send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str) - .map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| { // Try to parse the error as an engine error. @@ -219,53 +209,52 @@ impl EngineConnection { if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) = serde_json::from_str(&err_str) { - KclError::Engine(KclErrorDetails { - message: errors.iter().map(|e| e.message.clone()).collect::>().join("\n"), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + errors.iter().map(|e| e.message.clone()).collect::>().join("\n"), + vec![source_range], + )) } else if let Ok(data) = serde_json::from_str::>(&err_str) { if let Some(data) = data.first() { // It could also be an array of responses. - KclError::Engine(KclErrorDetails { - message: data - .errors + KclError::Engine(KclErrorDetails::new( + data.errors .iter() .map(|e| e.message.clone()) .collect::>() .join("\n"), - source_ranges: vec![source_range], - }) + vec![source_range], + )) } else { - KclError::Engine(KclErrorDetails { - message: "Received empty response from engine".into(), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + "Received empty response from engine".into(), + vec![source_range], + )) } } else { - KclError::Engine(KclErrorDetails { - message: format!("Failed to wait for promise from send modeling command: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to wait for promise from send modeling command: {:?}", e), + vec![source_range], + )) } })?; if value.is_null() || value.is_undefined() { - return Err(KclError::Engine(KclErrorDetails { - message: "Received null or undefined response from engine".into(), - source_ranges: vec![source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + "Received null or undefined response from engine".into(), + vec![source_range], + ))); } // Convert JsValue to a Uint8Array let data = js_sys::Uint8Array::from(value); let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to deserialize bson response from engine: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to deserialize bson response from engine: {:?}", e), + vec![source_range], + )) })?; Ok(ws_result) @@ -316,18 +305,16 @@ impl crate::engine::EngineManager for EngineConnection { *self.default_planes.write().await = Some(new_planes); // Start a new session. - let promise = self.manager.start_new_session().map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + let promise = self + .manager + .start_new_session() + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; crate::wasm::JsFuture::from(promise).await.map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to wait for promise from start new session: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to wait for promise from start new session: {:?}", e), + vec![source_range], + )) })?; Ok(()) diff --git a/rust/kcl-lib/src/engine/mod.rs b/rust/kcl-lib/src/engine/mod.rs index 385cf2ac7..935e55871 100644 --- a/rust/kcl-lib/src/engine/mod.rs +++ b/rust/kcl-lib/src/engine/mod.rs @@ -276,10 +276,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { { let duration = instant::Duration::from_millis(1); wasm_timer::Delay::new(duration).await.map_err(|err| { - KclError::Internal(KclErrorDetails { - message: format!("Failed to sleep: {:?}", err), - source_ranges: vec![source_range], - }) + KclError::Internal(KclErrorDetails::new( + format!("Failed to sleep: {:?}", err), + vec![source_range], + )) })?; } #[cfg(not(target_arch = "wasm32"))] @@ -293,10 +293,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { return Ok(response); } - Err(KclError::Engine(KclErrorDetails { - message: "async command timed out".to_string(), - source_ranges: vec![source_range], - })) + Err(KclError::Engine(KclErrorDetails::new( + "async command timed out".to_string(), + vec![source_range], + ))) } /// Ensure ALL async commands have been completed. @@ -547,10 +547,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { id_to_source_range.insert(Uuid::from(*cmd_id), *range); } _ => { - return Err(KclError::Engine(KclErrorDetails { - message: format!("The request is not a modeling command: {:?}", req), - source_ranges: vec![*range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + format!("The request is not a modeling command: {:?}", req), + vec![*range], + ))); } } } @@ -595,10 +595,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { self.parse_batch_responses(last_id.into(), id_to_source_range, responses) } else { // We should never get here. - Err(KclError::Engine(KclErrorDetails { - message: format!("Failed to get batch response: {:?}", response), - source_ranges: vec![source_range], - })) + Err(KclError::Engine(KclErrorDetails::new( + format!("Failed to get batch response: {:?}", response), + vec![source_range], + ))) } } WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd: _, cmd_id }) => { @@ -610,20 +610,20 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { // request so we need the original request source range in case the engine returns // an error. let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to get source range for command ID: {:?}", cmd_id), - source_ranges: vec![], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to get source range for command ID: {:?}", cmd_id), + vec![], + )) })?; let ws_resp = self .inner_send_modeling_cmd(cmd_id.into(), source_range, final_req, id_to_source_range) .await?; self.parse_websocket_response(ws_resp, source_range) } - _ => Err(KclError::Engine(KclErrorDetails { - message: format!("The final request is not a modeling command: {:?}", final_req), - source_ranges: vec![source_range], - })), + _ => Err(KclError::Engine(KclErrorDetails::new( + format!("The final request is not a modeling command: {:?}", final_req), + vec![source_range], + ))), } } @@ -729,10 +729,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { for (name, plane_id, color) in plane_settings { let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| { // We should never get here. - KclError::Engine(KclErrorDetails { - message: format!("Failed to get default plane info for: {:?}", name), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to get default plane info for: {:?}", name), + vec![source_range], + )) })?; planes.insert( name, @@ -763,15 +763,14 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { WebSocketResponse::Success(success) => Ok(success.resp), WebSocketResponse::Failure(fail) => { let _request_id = fail.request_id; - Err(KclError::Engine(KclErrorDetails { - message: fail - .errors + Err(KclError::Engine(KclErrorDetails::new( + fail.errors .iter() .map(|e| e.message.clone()) .collect::>() .join("\n"), - source_ranges: vec![source_range], - })) + vec![source_range], + ))) } } } @@ -806,25 +805,25 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { BatchResponse::Failure { errors } => { // Get the source range for the command. let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to get source range for command ID: {:?}", cmd_id), - source_ranges: vec![], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to get source range for command ID: {:?}", cmd_id), + vec![], + )) })?; - return Err(KclError::Engine(KclErrorDetails { - message: errors.iter().map(|e| e.message.clone()).collect::>().join("\n"), - source_ranges: vec![source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + errors.iter().map(|e| e.message.clone()).collect::>().join("\n"), + vec![source_range], + ))); } } } // Return an error that we did not get an error or the response we wanted. // This should never happen but who knows. - Err(KclError::Engine(KclErrorDetails { - message: format!("Failed to find response for command ID: {:?}", id), - source_ranges: vec![], - })) + Err(KclError::Engine(KclErrorDetails::new( + format!("Failed to find response for command ID: {:?}", id), + vec![], + ))) } async fn modify_grid( diff --git a/rust/kcl-lib/src/errors.rs b/rust/kcl-lib/src/errors.rs index e6fa260ff..b142d2978 100644 --- a/rust/kcl-lib/src/errors.rs +++ b/rust/kcl-lib/src/errors.rs @@ -380,20 +380,39 @@ impl miette::Diagnostic for Report { } #[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)] +#[serde(rename_all = "camelCase")] #[error("{message}")] #[ts(export)] pub struct KclErrorDetails { - #[serde(rename = "sourceRanges")] #[label(collection, "Errors")] pub source_ranges: Vec, + pub backtrace: Vec, #[serde(rename = "msg")] pub message: String, } +impl KclErrorDetails { + pub fn new(message: String, source_ranges: Vec) -> KclErrorDetails { + let backtrace = source_ranges + .iter() + .map(|s| BacktraceItem { + source_range: *s, + fn_name: None, + }) + .collect(); + KclErrorDetails { + source_ranges, + backtrace, + message, + } + } +} + impl KclError { pub fn internal(message: String) -> KclError { KclError::Internal(KclErrorDetails { source_ranges: Default::default(), + backtrace: Default::default(), message, }) } @@ -455,45 +474,122 @@ impl KclError { } } + pub fn backtrace(&self) -> Vec { + match self { + KclError::Lexical(e) + | KclError::Syntax(e) + | KclError::Semantic(e) + | KclError::ImportCycle(e) + | KclError::Type(e) + | KclError::Io(e) + | KclError::Unexpected(e) + | KclError::ValueAlreadyDefined(e) + | KclError::UndefinedValue(e) + | KclError::InvalidExpression(e) + | KclError::Engine(e) + | KclError::Internal(e) => e.backtrace.clone(), + } + } + pub(crate) fn override_source_ranges(&self, source_ranges: Vec) -> Self { let mut new = self.clone(); match &mut new { - KclError::Lexical(e) => e.source_ranges = source_ranges, - KclError::Syntax(e) => e.source_ranges = source_ranges, - KclError::Semantic(e) => e.source_ranges = source_ranges, - KclError::ImportCycle(e) => e.source_ranges = source_ranges, - KclError::Type(e) => e.source_ranges = source_ranges, - KclError::Io(e) => e.source_ranges = source_ranges, - KclError::Unexpected(e) => e.source_ranges = source_ranges, - KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges, - KclError::UndefinedValue(e) => e.source_ranges = source_ranges, - KclError::InvalidExpression(e) => e.source_ranges = source_ranges, - KclError::Engine(e) => e.source_ranges = source_ranges, - KclError::Internal(e) => e.source_ranges = source_ranges, + KclError::Lexical(e) + | KclError::Syntax(e) + | KclError::Semantic(e) + | KclError::ImportCycle(e) + | KclError::Type(e) + | KclError::Io(e) + | KclError::Unexpected(e) + | KclError::ValueAlreadyDefined(e) + | KclError::UndefinedValue(e) + | KclError::InvalidExpression(e) + | KclError::Engine(e) + | KclError::Internal(e) => { + e.backtrace = source_ranges + .iter() + .map(|s| BacktraceItem { + source_range: *s, + fn_name: None, + }) + .collect(); + e.source_ranges = source_ranges; + } } new } - pub(crate) fn add_source_ranges(&self, source_ranges: Vec) -> Self { + pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option) -> Self { let mut new = self.clone(); match &mut new { - KclError::Lexical(e) => e.source_ranges.extend(source_ranges), - KclError::Syntax(e) => e.source_ranges.extend(source_ranges), - KclError::Semantic(e) => e.source_ranges.extend(source_ranges), - KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges), - KclError::Type(e) => e.source_ranges.extend(source_ranges), - KclError::Io(e) => e.source_ranges.extend(source_ranges), - KclError::Unexpected(e) => e.source_ranges.extend(source_ranges), - KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges), - KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges), - KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges), - KclError::Engine(e) => e.source_ranges.extend(source_ranges), - KclError::Internal(e) => e.source_ranges.extend(source_ranges), + KclError::Lexical(e) + | KclError::Syntax(e) + | KclError::Semantic(e) + | KclError::ImportCycle(e) + | KclError::Type(e) + | KclError::Io(e) + | KclError::Unexpected(e) + | KclError::ValueAlreadyDefined(e) + | KclError::UndefinedValue(e) + | KclError::InvalidExpression(e) + | KclError::Engine(e) + | KclError::Internal(e) => { + if let Some(item) = e.backtrace.last_mut() { + item.fn_name = last_fn_name; + } + } } new } + + pub(crate) fn add_unwind_location(&self, last_fn_name: Option, source_range: SourceRange) -> Self { + let mut new = self.clone(); + match &mut new { + KclError::Lexical(e) + | KclError::Syntax(e) + | KclError::Semantic(e) + | KclError::ImportCycle(e) + | KclError::Type(e) + | KclError::Io(e) + | KclError::Unexpected(e) + | KclError::ValueAlreadyDefined(e) + | KclError::UndefinedValue(e) + | KclError::InvalidExpression(e) + | KclError::Engine(e) + | KclError::Internal(e) => { + if let Some(item) = e.backtrace.last_mut() { + item.fn_name = last_fn_name; + } + e.backtrace.push(BacktraceItem { + source_range, + fn_name: None, + }); + e.source_ranges.push(source_range); + } + } + + new + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct BacktraceItem { + pub source_range: SourceRange, + pub fn_name: Option, +} + +impl std::fmt::Display for BacktraceItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(fn_name) = &self.fn_name { + write!(f, "{fn_name}: {:?}", self.source_range) + } else { + write!(f, "(fn): {:?}", self.source_range) + } + } } impl IntoDiagnostic for KclError { @@ -551,6 +647,7 @@ impl From for KclError { fn from(error: pyo3::PyErr) -> Self { KclError::Internal(KclErrorDetails { source_ranges: vec![], + backtrace: Default::default(), message: error.to_string(), }) } @@ -629,8 +726,13 @@ impl CompilationError { impl From for KclErrorDetails { fn from(err: CompilationError) -> Self { + let backtrace = vec![BacktraceItem { + source_range: err.source_range, + fn_name: None, + }]; KclErrorDetails { source_ranges: vec![err.source_range], + backtrace, message: err.message, } } diff --git a/rust/kcl-lib/src/execution/annotations.rs b/rust/kcl-lib/src/execution/annotations.rs index f2a5e6ef8..5e53c78c8 100644 --- a/rust/kcl-lib/src/execution/annotations.rs +++ b/rust/kcl-lib/src/execution/annotations.rs @@ -70,10 +70,10 @@ pub(super) fn expect_properties<'a>( ) -> Result<&'a [Node], KclError> { assert_eq!(annotation.name().unwrap(), for_key); Ok(&**annotation.properties.as_ref().ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!("Empty `{for_key}` annotation"), - source_ranges: vec![annotation.as_source_range()], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Empty `{for_key}` annotation"), + vec![annotation.as_source_range()], + )) })?) } @@ -84,10 +84,10 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> { } } - Err(KclError::Semantic(KclErrorDetails { - message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(), - source_ranges: vec![expr.into()], - })) + Err(KclError::Semantic(KclErrorDetails::new( + "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(), + vec![expr.into()], + ))) } // Returns the unparsed number literal. @@ -98,10 +98,10 @@ pub(super) fn expect_number(expr: &Expr) -> Result { } } - Err(KclError::Semantic(KclErrorDetails { - message: "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(), - source_ranges: vec![expr.into()], - })) + Err(KclError::Semantic(KclErrorDetails::new( + "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(), + vec![expr.into()], + ))) } pub(super) fn get_impl(annotations: &[Node], source_range: SourceRange) -> Result, KclError> { @@ -113,14 +113,14 @@ pub(super) fn get_impl(annotations: &[Node], source_range: SourceRan if &*p.key.name == IMPL { if let Some(s) = p.value.ident_name() { return Impl::from_str(s).map(Some).map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Invalid value for {} attribute, expected one of: {}", IMPL, IMPL_VALUES.join(", ") ), - source_ranges: vec![source_range], - }) + vec![source_range], + )) }); } } @@ -139,12 +139,12 @@ impl UnitLen { "inch" | "in" => Ok(UnitLen::Inches), "ft" => Ok(UnitLen::Feet), "yd" => Ok(UnitLen::Yards), - value => Err(KclError::Semantic(KclErrorDetails { - message: format!( + value => Err(KclError::Semantic(KclErrorDetails::new( + format!( "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`" ), - source_ranges: vec![source_range], - })), + vec![source_range], + ))), } } } @@ -154,10 +154,10 @@ impl UnitAngle { match s { "deg" => Ok(UnitAngle::Degrees), "rad" => Ok(UnitAngle::Radians), - value => Err(KclError::Semantic(KclErrorDetails { - message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"), - source_ranges: vec![source_range], - })), + value => Err(KclError::Semantic(KclErrorDetails::new( + format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"), + vec![source_range], + ))), } } } diff --git a/rust/kcl-lib/src/execution/artifact.rs b/rust/kcl-lib/src/execution/artifact.rs index c85b4f2c5..286801a11 100644 --- a/rust/kcl-lib/src/execution/artifact.rs +++ b/rust/kcl-lib/src/execution/artifact.rs @@ -941,12 +941,10 @@ fn artifacts_to_update( ModelingCmd::StartPath(_) => { let mut return_arr = Vec::new(); let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| { - KclError::Internal(KclErrorDetails { - message: format!( - "Expected a current plane ID when processing StartPath command, but we have none: {id:?}" - ), - source_ranges: vec![range], - }) + KclError::Internal(KclErrorDetails::new( + format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"), + vec![range], + )) })?; return_arr.push(Artifact::Path(Path { id, @@ -1065,10 +1063,10 @@ fn artifacts_to_update( // TODO: Using the first one. Make sure to revisit this // choice, don't think it matters for now. path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| { - KclError::Internal(KclErrorDetails { - message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"), - source_ranges: vec![range], - }) + KclError::Internal(KclErrorDetails::new( + format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"), + vec![range], + )) })?), surface_ids: Vec::new(), edge_ids: Vec::new(), @@ -1108,12 +1106,12 @@ fn artifacts_to_update( }; last_path = Some(path); let path_sweep_id = path.sweep_id.ok_or_else(|| { - KclError::Internal(KclErrorDetails { - message:format!( + KclError::Internal(KclErrorDetails::new( + format!( "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}" ), - source_ranges: vec![range], - }) + vec![range], + )) })?; let extra_artifact = exec_artifacts.values().find(|a| { if let Artifact::StartSketchOnFace(s) = a { @@ -1162,12 +1160,12 @@ fn artifacts_to_update( continue; }; let path_sweep_id = path.sweep_id.ok_or_else(|| { - KclError::Internal(KclErrorDetails { - message:format!( + KclError::Internal(KclErrorDetails::new( + format!( "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}" ), - source_ranges: vec![range], - }) + vec![range], + )) })?; let extra_artifact = exec_artifacts.values().find(|a| { if let Artifact::StartSketchOnFace(s) = a { diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index a9949b673..67b26568b 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -131,10 +131,10 @@ impl ExecutorContext { match statement { BodyItem::ImportStatement(import_stmt) => { if !matches!(body_type, BodyType::Root) { - return Err(KclError::Semantic(KclErrorDetails { - message: "Imports are only supported at the top-level of a file.".to_owned(), - source_ranges: vec![import_stmt.into()], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Imports are only supported at the top-level of a file.".to_owned(), + vec![import_stmt.into()], + ))); } let source_range = SourceRange::from(import_stmt); @@ -157,28 +157,25 @@ impl ExecutorContext { let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned(); if value.is_err() && ty.is_err() { - return Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("{} is not defined in module", import_item.name.name), - source_ranges: vec![SourceRange::from(&import_item.name)], - })); + return Err(KclError::UndefinedValue(KclErrorDetails::new( + format!("{} is not defined in module", import_item.name.name), + vec![SourceRange::from(&import_item.name)], + ))); } // Check that the item is allowed to be imported (in at least one namespace). if value.is_ok() && !module_exports.contains(&import_item.name.name) { - value = Err(KclError::Semantic(KclErrorDetails { - message: format!( + value = Err(KclError::Semantic(KclErrorDetails::new( + format!( "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.", import_item.name.name ), - source_ranges: vec![SourceRange::from(&import_item.name)], - })); + vec![SourceRange::from(&import_item.name)], + ))); } if ty.is_ok() && !module_exports.contains(&ty_name) { - ty = Err(KclError::Semantic(KclErrorDetails { - message: String::new(), - source_ranges: vec![], - })); + ty = Err(KclError::Semantic(KclErrorDetails::new(String::new(), vec![]))); } if value.is_err() && ty.is_err() { @@ -225,10 +222,10 @@ impl ExecutorContext { .memory .get_from(name, env_ref, source_range, 0) .map_err(|_err| { - KclError::Internal(KclErrorDetails { - message: format!("{} is not defined in module (but was exported?)", name), - source_ranges: vec![source_range], - }) + KclError::Internal(KclErrorDetails::new( + format!("{} is not defined in module (but was exported?)", name), + vec![source_range], + )) })? .clone(); exec_state.mut_stack().add(name.to_owned(), item, source_range)?; @@ -297,10 +294,10 @@ impl ExecutorContext { let std_path = match &exec_state.mod_local.std_path { Some(p) => p, None => { - return Err(KclError::Semantic(KclErrorDetails { - message: "User-defined types are not yet supported.".to_owned(), - source_ranges: vec![metadata.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "User-defined types are not yet supported.".to_owned(), + vec![metadata.source_range], + ))); } }; let (t, props) = crate::std::std_ty(std_path, &ty.name.name); @@ -313,10 +310,10 @@ impl ExecutorContext { .mut_stack() .add(name_in_mem.clone(), value, metadata.source_range) .map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!("Redefinition of type {}.", ty.name.name), - source_ranges: vec![metadata.source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Redefinition of type {}.", ty.name.name), + vec![metadata.source_range], + )) })?; if let ItemVisibility::Export = ty.visibility { @@ -343,10 +340,10 @@ impl ExecutorContext { .mut_stack() .add(name_in_mem.clone(), value, metadata.source_range) .map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!("Redefinition of type {}.", ty.name.name), - source_ranges: vec![metadata.source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Redefinition of type {}.", ty.name.name), + vec![metadata.source_range], + )) })?; if let ItemVisibility::Export = ty.visibility { @@ -354,10 +351,10 @@ impl ExecutorContext { } } None => { - return Err(KclError::Semantic(KclErrorDetails { - message: "User-defined types are not yet supported.".to_owned(), - source_ranges: vec![metadata.source_range], - })) + return Err(KclError::Semantic(KclErrorDetails::new( + "User-defined types are not yet supported.".to_owned(), + vec![metadata.source_range], + ))) } }, } @@ -368,10 +365,10 @@ impl ExecutorContext { let metadata = Metadata::from(return_statement); if matches!(body_type, BodyType::Root) { - return Err(KclError::Semantic(KclErrorDetails { - message: "Cannot return from outside a function.".to_owned(), - source_ranges: vec![metadata.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Cannot return from outside a function.".to_owned(), + vec![metadata.source_range], + ))); } let value = self @@ -387,10 +384,10 @@ impl ExecutorContext { .mut_stack() .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range) .map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: "Multiple returns from a single function.".to_owned(), - source_ranges: vec![metadata.source_range], - }) + KclError::Semantic(KclErrorDetails::new( + "Multiple returns from a single function.".to_owned(), + vec![metadata.source_range], + )) })?; last_expr = None; } @@ -493,10 +490,10 @@ impl ExecutorContext { *cache = Some((val, er, items.clone())); (er, items) }), - ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails { - message: "Cannot import items from foreign modules".to_owned(), - source_ranges: vec![geom.source_range], - })), + ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new( + "Cannot import items from foreign modules".to_owned(), + vec![geom.source_range], + ))), ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path), }; @@ -572,13 +569,13 @@ impl ExecutorContext { err.override_source_ranges(vec![source_range]) } else { // TODO would be great to have line/column for the underlying error here - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Error loading imported file ({path}). Open it to view more details.\n {}", err.message() ), - source_ranges: vec![source_range], - }) + vec![source_range], + )) } }) } @@ -639,11 +636,10 @@ impl ExecutorContext { meta: vec![metadata.to_owned()], } } else { - return Err(KclError::Semantic(KclErrorDetails { - message: "Rust implementation of functions is restricted to the standard library" - .to_owned(), - source_ranges: vec![metadata.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Rust implementation of functions is restricted to the standard library".to_owned(), + vec![metadata.source_range], + ))); } } else { // Snapshotting memory here is crucial for semantics so that we close @@ -667,18 +663,18 @@ impl ExecutorContext { "you cannot declare variable {name} as %, because % can only be used in function calls" ); - return Err(KclError::Semantic(KclErrorDetails { + return Err(KclError::Semantic(KclErrorDetails::new( message, - source_ranges: vec![pipe_substitution.into()], - })); + vec![pipe_substitution.into()], + ))); } StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() { Some(x) => x, None => { - return Err(KclError::Semantic(KclErrorDetails { - message: "cannot use % outside a pipe expression".to_owned(), - source_ranges: vec![pipe_substitution.into()], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "cannot use % outside a pipe expression".to_owned(), + vec![pipe_substitution.into()], + ))); } }, }, @@ -750,13 +746,13 @@ fn apply_ascription( } else { "" }; - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "could not coerce {} value to type {ty}{suggestion}", value.human_friendly_type() ), - source_ranges: vec![source_range], - }) + vec![source_range], + )) }) } @@ -783,10 +779,10 @@ impl Node { ctx: &ExecutorContext, ) -> Result<&'a KclValue, KclError> { if self.abs_path { - return Err(KclError::Semantic(KclErrorDetails { - message: "Absolute paths (names beginning with `::` are not yet supported)".to_owned(), - source_ranges: self.as_source_ranges(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Absolute paths (names beginning with `::` are not yet supported)".to_owned(), + self.as_source_ranges(), + ))); } if self.path.is_empty() { @@ -798,10 +794,10 @@ impl Node { let value = match mem_spec { Some((env, exports)) => { if !exports.contains(&p.name) { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("Item {} not found in module's exported items", p.name), - source_ranges: p.as_source_ranges(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Item {} not found in module's exported items", p.name), + p.as_source_ranges(), + ))); } exec_state @@ -813,13 +809,13 @@ impl Node { }; let KclValue::Module { value: module_id, .. } = value else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Identifier in path must refer to a module, found {}", value.human_friendly_type() ), - source_ranges: p.as_source_ranges(), - })); + p.as_source_ranges(), + ))); }; mem_spec = Some( @@ -830,10 +826,10 @@ impl Node { let (env, exports) = mem_spec.unwrap(); if !exports.contains(&self.name.name) { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("Item {} not found in module's exported items", self.name.name), - source_ranges: self.name.as_source_ranges(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Item {} not found in module's exported items", self.name.name), + self.name.as_source_ranges(), + ))); } exec_state @@ -861,46 +857,44 @@ impl Node { if let Some(value) = map.get(&property) { Ok(value.to_owned()) } else { - Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("Property '{property}' not found in object"), - source_ranges: vec![self.clone().into()], - })) + Err(KclError::UndefinedValue(KclErrorDetails::new( + format!("Property '{property}' not found in object"), + vec![self.clone().into()], + ))) } } - (KclValue::Object { .. }, Property::String(property), true) => Err(KclError::Semantic(KclErrorDetails { - message: format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"), - source_ranges: vec![self.clone().into()], - })), + (KclValue::Object { .. }, Property::String(property), true) => { + Err(KclError::Semantic(KclErrorDetails::new( + format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"), + vec![self.clone().into()], + ))) + } (KclValue::Object { .. }, p, _) => { let t = p.type_name(); let article = article_for(t); - Err(KclError::Semantic(KclErrorDetails { - message: format!( - "Only strings can be used as the property of an object, but you're using {article} {t}", - ), - source_ranges: vec![self.clone().into()], - })) + Err(KclError::Semantic(KclErrorDetails::new( + format!("Only strings can be used as the property of an object, but you're using {article} {t}",), + vec![self.clone().into()], + ))) } (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => { let value_of_arr = arr.get(index); if let Some(value) = value_of_arr { Ok(value.to_owned()) } else { - Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("The array doesn't have any item at index {index}"), - source_ranges: vec![self.clone().into()], - })) + Err(KclError::UndefinedValue(KclErrorDetails::new( + format!("The array doesn't have any item at index {index}"), + vec![self.clone().into()], + ))) } } (KclValue::HomArray { .. }, p, _) => { let t = p.type_name(); let article = article_for(t); - Err(KclError::Semantic(KclErrorDetails { - message: format!( - "Only integers >= 0 can be used as the index of an array, but you're using {article} {t}", - ), - source_ranges: vec![self.clone().into()], - })) + Err(KclError::Semantic(KclErrorDetails::new( + format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",), + vec![self.clone().into()], + ))) } (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch { value: Box::new(value.sketch), @@ -918,10 +912,10 @@ impl Node { (being_indexed, _, _) => { let t = being_indexed.human_friendly_type(); let article = article_for(&t); - Err(KclError::Semantic(KclErrorDetails { - message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"), - source_ranges: vec![self.clone().into()], - })) + Err(KclError::Semantic(KclErrorDetails::new( + format!("Only arrays can be indexed, but you're trying to index {article} {t}"), + vec![self.clone().into()], + ))) } } } @@ -996,26 +990,26 @@ impl Node { meta: _, } = left_value else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Cannot apply logical operator to non-boolean value: {}", left_value.human_friendly_type() ), - source_ranges: vec![self.left.clone().into()], - })); + vec![self.left.clone().into()], + ))); }; let KclValue::Bool { value: right_value, meta: _, } = right_value else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Cannot apply logical operator to non-boolean value: {}", right_value.human_friendly_type() ), - source_ranges: vec![self.right.clone().into()], - })); + vec![self.right.clone().into()], + ))); }; let raw_value = match self.operator { BinaryOperator::Or => left_value || right_value, @@ -1115,13 +1109,13 @@ impl Node { meta: _, } = value else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Cannot apply unary operator ! to non-boolean value: {}", value.human_friendly_type() ), - source_ranges: vec![self.into()], - })); + vec![self.into()], + ))); }; let meta = vec![Metadata { source_range: self.into(), @@ -1136,13 +1130,13 @@ impl Node { let value = &self.argument.get_result(exec_state, ctx).await?; let err = || { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "You can only negate numbers, planes, or lines, but this is a {}", value.human_friendly_type() ), - source_ranges: vec![self.into()], - }) + vec![self.into()], + )) }; match value { KclValue::Number { value, ty, .. } => { @@ -1239,10 +1233,10 @@ pub(crate) async fn execute_pipe_body( ctx: &ExecutorContext, ) -> Result { let Some((first, body)) = body.split_first() else { - return Err(KclError::Semantic(KclErrorDetails { - message: "Pipe expressions cannot be empty".to_owned(), - source_ranges: vec![source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Pipe expressions cannot be empty".to_owned(), + vec![source_range], + ))); }; // Evaluate the first element in the pipeline. // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression, @@ -1277,10 +1271,10 @@ async fn inner_execute_pipe_body( ) -> Result { for expression in body { if let Expr::TagDeclarator(_) = expression { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("This cannot be in a PipeExpression: {:?}", expression), - source_ranges: vec![expression.into()], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("This cannot be in a PipeExpression: {:?}", expression), + vec![expression.into()], + ))); } let metadata = Metadata { source_range: SourceRange::from(expression), @@ -1349,35 +1343,37 @@ impl Node { StatementKind::Expression, ) .await?; - let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.into()], - message: format!("Expected int but found {}", start_val.human_friendly_type()), - }))?; + let (start, start_ty) = start_val + .as_int_with_ty() + .ok_or(KclError::Semantic(KclErrorDetails::new( + format!("Expected int but found {}", start_val.human_friendly_type()), + vec![self.into()], + )))?; let metadata = Metadata::from(&self.end_element); let end_val = ctx .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression) .await?; - let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.into()], - message: format!("Expected int but found {}", end_val.human_friendly_type()), - }))?; + let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails::new( + format!("Expected int but found {}", end_val.human_friendly_type()), + vec![self.into()], + )))?; if start_ty != end_ty { let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty }); let start = fmt::human_display_number(start.n, start.ty); let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty }); let end = fmt::human_display_number(end.n, end.ty); - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.into()], - message: format!("Range start and end must be of the same type, but found {start} and {end}"), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Range start and end must be of the same type, but found {start} and {end}"), + vec![self.into()], + ))); } if end < start { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.into()], - message: format!("Range start is greater than range end: {start} .. {end}"), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Range start is greater than range end: {start} .. {end}"), + vec![self.into()], + ))); } let range: Vec<_> = if self.end_inclusive { @@ -1438,10 +1434,10 @@ fn article_for>(s: S) -> &'static str { fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result { v.as_ty_f64().ok_or_else(|| { let actual_type = v.human_friendly_type(); - KclError::Semantic(KclErrorDetails { - source_ranges: vec![source_range], - message: format!("Expected a number, but found {actual_type}",), - }) + KclError::Semantic(KclErrorDetails::new( + format!("Expected a number, but found {actual_type}",), + vec![source_range], + )) }) } @@ -1530,16 +1526,16 @@ impl Property { if let Some(x) = crate::try_f64_to_usize(value) { Ok(Property::UInt(x)) } else { - Err(KclError::Semantic(KclErrorDetails { - source_ranges: property_sr, - message: format!("{value} is not a valid index, indices must be whole numbers >= 0"), - })) + Err(KclError::Semantic(KclErrorDetails::new( + format!("{value} is not a valid index, indices must be whole numbers >= 0"), + property_sr, + ))) } } - _ => Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![sr], - message: "Only numbers (>= 0) can be indexes".to_owned(), - })), + _ => Err(KclError::Semantic(KclErrorDetails::new( + "Only numbers (>= 0) can be indexes".to_owned(), + vec![sr], + ))), } } } @@ -1547,12 +1543,7 @@ impl Property { } fn jvalue_to_prop(value: &KclValue, property_sr: Vec, name: &str) -> Result { - let make_err = |message: String| { - Err::(KclError::Semantic(KclErrorDetails { - source_ranges: property_sr, - message, - })) - }; + let make_err = |message: String| Err::(KclError::Semantic(KclErrorDetails::new(message, property_sr))); match value { KclValue::Number{value: num, .. } => { let num = *num; @@ -1795,10 +1786,10 @@ d = b + c crate::engine::conn_mock::EngineConnection::new() .await .map_err(|err| { - KclError::Internal(crate::errors::KclErrorDetails { - message: format!("Failed to create mock engine connection: {}", err), - source_ranges: vec![SourceRange::default()], - }) + KclError::Internal(KclErrorDetails::new( + format!("Failed to create mock engine connection: {}", err), + vec![SourceRange::default()], + )) }) .unwrap(), )), diff --git a/rust/kcl-lib/src/execution/fn_call.rs b/rust/kcl-lib/src/execution/fn_call.rs index c2986fda9..92f3556a1 100644 --- a/rust/kcl-lib/src/execution/fn_call.rs +++ b/rust/kcl-lib/src/execution/fn_call.rs @@ -1,13 +1,16 @@ use async_recursion::async_recursion; use indexmap::IndexMap; -use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation}; +use super::{types::ArrayLen, EnvironmentRef}; use crate::{ docs::StdLibFn, errors::{KclError, KclErrorDetails}, execution::{ - kcl_value::FunctionSource, memory, types::RuntimeType, BodyType, ExecState, ExecutorContext, KclValue, - Metadata, StatementKind, TagEngineInfo, TagIdentifier, + cad_op::{Group, OpArg, OpKclValue, Operation}, + kcl_value::FunctionSource, + memory, + types::RuntimeType, + BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier, }, parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type}, source_range::SourceRange, @@ -15,9 +18,6 @@ use crate::{ CompilationError, }; -use super::types::ArrayLen; -use super::EnvironmentRef; - #[derive(Debug, Clone)] pub struct Args { /// Positional args. @@ -281,6 +281,13 @@ impl Node { def.call_kw(Some(func.name()), exec_state, ctx, args, callsite) .await .map(Option::unwrap) + .map_err(|e| { + // This is used for the backtrace display. We don't add + // another location the way we do for user-defined + // functions because the error uses the Args, which + // already points here. + e.set_last_backtrace_fn_name(Some(func.name())) + }) } None => { // Clone the function so that we can use a mutable reference to @@ -288,10 +295,10 @@ impl Node { let func = fn_name.get_result(exec_state, ctx).await?.clone(); let Some(fn_src) = func.as_fn() else { - return Err(KclError::Semantic(KclErrorDetails { - message: "cannot call this because it isn't a function".to_string(), - source_ranges: vec![callsite], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "cannot call this because it isn't a function".to_string(), + vec![callsite], + ))); }; let return_value = fn_src @@ -299,7 +306,10 @@ impl Node { .await .map_err(|e| { // Add the call expression to the source ranges. - e.add_source_ranges(vec![callsite]) + // + // TODO: Use the name that the function was defined + // with, not the identifier it was used with. + e.add_unwind_location(Some(fn_name.name.name.clone()), callsite) })?; let result = return_value.ok_or_else(move || { @@ -308,10 +318,10 @@ impl Node { if let KclValue::Function { meta, .. } = func { source_ranges = meta.iter().map(|m| m.source_range).collect(); }; - KclError::UndefinedValue(KclErrorDetails { - message: format!("Result of user-defined function {} is undefined", fn_name), + KclError::UndefinedValue(KclErrorDetails::new( + format!("Result of user-defined function {} is undefined", fn_name), source_ranges, - }) + )) })?; Ok(result) @@ -490,10 +500,10 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) { let mut t = t.clone(); let Some(info) = t.get_cur_info() else { - return Err(KclError::Internal(KclErrorDetails { - message: format!("Tag {} does not have path info", tag.name), - source_ranges: vec![tag.into()], - })); + return Err(KclError::Internal(KclErrorDetails::new( + format!("Tag {} does not have path info", tag.name), + vec![tag.into()], + ))); }; let mut info = info.clone(); @@ -608,10 +618,10 @@ fn type_check_params_kw( // TODO if we have access to the AST for the argument we could choose which example to suggest. message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`"); } - KclError::Semantic(KclErrorDetails { + KclError::Semantic(KclErrorDetails::new( message, - source_ranges: vec![arg.source_range], - }) + vec![arg.source_range], + )) })?; } } @@ -673,8 +683,8 @@ fn type_check_params_kw( exec_state, ) .map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "The input argument of {} requires a value with type `{}`, but found {}", fn_name .map(|n| format!("`{}`", n)) @@ -682,8 +692,8 @@ fn type_check_params_kw( ty, arg.1.value.human_friendly_type() ), - source_ranges: vec![arg.1.source_range], - }) + vec![arg.1.source_range], + )) })?; } } else if let Some((name, _)) = &fn_def.input_arg { @@ -730,13 +740,13 @@ fn assign_args_to_params_kw( .add(name.clone(), value, default_val.source_range())?; } None => { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges, - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "This function requires a parameter {}, but you haven't passed it one.", name ), - })); + source_ranges, + ))); } }, } @@ -747,16 +757,15 @@ fn assign_args_to_params_kw( let Some(unlabeled) = unlabelled else { return Err(if args.kw_args.labeled.contains_key(param_name) { - KclError::Semantic(KclErrorDetails { + KclError::Semantic(KclErrorDetails::new( + format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"), source_ranges, - message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"), - }) + )) } else { - KclError::Semantic(KclErrorDetails { + KclError::Semantic(KclErrorDetails::new( + "This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(), source_ranges, - message: "This function expects an unlabeled first parameter, but you haven't passed it one." - .to_owned(), - }) + )) }); }; exec_state.mut_stack().add( @@ -789,14 +798,14 @@ fn coerce_result_type( ty = RuntimeType::Union(vec![(**inner).clone(), ty]); } let val = val.coerce(&ty, exec_state).map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "This function requires its result to be of type `{}`, but found {}", ty.human_friendly_type(), val.human_friendly_type(), ), - source_ranges: ret_ty.as_source_ranges(), - }) + ret_ty.as_source_ranges(), + )) })?; Ok(Some(val)) } else { @@ -873,10 +882,10 @@ mod test { "all params required, none given, should error", vec![req_param("x")], vec![], - Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![SourceRange::default()], - message: "This function requires a parameter x, but you haven't passed it one.".to_owned(), - })), + Err(KclError::Semantic(KclErrorDetails::new( + "This function requires a parameter x, but you haven't passed it one.".to_owned(), + vec![SourceRange::default()], + ))), ), ( "all params optional, none given, should be OK", @@ -888,10 +897,10 @@ mod test { "mixed params, too few given", vec![req_param("x"), opt_param("y")], vec![], - Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![SourceRange::default()], - message: "This function requires a parameter x, but you haven't passed it one.".to_owned(), - })), + Err(KclError::Semantic(KclErrorDetails::new( + "This function requires a parameter x, but you haven't passed it one.".to_owned(), + vec![SourceRange::default()], + ))), ), ( "mixed params, minimum given, should be OK", diff --git a/rust/kcl-lib/src/execution/geometry.rs b/rust/kcl-lib/src/execution/geometry.rs index 4945d0400..f5f2e770a 100644 --- a/rust/kcl-lib/src/execution/geometry.rs +++ b/rust/kcl-lib/src/execution/geometry.rs @@ -469,18 +469,18 @@ impl TryFrom for PlaneInfo { PlaneData::NegYZ => PlaneName::NegYz, PlaneData::Plane(_) => { // We will never get here since we already checked for PlaneData::Plane. - return Err(KclError::Internal(KclErrorDetails { - message: format!("PlaneData {:?} not found", value), - source_ranges: Default::default(), - })); + return Err(KclError::Internal(KclErrorDetails::new( + format!("PlaneData {:?} not found", value), + Default::default(), + ))); } }; let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| { - KclError::Internal(KclErrorDetails { - message: format!("Plane {} not found", name), - source_ranges: Default::default(), - }) + KclError::Internal(KclErrorDetails::new( + format!("Plane {} not found", name), + Default::default(), + )) })?; Ok(info.clone()) diff --git a/rust/kcl-lib/src/execution/import.rs b/rust/kcl-lib/src/execution/import.rs index 68da2adaa..5316af2f4 100644 --- a/rust/kcl-lib/src/execution/import.rs +++ b/rust/kcl-lib/src/execution/import.rs @@ -37,53 +37,43 @@ pub async fn import_foreign( ) -> Result { // Make sure the file exists. if !ctxt.fs.exists(file_path, source_range).await? { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("File `{}` does not exist.", file_path.display()), - source_ranges: vec![source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("File `{}` does not exist.", file_path.display()), + vec![source_range], + ))); } let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!("No file extension found for `{}`", file_path.display()), - source_ranges: vec![source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("No file extension found for `{}`", file_path.display()), + vec![source_range], + )) })?) - .map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: e.to_string(), - source_ranges: vec![source_range], - }) - })?; + .map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; // Get the format type from the extension of the file. let format = if let Some(format) = format { // Validate the given format with the extension format. - validate_extension_format(ext_format, format.clone()).map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: e.to_string(), - source_ranges: vec![source_range], - }) - })?; + validate_extension_format(ext_format, format.clone()) + .map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; format } else { ext_format }; // Get the file contents for each file path. - let file_contents = ctxt.fs.read(file_path, source_range).await.map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: e.to_string(), - source_ranges: vec![source_range], - }) - })?; + let file_contents = ctxt + .fs + .read(file_path, source_range) + .await + .map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; // We want the file_path to be without the parent. let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!("Could not get the file name from the path `{}`", file_path.display()), - source_ranges: vec![source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Could not get the file name from the path `{}`", file_path.display()), + vec![source_range], + )) })?; let mut import_files = vec![kcmc::ImportFile { path: file_name.to_string(), @@ -96,12 +86,8 @@ pub async fn import_foreign( // Check if the file is a binary gltf file, in that case we don't need to import the bin // file. if !file_contents.starts_with(b"glTF") { - let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: e.to_string(), - source_ranges: vec![source_range], - }) - })?; + let json = gltf_json::Root::from_slice(&file_contents) + .map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; // Read the gltf file and check if there is a bin file. for buffer in json.buffers.iter() { @@ -109,18 +95,16 @@ pub async fn import_foreign( if !uri.starts_with("data:") { // We want this path relative to the file_path given. let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!("Could not get the parent path of the file `{}`", file_path.display()), - source_ranges: vec![source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Could not get the parent path of the file `{}`", file_path.display()), + vec![source_range], + )) })?; - let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: e.to_string(), - source_ranges: vec![source_range], - }) - })?; + let bin_contents = + ctxt.fs.read(&bin_path, source_range).await.map_err(|e| { + KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])) + })?; import_files.push(ImportFile { path: uri.to_string(), @@ -157,13 +141,13 @@ pub(super) fn format_from_annotations( if p.key.name == annotations::IMPORT_FORMAT { result = Some( get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Unknown format for import, expected one of: {}", crate::IMPORT_FILE_EXTENSIONS.join(", ") ), - source_ranges: vec![p.as_source_range()], - }) + vec![p.as_source_range()], + )) })?, ); break; @@ -175,10 +159,10 @@ pub(super) fn format_from_annotations( path.extension() .and_then(|ext| get_import_format_from_extension(ext).ok()) }) - .ok_or(KclError::Semantic(KclErrorDetails { - message: "Unknown or missing extension, and no specified format for imported file".to_owned(), - source_ranges: vec![import_source_range], - }))?; + .ok_or(KclError::Semantic(KclErrorDetails::new( + "Unknown or missing extension, and no specified format for imported file".to_owned(), + vec![import_source_range], + )))?; for p in props { match p.key.name.as_str() { @@ -190,15 +174,15 @@ pub(super) fn format_from_annotations( } annotations::IMPORT_FORMAT => {} _ => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Unexpected annotation for import, expected one of: {}, {}, {}", annotations::IMPORT_FORMAT, annotations::IMPORT_COORDS, annotations::IMPORT_LENGTH_UNIT ), - source_ranges: vec![p.as_source_range()], - })) + vec![p.as_source_range()], + ))) } } } @@ -215,8 +199,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan } let Some(coords) = coords else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Unknown coordinate system: {coords_str}, expected one of: {}", annotations::IMPORT_COORDS_VALUES .iter() @@ -224,8 +208,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan .collect::>() .join(", ") ), - source_ranges: vec![source_range], - })); + vec![source_range], + ))); }; match fmt { @@ -233,13 +217,13 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan InputFormat3d::Ply(opts) => opts.coords = coords, InputFormat3d::Stl(opts) => opts.coords = coords, _ => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "`{}` option cannot be applied to the specified format", annotations::IMPORT_COORDS ), - source_ranges: vec![source_range], - })) + vec![source_range], + ))) } } @@ -254,13 +238,13 @@ fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: Sourc InputFormat3d::Ply(opts) => opts.units = units.into(), InputFormat3d::Stl(opts) => opts.units = units.into(), _ => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "`{}` option cannot be applied to the specified format", annotations::IMPORT_LENGTH_UNIT ), - source_ranges: vec![source_range], - })) + vec![source_range], + ))) } } diff --git a/rust/kcl-lib/src/execution/kcl_value.rs b/rust/kcl-lib/src/execution/kcl_value.rs index 8a960321e..d5d6dcc0e 100644 --- a/rust/kcl-lib/src/execution/kcl_value.rs +++ b/rust/kcl-lib/src/execution/kcl_value.rs @@ -543,17 +543,13 @@ impl KclValue { /// If this value fits in a u32, return it. pub fn get_u32(&self, source_ranges: Vec) -> Result { let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: "Expected an integer >= 0".to_owned(), - source_ranges: source_ranges.clone(), - }) + KclError::Semantic(KclErrorDetails::new( + "Expected an integer >= 0".to_owned(), + source_ranges.clone(), + )) })?; - u32::try_from(u).map_err(|_| { - KclError::Semantic(KclErrorDetails { - message: "Number was too big".to_owned(), - source_ranges, - }) - }) + u32::try_from(u) + .map_err(|_| KclError::Semantic(KclErrorDetails::new("Number was too big".to_owned(), source_ranges))) } /// If this value is of type function, return it. @@ -568,10 +564,10 @@ impl KclValue { pub fn get_tag_identifier(&self) -> Result { match self { KclValue::TagIdentifier(t) => Ok(*t.clone()), - _ => Err(KclError::Semantic(KclErrorDetails { - message: format!("Not a tag identifier: {:?}", self), - source_ranges: self.clone().into(), - })), + _ => Err(KclError::Semantic(KclErrorDetails::new( + format!("Not a tag identifier: {:?}", self), + self.clone().into(), + ))), } } @@ -579,20 +575,20 @@ impl KclValue { pub fn get_tag_declarator(&self) -> Result { match self { KclValue::TagDeclarator(t) => Ok((**t).clone()), - _ => Err(KclError::Semantic(KclErrorDetails { - message: format!("Not a tag declarator: {:?}", self), - source_ranges: self.clone().into(), - })), + _ => Err(KclError::Semantic(KclErrorDetails::new( + format!("Not a tag declarator: {:?}", self), + self.clone().into(), + ))), } } /// If this KCL value is a bool, retrieve it. pub fn get_bool(&self) -> Result { let Self::Bool { value: b, .. } = self else { - return Err(KclError::Type(KclErrorDetails { - source_ranges: self.into(), - message: format!("Expected bool, found {}", self.human_friendly_type()), - })); + return Err(KclError::Type(KclErrorDetails::new( + format!("Expected bool, found {}", self.human_friendly_type()), + self.into(), + ))); }; Ok(*b) } diff --git a/rust/kcl-lib/src/execution/memory.rs b/rust/kcl-lib/src/execution/memory.rs index 084ebe200..5e495d6ee 100644 --- a/rust/kcl-lib/src/execution/memory.rs +++ b/rust/kcl-lib/src/execution/memory.rs @@ -364,10 +364,10 @@ impl ProgramMemory { }; } - Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("`{}` is not defined", var), - source_ranges: vec![source_range], - })) + Err(KclError::UndefinedValue(KclErrorDetails::new( + format!("`{}` is not defined", var), + vec![source_range], + ))) } /// Iterate over all key/value pairs in the specified environment which satisfy the provided @@ -485,10 +485,10 @@ impl ProgramMemory { }; } - Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("`{}` is not defined", var), - source_ranges: vec![], - })) + Err(KclError::UndefinedValue(KclErrorDetails::new( + format!("`{}` is not defined", var), + vec![], + ))) } } @@ -643,10 +643,10 @@ impl Stack { pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> { let env = self.memory.get_env(self.current_env.index()); if env.contains_key(&key) { - return Err(KclError::ValueAlreadyDefined(KclErrorDetails { - message: format!("Cannot redefine `{}`", key), - source_ranges: vec![source_range], - })); + return Err(KclError::ValueAlreadyDefined(KclErrorDetails::new( + format!("Cannot redefine `{}`", key), + vec![source_range], + ))); } self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed); diff --git a/rust/kcl-lib/src/execution/mod.rs b/rust/kcl-lib/src/execution/mod.rs index 2d1346c13..b898a6e49 100644 --- a/rust/kcl-lib/src/execution/mod.rs +++ b/rust/kcl-lib/src/execution/mod.rs @@ -858,10 +858,9 @@ impl ExecutorContext { for module in modules { let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else { - return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails { - message: format!("Module {module} not found in universe"), - source_ranges: Default::default(), - }))); + return Err(KclErrorWithOutputs::no_outputs(KclError::Internal( + KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()), + ))); }; let module_id = *module_id; let module_path = module_path.clone(); @@ -921,10 +920,10 @@ impl ExecutorContext { result.map(|val| ModuleRepr::Foreign(geom.clone(), val)) } - ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails { - message: format!("Module {module_path} not found in universe"), - source_ranges: vec![source_range], - })), + ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails::new( + format!("Module {module_path} not found in universe"), + vec![source_range], + ))), } }; @@ -1288,10 +1287,10 @@ impl ExecutorContext { .await?; let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else { - return Err(KclError::Internal(crate::errors::KclErrorDetails { - message: format!("Expected Export response, got {resp:?}",), - source_ranges: vec![SourceRange::default()], - })); + return Err(KclError::Internal(crate::errors::KclErrorDetails::new( + format!("Expected Export response, got {resp:?}",), + vec![SourceRange::default()], + ))); }; Ok(files) @@ -1308,10 +1307,10 @@ impl ExecutorContext { coords: *kittycad_modeling_cmds::coord::KITTYCAD, created: if deterministic_time { Some("2021-01-01T00:00:00Z".parse().map_err(|e| { - KclError::Internal(crate::errors::KclErrorDetails { - message: format!("Failed to parse date: {}", e), - source_ranges: vec![SourceRange::default()], - }) + KclError::Internal(crate::errors::KclErrorDetails::new( + format!("Failed to parse date: {}", e), + vec![SourceRange::default()], + )) })?) } else { None @@ -1388,10 +1387,10 @@ pub(crate) async fn parse_execute_with_project_dir( let exec_ctxt = ExecutorContext { engine: Arc::new(Box::new( crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| { - KclError::Internal(crate::errors::KclErrorDetails { - message: format!("Failed to create mock engine connection: {}", err), - source_ranges: vec![SourceRange::default()], - }) + KclError::Internal(crate::errors::KclErrorDetails::new( + format!("Failed to create mock engine connection: {}", err), + vec![SourceRange::default()], + )) })?, )), fs: Arc::new(crate::fs::FileManager::new()), @@ -1804,10 +1803,10 @@ foo let err = result.unwrap_err(); assert_eq!( err, - KclError::Syntax(KclErrorDetails { - message: "Unexpected token: #".to_owned(), - source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())], - }), + KclError::Syntax(KclErrorDetails::new( + "Unexpected token: #".to_owned(), + vec![SourceRange::new(14, 15, ModuleId::default())], + )), ); } @@ -2063,10 +2062,10 @@ notTagIdentifier = !myTag"; // TODO: We don't currently parse this, but we should. It should be // a runtime error instead. parse_execute(code10).await.unwrap_err(), - KclError::Syntax(KclErrorDetails { - message: "Unexpected token: !".to_owned(), - source_ranges: vec![SourceRange::new(10, 11, ModuleId::default())], - }) + KclError::Syntax(KclErrorDetails::new( + "Unexpected token: !".to_owned(), + vec![SourceRange::new(10, 11, ModuleId::default())], + )) ); let code11 = " @@ -2076,10 +2075,10 @@ notPipeSub = 1 |> identity(!%))"; // TODO: We don't currently parse this, but we should. It should be // a runtime error instead. parse_execute(code11).await.unwrap_err(), - KclError::Syntax(KclErrorDetails { - message: "Unexpected token: |>".to_owned(), - source_ranges: vec![SourceRange::new(44, 46, ModuleId::default())], - }) + KclError::Syntax(KclErrorDetails::new( + "Unexpected token: |>".to_owned(), + vec![SourceRange::new(44, 46, ModuleId::default())], + )) ); // TODO: Add these tests when we support these types. diff --git a/rust/kcl-lib/src/execution/state.rs b/rust/kcl-lib/src/execution/state.rs index a6305474b..994f01ace 100644 --- a/rust/kcl-lib/src/execution/state.rs +++ b/rust/kcl-lib/src/execution/state.rs @@ -276,8 +276,8 @@ impl ExecState { } pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError { - KclError::ImportCycle(KclErrorDetails { - message: format!( + KclError::ImportCycle(KclErrorDetails::new( + format!( "circular import of modules is not allowed: {} -> {}", self.global .mod_loader @@ -288,8 +288,8 @@ impl ExecState { .join(" -> "), path, ), - source_ranges: vec![source_range], - }) + vec![source_range], + )) } pub(crate) fn pipe_value(&self) -> Option<&KclValue> { @@ -389,14 +389,14 @@ impl MetaSettings { self.kcl_version = value; } name => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Unexpected settings key: `{name}`; expected one of `{}`, `{}`", annotations::SETTINGS_UNIT_LENGTH, annotations::SETTINGS_UNIT_ANGLE ), - source_ranges: vec![annotation.as_source_range()], - })) + vec![annotation.as_source_range()], + ))) } } } diff --git a/rust/kcl-lib/src/fs/local.rs b/rust/kcl-lib/src/fs/local.rs index 839f43407..9b5c29211 100644 --- a/rust/kcl-lib/src/fs/local.rs +++ b/rust/kcl-lib/src/fs/local.rs @@ -28,19 +28,19 @@ impl Default for FileManager { impl FileSystem for FileManager { async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result, KclError> { tokio::fs::read(&path.0).await.map_err(|e| { - KclError::Io(KclErrorDetails { - message: format!("Failed to read file `{}`: {}", path.display(), e), - source_ranges: vec![source_range], - }) + KclError::Io(KclErrorDetails::new( + format!("Failed to read file `{}`: {}", path.display(), e), + vec![source_range], + )) }) } async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result { tokio::fs::read_to_string(&path.0).await.map_err(|e| { - KclError::Io(KclErrorDetails { - message: format!("Failed to read file `{}`: {}", path.display(), e), - source_ranges: vec![source_range], - }) + KclError::Io(KclErrorDetails::new( + format!("Failed to read file `{}`: {}", path.display(), e), + vec![source_range], + )) }) } @@ -49,10 +49,10 @@ impl FileSystem for FileManager { if e.kind() == std::io::ErrorKind::NotFound { Ok(false) } else { - Err(KclError::Io(KclErrorDetails { - message: format!("Failed to check if file `{}` exists: {}", path.display(), e), - source_ranges: vec![source_range], - })) + Err(KclError::Io(KclErrorDetails::new( + format!("Failed to check if file `{}` exists: {}", path.display(), e), + vec![source_range], + ))) } }) } @@ -71,10 +71,10 @@ impl FileSystem for FileManager { } let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| { - KclError::Io(KclErrorDetails { - message: format!("Failed to read directory `{}`: {}", path.display(), e), - source_ranges: vec![source_range], - }) + KclError::Io(KclErrorDetails::new( + format!("Failed to read directory `{}`: {}", path.display(), e), + vec![source_range], + )) })?; while let Ok(Some(entry)) = read_dir.next_entry().await { diff --git a/rust/kcl-lib/src/fs/wasm.rs b/rust/kcl-lib/src/fs/wasm.rs index cb032486b..b7604b552 100644 --- a/rust/kcl-lib/src/fs/wasm.rs +++ b/rust/kcl-lib/src/fs/wasm.rs @@ -46,18 +46,16 @@ unsafe impl Sync for FileManager {} #[async_trait::async_trait] impl FileSystem for FileManager { async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result, KclError> { - let promise = self.manager.read_file(path.to_string_lossy()).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + let promise = self + .manager + .read_file(path.to_string_lossy()) + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; let value = JsFuture::from(promise).await.map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to wait for promise from engine: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to wait for promise from engine: {:?}", e), + vec![source_range], + )) })?; let array = js_sys::Uint8Array::new(&value); @@ -69,35 +67,33 @@ impl FileSystem for FileManager { async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result { let bytes = self.read(path, source_range).await?; let string = String::from_utf8(bytes).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to convert bytes to string: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to convert bytes to string: {:?}", e), + vec![source_range], + )) })?; Ok(string) } async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result { - let promise = self.manager.exists(path.to_string_lossy()).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + let promise = self + .manager + .exists(path.to_string_lossy()) + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; let value = JsFuture::from(promise).await.map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to wait for promise from engine: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to wait for promise from engine: {:?}", e), + vec![source_range], + )) })?; let it_exists = value.as_bool().ok_or_else(|| { - KclError::Engine(KclErrorDetails { - message: "Failed to convert value to bool".to_string(), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + "Failed to convert value to bool".to_string(), + vec![source_range], + )) })?; Ok(it_exists) @@ -108,32 +104,30 @@ impl FileSystem for FileManager { path: &TypedPath, source_range: SourceRange, ) -> Result, crate::errors::KclError> { - let promise = self.manager.get_all_files(path.to_string_lossy()).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: e.to_string().into(), - source_ranges: vec![source_range], - }) - })?; + let promise = self + .manager + .get_all_files(path.to_string_lossy()) + .map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; let value = JsFuture::from(promise).await.map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to wait for promise from javascript: {:?}", e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to wait for promise from javascript: {:?}", e), + vec![source_range], + )) })?; let s = value.as_string().ok_or_else(|| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to get string from response from javascript: `{:?}`", value), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to get string from response from javascript: `{:?}`", value), + vec![source_range], + )) })?; let files: Vec = serde_json::from_str(&s).map_err(|e| { - KclError::Engine(KclErrorDetails { - message: format!("Failed to parse json from javascript: `{}` `{:?}`", s, e), - source_ranges: vec![source_range], - }) + KclError::Engine(KclErrorDetails::new( + format!("Failed to parse json from javascript: `{}` `{:?}`", s, e), + vec![source_range], + )) })?; Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect()) diff --git a/rust/kcl-lib/src/lib.rs b/rust/kcl-lib/src/lib.rs index b0a163851..9e1e258de 100644 --- a/rust/kcl-lib/src/lib.rs +++ b/rust/kcl-lib/src/lib.rs @@ -86,7 +86,8 @@ mod wasm; pub use coredump::CoreDump; pub use engine::{AsyncTasks, EngineManager, EngineStats}; pub use errors::{ - CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, ReportWithOutputs, + BacktraceItem, CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, + ReportWithOutputs, }; pub use execution::{ bust_cache, clear_mem_cache, diff --git a/rust/kcl-lib/src/modules.rs b/rust/kcl-lib/src/modules.rs index bd5e09cbc..6e5098423 100644 --- a/rust/kcl-lib/src/modules.rs +++ b/rust/kcl-lib/src/modules.rs @@ -58,8 +58,8 @@ impl ModuleLoader { } pub(crate) fn import_cycle_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError { - KclError::ImportCycle(KclErrorDetails { - message: format!( + KclError::ImportCycle(KclErrorDetails::new( + format!( "circular import of modules is not allowed: {} -> {}", self.import_stack .iter() @@ -68,8 +68,8 @@ impl ModuleLoader { .join(" -> "), path, ), - source_ranges: vec![source_range], - }) + vec![source_range], + )) } pub(crate) fn enter_module(&mut self, path: &ModulePath) { @@ -169,10 +169,10 @@ impl ModulePath { ModulePath::Std { value: name } => Ok(ModuleSource { source: read_std(name) .ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: format!("Cannot find standard library module to import: std::{name}."), - source_ranges: vec![source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Cannot find standard library module to import: std::{name}."), + vec![source_range], + )) }) .map(str::to_owned)?, path: self.clone(), diff --git a/rust/kcl-lib/src/parsing/mod.rs b/rust/kcl-lib/src/parsing/mod.rs index 00be2289c..855db6050 100644 --- a/rust/kcl-lib/src/parsing/mod.rs +++ b/rust/kcl-lib/src/parsing/mod.rs @@ -51,7 +51,7 @@ pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult { } else { format!("found unknown tokens [{}]", token_list.join(", ")) }; - return KclError::Lexical(KclErrorDetails { source_ranges, message }).into(); + return KclError::Lexical(KclErrorDetails::new(message, source_ranges)).into(); } // Important, to not call this before the unknown tokens check. diff --git a/rust/kcl-lib/src/parsing/token/mod.rs b/rust/kcl-lib/src/parsing/token/mod.rs index 804587ca5..7d702998d 100644 --- a/rust/kcl-lib/src/parsing/token/mod.rs +++ b/rust/kcl-lib/src/parsing/token/mod.rs @@ -578,10 +578,10 @@ impl From, winnow::error::ContextError>> for KclError { // This is an offset, not an index, and may point to // the end of input (input.len()) on eof errors. - return KclError::Lexical(crate::errors::KclErrorDetails { - source_ranges: vec![SourceRange::new(offset, offset, module_id)], - message: "unexpected EOF while parsing".to_string(), - }); + return KclError::Lexical(crate::errors::KclErrorDetails::new( + "unexpected EOF while parsing".to_owned(), + vec![SourceRange::new(offset, offset, module_id)], + )); } // TODO: Add the Winnow tokenizer context to the error. @@ -589,9 +589,9 @@ impl From, winnow::error::ContextError>> for KclError { let bad_token = &input[offset]; // TODO: Add the Winnow parser context to the error. // See https://github.com/KittyCAD/modeling-app/issues/784 - KclError::Lexical(crate::errors::KclErrorDetails { - source_ranges: vec![SourceRange::new(offset, offset + 1, module_id)], - message: format!("found unknown token '{}'", bad_token), - }) + KclError::Lexical(crate::errors::KclErrorDetails::new( + format!("found unknown token '{}'", bad_token), + vec![SourceRange::new(offset, offset + 1, module_id)], + )) } } diff --git a/rust/kcl-lib/src/std/appearance.rs b/rust/kcl-lib/src/std/appearance.rs index d0cecaa5f..ad43291fa 100644 --- a/rust/kcl-lib/src/std/appearance.rs +++ b/rust/kcl-lib/src/std/appearance.rs @@ -32,10 +32,10 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result::from_hex(&color).map_err(|err| { - KclError::Semantic(KclErrorDetails { - message: format!("Invalid hex color (`{color}`): {err}"), - source_ranges: vec![args.source_range], - }) + KclError::Semantic(KclErrorDetails::new( + format!("Invalid hex color (`{color}`): {err}"), + vec![args.source_range], + )) })?; let color = Color { diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 8c500f7f5..9f7abea32 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -122,14 +122,14 @@ impl Args { } T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| { - KclError::Type(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!( + KclError::Type(KclErrorDetails::new( + format!( "The arg {label} was given, but it was the wrong type. It should be type {} but it was {}", tynm::type_name::(), arg.value.human_friendly_type(), ), - }) + vec![self.source_range], + )) }) } @@ -155,10 +155,10 @@ impl Args { T: FromKclValue<'a>, { self.get_kw_arg_opt(label)?.ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!("This function requires a keyword argument '{label}'"), - }) + KclError::Semantic(KclErrorDetails::new( + format!("This function requires a keyword argument '{label}'"), + vec![self.source_range], + )) }) } @@ -172,10 +172,10 @@ impl Args { T: for<'a> FromKclValue<'a>, { let Some(arg) = self.kw_args.labeled.get(label) else { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!("This function requires a keyword argument '{label}'"), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("This function requires a keyword argument '{label}'"), + vec![self.source_range], + ))); }; let arg = arg.value.coerce(ty, exec_state).map_err(|_| { @@ -206,10 +206,7 @@ impl Args { if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") { message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}"); } - KclError::Semantic(KclErrorDetails { - source_ranges: arg.source_ranges(), - message, - }) + KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges())) })?; // TODO unnecessary cloning @@ -223,21 +220,21 @@ impl Args { T: FromKclValue<'a>, { let Some(arg) = self.kw_args.labeled.get(label) else { - let err = KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!("This function requires a keyword argument '{label}'"), - }); + let err = KclError::Semantic(KclErrorDetails::new( + format!("This function requires a keyword argument '{label}'"), + vec![self.source_range], + )); return Err(err); }; let Some(array) = arg.value.as_array() else { - let err = KclError::Semantic(KclErrorDetails { - source_ranges: vec![arg.source_range], - message: format!( + let err = KclError::Semantic(KclErrorDetails::new( + format!( "Expected an array of {} but found {}", tynm::type_name::(), arg.value.human_friendly_type() ), - }); + vec![arg.source_range], + )); return Err(err); }; array @@ -245,14 +242,14 @@ impl Args { .map(|item| { let source = SourceRange::from(item); let val = FromKclValue::from_kcl_val(item).ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - source_ranges: arg.source_ranges(), - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Expected a {} but found {}", tynm::type_name::(), arg.value.human_friendly_type() ), - }) + arg.source_ranges(), + )) })?; Ok((val, source)) }) @@ -267,19 +264,19 @@ impl Args { { let arg = self .unlabeled_kw_arg_unconverted() - .ok_or(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"), - }))?; + .ok_or(KclError::Semantic(KclErrorDetails::new( + format!("This function requires a value for the special unlabeled first parameter, '{label}'"), + vec![self.source_range], + )))?; T::from_kcl_val(&arg.value).ok_or_else(|| { let expected_type_name = tynm::type_name::(); let actual_type_name = arg.value.human_friendly_type(); let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}"); - KclError::Semantic(KclErrorDetails { - source_ranges: arg.source_ranges(), + KclError::Semantic(KclErrorDetails::new( message, - }) + arg.source_ranges(), + )) }) } @@ -296,10 +293,10 @@ impl Args { { let arg = self .unlabeled_kw_arg_unconverted() - .ok_or(KclError::Semantic(KclErrorDetails { - source_ranges: vec![self.source_range], - message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"), - }))?; + .ok_or(KclError::Semantic(KclErrorDetails::new( + format!("This function requires a value for the special unlabeled first parameter, '{label}'"), + vec![self.source_range], + )))?; let arg = arg.value.coerce(ty, exec_state).map_err(|_| { let actual_type = arg.value.principal_type(); @@ -330,17 +327,14 @@ impl Args { if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") { message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}"); } - KclError::Semantic(KclErrorDetails { - source_ranges: arg.source_ranges(), - message, - }) + KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges())) })?; T::from_kcl_val(&arg).ok_or_else(|| { - KclError::Internal(KclErrorDetails { - source_ranges: vec![self.source_range], - message: "Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(), - }) + KclError::Internal(KclErrorDetails::new( + "Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(), + vec![self.source_range], + )) }) } @@ -383,17 +377,17 @@ impl Args { exec_state.stack().get_from_call_stack(&tag.value, self.source_range)? { let info = t.get_info(epoch).ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Tag `{}` does not have engine info", tag.value), - source_ranges: vec![self.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Tag `{}` does not have engine info", tag.value), + vec![self.source_range], + )) })?; Ok(info) } else { - Err(KclError::Type(KclErrorDetails { - message: format!("Tag `{}` does not exist", tag.value), - source_ranges: vec![self.source_range], - })) + Err(KclError::Type(KclErrorDetails::new( + format!("Tag `{}` does not exist", tag.value), + vec![self.source_range], + ))) } } @@ -522,19 +516,19 @@ impl Args { must_be_planar: bool, ) -> Result { if tag.value.is_empty() { - return Err(KclError::Type(KclErrorDetails { - message: "Expected a non-empty tag for the face".to_string(), - source_ranges: vec![self.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Expected a non-empty tag for the face".to_string(), + vec![self.source_range], + ))); } let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?; let surface = engine_info.surface.as_ref().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Tag `{}` does not have a surface", tag.value), - source_ranges: vec![self.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Tag `{}` does not have a surface", tag.value), + vec![self.source_range], + )) })?; if let Some(face_from_surface) = match surface { @@ -550,10 +544,10 @@ impl Args { } } // The must be planar check must be called before the arc check. - ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails { - message: format!("Tag `{}` is a non-planar surface", tag.value), - source_ranges: vec![self.source_range], - }))), + ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new( + format!("Tag `{}` is a non-planar surface", tag.value), + vec![self.source_range], + )))), ExtrudeSurface::ExtrudeArc(extrude_arc) => { if let Some(arc_tag) = &extrude_arc.tag { if arc_tag.name == tag.value { @@ -577,10 +571,10 @@ impl Args { } } // The must be planar check must be called before the fillet check. - ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails { - message: format!("Tag `{}` is a non-planar surface", tag.value), - source_ranges: vec![self.source_range], - }))), + ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new( + format!("Tag `{}` is a non-planar surface", tag.value), + vec![self.source_range], + )))), ExtrudeSurface::Fillet(fillet) => { if let Some(fillet_tag) = &fillet.tag { if fillet_tag.name == tag.value { @@ -597,10 +591,10 @@ impl Args { } // If we still haven't found the face, return an error. - Err(KclError::Type(KclErrorDetails { - message: format!("Expected a face with the tag `{}`", tag.value), - source_ranges: vec![self.source_range], - })) + Err(KclError::Type(KclErrorDetails::new( + format!("Expected a face with the tag `{}`", tag.value), + vec![self.source_range], + ))) } } @@ -622,20 +616,20 @@ where { fn from_args(args: &'a Args, i: usize) -> Result { let Some(arg) = args.args.get(i) else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("Expected an argument at index {i}"), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Expected an argument at index {i}"), + vec![args.source_range], + ))); }; let Some(val) = T::from_kcl_val(&arg.value) else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Argument at index {i} was supposed to be type {} but found {}", tynm::type_name::(), arg.value.human_friendly_type(), ), - source_ranges: arg.source_ranges(), - })); + arg.source_ranges(), + ))); }; Ok(val) } @@ -651,14 +645,14 @@ where return Ok(None); } let Some(val) = T::from_kcl_val(&arg.value) else { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Argument at index {i} was supposed to be type Option<{}> but found {}", tynm::type_name::(), arg.value.human_friendly_type() ), - source_ranges: arg.source_ranges(), - })); + arg.source_ranges(), + ))); }; Ok(Some(val)) } diff --git a/rust/kcl-lib/src/std/array.rs b/rust/kcl-lib/src/std/array.rs index abb9ad15b..5a1892687 100644 --- a/rust/kcl-lib/src/std/array.rs +++ b/rust/kcl-lib/src/std/array.rs @@ -58,10 +58,10 @@ async fn call_map_closure( let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?; let source_ranges = vec![source_range]; let output = output.ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: "Map function must return a value".to_string(), + KclError::Semantic(KclErrorDetails::new( + "Map function must return a value".to_owned(), source_ranges, - }) + )) })?; Ok(output) } @@ -118,10 +118,10 @@ async fn call_reduce_closure( // Unpack the returned transform object. let source_ranges = vec![source_range]; let out = transform_fn_return.ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: "Reducer function must return a value".to_string(), - source_ranges: source_ranges.clone(), - }) + KclError::Semantic(KclErrorDetails::new( + "Reducer function must return a value".to_string(), + source_ranges.clone(), + )) })?; Ok(out) } @@ -133,10 +133,10 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result Result Result, args: &Args) -> Result, KclError> { if array.is_empty() { - return Err(KclError::Semantic(KclErrorDetails { - message: "Cannot pop from an empty array".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Cannot pop from an empty array".to_string(), + vec![args.source_range], + ))); } // Create a new array with all elements except the last one diff --git a/rust/kcl-lib/src/std/assert.rs b/rust/kcl-lib/src/std/assert.rs index c4953b260..cac0d524b 100644 --- a/rust/kcl-lib/src/std/assert.rs +++ b/rust/kcl-lib/src/std/assert.rs @@ -12,10 +12,10 @@ use crate::{ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> { if !value { - return Err(KclError::Type(KclErrorDetails { - message: format!("assert failed: {}", message), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + format!("assert failed: {}", message), + vec![args.source_range], + ))); } Ok(()) } @@ -111,19 +111,18 @@ async fn inner_assert( .iter() .all(|cond| cond.is_none()); if no_condition_given { - return Err(KclError::Type(KclErrorDetails { - message: "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(), + vec![args.source_range], + ))); } if tolerance.is_some() && is_equal_to.is_none() { - return Err(KclError::Type(KclErrorDetails { - message: - "The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg." - .to_owned(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg." + .to_owned(), + vec![args.source_range], + ))); } let suffix = if let Some(err_string) = error { diff --git a/rust/kcl-lib/src/std/chamfer.rs b/rust/kcl-lib/src/std/chamfer.rs index 50fcef2f0..3bd72adee 100644 --- a/rust/kcl-lib/src/std/chamfer.rs +++ b/rust/kcl-lib/src/std/chamfer.rs @@ -41,10 +41,10 @@ async fn inner_chamfer( // If you try and tag multiple edges with a tagged chamfer, we want to return an // error to the user that they can only tag one edge at a time. if tag.is_some() && tags.len() > 1 { - return Err(KclError::Type(KclErrorDetails { - message: "You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(), + vec![args.source_range], + ))); } let mut solid = solid.clone(); diff --git a/rust/kcl-lib/src/std/clone.rs b/rust/kcl-lib/src/std/clone.rs index 9f659aaba..373ef199f 100644 --- a/rust/kcl-lib/src/std/clone.rs +++ b/rust/kcl-lib/src/std/clone.rs @@ -84,10 +84,10 @@ async fn inner_clone( fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args) .await .map_err(|e| { - KclError::Internal(KclErrorDetails { - message: format!("failed to fix tags and references: {:?}", e), - source_ranges: vec![args.source_range], - }) + KclError::Internal(KclErrorDetails::new( + format!("failed to fix tags and references: {:?}", e), + vec![args.source_range], + )) })?; Ok(new_geometry) diff --git a/rust/kcl-lib/src/std/csg.rs b/rust/kcl-lib/src/std/csg.rs index b56b547a5..b59cc8885 100644 --- a/rust/kcl-lib/src/std/csg.rs +++ b/rust/kcl-lib/src/std/csg.rs @@ -24,10 +24,10 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?; if solids.len() < 2 { - return Err(KclError::UndefinedValue(KclErrorDetails { - message: "At least two solids are required for a union operation.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::UndefinedValue(KclErrorDetails::new( + "At least two solids are required for a union operation.".to_string(), + vec![args.source_range], + ))); } let solids = inner_union(solids, tolerance, exec_state, args).await?; @@ -147,10 +147,10 @@ pub(crate) async fn inner_union( modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }), } = result else { - return Err(KclError::Internal(KclErrorDetails { - message: "Failed to get the result of the union operation.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Internal(KclErrorDetails::new( + "Failed to get the result of the union operation.".to_string(), + vec![args.source_range], + ))); }; // If we have more solids, set those as well. @@ -169,10 +169,10 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?; if solids.len() < 2 { - return Err(KclError::UndefinedValue(KclErrorDetails { - message: "At least two solids are required for an intersect operation.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::UndefinedValue(KclErrorDetails::new( + "At least two solids are required for an intersect operation.".to_string(), + vec![args.source_range], + ))); } let solids = inner_intersect(solids, tolerance, exec_state, args).await?; @@ -273,10 +273,10 @@ pub(crate) async fn inner_intersect( modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }), } = result else { - return Err(KclError::Internal(KclErrorDetails { - message: "Failed to get the result of the intersection operation.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Internal(KclErrorDetails::new( + "Failed to get the result of the intersection operation.".to_string(), + vec![args.source_range], + ))); }; // If we have more solids, set those as well. @@ -397,10 +397,10 @@ pub(crate) async fn inner_subtract( modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }), } = result else { - return Err(KclError::Internal(KclErrorDetails { - message: "Failed to get the result of the subtract operation.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Internal(KclErrorDetails::new( + "Failed to get the result of the subtract operation.".to_string(), + vec![args.source_range], + ))); }; // If we have more solids, set those as well. diff --git a/rust/kcl-lib/src/std/edge.rs b/rust/kcl-lib/src/std/edge.rs index c912ed36e..3e9b6434b 100644 --- a/rust/kcl-lib/src/std/edge.rs +++ b/rust/kcl-lib/src/std/edge.rs @@ -87,10 +87,10 @@ async fn inner_get_opposite_edge( modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge), } = &resp else { - return Err(KclError::Engine(KclErrorDetails { - message: format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp), + vec![args.source_range], + ))); }; Ok(opposite_edge.edge) @@ -172,20 +172,20 @@ async fn inner_get_next_adjacent_edge( modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge), } = &resp else { - return Err(KclError::Engine(KclErrorDetails { - message: format!( + return Err(KclError::Engine(KclErrorDetails::new( + format!( "mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}", resp ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); }; adjacent_edge.edge.ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("No edge found next adjacent to tag: `{}`", edge.value), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("No edge found next adjacent to tag: `{}`", edge.value), + vec![args.source_range], + )) }) } @@ -264,20 +264,20 @@ async fn inner_get_previous_adjacent_edge( modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge), } = &resp else { - return Err(KclError::Engine(KclErrorDetails { - message: format!( + return Err(KclError::Engine(KclErrorDetails::new( + format!( "mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}", resp ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); }; adjacent_edge.edge.ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("No edge found previous adjacent to tag: `{}`", edge.value), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("No edge found previous adjacent to tag: `{}`", edge.value), + vec![args.source_range], + )) }) } @@ -336,10 +336,10 @@ async fn inner_get_common_edge( } if faces.len() != 2 { - return Err(KclError::Type(KclErrorDetails { - message: "getCommonEdge requires exactly two tags for faces".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "getCommonEdge requires exactly two tags for faces".to_string(), + vec![args.source_range], + ))); } let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?; let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?; @@ -348,10 +348,10 @@ async fn inner_get_common_edge( let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?; if first_tagged_path.sketch != second_tagged_path.sketch { - return Err(KclError::Type(KclErrorDetails { - message: "getCommonEdge requires the faces to be in the same original sketch".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "getCommonEdge requires the faces to be in the same original sketch".to_string(), + vec![args.source_range], + ))); } // Flush the batch for our fillets/chamfers if there are any. @@ -377,19 +377,19 @@ async fn inner_get_common_edge( modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge), } = &resp else { - return Err(KclError::Engine(KclErrorDetails { - message: format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp), + vec![args.source_range], + ))); }; common_edge.edge.ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!( + KclError::Type(KclErrorDetails::new( + format!( "No common edge was found between `{}` and `{}`", faces[0].value, faces[1].value ), - source_ranges: vec![args.source_range], - }) + vec![args.source_range], + )) }) } diff --git a/rust/kcl-lib/src/std/extrude.rs b/rust/kcl-lib/src/std/extrude.rs index 746710b19..97c4df519 100644 --- a/rust/kcl-lib/src/std/extrude.rs +++ b/rust/kcl-lib/src/std/extrude.rs @@ -175,11 +175,11 @@ async fn inner_extrude( let mut solids = Vec::new(); if symmetric.unwrap_or(false) && bidirectional_length.is_some() { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other" + return Err(KclError::Semantic(KclErrorDetails::new( + "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other" .to_owned(), - })); + vec![args.source_range], + ))); } let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm())); @@ -262,10 +262,10 @@ pub(crate) async fn do_post_extrude<'a>( // The "get extrusion face info" API call requires *any* edge on the sketch being extruded. // So, let's just use the first one. let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else { - return Err(KclError::Type(KclErrorDetails { - message: "Expected a non-empty sketch".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Expected a non-empty sketch".to_owned(), + vec![args.source_range], + ))); }; any_edge_id }; @@ -387,13 +387,13 @@ pub(crate) async fn do_post_extrude<'a>( // Add the tags for the start or end caps. if let Some(tag_start) = named_cap_tags.start { let Some(start_cap_id) = start_cap_id else { - return Err(KclError::Type(KclErrorDetails { - message: format!( + return Err(KclError::Type(KclErrorDetails::new( + format!( "Expected a start cap ID for tag `{}` for extrusion of sketch {:?}", tag_start.name, sketch.id ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); }; new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane { @@ -407,13 +407,13 @@ pub(crate) async fn do_post_extrude<'a>( } if let Some(tag_end) = named_cap_tags.end { let Some(end_cap_id) = end_cap_id else { - return Err(KclError::Type(KclErrorDetails { - message: format!( + return Err(KclError::Type(KclErrorDetails::new( + format!( "Expected an end cap ID for tag `{}` for extrusion of sketch {:?}", tag_end.name, sketch.id ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); }; new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane { diff --git a/rust/kcl-lib/src/std/fillet.rs b/rust/kcl-lib/src/std/fillet.rs index a45f7573c..5073f0f0b 100644 --- a/rust/kcl-lib/src/std/fillet.rs +++ b/rust/kcl-lib/src/std/fillet.rs @@ -49,10 +49,11 @@ pub(super) fn validate_unique(tags: &[(T, SourceRange)] } } if !duplicate_tags_source.is_empty() { - return Err(KclError::Type(KclErrorDetails { - message: "The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge".to_string(), - source_ranges: duplicate_tags_source, - })); + return Err(KclError::Type(KclErrorDetails::new( + "The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge" + .to_string(), + duplicate_tags_source, + ))); } Ok(()) } diff --git a/rust/kcl-lib/src/std/helix.rs b/rust/kcl-lib/src/std/helix.rs index e8fc0ed20..a60dafcb6 100644 --- a/rust/kcl-lib/src/std/helix.rs +++ b/rust/kcl-lib/src/std/helix.rs @@ -6,7 +6,7 @@ use kittycad_modeling_cmds::{self as kcmc, shared::Point3d}; use super::args::TyF64; use crate::{ - errors::KclError, + errors::{KclError, KclErrorDetails}, execution::{ types::{PrimitiveType, RuntimeType}, ExecState, Helix as HelixValue, KclValue, Solid, @@ -33,50 +33,50 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result { // Make sure they gave us a length. let Some(length) = length else { - return Err(KclError::Semantic(crate::errors::KclErrorDetails { - message: "Length is required when creating a helix around an axis.".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Length is required when creating a helix around an axis.".to_owned(), + vec![args.source_range], + ))); }; args.batch_modeling_cmd( diff --git a/rust/kcl-lib/src/std/loft.rs b/rust/kcl-lib/src/std/loft.rs index 5fb9afbda..cb2d36b51 100644 --- a/rust/kcl-lib/src/std/loft.rs +++ b/rust/kcl-lib/src/std/loft.rs @@ -148,13 +148,13 @@ async fn inner_loft( ) -> Result, KclError> { // Make sure we have at least two sketches. if sketches.len() < 2 { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Loft requires at least two sketches, but only {} were provided.", sketches.len() ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); } let id = exec_state.next_uuid(); diff --git a/rust/kcl-lib/src/std/math.rs b/rust/kcl-lib/src/std/math.rs index df513e721..a7b18c128 100644 --- a/rust/kcl-lib/src/std/math.rs +++ b/rust/kcl-lib/src/std/math.rs @@ -56,13 +56,13 @@ pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result= 2 { @@ -112,10 +112,10 @@ async fn inner_mirror_2d( let child_id = child_ids[1]; sketch.mirror = Some(child_id); } else { - return Err(KclError::Type(KclErrorDetails { - message: "Expected child uuids to be >= 2".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Expected child uuids to be >= 2".to_string(), + vec![args.source_range], + ))); } } diff --git a/rust/kcl-lib/src/std/patterns.rs b/rust/kcl-lib/src/std/patterns.rs index 744814aa2..883ee787a 100644 --- a/rust/kcl-lib/src/std/patterns.rs +++ b/rust/kcl-lib/src/std/patterns.rs @@ -264,10 +264,10 @@ async fn inner_pattern_transform<'a>( // Build the vec of transforms, one for each repetition. let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap()); if instances < 1 { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: MUST_HAVE_ONE_INSTANCE.to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + MUST_HAVE_ONE_INSTANCE.to_owned(), + vec![args.source_range], + ))); } for i in 1..instances { let t = make_transform::(i, transform, args.source_range, exec_state, &args.ctx).await?; @@ -318,10 +318,10 @@ async fn inner_pattern_transform_2d<'a>( // Build the vec of transforms, one for each repetition. let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap()); if instances < 1 { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: MUST_HAVE_ONE_INSTANCE.to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + MUST_HAVE_ONE_INSTANCE.to_owned(), + vec![args.source_range], + ))); } for i in 1..instances { let t = make_transform::(i, transform, args.source_range, exec_state, &args.ctx).await?; @@ -398,10 +398,10 @@ async fn send_pattern_transform( } &mock_ids } else { - return Err(KclError::Engine(KclErrorDetails { - message: format!("EntityLinearPattern response was not as expected: {:?}", resp), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + format!("EntityLinearPattern response was not as expected: {:?}", resp), + vec![args.source_range], + ))); }; let mut geometries = vec![solid.clone()]; @@ -444,10 +444,10 @@ async fn make_transform( // Unpack the returned transform object. let source_ranges = vec![source_range]; let transform_fn_return = transform_fn_return.ok_or_else(|| { - KclError::Semantic(KclErrorDetails { - message: "Transform function must return a value".to_string(), - source_ranges: source_ranges.clone(), - }) + KclError::Semantic(KclErrorDetails::new( + "Transform function must return a value".to_string(), + source_ranges.clone(), + )) })?; let transforms = match transform_fn_return { KclValue::Object { value, meta: _ } => vec![value], @@ -455,19 +455,19 @@ async fn make_transform( let transforms: Vec<_> = value .into_iter() .map(|val| { - val.into_object().ok_or(KclError::Semantic(KclErrorDetails { - message: "Transform function must return a transform object".to_string(), - source_ranges: source_ranges.clone(), - })) + val.into_object().ok_or(KclError::Semantic(KclErrorDetails::new( + "Transform function must return a transform object".to_string(), + source_ranges.clone(), + ))) }) .collect::>()?; transforms } _ => { - return Err(KclError::Semantic(KclErrorDetails { - message: "Transform function must return a transform object".to_string(), - source_ranges: source_ranges.clone(), - })) + return Err(KclError::Semantic(KclErrorDetails::new( + "Transform function must return a transform object".to_string(), + source_ranges.clone(), + ))) } }; @@ -487,10 +487,10 @@ fn transform_from_obj_fields( Some(KclValue::Bool { value: true, .. }) => true, Some(KclValue::Bool { value: false, .. }) => false, Some(_) => { - return Err(KclError::Semantic(KclErrorDetails { - message: "The 'replicate' key must be a bool".to_string(), - source_ranges: source_ranges.clone(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "The 'replicate' key must be a bool".to_string(), + source_ranges.clone(), + ))); } None => true, }; @@ -519,11 +519,10 @@ fn transform_from_obj_fields( let mut rotation = Rotation::default(); if let Some(rot) = transform.get("rotation") { let KclValue::Object { value: rot, meta: _ } = rot else { - return Err(KclError::Semantic(KclErrorDetails { - message: "The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')" - .to_string(), - source_ranges: source_ranges.clone(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')".to_owned(), + source_ranges.clone(), + ))); }; if let Some(axis) = rot.get("axis") { rotation.axis = point_3d_to_mm(T::array_to_point3d(axis, source_ranges.clone(), exec_state)?).into(); @@ -534,10 +533,10 @@ fn transform_from_obj_fields( rotation.angle = Angle::from_degrees(*number); } _ => { - return Err(KclError::Semantic(KclErrorDetails { - message: "The 'rotation.angle' key must be a number (of degrees)".to_string(), - source_ranges: source_ranges.clone(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "The 'rotation.angle' key must be a number (of degrees)".to_owned(), + source_ranges.clone(), + ))); } } } @@ -568,15 +567,15 @@ fn array_to_point3d( ) -> Result<[TyF64; 3], KclError> { val.coerce(&RuntimeType::point3d(), exec_state) .map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Expected an array of 3 numbers (i.e., a 3D point), found {}", e.found .map(|t| t.human_friendly_type()) .unwrap_or_else(|| val.human_friendly_type().to_owned()) ), source_ranges, - }) + )) }) .map(|val| val.as_point3d().unwrap()) } @@ -588,15 +587,15 @@ fn array_to_point2d( ) -> Result<[TyF64; 2], KclError> { val.coerce(&RuntimeType::point2d(), exec_state) .map_err(|e| { - KclError::Semantic(KclErrorDetails { - message: format!( + KclError::Semantic(KclErrorDetails::new( + format!( "Expected an array of 2 numbers (i.e., a 2D point), found {}", e.found .map(|t| t.human_friendly_type()) .unwrap_or_else(|| val.human_friendly_type().to_owned()) ), source_ranges, - }) + )) }) .map(|val| val.as_point2d().unwrap()) } @@ -757,12 +756,11 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result let axis = axis.to_point2d(); if axis[0].n == 0.0 && axis[1].n == 0.0 { - return Err(KclError::Semantic(KclErrorDetails { - message: - "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." - .to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." + .to_owned(), + vec![args.source_range], + ))); } let sketches = inner_pattern_linear_2d(sketches, instances, distance, axis, use_original, exec_state, args).await?; @@ -860,12 +858,11 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result let axis = axis.to_point3d(); if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 { - return Err(KclError::Semantic(KclErrorDetails { - message: - "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." - .to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." + .to_owned(), + vec![args.source_range], + ))); } let solids = inner_pattern_linear_3d(solids, instances, distance, axis, use_original, exec_state, args).await?; @@ -1210,10 +1207,10 @@ async fn inner_pattern_circular_2d( .await?; let Geometries::Sketches(new_sketches) = geometries else { - return Err(KclError::Semantic(KclErrorDetails { - message: "Expected a vec of sketches".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Expected a vec of sketches".to_string(), + vec![args.source_range], + ))); }; sketches.extend(new_sketches); @@ -1333,10 +1330,10 @@ async fn inner_pattern_circular_3d( .await?; let Geometries::Solids(new_solids) = geometries else { - return Err(KclError::Semantic(KclErrorDetails { - message: "Expected a vec of solids".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Expected a vec of solids".to_string(), + vec![args.source_range], + ))); }; solids.extend(new_solids); @@ -1358,10 +1355,10 @@ async fn pattern_circular( return Ok(Geometries::from(geometry)); } RepetitionsNeeded::Invalid => { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: MUST_HAVE_ONE_INSTANCE.to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + MUST_HAVE_ONE_INSTANCE.to_owned(), + vec![args.source_range], + ))); } }; @@ -1403,10 +1400,10 @@ async fn pattern_circular( } &mock_ids } else { - return Err(KclError::Engine(KclErrorDetails { - message: format!("EntityCircularPattern response was not as expected: {:?}", resp), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Engine(KclErrorDetails::new( + format!("EntityCircularPattern response was not as expected: {:?}", resp), + vec![args.source_range], + ))); }; let geometries = match geometry { diff --git a/rust/kcl-lib/src/std/revolve.rs b/rust/kcl-lib/src/std/revolve.rs index 4a5e65249..7bae6f714 100644 --- a/rust/kcl-lib/src/std/revolve.rs +++ b/rust/kcl-lib/src/std/revolve.rs @@ -75,10 +75,10 @@ async fn inner_revolve( // We don't use validate() here because we want to return a specific error message that is // nice and we use the other data in the docs, so we still need use the derive above for the json schema. if !(-360.0..=360.0).contains(&angle) || angle == 0.0 { - return Err(KclError::Semantic(KclErrorDetails { - message: format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle), + vec![args.source_range], + ))); } } @@ -87,35 +87,35 @@ async fn inner_revolve( // We don't use validate() here because we want to return a specific error message that is // nice and we use the other data in the docs, so we still need use the derive above for the json schema. if !(-360.0..=360.0).contains(&bidirectional_angle) || bidirectional_angle == 0.0 { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Expected bidirectional angle to be between -360 and 360 and not 0, found `{}`", bidirectional_angle ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); } if let Some(angle) = angle { let ang = angle.signum() * bidirectional_angle + angle; if !(-360.0..=360.0).contains(&ang) { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( + return Err(KclError::Semantic(KclErrorDetails::new( + format!( "Combined angle and bidirectional must be between -360 and 360, found '{}'", ang ), - source_ranges: vec![args.source_range], - })); + vec![args.source_range], + ))); } } } if symmetric.unwrap_or(false) && bidirectional_angle.is_some() { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other" + return Err(KclError::Semantic(KclErrorDetails::new( + "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other" .to_owned(), - })); + vec![args.source_range], + ))); } let angle = Angle::from_degrees(angle.unwrap_or(360.0)); diff --git a/rust/kcl-lib/src/std/segment.rs b/rust/kcl-lib/src/std/segment.rs index 547fb32ec..01c427771 100644 --- a/rust/kcl-lib/src/std/segment.rs +++ b/rust/kcl-lib/src/std/segment.rs @@ -59,10 +59,10 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result Result<[TyF64; 2], KclError> { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; let (p, ty) = path.end_point_components(); // Docs generation isn't smart enough to handle ([f64; 2], NumericType). @@ -104,10 +104,10 @@ pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; Ok(TyF64::new(path.get_base().to[0], path.get_base().units.into())) @@ -147,10 +147,10 @@ pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; Ok(path.get_to()[1].clone()) @@ -201,10 +201,10 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result Result<[TyF64; 2], KclError> { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; let (p, ty) = path.start_point_components(); // Docs generation isn't smart enough to handle ([f64; 2], NumericType). @@ -246,10 +246,10 @@ pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; Ok(path.get_from()[0].clone()) @@ -289,10 +289,10 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; Ok(path.get_from()[1].clone()) @@ -334,10 +334,10 @@ fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result { .paths .last() .ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a Sketch with at least one segment, found `{:?}`", sketch), + vec![args.source_range], + )) })? .get_base(); @@ -381,10 +381,10 @@ fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result { .paths .last() .ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a Sketch with at least one segment, found `{:?}`", sketch), + vec![args.source_range], + )) })? .get_base(); @@ -429,10 +429,10 @@ pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; Ok(path.length()) @@ -473,10 +473,10 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; let result = between(path.get_base().from, path.get_base().to); @@ -575,10 +575,10 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result Result { let line = args.get_tag_engine_info(exec_state, tag)?; let path = line.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected a line segment with a path, found `{:?}`", line), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected a line segment with a path, found `{:?}`", line), + vec![args.source_range], + )) })?; let from = untype_point(path.get_to()).0; diff --git a/rust/kcl-lib/src/std/shapes.rs b/rust/kcl-lib/src/std/shapes.rs index ed971cdb2..8d96c62bf 100644 --- a/rust/kcl-lib/src/std/shapes.rs +++ b/rust/kcl-lib/src/std/shapes.rs @@ -325,17 +325,17 @@ async fn inner_polygon( args: Args, ) -> Result { if num_sides < 3 { - return Err(KclError::Type(KclErrorDetails { - message: "Polygon must have at least 3 sides".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Polygon must have at least 3 sides".to_string(), + vec![args.source_range], + ))); } if radius.n <= 0.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Radius must be greater than 0".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Radius must be greater than 0".to_string(), + vec![args.source_range], + ))); } let (sketch_surface, units) = match sketch_surface_or_group { diff --git a/rust/kcl-lib/src/std/shell.rs b/rust/kcl-lib/src/std/shell.rs index fd15ff83e..dfc95aec5 100644 --- a/rust/kcl-lib/src/std/shell.rs +++ b/rust/kcl-lib/src/std/shell.rs @@ -36,17 +36,17 @@ async fn inner_shell( args: Args, ) -> Result, KclError> { if faces.is_empty() { - return Err(KclError::Type(KclErrorDetails { - message: "You must shell at least one face".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "You must shell at least one face".to_owned(), + vec![args.source_range], + ))); } if solids.is_empty() { - return Err(KclError::Type(KclErrorDetails { - message: "You must shell at least one solid".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "You must shell at least one solid".to_owned(), + vec![args.source_range], + ))); } let mut face_ids = Vec::new(); @@ -63,20 +63,19 @@ async fn inner_shell( } if face_ids.is_empty() { - return Err(KclError::Type(KclErrorDetails { - message: "Expected at least one valid face".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Expected at least one valid face".to_owned(), + vec![args.source_range], + ))); } // Make sure all the solids have the same id, as we are going to shell them all at // once. if !solids.iter().all(|eg| eg.id == solids[0].id) { - return Err(KclError::Type(KclErrorDetails { - message: "All solids stem from the same root object, like multiple sketch on face extrusions, etc." - .to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(), + vec![args.source_range], + ))); } args.batch_modeling_cmd( diff --git a/rust/kcl-lib/src/std/sketch.rs b/rust/kcl-lib/src/std/sketch.rs index 614368e71..e8cb0f567 100644 --- a/rust/kcl-lib/src/std/sketch.rs +++ b/rust/kcl-lib/src/std/sketch.rs @@ -64,16 +64,16 @@ impl FaceTag { match self { FaceTag::Tag(ref t) => args.get_adjacent_face_to_tag(exec_state, t, must_be_planar).await, FaceTag::StartOrEnd(StartOrEnd::Start) => solid.start_cap_id.ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: "Expected a start face".to_string(), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + "Expected a start face".to_string(), + vec![args.source_range], + )) }), FaceTag::StartOrEnd(StartOrEnd::End) => solid.end_cap_id.ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: "Expected an end face".to_string(), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + "Expected an end face".to_string(), + vec![args.source_range], + )) }), } } @@ -329,19 +329,18 @@ async fn straight_line( let from = sketch.current_pen_position()?; let (point, is_absolute) = match (end_absolute, end) { (Some(_), Some(_)) => { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other" - .to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other".to_owned(), + vec![args.source_range], + ))); } (Some(end_absolute), None) => (end_absolute, true), (None, Some(end)) => (end, false), (None, None) => { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: format!("You must supply either `{relative_name}` or `endAbsolute` arguments"), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + format!("You must supply either `{relative_name}` or `endAbsolute` arguments"), + vec![args.source_range], + ))); } }; @@ -608,10 +607,10 @@ async fn inner_angled_line( .filter(|x| x.is_some()) .count(); if options_given > 1 { - return Err(KclError::Type(KclErrorDetails { - message: " one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + " one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_string(), + vec![args.source_range], + ))); } if let Some(length_x) = length_x { return inner_angled_line_of_x_length(angle, length_x, sketch, tag, exec_state, args).await; @@ -636,15 +635,14 @@ async fn inner_angled_line( (None, None, None, None, Some(end_absolute_y)) => { inner_angled_line_to_y(angle_degrees, end_absolute_y, sketch, tag, exec_state, args).await } - (None, None, None, None, None) => Err(KclError::Type(KclErrorDetails { - message: "One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` must be given".to_string(), - source_ranges: vec![args.source_range], - })), - _ => Err(KclError::Type(KclErrorDetails { - message: "Only One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given" - .to_string(), - source_ranges: vec![args.source_range], - })), + (None, None, None, None, None) => Err(KclError::Type(KclErrorDetails::new( + "One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` must be given".to_string(), + vec![args.source_range], + ))), + _ => Err(KclError::Type(KclErrorDetails::new( + "Only One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_owned(), + vec![args.source_range], + ))), } } @@ -715,17 +713,17 @@ async fn inner_angled_line_of_x_length( args: Args, ) -> Result { if angle_degrees.abs() == 270.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have an x constrained angle of 270 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have an x constrained angle of 270 degrees".to_string(), + vec![args.source_range], + ))); } if angle_degrees.abs() == 90.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have an x constrained angle of 90 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have an x constrained angle of 90 degrees".to_string(), + vec![args.source_range], + ))); } let to = get_y_component(Angle::from_degrees(angle_degrees), length.n); @@ -747,17 +745,17 @@ async fn inner_angled_line_to_x( let from = sketch.current_pen_position()?; if angle_degrees.abs() == 270.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have an x constrained angle of 270 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have an x constrained angle of 270 degrees".to_string(), + vec![args.source_range], + ))); } if angle_degrees.abs() == 90.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have an x constrained angle of 90 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have an x constrained angle of 90 degrees".to_string(), + vec![args.source_range], + ))); } let x_component = x_to.to_length_units(from.units) - from.x; @@ -782,17 +780,17 @@ async fn inner_angled_line_of_y_length( args: Args, ) -> Result { if angle_degrees.abs() == 0.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have a y constrained angle of 0 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have a y constrained angle of 0 degrees".to_string(), + vec![args.source_range], + ))); } if angle_degrees.abs() == 180.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have a y constrained angle of 180 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have a y constrained angle of 180 degrees".to_string(), + vec![args.source_range], + ))); } let to = get_x_component(Angle::from_degrees(angle_degrees), length.n); @@ -814,17 +812,17 @@ async fn inner_angled_line_to_y( let from = sketch.current_pen_position()?; if angle_degrees.abs() == 0.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have a y constrained angle of 0 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have a y constrained angle of 0 degrees".to_string(), + vec![args.source_range], + ))); } if angle_degrees.abs() == 180.0 { - return Err(KclError::Type(KclErrorDetails { - message: "Cannot have a y constrained angle of 180 degrees".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Cannot have a y constrained angle of 180 degrees".to_string(), + vec![args.source_range], + ))); } let y_component = y_to.to_length_units(from.units) - from.y; @@ -898,10 +896,10 @@ pub async fn inner_angled_line_that_intersects( ) -> Result { let intersect_path = args.get_tag_engine_info(exec_state, &intersect_tag)?; let path = intersect_path.path.clone().ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Expected an intersect path with a path, found `{:?}`", intersect_path), - source_ranges: vec![args.source_range], - }) + KclError::Type(KclErrorDetails::new( + format!("Expected an intersect path with a path, found `{:?}`", intersect_path), + vec![args.source_range], + )) })?; let from = sketch.current_pen_position()?; @@ -1176,10 +1174,10 @@ async fn inner_start_sketch_on( SketchData::Plane(plane) => { if plane.value == crate::exec::PlaneType::Uninit { if plane.info.origin.units == UnitLen::Unknown { - return Err(KclError::Semantic(KclErrorDetails { - message: "Origin of plane has unknown units".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "Origin of plane has unknown units".to_string(), + vec![args.source_range], + ))); } let plane = make_sketch_plane_from_orientation(plane.info.into_plane_data(), exec_state, args).await?; Ok(SketchSurface::Plane(plane)) @@ -1200,10 +1198,10 @@ async fn inner_start_sketch_on( } SketchData::Solid(solid) => { let Some(tag) = face else { - return Err(KclError::Type(KclErrorDetails { - message: "Expected a tag for the face to sketch on".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Expected a tag for the face to sketch on".to_string(), + vec![args.source_range], + ))); }; let face = start_sketch_on_face(solid, tag, exec_state, args).await?; @@ -1715,12 +1713,10 @@ pub(crate) async fn inner_arc( absolute_arc(&args, id, exec_state, sketch, from, interior_absolute, end_absolute, tag).await } _ => { - Err(KclError::Type(KclErrorDetails { - message: - "Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)" - .to_string(), - source_ranges: vec![args.source_range], - })) + Err(KclError::Type(KclErrorDetails::new( + "Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)".to_owned(), + vec![args.source_range], + ))) } } } @@ -1804,10 +1800,10 @@ pub async fn relative_arc( let radius = radius.to_length_units(from.units); let (center, end) = arc_center_and_end(from.ignore_units(), a_start, a_end, radius); if a_start == a_end { - return Err(KclError::Type(KclErrorDetails { - message: "Arc start and end angles must be different".to_string(), - source_ranges: vec![args.source_range], - })); + return Err(KclError::Type(KclErrorDetails::new( + "Arc start and end angles must be different".to_string(), + vec![args.source_range], + ))); } let ccw = a_start < a_end; @@ -1958,19 +1954,18 @@ async fn inner_tangential_arc( let data = TangentialArcData::RadiusAndOffset { radius, offset: angle }; inner_tangential_arc_radius_angle(data, sketch, tag, exec_state, args).await } - (Some(_), Some(_), None, None) => Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other" - .to_owned(), - })), - (None, None, Some(_), None) | (None, None, None, Some(_)) => Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You must supply both `radius` and `angle` arguments".to_owned(), - })), - (_, _, _, _) => Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: "You must supply `end`, `endAbsolute`, or both `radius` and `angle` arguments".to_owned(), - })), + (Some(_), Some(_), None, None) => Err(KclError::Semantic(KclErrorDetails::new( + "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other".to_owned(), + vec![args.source_range], + ))), + (None, None, Some(_), None) | (None, None, None, Some(_)) => Err(KclError::Semantic(KclErrorDetails::new( + "You must supply both `radius` and `angle` arguments".to_owned(), + vec![args.source_range], + ))), + (_, _, _, _) => Err(KclError::Semantic(KclErrorDetails::new( + "You must supply `end`, `endAbsolute`, or both `radius` and `angle` arguments".to_owned(), + vec![args.source_range], + ))), } } @@ -2121,19 +2116,17 @@ async fn inner_tangential_arc_to_point( }); if result.center[0].is_infinite() { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: - "could not sketch tangential arc, because its center would be infinitely far away in the X direction" - .to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "could not sketch tangential arc, because its center would be infinitely far away in the X direction" + .to_owned(), + vec![args.source_range], + ))); } else if result.center[1].is_infinite() { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![args.source_range], - message: - "could not sketch tangential arc, because its center would be infinitely far away in the Y direction" - .to_owned(), - })); + return Err(KclError::Semantic(KclErrorDetails::new( + "could not sketch tangential arc, because its center would be infinitely far away in the Y direction" + .to_owned(), + vec![args.source_range], + ))); } let delta = if is_absolute { diff --git a/rust/kcl-lib/src/std/sweep.rs b/rust/kcl-lib/src/std/sweep.rs index facb1edce..b2ef7008e 100644 --- a/rust/kcl-lib/src/std/sweep.rs +++ b/rust/kcl-lib/src/std/sweep.rs @@ -193,10 +193,10 @@ async fn inner_sweep( Some("sketchPlane") => RelativeTo::SketchPlane, Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve, Some(_) => { - return Err(KclError::Syntax(crate::errors::KclErrorDetails { - source_ranges: vec![args.source_range], - message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(), - })) + return Err(KclError::Syntax(crate::errors::KclErrorDetails::new( + "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(), + vec![args.source_range], + ))) } }; diff --git a/rust/kcl-lib/src/std/transform.rs b/rust/kcl-lib/src/std/transform.rs index d8083550b..0aaf30595 100644 --- a/rust/kcl-lib/src/std/transform.rs +++ b/rust/kcl-lib/src/std/transform.rs @@ -36,10 +36,10 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result Result Result Result Result<(), anyhow::Error> { let files = walk_dir(&dir.to_path_buf()).await.map_err(|err| { - crate::KclError::Internal(crate::errors::KclErrorDetails { - message: format!("Failed to walk directory `{}`: {:?}", dir.display(), err), - source_ranges: vec![crate::SourceRange::default()], - }) + crate::KclError::Internal(crate::errors::KclErrorDetails::new( + format!("Failed to walk directory `{}`: {:?}", dir.display(), err), + vec![crate::SourceRange::default()], + )) })?; let futures = files diff --git a/rust/kcl-lib/src/walk/import_graph.rs b/rust/kcl-lib/src/walk/import_graph.rs index 3c9d2c645..6f040012a 100644 --- a/rust/kcl-lib/src/walk/import_graph.rs +++ b/rust/kcl-lib/src/walk/import_graph.rs @@ -96,11 +96,11 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result>, KclEr if stage_modules.is_empty() { waiting_modules.sort(); - return Err(KclError::ImportCycle(KclErrorDetails { - message: format!("circular import of modules not allowed: {}", waiting_modules.join(", ")), + return Err(KclError::ImportCycle(KclErrorDetails::new( + format!("circular import of modules not allowed: {}", waiting_modules.join(", ")), // TODO: we can get the right import lines from the AST, but we don't - source_ranges: vec![SourceRange::default()], - })); + vec![SourceRange::default()], + ))); } // not strictly needed here, but perhaps helpful to avoid thinking @@ -137,20 +137,20 @@ pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> R // This is a bit of a hack, but it works for now. ret.lock() .map_err(|err| { - KclError::Internal(KclErrorDetails { - message: format!("Failed to lock mutex: {}", err), - source_ranges: Default::default(), - }) + KclError::Internal(KclErrorDetails::new( + format!("Failed to lock mutex: {}", err), + Default::default(), + )) })? .push((filename.to_string(), is.clone(), resolved_path)); } ImportPath::Foreign { path } => { ret.lock() .map_err(|err| { - KclError::Internal(KclErrorDetails { - message: format!("Failed to lock mutex: {}", err), - source_ranges: Default::default(), - }) + KclError::Internal(KclErrorDetails::new( + format!("Failed to lock mutex: {}", err), + Default::default(), + )) })? .push((path.to_string(), is.clone(), resolved_path)); } @@ -169,10 +169,10 @@ pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> R walk(ret.clone(), prog.into(), ctx)?; let ret = ret.lock().map_err(|err| { - KclError::Internal(KclErrorDetails { - message: format!("Failed to lock mutex: {}", err), - source_ranges: Default::default(), - }) + KclError::Internal(KclErrorDetails::new( + format!("Failed to lock mutex: {}", err), + Default::default(), + )) })?; Ok(ret.clone()) @@ -213,10 +213,10 @@ pub(crate) async fn import_universe( let repr = { let Some(module_info) = exec_state.get_module(module_id) else { - return Err(KclError::Internal(KclErrorDetails { - message: format!("Module {} not found", module_id), - source_ranges: vec![import_stmt.into()], - })); + return Err(KclError::Internal(KclErrorDetails::new( + format!("Module {} not found", module_id), + vec![import_stmt.into()], + ))); }; module_info.repr.clone() }; diff --git a/src/components/ModelingSidebar/ModelingPanes/index.tsx b/src/components/ModelingSidebar/ModelingPanes/index.tsx index d840fdc86..1af1bad75 100644 --- a/src/components/ModelingSidebar/ModelingPanes/index.tsx +++ b/src/components/ModelingSidebar/ModelingPanes/index.tsx @@ -118,7 +118,9 @@ export const sidebarPanes: SidebarPane[] = [ keybinding: 'Shift + C', showBadge: { value: ({ kclContext }) => { - return kclContext.diagnostics.length + return kclContext.diagnostics.filter( + (diagnostic) => diagnostic.severity === 'error' + ).length }, onClick: (e) => { e.preventDefault() diff --git a/src/lang/errors.test.ts b/src/lang/errors.test.ts index de7e59531..d10aa24a5 100644 --- a/src/lang/errors.test.ts +++ b/src/lang/errors.test.ts @@ -12,6 +12,7 @@ describe('test kclErrToDiagnostic', () => { kind: 'semantic', msg: 'Semantic error', sourceRange: topLevelRange(0, 1), + kclBacktrace: [], nonFatal: [], operations: [], artifactCommands: [], @@ -25,6 +26,7 @@ describe('test kclErrToDiagnostic', () => { kind: 'type', msg: 'Type error', sourceRange: topLevelRange(4, 5), + kclBacktrace: [], nonFatal: [], operations: [], artifactCommands: [], diff --git a/src/lang/errors.ts b/src/lang/errors.ts index 30c27b389..e154acbd6 100644 --- a/src/lang/errors.ts +++ b/src/lang/errors.ts @@ -18,12 +18,14 @@ import type { SourceRange } from '@rust/kcl-lib/bindings/SourceRange' import { defaultArtifactGraph } from '@src/lang/std/artifactGraph' import { isTopLevelModule } from '@src/lang/util' import type { ArtifactGraph } from '@src/lang/wasm' +import type { BacktraceItem } from '@rust/kcl-lib/bindings/BacktraceItem' type ExtractKind = T extends { kind: infer K } ? K : never export class KCLError extends Error { kind: ExtractKind | 'name' sourceRange: SourceRange msg: string + kclBacktrace: BacktraceItem[] nonFatal: CompilationError[] operations: Operation[] artifactCommands: ArtifactCommand[] @@ -35,7 +37,8 @@ export class KCLError extends Error { kind: ExtractKind | 'name', msg: string, sourceRange: SourceRange, - nonFatal: CompilationError[] = [], + kclBacktrace: BacktraceItem[], + nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], artifactGraph: ArtifactGraph, @@ -46,6 +49,7 @@ export class KCLError extends Error { this.kind = kind this.msg = msg this.sourceRange = sourceRange + this.kclBacktrace = kclBacktrace this.nonFatal = nonFatal this.operations = operations this.artifactCommands = artifactCommands @@ -60,6 +64,7 @@ export class KCLLexicalError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -71,6 +76,7 @@ export class KCLLexicalError extends KCLError { 'lexical', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -86,6 +92,7 @@ export class KCLInternalError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -97,6 +104,7 @@ export class KCLInternalError extends KCLError { 'internal', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -112,6 +120,7 @@ export class KCLSyntaxError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -123,6 +132,7 @@ export class KCLSyntaxError extends KCLError { 'syntax', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -138,6 +148,7 @@ export class KCLSemanticError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -149,6 +160,7 @@ export class KCLSemanticError extends KCLError { 'semantic', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -164,6 +176,7 @@ export class KCLTypeError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -175,6 +188,7 @@ export class KCLTypeError extends KCLError { 'type', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -190,6 +204,7 @@ export class KCLIoError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -201,6 +216,7 @@ export class KCLIoError extends KCLError { 'io', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -216,6 +232,7 @@ export class KCLUnexpectedError extends KCLError { constructor( msg: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -227,6 +244,7 @@ export class KCLUnexpectedError extends KCLError { 'unexpected', msg, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -242,6 +260,7 @@ export class KCLValueAlreadyDefined extends KCLError { constructor( key: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -253,6 +272,7 @@ export class KCLValueAlreadyDefined extends KCLError { 'name', `Key ${key} was already defined elsewhere`, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -268,6 +288,7 @@ export class KCLUndefinedValueError extends KCLError { constructor( key: string, sourceRange: SourceRange, + kclBacktrace: BacktraceItem[], nonFatal: CompilationError[], operations: Operation[], artifactCommands: ArtifactCommand[], @@ -279,6 +300,7 @@ export class KCLUndefinedValueError extends KCLError { 'name', `Key ${key} has not been defined`, sourceRange, + kclBacktrace, nonFatal, operations, artifactCommands, @@ -308,6 +330,7 @@ export function lspDiagnosticsToKclErrors( [], [], [], + [], defaultArtifactGraph(), {}, null @@ -336,16 +359,44 @@ export function kclErrorsToDiagnostics( let nonFatal: CodeMirrorDiagnostic[] = [] const errs = errors ?.filter((err) => isTopLevelModule(err.sourceRange)) - .map((err): CodeMirrorDiagnostic => { + .flatMap((err) => { + const diagnostics: CodeMirrorDiagnostic[] = [] + let message = err.msg + if (err.kclBacktrace.length > 0) { + // Show the backtrace in the error message. + for (let i = 0; i < err.kclBacktrace.length; i++) { + const item = err.kclBacktrace[i] + if ( + i > 0 && + isTopLevelModule(item.sourceRange) && + item.sourceRange[0] !== err.sourceRange[0] && + item.sourceRange[1] !== err.sourceRange[1] + ) { + diagnostics.push({ + from: item.sourceRange[0], + to: item.sourceRange[1], + message: 'Part of the error backtrace', + severity: 'hint', + }) + } + if (i === err.kclBacktrace.length - 1 && !item.fnName) { + // The top-level doesn't have a name. + break + } + const name = item.fnName ? `${item.fnName}()` : '(anonymous)' + message += `\n${name}` + } + } if (err.nonFatal.length > 0) { nonFatal = nonFatal.concat(compilationErrorsToDiagnostics(err.nonFatal)) } - return { + diagnostics.push({ from: err.sourceRange[0], to: err.sourceRange[1], - message: err.msg, + message, severity: 'error', - } + }) + return diagnostics }) return errs.concat(nonFatal) } diff --git a/src/lang/executor.test.ts b/src/lang/executor.test.ts index 05d296644..7dad46df8 100644 --- a/src/lang/executor.test.ts +++ b/src/lang/executor.test.ts @@ -464,6 +464,7 @@ theExtrude = startSketchOn(XY) expect.any(Object), expect.any(Object), expect.any(Object), + expect.any(Object), null ) ) diff --git a/src/lang/wasm.test.ts b/src/lang/wasm.test.ts index d5a88549f..85ae288ba 100644 --- a/src/lang/wasm.test.ts +++ b/src/lang/wasm.test.ts @@ -47,11 +47,11 @@ it('formats numbers with units', () => { describe('test errFromErrWithOutputs', () => { it('converts KclErrorWithOutputs to KclError', () => { const blob = - '{"error":{"kind":"internal","sourceRanges":[],"msg":"Cache busted"},"nonFatal":[],"operations":[],"artifactCommands":[],"artifactGraph":{"map":{}},"filenames":{},"sourceFiles":{},"defaultPlanes":null}' + '{"error":{"kind":"internal","sourceRanges":[],"backtrace":[],"msg":"Cache busted"},"nonFatal":[],"operations":[],"artifactCommands":[],"artifactGraph":{"map":{}},"filenames":{},"sourceFiles":{},"defaultPlanes":null}' const error = errFromErrWithOutputs(blob) const errorStr = JSON.stringify(error) expect(errorStr).toEqual( - '{"kind":"internal","sourceRange":[0,0,0],"msg":"Cache busted","nonFatal":[],"operations":[],"artifactCommands":[],"artifactGraph":{},"filenames":{},"defaultPlanes":null}' + '{"kind":"internal","sourceRange":[0,0,0],"msg":"Cache busted","kclBacktrace":[],"nonFatal":[],"operations":[],"artifactCommands":[],"artifactGraph":{},"filenames":{},"defaultPlanes":null}' ) }) }) diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index e0f848c0e..fa7563c21 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -164,17 +164,15 @@ function bestSourceRange(error: RustKclError): SourceRange { } // When there's an error, the call stack is unwound, and the locations are - // built up from deepest location to shallowest. So the shallowest call is - // last. That's the most useful to the user. - for (let i = error.sourceRanges.length - 1; i >= 0; i--) { - const range = error.sourceRanges[i] + // built up from deepest location to shallowest. So the deepest call is first. + for (const range of error.sourceRanges) { // Skip ranges pointing into files that aren't the top-level module. if (isTopLevelModule(range)) { return sourceRangeFromRust(range) } } - // We didn't find a top-level module range, so just use the last one. - return sourceRangeFromRust(error.sourceRanges[error.sourceRanges.length - 1]) + // We didn't find a top-level module range, so just use the first one. + return sourceRangeFromRust(error.sourceRanges[0]) } const splitErrors = ( @@ -248,6 +246,7 @@ export const parse = (code: string | Error): ParseResult | Error => { [], [], [], + [], defaultArtifactGraph(), {}, null @@ -402,6 +401,7 @@ export const errFromErrWithOutputs = (e: any): KCLError => { parsed.error.kind, parsed.error.msg, bestSourceRange(parsed.error), + parsed.error.backtrace, parsed.nonFatal, parsed.operations, parsed.artifactCommands,