Skip to content

[OTHER]: Generated stubs for nb::ndarray are too strict about dtype (e.g., NDArray[float64]) when nanobind converts inputs implicitly #1155

@nurpax

Description

@nurpax

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[...] vs ArrayLike)

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

  1. Stub generation policy knob: emit NDArray[Any] (or ArrayLike) for convertible input arguments unless .noconvert() is present.

  2. Fine-grained overrides: allow "arg"_a.sig("NDArray[Any]") to override type of a single argument without re-specifying the whole signature.

  3. 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].

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions