Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
180 changes: 139 additions & 41 deletions src/fable-library-py/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ fn ensure_array<'py>(py: Python<'py>, ob: &'py Bound<'py, PyAny>) -> PyResult<Ar
// Check if the object is iterable (Python protocol)
if let Ok(iter) = ob.try_iter() {
// Convert iterable directly to FSharpArray
return Ok(ArrayRef::Owned(FSharpArray::new(py, Some(iter.as_any()), None)?));
return Ok(ArrayRef::Owned(FSharpArray::new(
py,
Some(iter.as_any()),
None,
)?));
}

// Check if the object implements IEnumerable (F# protocol with GetEnumerator)
Expand All @@ -152,7 +156,11 @@ fn ensure_array<'py>(py: Python<'py>, ob: &'py Bound<'py, PyAny>) -> PyResult<Ar

// If it's a single item, create a singleton array
let singleton_list = PyList::new(py, [ob])?;
Ok(ArrayRef::Owned(FSharpArray::new(py, Some(&singleton_list), None)?))
Ok(ArrayRef::Owned(FSharpArray::new(
py,
Some(&singleton_list),
None,
)?))
}

fn ensure_equal_length_arrays<'py>(
Expand Down Expand Up @@ -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<FSharpArray> = unsafe {
Bound::from_borrowed_ptr(py, slf.as_ptr())
.cast_into_unchecked::<FSharpArray>()
Bound::from_borrowed_ptr(py, slf.as_ptr()).cast_into_unchecked::<FSharpArray>()
}
.unbind();
let iter = FSharpArrayIter {
Expand All @@ -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<Py<PyAny>> {
pub fn GetEnumerator(
slf: PyRef<'_, Self>,
py: Python<'_>,
_unit: Option<&Bound<'_, PyAny>>,
) -> PyResult<Py<PyAny>> {
let len = slf.storage.len();
// SAFETY: slf.as_ptr() is valid and from_borrowed_ptr increments refcount
let array: Py<FSharpArray> = unsafe {
Bound::from_borrowed_ptr(py, slf.as_ptr())
.cast_into_unchecked::<FSharpArray>()
Bound::from_borrowed_ptr(py, slf.as_ptr()).cast_into_unchecked::<FSharpArray>()
}
.unbind();
let enumerator = FSharpArrayEnumerator {
Expand Down Expand Up @@ -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<FSharpArray> {
pub fn zero_create(
py: Python<'_>,
count: usize,
value: &Bound<'_, PyAny>,
) -> PyResult<FSharpArray> {
// Create an array filled with the zero value for the type
FSharpArray::create(py, count, value)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -3992,7 +4010,11 @@ pub fn scan_back(
}

#[pyfunction]
pub fn split_into(py: Python<'_>, chunks: usize, array: &Bound<'_, PyAny>) -> PyResult<FSharpArray> {
pub fn split_into(
py: Python<'_>,
chunks: usize,
array: &Bound<'_, PyAny>,
) -> PyResult<FSharpArray> {
let array = ensure_array(py, array)?;
array.split_into(py, chunks)
}
Expand Down Expand Up @@ -4029,7 +4051,11 @@ pub fn try_find_index_back(
}

#[pyfunction]
pub fn windowed(py: Python<'_>, window_size: usize, array: &Bound<'_, PyAny>) -> PyResult<FSharpArray> {
pub fn windowed(
py: Python<'_>,
window_size: usize,
array: &Bound<'_, PyAny>,
) -> PyResult<FSharpArray> {
let array = ensure_array(py, array)?;
array.windowed(py, window_size)
}
Expand Down Expand Up @@ -4140,7 +4166,11 @@ pub fn exists_offset(
}

#[pyfunction]
pub fn exists(py: Python<'_>, predicate: &Bound<'_, PyAny>, array: &Bound<'_, PyAny>) -> PyResult<bool> {
pub fn exists(
py: Python<'_>,
predicate: &Bound<'_, PyAny>,
array: &Bound<'_, PyAny>,
) -> PyResult<bool> {
let array = ensure_array(py, array)?;
array.exists(py, predicate)
}
Expand Down Expand Up @@ -4573,11 +4603,7 @@ pub fn random_sample_with(
}

#[pyfunction]
pub fn random_sample(
py: Python<'_>,
count: isize,
xs: &Bound<'_, PyAny>,
) -> PyResult<FSharpArray> {
pub fn random_sample(py: Python<'_>, count: isize, xs: &Bound<'_, PyAny>) -> PyResult<FSharpArray> {
let xs = ensure_array(py, xs)?;
xs.random_sample(py, count)
}
Expand Down Expand Up @@ -5119,107 +5145,179 @@ impl FSharpCons {
impl Int8Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int8"))?).add_subclass(Int8Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int8"))?)
.add_subclass(Int8Array {}),
)
}
}

#[pymethods]
impl UInt8Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt8"))?).add_subclass(UInt8Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt8"))?)
.add_subclass(UInt8Array {}),
)
}
}

#[pymethods]
impl Int16Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int16"))?).add_subclass(Int16Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int16"))?)
.add_subclass(Int16Array {}),
)
}
}

#[pymethods]
impl UInt16Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt16"))?).add_subclass(UInt16Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt16"))?)
.add_subclass(UInt16Array {}),
)
}
}

#[pymethods]
impl Int32Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int32"))?).add_subclass(Int32Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int32"))?)
.add_subclass(Int32Array {}),
)
}
}

#[pymethods]
impl UInt32Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt32"))?).add_subclass(UInt32Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt32"))?)
.add_subclass(UInt32Array {}),
)
}
}

#[pymethods]
impl Int64Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int64"))?).add_subclass(Int64Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Int64"))?)
.add_subclass(Int64Array {}),
)
}
}

#[pymethods]
impl UInt64Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt64"))?).add_subclass(UInt64Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("UInt64"))?)
.add_subclass(UInt64Array {}),
)
}
}

#[pymethods]
impl Float32Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float32"))?).add_subclass(Float32Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float32"))?)
.add_subclass(Float32Array {}),
)
}
}

#[pymethods]
impl Float64Array {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float64"))?).add_subclass(Float64Array {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Float64"))?)
.add_subclass(Float64Array {}),
)
}
}

#[pymethods]
impl BoolArray {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("Bool"))?).add_subclass(BoolArray {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("Bool"))?)
.add_subclass(BoolArray {}),
)
}
}

#[pymethods]
impl GenericArray {
#[new]
#[pyo3(signature = (elements=None))]
fn new(py: Python<'_>, elements: Option<&Bound<'_, PyAny>>) -> PyResult<PyClassInitializer<Self>> {
Ok(PyClassInitializer::from(FSharpArray::new(py, elements, Some("generic"))?).add_subclass(GenericArray {}))
fn new(
py: Python<'_>,
elements: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(FSharpArray::new(py, elements, Some("generic"))?)
.add_subclass(GenericArray {}),
)
}
}

Expand Down
12 changes: 9 additions & 3 deletions src/fable-library-py/src/floats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ macro_rules! float_variant {
impl Deref for $name {
type Target = $type;


#[inline]
fn deref(&self) -> &Self::Target {
&self.0
Expand Down Expand Up @@ -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<Self> {
pub fn __pow__(
&self,
other: &Bound<'_, PyAny>,
modulo: Option<$type>,
) -> PyResult<Self> {
if modulo.is_some() {
return Err(PyErr::new::<exceptions::PyTypeError, _>(
"pow() with modulo not supported for floats",
Expand Down Expand Up @@ -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())
}
Expand Down
Loading
Loading