-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Flag enums #599
Conversation
67664ab
to
5a67564
Compare
Adding |
Could you tell me which behavior changed? The new enums should mostly behave like the old ones. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A small first round of comments
docs/api_core.rst
Outdated
@@ -1926,6 +1926,14 @@ The following annotations can be specified using the variable-length | |||
mixed enum types (such as ``Shape.Circle + Color.Red``) are | |||
permissible. | |||
|
|||
.. cpp:struct:: is_enum_flag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flag_enum
docs/api_core.rst
Outdated
operations. This enables the bitwise operators ``| & ^ ~`` | ||
with two enumeration as operands. | ||
The result will an enumeration of the same type. | ||
So ``Shape(2) | Shape(1) == Shepe(3)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shepe -> Shape
docs/changelog.rst
Outdated
Previously, nanobind relied on a custom enumeration base class that was a | ||
frequent source of friction for users. | ||
A new flag :cpp:class:`nb::is_flag_enum() <is_flag_enum>` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need to go to a separate changelog entry for 2.1.0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you added the new changelog entry but didn't remove the old one.
docs/classes.rst
Outdated
@@ -295,8 +295,12 @@ C++11-style strongly typed enumerations. | |||
|
|||
When the annotation :cpp:class:`nb::is_arithmetic() <is_arithmetic>` is | |||
passed to :cpp:class:`nb::enum_\<T\> <enum_>`, the resulting Python type | |||
will support arithmetic and bit-level operations like comparisons, and, or, | |||
xor, negation, etc. | |||
will support arithmetic and bit-level operations and, or, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and, or ..) (in parentheses)
/// Is the underlying enumeration type Flag? | ||
is_flag_enum = (1 << 18) | ||
|
||
// No more flag bits available (18). Needs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥲
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we need this (as we undoubtedly will eventually), suggest splitting the type_init_flags
to be in a separate field of type_init_data
that is not preserved in type_data
. That will free up six bits. I think some of the new type_flags
might be able to move to type_init_flags
also (is_generic
?), which would free up further space.
Sure. A simplified version of the code is:
And at runtime we get an error that a combination of the flags is not valid.
|
Aha! Yes, that makes sense. The fact that it worked previously is effectively undefined behavior. We expect that the user returns a valid/bound enum but do not check if that is actually the case. |
6ec06c8
to
972971f
Compare
What's the status of this PR? Do you still plan to make changes? (it's in draft mode) Would you like me to review it? |
There are some more unresolved issues, for example |
Yeah, that seems tricky. The existing type caster does a hash table lookup that looks for enumerants. In this case, you'd need to either need to populate the table with all possible combinations (combinatorial blowup) or perform some kind of integer conversion. I assume that the same issue would appear on the way back. The special case to support flag enums in this way might add a computational cost to the default enums (which I would not want). I don't have any suggestions, unfortunately. |
It seems to me that it would work fine to fall back from the map lookups to a slightly slower alternate, either |
0f7d119
to
90a8ecb
Compare
d496007
to
08dd9ef
Compare
08dd9ef
to
ab998d4
Compare
Fixed the 3.11+ compilation failures with stable ABI enabled. |
It would be helpful to have feedback from people who are invested into this -- @skallweitNV, @keithlostracco. Does it look okay to you as well @oremanj? From what I can see, the modified PR now adopts your suggestion. |
Question: Does it make sense to base nanobind's enum types on |
For non-arithmetic enums (e.g. C++11-style enum classes), Is your point mainly about flags? |
This appears to be exactly the functionality I was looking for. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach in general seems fine. I would like to see some more thought put into the performance of Python/C++ interconversions, and I have some documentation and style suggestions as well. (Ultimately it's Wenzel's opinion that matters here, not mine, so if he disagrees with something I suggested, his opinion wins.)
docs/api_core.rst
Outdated
with two enumeration as operands. | ||
The result will an enumeration of the same type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with two enumeration as operands. | |
The result will an enumeration of the same type. | |
with two enumerators as operands. | |
The result will have the same enumeration type as the operands. |
The enumeration is the type; the named values are called enumerators. (It is a little bit ambiguous whether 3 is properly an "enumerator" if the defined values are 1 and 2, so I suggest wording so as to sidestep that question.)
docs/changelog.rst
Outdated
* The :cpp:class:`nb::enum_\<T\>() <enum_>` can now create an``enum.Flag``-derived type | ||
with flag :cpp:class:`nb::is_flag_enum() <is_flag_enum>`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* The :cpp:class:`nb::enum_\<T\>() <enum_>` can now create an``enum.Flag``-derived type | |
with flag :cpp:class:`nb::is_flag_enum() <is_flag_enum>`. | |
* nanobind now allows a :cpp:class:`nb::enum_\<T\>() <enum_>` to specify the | |
new class binding annotation :cpp:class:`nb::is_flag_enum() <is_flag_enum>`, | |
which produces an enumeration type derived from `enum.Flag`. Instances of such | |
an enumeration type represent a bitwise combination of the defined enumerators, | |
and they may be combined using bitwise operators ``& | ^ ~``. |
docs/changelog.rst
Outdated
Previously, nanobind relied on a custom enumeration base class that was a | ||
frequent source of friction for users. | ||
A new flag :cpp:class:`nb::is_flag_enum() <is_flag_enum>` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you added the new changelog entry but didn't remove the old one.
docs/classes.rst
Outdated
will support arithmetic and bit-level operations like comparisons, and, or, | ||
xor, negation, etc. | ||
will support arithmetic and bit-level operations (and, or, | ||
xor, negation). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xor, negation). | |
xor, negation). The operands of these operations may be either enumerators | |
(including of two different `is_arithmetic` enumeration types) or integers, and the | |
result will be an integer. | |
src/nb_enum.cpp
Outdated
if (is_flag_enum) | ||
result.attr("__str__") = enum_mod.attr("Flag").attr("__str__"); | ||
#if PY_VERSION_HEX >= 0x030B0000 | ||
result.attr("_boundary_") = enum_mod.attr("FlagBoundary").attr("KEEP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This choice seems not in keeping with nanobind's general defaults of strictness around enums. I think the default STRICT policy is more appropriate. People can use is_arithmetic (producing an IntFlag) if they want more flexibility. For IntFlag, KEEP is the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also my point above. We likely want the full matrix of combinations.
src/nb_enum.cpp
Outdated
@@ -150,6 +154,28 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8 | |||
if (!t) | |||
return false; | |||
|
|||
if ((t->flags & (uint32_t) type_flags::is_flag_enum) !=0) { | |||
auto base = PyObject_GetAttrString((PyObject *)o->ob_type, "__base__"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These Python API calls return new object references which you must drop (Py_DECREF
) when you're done using them. Also, this is way too much effort to spend on every individual enum-value conversion from Python to C++. You should do this only if the map lookup in enum_tbl.rev
fails. If this slower-path conversion succeeds, then you can add to the enum_tbl.rev
map to save time when converting the same flags value in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually wondering if it makes sense to cache these. With flags, it would seem one can generate 2^n cache entries for n bits, which has the potential of being undesirably large. Maybe flags enums are OK to go on a slow path? Or we somehow restrict the size of the cache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT, they are already being cached in members of the enum type. This "singleton-izes" so that one can test for flag combinations with is
.
>>> import enum
>>> class Test(enum.Flag):
... A, B, C, D, E, F, G, H = 1, 2, 4, 8, 16, 32, 64, 128
...
>>> for i in range(256): Test(i)
...
<Test: 0>
<Test.A: 1>
<Test.B: 2>
[etc ...]
>>> len(Test._value2member_map_)
256
The existing cache takes, for each referenced combination, one dict entry (like 32 bytes I think?) plus the footprint of the enum value object itself (sys.getsizeof says 48 bytes, but that doesn't include the 4-entry instance dictionary, name string, etc -- it's probably at least 128 bytes total). We're adding one robin_map entry (24 bytes?) in each direction (C++->Py and Py->C++) for each combination that is passed across the language boundary. So the additional cost of caching is something like 30% above what we're already paying just for using enum.Flag
. That seems reasonable to me.
src/nb_enum.cpp
Outdated
auto basename = PyObject_GetAttrString(base, "__name__"); | ||
Py_ssize_t size; | ||
const char* data = PyUnicode_AsUTF8AndSize(basename, &size); | ||
std::string s(data, size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a sufficient reason to use std::string
; nanobind generally tries to keep a low footprint of standard-library includes, to avoid code bloat. Check the size, then use memcmp()
.
Also, I don't think this logic is correct. You should instead check whether Py_TYPE(o)
-- the Python type of the incoming might-be-an-enumeration-value -- matches t->type_py
-- the Python type of the nanobind enumeration you're trying to convert to. What you have here is both much slower than that, and is foolable:
class Flag:
pass
class Dummy(Flag):
value = 42
some_nanobind_function(Dummy()) # result is treated as flags of 42!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
src/nb_enum.cpp
Outdated
@@ -150,6 +154,28 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8 | |||
if (!t) | |||
return false; | |||
|
|||
if ((t->flags & (uint32_t) type_flags::is_flag_enum) !=0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the code style of the rest of this file. Spaces on both sides of binary operators, space after if
(several instances of that further below), etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
src/nb_enum.cpp
Outdated
enum_map *fwd = (enum_map *) t->enum_tbl.fwd; | ||
|
||
if(t->flags & (uint32_t) type_flags::is_flag_enum) { | ||
PyObject *value = PyLong_FromLongLong(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely wrong and the fact that it wasn't caught by a unit test means you need better test coverage. This will turn any flag-enum value into a plain Python int, i.e., it loses its flag-enum membership. That kind of defeats the point of a flag enum, and is extra confusing because you're not allowed to pass that thing back into a C++ function that expects an instance of the flag-enum type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments
docs/api_core.rst
Outdated
@@ -1926,6 +1926,14 @@ The following annotations can be specified using the variable-length | |||
mixed enum types (such as ``Shape.Circle + Color.Red``) are | |||
permissible. | |||
|
|||
.. cpp:struct:: is_flag_enum |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we give it a shorter name? is_flag
? (for symmetry with the arithmetic flag, which is called is_arithmetic
)
docs/changelog.rst
Outdated
@@ -138,9 +143,11 @@ according to `SemVer <http://semver.org>`__. The following changes are | |||
noteworthy: | |||
|
|||
* The :cpp:class:`nb::enum_\<T\>() <enum_>` binding declaration is now a | |||
wrapper that creates either a ``enum.Enum`` or ``enum.IntEnum``-derived type. | |||
wrapper that creates either a ``enum.Enum``, ``enum.IntEnum`` or ``enum.Flag``-derived type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would make sense to support a matrix of 4 base classes: nb::is_arithmetic
switches between enum.*
and enum.Int*
while nb::is_flag
switches between Enum
and Flag
.
src/nb_enum.cpp
Outdated
@@ -1,5 +1,5 @@ | |||
#include "nb_internals.h" | |||
|
|||
#include <string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid depending on std::string
? I've tried hard not to have anything depend on it in the core library.
src/nb_enum.cpp
Outdated
auto basename = PyObject_GetAttrString(base, "__name__"); | ||
Py_ssize_t size; | ||
const char* data = PyUnicode_AsUTF8AndSize(basename, &size); | ||
std::string s(data, size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
src/nb_enum.cpp
Outdated
@@ -150,6 +154,28 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8 | |||
if (!t) | |||
return false; | |||
|
|||
if ((t->flags & (uint32_t) type_flags::is_flag_enum) !=0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
src/nb_enum.cpp
Outdated
if (is_flag_enum) | ||
result.attr("__str__") = enum_mod.attr("Flag").attr("__str__"); | ||
#if PY_VERSION_HEX >= 0x030B0000 | ||
result.attr("_boundary_") = enum_mod.attr("FlagBoundary").attr("KEEP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
src/nb_enum.cpp
Outdated
if (is_flag_enum) | ||
result.attr("__str__") = enum_mod.attr("Flag").attr("__str__"); | ||
#if PY_VERSION_HEX >= 0x030B0000 | ||
result.attr("_boundary_") = enum_mod.attr("FlagBoundary").attr("KEEP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also my point above. We likely want the full matrix of combinations.
src/nb_enum.cpp
Outdated
@@ -150,6 +154,28 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8 | |||
if (!t) | |||
return false; | |||
|
|||
if ((t->flags & (uint32_t) type_flags::is_flag_enum) !=0) { | |||
auto base = PyObject_GetAttrString((PyObject *)o->ob_type, "__base__"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually wondering if it makes sense to cache these. With flags, it would seem one can generate 2^n cache entries for n bits, which has the potential of being undesirably large. Maybe flags enums are OK to go on a slow path? Or we somehow restrict the size of the cache?
True, for class enums the Enum is the closest match.
Yes, I need support for flags. Supporting the full matrix for is_arithmetic and is_flag seems like the perfect solution. |
docs/api_core.rst
Outdated
with two with two enumerators as operands. | ||
The result will have the same enumeration type as the operands. | ||
So ``Shape(2) | Shape(1)`` is equivalent to ``Shape(3)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with two with two enumerators as operands. | |
The result will have the same enumeration type as the operands. | |
So ``Shape(2) | Shape(1)`` is equivalent to ``Shape(3)` | |
with two enumerators as operands. | |
The result will have the same enumeration type as the operands. | |
So ``Shape(2) | Shape(1)`` is equivalent to ``Shape(3)`. |
docs/classes.rst
Outdated
xor, negation). The operands of these operations may be either enumerators | ||
When the annotation :cpp:class:`nb::flag_enum() <flag_enum>` is passed to | ||
to :cpp:class:`nb::enum_\<T\> <enum_>`, the resulting Python type will be an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xor, negation). The operands of these operations may be either enumerators | |
When the annotation :cpp:class:`nb::flag_enum() <flag_enum>` is passed to | |
to :cpp:class:`nb::enum_\<T\> <enum_>`, the resulting Python type will be an | |
xor, negation). The operands of these operations may be either enumerators | |
When the annotation :cpp:class:`nb::flag_enum() <flag_enum>` is passed to | |
:cpp:class:`nb::enum_\<T\> <enum_>`, the resulting Python type will be an |
Also, judging from the tests, the operands must be both enumerators?
src/nb_enum.cpp
Outdated
@@ -56,7 +56,8 @@ PyObject *enum_create(enum_init_data *ed) noexcept { | |||
|
|||
if (is_arithmetic) | |||
result.attr("__str__") = enum_mod.attr("Enum").attr("__str__"); | |||
|
|||
if (is_flag) | |||
result.attr("__str__") = enum_mod.attr("Flag").attr("__str__"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be IntFlag.__str__
, since we use that factory above (L46)?
…bind 2.0. Incorporates the nanobind_bazel change from google#1795. nanobind 2.0 reworked the nanobind::enum_ class so it uses a real Python enum or intenum rather than its previous hand-rolled implementation. google-deepmind/clrs#119 (comment) As a consequence of that change, nanobind now checks when casting an integer to a enum value that the integer corresponds to a valid enum. Counter::Flags is a bitmask, and many combinations are not valid enum members. This change: a) sets nb::is_arithmetic(), which means Counter::Flags becomes an IntEnum that can be freely cast to an integer. b) defines the | operator for flags to return an integer, not an enum, avoiding the error. c) changes Counter's constructor to accept an int, not a Counter::Flags enum. Since Counter::Flags is an IntEnum now, it can be freely coerced to an int. If wjakob/nanobind#599 is merged into nanobind, then we can perhaps use a flag enum here instead.
…bind 2.0. Incorporates the nanobind_bazel change from google#1795. nanobind 2.0 reworked the nanobind::enum_ class so it uses a real Python enum or intenum rather than its previous hand-rolled implementation. https://nanobind.readthedocs.io/en/latest/changelog.html#version-2-0-0-may-23-2024 As a consequence of that change, nanobind now checks when casting an integer to a enum value that the integer corresponds to a valid enum. Counter::Flags is a bitmask, and many combinations are not valid enum members. This change: a) sets nb::is_arithmetic(), which means Counter::Flags becomes an IntEnum that can be freely cast to an integer. b) defines the | operator for flags to return an integer, not an enum, avoiding the error. c) changes Counter's constructor to accept an int, not a Counter::Flags enum. Since Counter::Flags is an IntEnum now, it can be freely coerced to an int. If wjakob/nanobind#599 is merged into nanobind, then we can perhaps use a flag enum here instead.
…bind 2.0. Incorporates the nanobind_bazel change from google#1795. nanobind 2.0 reworked the nanobind::enum_ class so it uses a real Python enum or intenum rather than its previous hand-rolled implementation. https://nanobind.readthedocs.io/en/latest/changelog.html#version-2-0-0-may-23-2024 As a consequence of that change, nanobind now checks when casting an integer to a enum value that the integer corresponds to a valid enum. Counter::Flags is a bitmask, and many combinations are not valid enum members. This change: a) sets nb::is_arithmetic(), which means Counter::Flags becomes an IntEnum that can be freely cast to an integer. b) defines the | operator for flags to return an integer, not an enum, avoiding the error. c) changes Counter's constructor to accept an int, not a Counter::Flags enum. Since Counter::Flags is an IntEnum now, it can be freely coerced to an int. If wjakob/nanobind#599 is merged into nanobind, then we can perhaps use a flag enum here instead.
…bind 2.0. Incorporates the nanobind_bazel change from google#1795. nanobind 2.0 reworked the nanobind::enum_ class so it uses a real Python enum or intenum rather than its previous hand-rolled implementation. https://nanobind.readthedocs.io/en/latest/changelog.html#version-2-0-0-may-23-2024 As a consequence of that change, nanobind now checks when casting an integer to a enum value that the integer corresponds to a valid enum. Counter::Flags is a bitmask, and many combinations are not valid enum members. This change: a) sets nb::is_arithmetic(), which means Counter::Flags becomes an IntEnum that can be freely cast to an integer. b) defines the | operator for flags to return an integer, not an enum, avoiding the error. c) changes Counter's constructor to accept an int, not a Counter::Flags enum. Since Counter::Flags is an IntEnum now, it can be freely coerced to an int. If wjakob/nanobind#599 is merged into nanobind, then we can perhaps use a flag enum here instead.
…bind 2.0. (#1817) Incorporates the nanobind_bazel change from #1795. nanobind 2.0 reworked the nanobind::enum_ class so it uses a real Python enum or intenum rather than its previous hand-rolled implementation. https://nanobind.readthedocs.io/en/latest/changelog.html#version-2-0-0-may-23-2024 As a consequence of that change, nanobind now checks when casting an integer to a enum value that the integer corresponds to a valid enum. Counter::Flags is a bitmask, and many combinations are not valid enum members. This change: a) sets nb::is_arithmetic(), which means Counter::Flags becomes an IntEnum that can be freely cast to an integer. b) defines the | operator for flags to return an integer, not an enum, avoiding the error. c) changes Counter's constructor to accept an int, not a Counter::Flags enum. Since Counter::Flags is an IntEnum now, it can be freely coerced to an int. If wjakob/nanobind#599 is merged into nanobind, then we can perhaps use a flag enum here instead.
What's the status of this PR? Is it ready to be merged? I see that there are still some failing tests related to stubs. |
@@ -43,7 +43,7 @@ PyObject *enum_create(enum_init_data *ed) noexcept { | |||
PyUnicode_FromFormat("%U.%U", scope_qualname.ptr(), name.ptr())); | |||
} | |||
|
|||
const char *factory_name = is_arithmetic ? "IntEnum" : "Enum"; | |||
const char *factory_name = (is_arithmetic || is_flag) ? (is_flag ? "IntFlag" : "IntEnum") : "Enum"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not responsive to the previous discussion of which enum metaclass to use. is_flag
without is_arithmetic
should be Flag
.
@wjakob I had not yet done another round of review because it seemed like there were review comments from the previous round that were unresolved - so I figured the author was still working on it. I flagged one thing I noticed. |
I was stuck on the python 3.10 failure. Will get back to this ticket. |
It seems to me that this is a feature that a number of people are waiting for. @mwittgen please let us know if you'd like someone else to continue developing the PR so that it can be merged. |
Yes, please. |
I have some time in the upcoming days, and would like to advance this further. |
That sounds good. Could you create a new PR in this case @nicholasjng? |
Closing this PR, development continues in PR #688. |
This PR extends nanobind with the capability to create flag enumerations (`enum.Flag`, `enum.IntFlag`) that support bit-wise arithmetic. Co-authored-by: Matthias Wittgen <Matthias.Michael.Wittgen@cern.ch> Co-authored-by: Nicholas Junge <nicholas.junge@web.de>
With the major overhaul of enums in nanobind 2.0, the
Flag
enum type could be easily supported.Some existing pybind11 applications rely on enums supporting bitwise operations with the result being an
enum
and not anint
.