From 0edb9f485753e124dfe23ba8e393d2700f98bf8d Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sat, 30 May 2026 18:16:21 +0200 Subject: [PATCH] fix(bindings): clear clippy pedantic lints and lint bindings in CI (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(bindings): clear clippy pedantic lints and lint bindings in CI Resolve the pedantic lints that only surfaced under a full `cargo clippy --workspace` (the CI clippy job covered only the core crates, so the Python/Node bindings drifted): - manual_midpoint: `(a + b) / 2.0` -> `f64::midpoint(a, b)` (node + python) - new_without_default: add `Default` impls for the six no-arg Node nodes - type_complexity: factor the pivot/Ichimoku return tuples into `PivotLevels` / `WoodieLevels` / `IchimokuLines` aliases (python) - many_single_char_names: allow at crate level — OHLCV batch helpers bind the conventional o/h/l/c/v column names Add a dedicated `clippy-bindings` CI job (ubuntu-only, with Python + Node toolchains) so future binding lints fail CI instead of slipping through. * ci: lint Python and Node bindings in a dedicated clippy job The main `rust` job's clippy step only covers wickra-core/wickra/ wickra-data/wickra-wasm, so pedantic lints in the PyO3/napi bindings slipped through. Add an ubuntu-only `clippy-bindings` job that provisions Python + Node (needed by the build scripts) and runs `cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings`. --- .github/workflows/ci.yml | 31 ++++++++++++++++++++++++++ bindings/node/src/lib.rs | 44 +++++++++++++++++++++++++++++++++---- bindings/python/src/lib.rs | 45 +++++++++++++++++++------------------- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4adc5f9..7c1ea902 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,37 @@ jobs: # streaming. run: cargo build -p wickra-examples --bins + # Clippy for the Python and Node bindings. These are kept out of the main + # `rust` job because PyO3 / napi build scripts need a Python interpreter and + # a Node toolchain on PATH, which the 3-OS matrix job does not provision. + # Ubuntu-only is sufficient: the lints are platform-independent. + clippy-bindings: + name: Clippy bindings + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 + with: + components: clippy + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + + - name: Set up Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "20" + + - name: Cache cargo + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Clippy (bindings, all targets) + run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings + # Verify the crates still build and test on their declared minimum supported # Rust version. The workspace pins rust-version = "1.86" — that floor is # set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the diff --git a/bindings/node/src/lib.rs b/bindings/node/src/lib.rs index 4ebc86f6..65106837 100644 --- a/bindings/node/src/lib.rs +++ b/bindings/node/src/lib.rs @@ -7310,6 +7310,12 @@ pub struct TdRangeProjectionNode { inner: wc::TdRangeProjection, } +impl Default for TdRangeProjectionNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl TdRangeProjectionNode { #[napi(constructor)] @@ -7379,6 +7385,12 @@ pub struct TdDifferentialNode { inner: wc::TdDifferential, } +impl Default for TdDifferentialNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl TdDifferentialNode { #[napi(constructor)] @@ -7434,6 +7446,12 @@ pub struct TdOpenNode { inner: wc::TdOpen, } +impl Default for TdOpenNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl TdOpenNode { #[napi(constructor)] @@ -7712,6 +7730,12 @@ pub struct HilbertDominantCycleNode { inner: wc::HilbertDominantCycle, } +impl Default for HilbertDominantCycleNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl HilbertDominantCycleNode { #[napi(constructor)] @@ -7747,6 +7771,12 @@ pub struct AdaptiveCycleNode { inner: wc::AdaptiveCycle, } +impl Default for AdaptiveCycleNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl AdaptiveCycleNode { #[napi(constructor)] @@ -7782,6 +7812,12 @@ pub struct SineWaveNode { inner: wc::SineWave, } +impl Default for SineWaveNode { + fn default() -> Self { + Self::new() + } +} + #[napi] impl SineWaveNode { #[napi(constructor)] @@ -8138,7 +8174,7 @@ impl ValueAreaNode { low: f64, volume: f64, ) -> napi::Result> { - let mid = (high + low) / 2.0; + let mid = f64::midpoint(high, low); let candle = wc::Candle::new(mid, high, low, mid, volume, 0).map_err(map_err)?; Ok(self.inner.update(candle).map(|o| ValueAreaValue { poc: o.poc, @@ -8161,7 +8197,7 @@ impl ValueAreaNode { let n = high.len(); let mut out = vec![f64::NAN; n * 3]; for i in 0..n { - let mid = (high[i] + low[i]) / 2.0; + let mid = f64::midpoint(high[i], low[i]); let candle = wc::Candle::new(mid, high[i], low[i], mid, volume[i], 0).map_err(map_err)?; if let Some(o) = self.inner.update(candle) { @@ -8212,7 +8248,7 @@ impl InitialBalanceNode { } #[napi] pub fn update(&mut self, high: f64, low: f64) -> napi::Result> { - let mid = (high + low) / 2.0; + let mid = f64::midpoint(high, low); let candle = wc::Candle::new(mid, high, low, mid, 0.0, 0).map_err(map_err)?; Ok(self.inner.update(candle).map(|o| InitialBalanceValue { high: o.high, @@ -8229,7 +8265,7 @@ impl InitialBalanceNode { let n = high.len(); let mut out = vec![f64::NAN; n * 2]; for i in 0..n { - let mid = (high[i] + low[i]) / 2.0; + let mid = f64::midpoint(high[i], low[i]); let candle = wc::Candle::new(mid, high[i], low[i], mid, 0.0, 0).map_err(map_err)?; if let Some(o) = self.inner.update(candle) { out[i * 2] = o.high; diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index afd5aadf..85aecbed 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -8,6 +8,9 @@ // mandatory even when its body does not read state (e.g. parameterless indicators // like `TypicalPrice`). Clippy's `unused_self` triggers on those signatures. #![allow(clippy::unused_self)] +// OHLCV batch helpers bind the conventional single-letter column names +// (o/h/l/c/v) that match the domain and the NumPy call sites. +#![allow(clippy::many_single_char_names)] use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1}; use pyo3::exceptions::{PyTypeError, PyValueError}; @@ -39,6 +42,19 @@ fn flatten(values: Vec>) -> Vec { /// Raised instead of panicking when a `NumPy` input is not C-contiguous. const NON_CONTIGUOUS: &str = "array must be C-contiguous; pass np.ascontiguousarray(arr)"; +/// `(pp, r1, r2, r3, s1, s2, s3)` pivot levels returned by Classic/Fibonacci pivots. +type PivotLevels = (f64, f64, f64, f64, f64, f64, f64); +/// `(pp, r1, r2, s1, s2)` pivot levels returned by Woodie pivots. +type WoodieLevels = (f64, f64, f64, f64, f64); +/// `(tenkan, kijun, senkou_a, senkou_b, chikou)` Ichimoku lines, each optional during warmup. +type IchimokuLines = ( + Option, + Option, + Option, + Option, + Option, +); + // ============================== SMA ============================== #[pyclass(name = "SMA", module = "wickra._wickra", skip_from_py_object)] @@ -8064,10 +8080,7 @@ impl PyClassicPivots { } } /// Returns `(pp, r1, r2, r3, s1, s2, s3)` or None during warmup. - fn update( - &mut self, - candle: &Bound<'_, PyAny>, - ) -> PyResult> { + fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult> { let c = extract_candle(candle)?; Ok(self .inner @@ -8146,10 +8159,7 @@ impl PyFibonacciPivots { inner: wc::FibonacciPivots::new(), } } - fn update( - &mut self, - candle: &Bound<'_, PyAny>, - ) -> PyResult> { + fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult> { let c = extract_candle(candle)?; Ok(self .inner @@ -8305,7 +8315,7 @@ impl PyWoodiePivots { } } /// Returns `(pp, r1, r2, s1, s2)` or None during warmup. - fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult> { + fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult> { let c = extract_candle(candle)?; Ok(self.inner.update(c).map(|o| (o.pp, o.r1, o.r2, o.s1, o.s2))) } @@ -9949,18 +9959,7 @@ impl PyIchimoku { } /// Returns `(tenkan, kijun, senkou_a, senkou_b, chikou)` as a 5-tuple /// where each element is `float` or `None`. - fn update( - &mut self, - candle: &Bound<'_, PyAny>, - ) -> PyResult< - Option<( - Option, - Option, - Option, - Option, - Option, - )>, - > { + fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult> { let c = extract_candle(candle)?; Ok(self .inner @@ -10864,7 +10863,7 @@ impl PyValueArea { let mut out = vec![f64::NAN; n * 3]; for i in 0..n { // open / close pinned to the midpoint so the candle validates. - let mid = (h[i] + l[i]) / 2.0; + let mid = f64::midpoint(h[i], l[i]); let candle = wc::Candle::new(mid, h[i], l[i], mid, v[i], 0).map_err(map_err)?; if let Some(o) = self.inner.update(candle) { out[i * 3] = o.poc; @@ -10940,7 +10939,7 @@ impl PyInitialBalance { let n = h.len(); let mut out = vec![f64::NAN; n * 2]; for i in 0..n { - let mid = (h[i] + l[i]) / 2.0; + let mid = f64::midpoint(h[i], l[i]); let candle = wc::Candle::new(mid, h[i], l[i], mid, 0.0, 0).map_err(map_err)?; if let Some(o) = self.inner.update(candle) { out[i * 2] = o.high;