-
Notifications
You must be signed in to change notification settings - Fork 252
Description
Summary
Recent nanobind generates .pyi
where parameters typed as nb::ndarray<const double, ...>
become Annotated[NDArray[float64], ...]
. Static checkers (e.g., VS Code Pylance) then reject valid calls that pass non-float64
arrays -- even though nanobind converts these to double
at runtime. Older nanobind emitted ArrayLike
, which didn’t trigger false positives.
Request: a way to generate looser stubs to reflect accepted inputs when dtype conversion is allowed.
Environment
- nanobind @ 10cc510
- Affected checker: VS Code Pylance (strict about
NDArray[...]
vsArrayLike
)
Background / behavior change
Example C++ binding:
using ndarray_1d = nb::ndarray<const double, nb::ndim<1>, nb::device::cpu, nb::c_contig>;
m.def("plot_line", [](const char* label_id, ndarray_1d& xs, ndarray_1d& ys, ImPlotLineFlags_ flags) {
ImPlot::PlotLine(label_id, (const double*)xs.data(), (const double*)ys.data(), xs.shape(0), flags);
}, "label_id"_a, "xs"_a, "ys"_a, "flags"_a.sig("LineFlags.NONE") = ImPlotLineFlags_None);
Old stubs:
xs: Annotated[ArrayLike, dict(dtype='float64', shape=(None), order='C', device='cpu', writable=False)]
New stubs that issue a type error on valid inputs:
xs: Annotated[NDArray[float64], dict(shape=(None,), order='C', device='cpu', writable=False)]
Problem (false positive type errors)
A call like this is valid at runtime (nanobind converts to double
) but flagged by Pylance:
import numpy as np
data_x = np.arange(0, 10, 0.25) # NDArray[floating[Any]]
data_y = np.sin(data_x)
implot.plot_line("Sine Wave", data_x, data_y)
Error:
Argument of type "_Array1D[floating[Any]]" cannot be assigned to parameter "xs" of type "NDArray[float64]" ...
Why this seems incorrect
The C++ signature allows implicit conversion to double
(const double
element type). nanobind does the conversion automatically. The generated stub, however, requires NDArray[np.float64]
, which is stricter than the actual accepted inputs.
Attempted workaround (dtype-agnostic typedef)
Changing the binding to a dtype-agnostic ndarray and converting inside:
using ndarray_1d_any = nb::ndarray<nb::ndim<1>, nb::device::cpu, nb::c_contig>; // no double constraint
m.def("plot_line", [](const char* label_id, ndarray_1d_any& xs, ndarray_1d_any& ys, ImPlotLineFlags_ flags) {
auto xv = xs.view<const double, nb::ndim<1>, nb::device::cpu, nb::c_contig>();
auto yv = ys.view<const double, nb::ndim<1>, nb::device::cpu, nb::c_contig>();
ImPlot::PlotLine(label_id, (const double*)xv.data(), (const double*)yv.data(), xv.shape(0), flags);
}, "label_id"_a, "xs"_a, "ys"_a, "flags"_a.sig("LineFlags.NONE") = ImPlotLineFlags_None);
This produces the desired looser stub:
xs: Annotated[NDArray, dict(shape=(None,), order='C', device='cpu')]
…but the .view<...>()
does not perform dtype conversion, so the implementation isn’t correct for non-float64
inputs.
Proposal directions
-
Stub generation policy knob: emit
NDArray[Any]
(orArrayLike
) for convertible input arguments unless.noconvert()
is present. -
Fine-grained overrides: allow
"arg"_a.sig("NDArray[Any]")
to override type of a single argument without re-specifying the whole signature. -
Declare bindings without
nb::ndarray<>
constraints, allow binding code to perform explicit array conversion inside the binding code. How to do this currently -- this would be a decent workaround?
Workarounds today
- Explicit Python-side conversion (
np.asarray(x, dtype=np.float64, order='C')
). - Full manual stub override with
nb::sig(...)
(tedious for multi-arg functions). - Stick with older nanobind (where ArrayLike was used).
- Post-process the generated .pyi files to
sed
NDArray[np.float64]
->NDArray[Any]
.