From ffbc9085c2e8cc9e67d869d42b31759983de25ec Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 21 Jul 2026 18:00:15 +0200 Subject: [PATCH] chore(python): format fable-library-py Rust sources and gate it in CI `cargo fmt --check` reported 35 unformatted hunks across six files in the fable-library-py crate. Nothing enforced rustfmt, so the drift accumulated silently and showed up as noise when reviewing unrelated changes. Run `cargo fmt` over the crate and add a `lint-fable-library-py` job that runs `cargo fmt --check`, so it cannot drift again. The job is separate from `test-fable-library-py` rather than a step inside it, so it runs once instead of once per cell of that job's 3x3 platform/Python matrix. The reformat is behavior-neutral: comparing the whitespace-stripped sources before and after, the only characters that change are trailing commas, which rustfmt adds or removes when it re-wraps a line. The crate compiles clean and the 249 native tests pass. src/fable-library-rust is already rustfmt-clean and could be added to the same gate later; it is left out here to keep this to one crate. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build.yml | 19 +++ src/fable-library-py/src/array.rs | 180 +++++++++++++++++------ src/fable-library-py/src/floats.rs | 12 +- src/fable-library-py/src/ints.rs | 11 +- src/fable-library-py/src/native_array.rs | 12 +- src/fable-library-py/src/options.rs | 6 +- src/fable-library-py/src/strings.rs | 19 ++- 7 files changed, 191 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88af3836da..c64d4436ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -230,6 +230,25 @@ jobs: uv run --frozen maturin develop --release uv run --frozen pytest tests + # Formatting gate for the fable-library-py Rust extension. Deliberately a + # separate job rather than a step in test-fable-library-py, so it runs once + # instead of once per cell of that job's 3x3 platform/Python matrix. + lint-fable-library-py: + timeout-minutes: 10 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check formatting + working-directory: ./src/fable-library-py + run: cargo fmt --check + # Separate build job for Rust (will run in parallel) build-rust: timeout-minutes: 75 diff --git a/src/fable-library-py/src/array.rs b/src/fable-library-py/src/array.rs index 75e08ee860..498a304703 100644 --- a/src/fable-library-py/src/array.rs +++ b/src/fable-library-py/src/array.rs @@ -141,7 +141,11 @@ fn ensure_array<'py>(py: Python<'py>, ob: &'py Bound<'py, PyAny>) -> PyResult(py: Python<'py>, ob: &'py Bound<'py, PyAny>) -> PyResult( @@ -496,8 +504,7 @@ impl FSharpArray { let len = slf.storage.len(); // SAFETY: slf.as_ptr() is valid and from_borrowed_ptr increments refcount let array: Py = unsafe { - Bound::from_borrowed_ptr(py, slf.as_ptr()) - .cast_into_unchecked::() + Bound::from_borrowed_ptr(py, slf.as_ptr()).cast_into_unchecked::() } .unbind(); let iter = FSharpArrayIter { @@ -512,12 +519,15 @@ impl FSharpArray { /// Implements the .NET IEnumerable.GetEnumerator() interface. #[allow(non_snake_case)] #[pyo3(signature = (_unit=None))] - pub fn GetEnumerator(slf: PyRef<'_, Self>, py: Python<'_>, _unit: Option<&Bound<'_, PyAny>>) -> PyResult> { + pub fn GetEnumerator( + slf: PyRef<'_, Self>, + py: Python<'_>, + _unit: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { let len = slf.storage.len(); // SAFETY: slf.as_ptr() is valid and from_borrowed_ptr increments refcount let array: Py = unsafe { - Bound::from_borrowed_ptr(py, slf.as_ptr()) - .cast_into_unchecked::() + Bound::from_borrowed_ptr(py, slf.as_ptr()).cast_into_unchecked::() } .unbind(); let enumerator = FSharpArrayEnumerator { @@ -3571,7 +3581,11 @@ pub fn create(py: Python<'_>, count: usize, value: &Bound<'_, PyAny>) -> PyResul } #[pyfunction] -pub fn zero_create(py: Python<'_>, count: usize, value: &Bound<'_, PyAny>) -> PyResult { +pub fn zero_create( + py: Python<'_>, + count: usize, + value: &Bound<'_, PyAny>, +) -> PyResult { // Create an array filled with the zero value for the type FSharpArray::create(py, count, value) } @@ -3861,7 +3875,11 @@ pub fn fold2( } #[pyfunction] -pub fn iterate(py: Python<'_>, action: &Bound<'_, PyAny>, array: &Bound<'_, PyAny>) -> PyResult<()> { +pub fn iterate( + py: Python<'_>, + action: &Bound<'_, PyAny>, + array: &Bound<'_, PyAny>, +) -> PyResult<()> { let array = ensure_array(py, array)?; array.iterate(py, action) } @@ -3992,7 +4010,11 @@ pub fn scan_back( } #[pyfunction] -pub fn split_into(py: Python<'_>, chunks: usize, array: &Bound<'_, PyAny>) -> PyResult { +pub fn split_into( + py: Python<'_>, + chunks: usize, + array: &Bound<'_, PyAny>, +) -> PyResult { let array = ensure_array(py, array)?; array.split_into(py, chunks) } @@ -4029,7 +4051,11 @@ pub fn try_find_index_back( } #[pyfunction] -pub fn windowed(py: Python<'_>, window_size: usize, array: &Bound<'_, PyAny>) -> PyResult { +pub fn windowed( + py: Python<'_>, + window_size: usize, + array: &Bound<'_, PyAny>, +) -> PyResult { let array = ensure_array(py, array)?; array.windowed(py, window_size) } @@ -4140,7 +4166,11 @@ pub fn exists_offset( } #[pyfunction] -pub fn exists(py: Python<'_>, predicate: &Bound<'_, PyAny>, array: &Bound<'_, PyAny>) -> PyResult { +pub fn exists( + py: Python<'_>, + predicate: &Bound<'_, PyAny>, + array: &Bound<'_, PyAny>, +) -> PyResult { let array = ensure_array(py, array)?; array.exists(py, predicate) } @@ -4573,11 +4603,7 @@ pub fn random_sample_with( } #[pyfunction] -pub fn random_sample( - py: Python<'_>, - count: isize, - xs: &Bound<'_, PyAny>, -) -> PyResult { +pub fn random_sample(py: Python<'_>, count: isize, xs: &Bound<'_, PyAny>) -> PyResult { let xs = ensure_array(py, xs)?; xs.random_sample(py, count) } @@ -5119,8 +5145,14 @@ impl FSharpCons { impl Int8Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int8"))?).add_subclass(Int8Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int8"))?) + .add_subclass(Int8Array {}), + ) } } @@ -5128,8 +5160,14 @@ impl Int8Array { impl UInt8Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt8"))?).add_subclass(UInt8Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt8"))?) + .add_subclass(UInt8Array {}), + ) } } @@ -5137,8 +5175,14 @@ impl UInt8Array { impl Int16Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int16"))?).add_subclass(Int16Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int16"))?) + .add_subclass(Int16Array {}), + ) } } @@ -5146,8 +5190,14 @@ impl Int16Array { impl UInt16Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt16"))?).add_subclass(UInt16Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt16"))?) + .add_subclass(UInt16Array {}), + ) } } @@ -5155,8 +5205,14 @@ impl UInt16Array { impl Int32Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int32"))?).add_subclass(Int32Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int32"))?) + .add_subclass(Int32Array {}), + ) } } @@ -5164,8 +5220,14 @@ impl Int32Array { impl UInt32Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt32"))?).add_subclass(UInt32Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt32"))?) + .add_subclass(UInt32Array {}), + ) } } @@ -5173,8 +5235,14 @@ impl UInt32Array { impl Int64Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int64"))?).add_subclass(Int64Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int64"))?) + .add_subclass(Int64Array {}), + ) } } @@ -5182,8 +5250,14 @@ impl Int64Array { impl UInt64Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt64"))?).add_subclass(UInt64Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt64"))?) + .add_subclass(UInt64Array {}), + ) } } @@ -5191,8 +5265,14 @@ impl UInt64Array { impl Float32Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float32"))?).add_subclass(Float32Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float32"))?) + .add_subclass(Float32Array {}), + ) } } @@ -5200,8 +5280,14 @@ impl Float32Array { impl Float64Array { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float64"))?).add_subclass(Float64Array {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float64"))?) + .add_subclass(Float64Array {}), + ) } } @@ -5209,8 +5295,14 @@ impl Float64Array { impl BoolArray { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Bool"))?).add_subclass(BoolArray {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("Bool"))?) + .add_subclass(BoolArray {}), + ) } } @@ -5218,8 +5310,14 @@ impl BoolArray { impl GenericArray { #[new] #[pyo3(signature = (elements=None))] - fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult> { - Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("generic"))?).add_subclass(GenericArray {})) + fn new( + py: Python<'_>, + elements: Option<&Bound<'_, PyAny>>, + ) -> PyResult> { + Ok( + PyClassInitializer::from(FSharpArray::new(py, elements, Some("generic"))?) + .add_subclass(GenericArray {}), + ) } } diff --git a/src/fable-library-py/src/floats.rs b/src/fable-library-py/src/floats.rs index d76c5767d7..63d1968bf9 100644 --- a/src/fable-library-py/src/floats.rs +++ b/src/fable-library-py/src/floats.rs @@ -18,7 +18,6 @@ macro_rules! float_variant { impl Deref for $name { type Target = $type; - #[inline] fn deref(&self) -> &Self::Target { &self.0 @@ -145,7 +144,11 @@ macro_rules! float_variant { Ok(Self(other_val % self.0)) } - pub fn __pow__(&self, other: &Bound<'_, PyAny>, modulo: Option<$type>) -> PyResult { + pub fn __pow__( + &self, + other: &Bound<'_, PyAny>, + modulo: Option<$type>, + ) -> PyResult { if modulo.is_some() { return Err(PyErr::new::( "pow() with modulo not supported for floats", @@ -560,7 +563,10 @@ macro_rules! float_variant { let py = value.py(); // If the value has __float__, extract it as a Python float if value.hasattr("__float__")? { - Ok(value.call_method0("__float__")?.into_pyobject(py).map(|o| o.unbind())?) + Ok(value + .call_method0("__float__")? + .into_pyobject(py) + .map(|o| o.unbind())?) } else { Ok(value.clone().unbind()) } diff --git a/src/fable-library-py/src/ints.rs b/src/fable-library-py/src/ints.rs index eeb7b5a042..9845c18f7b 100644 --- a/src/fable-library-py/src/ints.rs +++ b/src/fable-library-py/src/ints.rs @@ -881,10 +881,8 @@ macro_rules! integer_variant { let serializer_fn = cls.getattr("_pydantic_serializer")?; // Build the serialization schema - let ser_schema = core_schema.call_method1( - "plain_serializer_function_ser_schema", - (serializer_fn,), - )?; + let ser_schema = core_schema + .call_method1("plain_serializer_function_ser_schema", (serializer_fn,))?; // Create an int schema as the base - this enables JSON Schema generation let int_schema = core_schema.call_method0("int_schema")?; @@ -922,7 +920,10 @@ macro_rules! integer_variant { let py = value.py(); // If the value has __int__, extract it as a Python int if value.hasattr("__int__")? { - Ok(value.call_method0("__int__")?.into_pyobject(py).map(|o| o.unbind())?) + Ok(value + .call_method0("__int__")? + .into_pyobject(py) + .map(|o| o.unbind())?) } else { Ok(value.clone().unbind()) } diff --git a/src/fable-library-py/src/native_array.rs b/src/fable-library-py/src/native_array.rs index 6aa607844f..4a8992c12a 100644 --- a/src/fable-library-py/src/native_array.rs +++ b/src/fable-library-py/src/native_array.rs @@ -447,9 +447,7 @@ impl NativeArray { ArrayType::Float32 => NativeArray::Float32(Vec::with_capacity(capacity.unwrap_or(0))), ArrayType::Float64 => NativeArray::Float64(Vec::with_capacity(capacity.unwrap_or(0))), ArrayType::Bool => NativeArray::Bool(Vec::with_capacity(capacity.unwrap_or(0))), - ArrayType::Generic => { - NativeArray::PyObject(Vec::with_capacity(capacity.unwrap_or(0))) - } + ArrayType::Generic => NativeArray::PyObject(Vec::with_capacity(capacity.unwrap_or(0))), } } @@ -951,7 +949,8 @@ impl NativeArray { let py_b = b.bind(py); let proj_a = projection.call1((py_a,))?; let proj_b = projection.call1((py_b,))?; - let cmp_result = comparer.call_method1(intern!(py, "Compare"), (proj_a, proj_b))?; + let cmp_result = + comparer.call_method1(intern!(py, "Compare"), (proj_a, proj_b))?; Ok(cmp_result.extract::()?.cmp(&0)) })(); match result { @@ -1111,10 +1110,7 @@ impl NativeArray { /// the element type and scans natively; if `value` cannot be represented as the /// element type, returns `None` so the caller falls back to the general /// `rich_compare` path (which preserves Python cross-type equality semantics). - pub fn try_native_contains( - &self, - value: &Bound<'_, PyAny>, - ) -> Option> { + pub fn try_native_contains(&self, value: &Bound<'_, PyAny>) -> Option> { macro_rules! contains_prim { ($vec:expr, $t:ty) => {{ match value.extract::<$t>() { diff --git a/src/fable-library-py/src/options.rs b/src/fable-library-py/src/options.rs index 531d1e3d48..9d59505f12 100644 --- a/src/fable-library-py/src/options.rs +++ b/src/fable-library-py/src/options.rs @@ -112,10 +112,8 @@ impl SomeWrapper { let serializer_fn = cls.getattr("_pydantic_serializer")?; // Build the serialization schema - let ser_schema = core_schema.call_method1( - "plain_serializer_function_ser_schema", - (serializer_fn,), - )?; + let ser_schema = + core_schema.call_method1("plain_serializer_function_ser_schema", (serializer_fn,))?; // Use any_schema as base since SomeWrapper can hold any value let any_schema = core_schema.call_method0("any_schema")?; diff --git a/src/fable-library-py/src/strings.rs b/src/fable-library-py/src/strings.rs index e1b17dd716..dfbe226c70 100644 --- a/src/fable-library-py/src/strings.rs +++ b/src/fable-library-py/src/strings.rs @@ -568,7 +568,11 @@ mod printf { } /// Handle remaining simple patterns - fn handle_remaining_patterns(result: &mut String, remaining_args: &[String], remaining_is_str: &[bool]) { + fn handle_remaining_patterns( + result: &mut String, + remaining_args: &[String], + remaining_is_str: &[bool], + ) { for (i, arg) in remaining_args.iter().enumerate() { if let Some(pos) = result.find("%s") { result.replace_range(pos..pos + 2, arg); @@ -1130,11 +1134,7 @@ mod formatting { // Zero-padding integers: 0000 spec if spec.starts_with('0') => apply_zero_padding_format(value, spec), // Standard .NET format specifiers: d4, X8, f2, etc. - spec if spec - .chars() - .next() - .is_some_and(|c| c.is_ascii_alphabetic()) => - { + spec if spec.chars().next().is_some_and(|c| c.is_ascii_alphabetic()) => { apply_standard_format(value, spec) } // Unknown format - return as-is @@ -1633,7 +1633,12 @@ mod formatting { /// Convert string to character array #[pyfunction] - pub fn to_char_array2(py: Python<'_>, string: &str, start_index: usize, length: usize) -> PyResult { + pub fn to_char_array2( + py: Python<'_>, + string: &str, + start_index: usize, + length: usize, + ) -> PyResult { let chars: Vec = string.chars().collect(); if start_index + length > chars.len() {