Skip to content

Conversation

@InvincibleRMC
Copy link
Contributor

@InvincibleRMC InvincibleRMC commented Oct 23, 2025

Description

Breaking change to allow Python ints into float when under strict typing. This change was also done to complex to allow floats and ints. Now that Python ints can be passed into floats it changes behavior for overload resolution. A method that takes float that is registered before a method that takes an int will now get executed when a Python int is passed in. This overload resolution also affects methods with std::complex.

This was done to better match PEP 484 numeric tower rules.

Add typing.SupportsIndex to the int/float input types to better match fall backs.
This corrects a mistake that these where supported but, the type hint was not updated.

Change complex input types to typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex to match runtime conversion rules.

Resolves #5878
Provides a work around for #5767

Suggested changelog entry:

Expand float and complex strict mode to allow ints and ints/float. Updates type hints to match better with typing.SupportsIndex.


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/


📚 Documentation preview 📚: https://pybind11--5879.org.readthedocs.build/

Signed-off-by: Michael Carlstrom <[email protected]>
@gentlegiantJGC
Copy link
Contributor

I have started working on this too.
Your code change is more complex than required.
I will finish up the documentation changes and commit what I have and you can take it from there.

@gentlegiantJGC
Copy link
Contributor

gentlegiantJGC commented Oct 23, 2025

Here is my implementation and documentation changes.
I have modified the behaviour of complex as well.
https://github.com/gentlegiantJGC/pybind11/tree/fix-pep-484
master...gentlegiantJGC:pybind11:fix-pep-484

It needs tests but I am going to stop for today.
You are welcome to use my commits.

@InvincibleRMC InvincibleRMC changed the title Expand float strict to take int Expand float strict to take int and complex strict to take float/int Oct 23, 2025
@gentlegiantJGC
Copy link
Contributor

You will need to remove the constexpr check. I didn't realise it was a newer C++ feature

@InvincibleRMC
Copy link
Contributor Author

TODO: figure out why it doesn't work in C++11.

Signed-off-by: Michael Carlstrom <[email protected]>
@InvincibleRMC InvincibleRMC marked this pull request as ready for review October 24, 2025 00:59
@gentlegiantJGC
Copy link
Contributor

I have just found that a python float passed to an int argument will raise a TypeError.
It will happily except anything that implements __int__ (eg a numpy float) but explicity not a python float.

This is probably something we should resolve here because it effects these tests.

m.def("func", [](int){});
>>> func.__doc__
  func(arg: typing.SupportsInt) -> None
>>> func(5.5)
  TypeError
>>> func(numpy.float32(5.5))  # This works fine

@gentlegiantJGC
Copy link
Contributor

Here is the commit that implemented that.
3f200fa

@rwgk Can I get your input on this?
The int type caster supports anything that implements __int__ with the explicit exception of the python float.

This conflicts with the typing.SupportsInt type hint.

The int type caster allows anything that implements __int__ with explicit exception of the python float. I can't see any reason for this.
This modifies the int casting behaviour to accept a float.
If the argument is marked as noconvert() it will only accept int.
@gentlegiantJGC
Copy link
Contributor

gentlegiantJGC commented Oct 24, 2025

Here is my proposed change. You will need to update the tests.
gentlegiantJGC@d42c8e8
https://github.com/gentlegiantJGC/pybind11/tree/pr-5879-jgc

Here are my unittests
test_cast.py
cast.cpp

@InvincibleRMC
Copy link
Contributor Author

I noticed something else to have address. According to the documentation all of int,float,complex support falling back on __index__ when converting so updated the type hint from typing.SupportsInt -> typing.SupportsInt | typing.SupportsIndex. complex also supports a __float__ fall back so its type is now typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex

@InvincibleRMC InvincibleRMC changed the title Expand float strict to take int and complex strict to take float/int Expand float and complex strict mode to allow ints and ints/float. Updates type hints to match better with typing.SupportsIndex Oct 24, 2025
@rwgk
Copy link
Collaborator

rwgk commented Oct 26, 2025

Here is the commit that implemented that. 3f200fa

@rwgk Can I get your input on this? The int type caster supports anything that implements __int__ with the explicit exception of the python float.

This conflicts with the typing.SupportsInt type hint.

I need to find a block of time to look at this carefully. Might be a couple days.

Thanks for working on this!

InvincibleRMC and others added 8 commits October 26, 2025 13:52
Signed-off-by: Michael Carlstrom <[email protected]>
Signed-off-by: Michael Carlstrom <[email protected]>
Signed-off-by: Michael Carlstrom <[email protected]>
Signed-off-by: Michael Carlstrom <[email protected]>
Signed-off-by: Michael Carlstrom <[email protected]>
Signed-off-by: Michael Carlstrom <[email protected]>
@rwgk
Copy link
Collaborator

rwgk commented Nov 8, 2025

