Show KCL backtraces (#7033)
* Add backtrace to errors * Add display of backtraces with hints * Change pane badge to only show count of errors * Fix property name to not collide with Error superclass * Increase min stack again * Add e2e test that checks that the diagnostics are created in CodeMirror * Remove unneeded code * Change to the new hotness
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/cargo-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/cargo-test.yml
									
									
									
									
										vendored
									
									
								
							@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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")]
 | 
			
		||||
 | 
			
		||||
@ -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<ModelingSessionData> {
 | 
			
		||||
 | 
			
		||||
@ -147,32 +147,27 @@ impl EngineConnection {
 | 
			
		||||
        id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
 | 
			
		||||
    ) -> 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<uuid::Uuid, SourceRange>,
 | 
			
		||||
    ) -> Result<WebSocketResponse, 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],
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        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::<Vec<_>>().join("\n"),
 | 
			
		||||
                    source_ranges: vec![source_range],
 | 
			
		||||
                })
 | 
			
		||||
                KclError::Engine(KclErrorDetails::new(
 | 
			
		||||
                    errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
 | 
			
		||||
                    vec![source_range],
 | 
			
		||||
                ))
 | 
			
		||||
            } else if let Ok(data) =
 | 
			
		||||
                serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&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::<Vec<_>>()
 | 
			
		||||
                            .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(())
 | 
			
		||||
 | 
			
		||||