Hi @InvincibleRMC @gentlegiantJGC, I will review this PR now / this morning. QQ just in case you're around: Are there still any open questions from your side?

@gentlegiantJGC
Copy link
Contributor

gentlegiantJGC commented Nov 8, 2025

My main question is the casting behaviour to C++ and how noconvert and converting casting should be implemented.
It seems to be a bit inconsistently implemented and in some cases noconvert is not implemented (eg #5889)

My understanding is that in the default (convert) mode any python type that can be converted to that type should be.
In the noconvert mode it should only accept the types that match the type hint.
What is the convention on what the noconvert type hint should be?

The converting integral type caster accepts any object that implements __int__ with the explicit exception of float which stems from this commit from 2016. 3f200fa
It will accept numpy floats but not python floats.
Is this behaviour intended. If so why?

tests: Add overload resolution test for float/int breaking change

Add test_overload_resolution_float_int() to explicitly test the breaking
change where int arguments now match float overloads when registered first.

The existing tests verify conversion behavior (int -> float, int/float -> complex)
but do not test overload resolution when both float and int overloads exist.
This test fills that gap by:

- Testing that float overload registered before int overload matches int(42)
- Testing strict mode (noconvert) overload resolution breaking change
- Testing complex overload resolution with int/float/complex overloads
- Documenting the breaking change explicitly

This complements existing tests which verify 'can it convert?' by testing
'which overload wins when multiple can convert?'
@rwgk
Copy link
Collaborator

rwgk commented Nov 8, 2025

FYI — I'm playing with cursor-agent while reviewing this PR. The commit that I just pushed was generated by cursor.

I'm pushing it JIC you want to follow along. There are some other things I still want to work on. I might not get all the way through today. I'll let you know here when I've arrived at a stable point, but please let me know any comments anytime.

@gentlegiantJGC
Copy link
Contributor

Follow up to my last message:
Should the noconverting caster accept only python int or any object that can be implicitly converted to int (eg a numpy int)?
In the latter case the type hint would be typing.SupportsIndex

I don't think there is a way to tell if an object is inherintly a float so I don't know how that would work for floating types.

…rrectly with complex conversion. These should be consistent across CPython, PyPy, and GraalPy.
@rwgk
Copy link
Collaborator

rwgk commented Nov 8, 2025

Here is how far I got today (I'll have another block of time tomorrow afternoon):

To the best of my current understanding, please nudge/correct as needed (this was partially generated by cursor; hope that's OK in terms of tone):

Focus of this PR: I think it'll be best to keep this PR focused on allowing intfloat and int/floatcomplex in strict mode (noconvert), aligning with PEP 484's numeric tower.

Regarding noconvert and __index__:

For float and complex casters, this PR LGTM as-is: noconvert does not convert via __index__.

  • Good IMO: Float caster (cast.h line 248): Strict mode only accepts PyFloat_Check OR PYBIND11_LONG_CHECK (Python int). It does not check PYBIND11_INDEX_CHECK, so custom __index__ objects are rejected in strict mode.

  • Good IMO: Complex caster (complex.h lines 54-58): Same pattern — strict mode only accepts PyComplex_Check, PyFloat_Check, or PYBIND11_LONG_CHECK. No PYBIND11_INDEX_CHECK.

The tests confirm this: requires_conversion(Index()) for both float and complex casters — custom __index__ objects are correctly rejected in strict mode.

The inconsistency you mentioned: The int caster is the outlier — it currently accepts __index__ objects in strict mode (line 253 checks PYBIND11_INDEX_CHECK). However, that's a separate issue from this PR's scope. I'd suggest handling that in a follow-up PR/issue to keep this PR focused.

Regarding type hints: The type hints for noconvert currently say int/float/complex, which matches the actual behavior for float/complex (they accept Python int via PYBIND11_LONG_CHECK, but not custom __index__ objects). The int caster's type hint vs. behavior mismatch is part of that separate issue.

Regarding float exclusion from int caster: That's also a separate design question (related to #5889 you mentioned) that should be addressed independently.


I'll see your comments, but probably won't be able to focus on this PR until tomorrow afternoon.

@rwgk
Copy link
Collaborator

rwgk commented Nov 10, 2025

I looked more at this PR, but still need more time.

There are many changes to the tests. I'm wondering about this part:

Add typing.SupportsIndex to the int/float input types to better match fall backs.
This corrects a mistake that these where supported but, the type hint was not updated.

Could we break that out as a separate PR? Would that lead to a significant reduction in the number of test changes in the main PR?

@InvincibleRMC
Copy link
Contributor Author

I'll look into how easy it is to split them apart after work.

@rwgk
Copy link
Collaborator

rwgk commented Nov 10, 2025

I'll look into how easy it is to split them apart after work.

Give me a moment, I'm trying to do that with cursor right now. I'm hoping that way we don't have to do the legwork ourselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Type hint conflict with noconvert float

3 participants