@ -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::<Vec<_>>()
 | 
			
		||||
                        .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::<Vec<_>>().join("\n"),
 | 
			
		||||
                        source_ranges: vec![source_range],
 | 
			
		||||
                    }));
 | 
			
		||||
                    return Err(KclError::Engine(KclErrorDetails::new(
 | 
			
		||||
                        errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().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(
 | 
			
		||||
 | 
			
		||||
@ -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<SourceRange>,
 | 
			
		||||
    pub backtrace: Vec<BacktraceItem>,
 | 
			
		||||
    #[serde(rename = "msg")]
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl KclErrorDetails {
 | 
			
		||||
    pub fn new(message: String, source_ranges: Vec<SourceRange>) -> 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<BacktraceItem> {
 | 
			
		||||
        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<SourceRange>) -> 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<SourceRange>) -> Self {
 | 
			
		||||
    pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> 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<String>, 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<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<pyo3::PyErr> 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<CompilationError> 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -70,10 +70,10 @@ pub(super) fn expect_properties<'a>(
 | 
			
		||||
) -> Result<&'a [Node<ObjectProperty>], 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<String, KclError> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> {
 | 
			
		||||
@ -113,14 +113,14 @@ pub(super) fn get_impl(annotations: &[Node<Annotation>], 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],
 | 
			
		||||
            ))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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<Name> {
 | 
			
		||||
        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<Name> {
 | 
			
		||||
            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<Name> {
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            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<Name> {
 | 
			
		||||
 | 
			
		||||
        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<MemberExpression> {
 | 
			
		||||
                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<MemberExpression> {
 | 
			
		||||
            (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<BinaryExpression> {
 | 
			
		||||
                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<UnaryExpression> {
 | 
			
		||||
                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<UnaryExpression> {
 | 
			
		||||
 | 
			
		||||
        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<KclValue, KclError> {
 | 
			
		||||
    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<KclValue, KclError> {
 | 
			
		||||
    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<ArrayRangeExpression> {
 | 
			
		||||
                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: AsRef<str>>(s: S) -> &'static str {
 | 
			
		||||
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
 | 
			
		||||
    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<SourceRange>, name: &str) -> Result<Property, KclError> {
 | 
			
		||||
    let make_err = |message: String| {
 | 
			
		||||
        Err::<Property, _>(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            source_ranges: property_sr,
 | 
			
		||||
            message,
 | 
			
		||||
        }))
 | 
			
		||||
    };
 | 
			
		||||
    let make_err = |message: String| Err::<Property, _>(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(),
 | 
			
		||||
            )),
 | 
			
		||||
 | 
			
		||||
@ -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<CallExpressionKw> {
 | 
			
		||||
                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<CallExpressionKw> {
 | 
			
		||||
                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<CallExpressionKw> {
 | 
			
		||||
                    .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<CallExpressionKw> {
 | 
			
		||||
                    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",
 | 
			
		||||
 | 
			
		||||
@ -469,18 +469,18 @@ impl TryFrom<PlaneData> 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())
 | 
			
		||||
 | 
			
		||||
@ -37,53 +37,43 @@ pub async fn import_foreign(
 | 
			
		||||
) -> Result<PreImportedGeometry, KclError> {
 | 
			
		||||
    // 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::<Vec<_>>()
 | 
			
		||||
                    .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],
 | 
			
		||||
            )))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -543,17 +543,13 @@ impl KclValue {
 | 
			
		||||
    /// If this value fits in a u32, return it.
 | 
			
		||||
    pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
 | 
			
		||||
        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<TagIdentifier, KclError> {
 | 
			
		||||
        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<TagNode, KclError> {
 | 
			
		||||
        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<bool, KclError> {
 | 
			
		||||
        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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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()],
 | 
			
		||||
                    )))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -28,19 +28,19 @@ impl Default for FileManager {
 | 
			
		||||
impl FileSystem for FileManager {
 | 
			
		||||
    async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, 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<String, KclError> {
 | 
			
		||||
        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 {
 | 
			
		||||
 | 
			
		||||
@ -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<Vec<u8>, 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<String, KclError> {
 | 
			
		||||
        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<bool, crate::errors::KclError> {
 | 
			
		||||
        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<Vec<TypedPath>, 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<String> = 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())
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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(),
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -578,10 +578,10 @@ impl From<ParseError<Input<'_>, 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<ParseError<Input<'_>, 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)],
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,10 +32,10 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
 | 
			
		||||
 | 
			
		||||
    // Make sure the color if set is valid.
 | 
			
		||||
    if !HEX_REGEX.is_match(&color) {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            message: format!("Invalid hex color (`{}`), try something like `#fff000`", color),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            format!("Invalid hex color (`{}`), try something like `#fff000`", color),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result = inner_appearance(
 | 
			
		||||
@ -282,10 +282,10 @@ async fn inner_appearance(
 | 
			
		||||
    for solid_id in solids.ids(&args.ctx).await? {
 | 
			
		||||
        // Set the material properties.
 | 
			
		||||
        let rgb = rgba_simple::RGB::<f32>::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 {
 | 
			
		||||
 | 
			
		||||
@ -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::<T>(),
 | 
			
		||||
                    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::<T>(),
 | 
			
		||||
                    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::<T>(),
 | 
			
		||||
                            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::<T>();
 | 
			
		||||
            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<uuid::Uuid, KclError> {
 | 
			
		||||
        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<Self, KclError> {
 | 
			
		||||
        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::<T>(),
 | 
			
		||||
                    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::<T>(),
 | 
			
		||||
                    arg.value.human_friendly_type()
 | 
			
		||||
                ),
 | 
			
		||||
                source_ranges: arg.source_ranges(),
 | 
			
		||||
            }));
 | 
			
		||||
                arg.source_ranges(),
 | 
			
		||||
            )));
 | 
			
		||||
        };
 | 
			
		||||
        Ok(Some(val))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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<KclValue, K
 | 
			
		||||
    let KclValue::HomArray { value: values, ty } = array else {
 | 
			
		||||
        let meta = vec![args.source_range];
 | 
			
		||||
        let actual_type = array.human_friendly_type();
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            source_ranges: meta,
 | 
			
		||||
            message: format!("You can't push to a value of type {actual_type}, only an array"),
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            format!("You can't push to a value of type {actual_type}, only an array"),
 | 
			
		||||
            meta,
 | 
			
		||||
        )));
 | 
			
		||||
    };
 | 
			
		||||
    let ty = if item.has_type(&ty) {
 | 
			
		||||
        ty
 | 
			
		||||
@ -161,10 +161,10 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
 | 
			
		||||
    let KclValue::HomArray { value: values, ty } = array else {
 | 
			
		||||
        let meta = vec![args.source_range];
 | 
			
		||||
        let actual_type = array.human_friendly_type();
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            source_ranges: meta,
 | 
			
		||||
            message: format!("You can't pop from a value of type {actual_type}, only an array"),
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            format!("You can't pop from a value of type {actual_type}, only an array"),
 | 
			
		||||
            meta,
 | 
			
		||||
        )));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let new_array = inner_pop(values, &args)?;
 | 
			
		||||
@ -173,10 +173,10 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
 | 
			
		||||
 | 
			
		||||
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, 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
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -24,10 +24,10 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
 | 
			
		||||
    let tolerance: Option<TyF64> = 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<KclValu
 | 
			
		||||
    let tolerance: Option<TyF64> = 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.
 | 
			
		||||
 | 
			
		||||
@ -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],
 | 
			
		||||
        ))
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -49,10 +49,11 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<KclValue, K
 | 
			
		||||
 | 
			
		||||
    // Make sure we have a radius if we don't have a cylinder.
 | 
			
		||||
    if radius.is_none() && cylinder.is_none() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Radius is required when creating a helix without a cylinder.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Radius is required when creating a helix without a cylinder.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we don't have a radius if we have a cylinder.
 | 
			
		||||
    if radius.is_some() && cylinder.is_some() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Radius is not allowed when creating a helix with a cylinder.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Radius is not allowed when creating a helix with a cylinder.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we have an axis if we don't have a cylinder.
 | 
			
		||||
    if axis.is_none() && cylinder.is_none() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Axis is required when creating a helix without a cylinder.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Axis is required when creating a helix without a cylinder.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we don't have an axis if we have a cylinder.
 | 
			
		||||
    if axis.is_some() && cylinder.is_some() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Axis is not allowed when creating a helix with a cylinder.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Axis is not allowed when creating a helix with a cylinder.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we have a radius if we have an axis.
 | 
			
		||||
    if radius.is_none() && axis.is_some() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Radius is required when creating a helix around an axis.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Radius is required when creating a helix around an axis.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we have an axis if we have a radius.
 | 
			
		||||
    if axis.is_none() && radius.is_some() {
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails {
 | 
			
		||||
            message: "Axis is required when creating a helix around an axis.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
 | 
			
		||||
            "Axis is required when creating a helix around an axis.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let value = inner_helix(
 | 
			
		||||
@ -140,10 +140,10 @@ async fn inner_helix(
 | 
			
		||||
            Axis3dOrEdgeReference::Axis { direction, origin } => {
 | 
			
		||||
                // 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(
 | 
			
		||||
 | 
			
		||||
@ -148,13 +148,13 @@ async fn inner_loft(
 | 
			
		||||
) -> Result<Box<Solid>, 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();
 | 
			
		||||
 | 
			
		||||
@ -56,13 +56,13 @@ pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
 | 
			
		||||
    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
 | 
			
		||||
 | 
			
		||||
    if input.n < 0.0 {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
            message: format!(
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            format!(
 | 
			
		||||
                "Attempt to take square root (`sqrt`) of a number less than zero ({})",
 | 
			
		||||
                input.n
 | 
			
		||||
            ),
 | 
			
		||||
        }));
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result = input.n.sqrt();
 | 
			
		||||
 | 
			
		||||
@ -101,10 +101,10 @@ async fn inner_mirror_2d(
 | 
			
		||||
                OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
 | 
			
		||||
        } = response
 | 
			
		||||
        else {
 | 
			
		||||
            return Err(KclError::Internal(KclErrorDetails {
 | 
			
		||||
                message: "Expected a successful response from EntityGetAllChildUuids".to_string(),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Internal(KclErrorDetails::new(
 | 
			
		||||
                "Expected a successful response from EntityGetAllChildUuids".to_string(),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if child_ids.len() >= 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],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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::<Solid>(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::<Sketch>(i, transform, args.source_range, exec_state, &args.ctx).await?;
 | 
			
		||||
@ -398,10 +398,10 @@ async fn send_pattern_transform<T: GeometryTrait>(
 | 
			
		||||
        }
 | 
			
		||||
        &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<T: GeometryTrait>(
 | 
			
		||||
    // 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<T: GeometryTrait>(
 | 
			
		||||
            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::<Result<_, _>>()?;
 | 
			
		||||
            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<T: GeometryTrait>(
 | 
			
		||||
        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<T: GeometryTrait>(
 | 
			
		||||
    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<T: GeometryTrait>(
 | 
			
		||||
                    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 {
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
 | 
			
		||||
@ -59,10 +59,10 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
 | 
			
		||||
fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> 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<Kcl
 | 
			
		||||
fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, 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],
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    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<Kcl
 | 
			
		||||
fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, 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],
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(path.get_to()[1].clone())
 | 
			
		||||
@ -201,10 +201,10 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
 | 
			
		||||
fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> 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<K
 | 
			
		||||
fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, 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],
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(path.get_from()[0].clone())
 | 
			
		||||
@ -289,10 +289,10 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<K
 | 
			
		||||
fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, 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],
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(path.get_from()[1].clone())
 | 
			
		||||
@ -334,10 +334,10 @@ fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
 | 
			
		||||
        .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<TyF64, KclError> {
 | 
			
		||||
        .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<Kc
 | 
			
		||||
fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, 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],
 | 
			
		||||
        ))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(path.length())
 | 
			
		||||
@ -473,10 +473,10 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
 | 
			
		||||
fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, 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 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<Kc
 | 
			
		||||
async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, 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 from = untype_point(path.get_to()).0;
 | 
			
		||||
 | 
			
		||||
@ -325,17 +325,17 @@ async fn inner_polygon(
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    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 {
 | 
			
		||||
 | 
			
		||||
@ -36,17 +36,17 @@ async fn inner_shell(
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Vec<Solid>, 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(
 | 
			
		||||
 | 
			
		||||
@ -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<Sketch, KclError> {
 | 
			
		||||
    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<Sketch, KclError> {
 | 
			
		||||
    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<Sketch, KclError> {
 | 
			
		||||
    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 {
 | 
			
		||||
 | 
			
		||||
@ -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],
 | 
			
		||||
            )))
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,10 +36,10 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
 | 
			
		||||
 | 
			
		||||
    // Ensure at least one scale value is provided.
 | 
			
		||||
    if scale_x.is_none() && scale_y.is_none() && scale_z.is_none() {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            message: "Expected `x`, `y`, or `z` to be provided.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            "Expected `x`, `y`, or `z` to be provided.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let objects = inner_scale(
 | 
			
		||||
@ -219,10 +219,10 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
 | 
			
		||||
 | 
			
		||||
    // Ensure at least one translation value is provided.
 | 
			
		||||
    if translate_x.is_none() && translate_y.is_none() && translate_z.is_none() {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            message: "Expected `x`, `y`, or `z` to be provided.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            "Expected `x`, `y`, or `z` to be provided.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let objects = inner_translate(objects, translate_x, translate_y, translate_z, global, exec_state, args).await?;
 | 
			
		||||
@ -452,82 +452,82 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
 | 
			
		||||
    // Check if no rotation values are provided.
 | 
			
		||||
    if roll.is_none() && pitch.is_none() && yaw.is_none() && axis.is_none() && angle.is_none() {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
            message: "Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
 | 
			
		||||
            source_ranges: vec![args.source_range],
 | 
			
		||||
        }));
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
            "Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
 | 
			
		||||
            vec![args.source_range],
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If they give us a roll, pitch, or yaw, they must give us at least one of them.
 | 
			
		||||
    if roll.is_some() || pitch.is_some() || yaw.is_some() {
 | 
			
		||||
        // Ensure they didn't also provide an axis or angle.
 | 
			
		||||
        if axis.is_some() || angle.is_some() {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: "Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                "Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
 | 
			
		||||
                    .to_owned(),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If they give us an axis or angle, they must give us both.
 | 
			
		||||
    if axis.is_some() || angle.is_some() {
 | 
			
		||||
        if axis.is_none() {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: "Expected `axis` to be provided when `angle` is provided.".to_string(),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                "Expected `axis` to be provided when `angle` is provided.".to_string(),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
        if angle.is_none() {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: "Expected `angle` to be provided when `axis` is provided.".to_string(),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                "Expected `angle` to be provided when `axis` is provided.".to_string(),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ensure they didn't also provide a roll, pitch, or yaw.
 | 
			
		||||
        if roll.is_some() || pitch.is_some() || yaw.is_some() {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: "Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                "Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
 | 
			
		||||
                    .to_owned(),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate the roll, pitch, and yaw values.
 | 
			
		||||
    if let Some(roll) = &roll {
 | 
			
		||||
        if !(-360.0..=360.0).contains(&roll.n) {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(pitch) = &pitch {
 | 
			
		||||
        if !(-360.0..=360.0).contains(&pitch.n) {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(yaw) = &yaw {
 | 
			
		||||
        if !(-360.0..=360.0).contains(&yaw.n) {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate the axis and angle values.
 | 
			
		||||
    if let Some(angle) = &angle {
 | 
			
		||||
        if !(-360.0..=360.0).contains(&angle.n) {
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
 | 
			
		||||
                source_ranges: vec![args.source_range],
 | 
			
		||||
            }));
 | 
			
		||||
            return Err(KclError::Semantic(KclErrorDetails::new(
 | 
			
		||||
                format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
 | 
			
		||||
                vec![args.source_range],
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -881,10 +881,10 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -> 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
 | 
			
		||||
 | 
			
		||||
@ -96,11 +96,11 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, 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()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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: [],
 | 
			
		||||
 | 
			
		||||
@ -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> = T extends { kind: infer K } ? K : never
 | 
			
		||||
export class KCLError extends Error {
 | 
			
		||||
  kind: ExtractKind<RustKclError> | 'name'
 | 
			
		||||
  sourceRange: SourceRange
 | 
			
		||||
  msg: string
 | 
			
		||||
  kclBacktrace: BacktraceItem[]
 | 
			
		||||
  nonFatal: CompilationError[]
 | 
			
		||||
  operations: Operation[]
 | 
			
		||||
  artifactCommands: ArtifactCommand[]
 | 
			
		||||
@ -35,7 +37,8 @@ export class KCLError extends Error {
 | 
			
		||||
    kind: ExtractKind<RustKclError> | '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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -464,6 +464,7 @@ theExtrude = startSketchOn(XY)
 | 
			
		||||
        expect.any(Object),
 | 
			
		||||
        expect.any(Object),
 | 
			
		||||
        expect.any(Object),
 | 
			
		||||
        expect.any(Object),
 | 
			
		||||
        null
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -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}'
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user