Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
autowrap 0.24.0 (unreleased)

- Support for enums with the same name in different C++ namespaces using
scoped enum declarations with `wrap-as` annotation for renaming
- Support for arbitrary key types in `operator[]` (getitem/setitem), not
just integer types like `size_t`

autowrap 0.23.0

Support for Cython 3.1! This means the removal of some py2 compatibility code, no more python distinction between long and int, some fixes to multiline comment processing.
39 changes: 38 additions & 1 deletion tests/test_code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@


def test_enums():
"""
Test wrapping of C++ enums, including enums with the same name in different namespaces.

This test demonstrates how autowrap handles:
1. Scoped enums (enum class) from C++ mapped to Python Enum classes
2. Enums with the same name in different namespaces (Foo::MyEnum vs Foo2::MyEnum)
3. Type-safe enum validation (passing wrong enum type raises AssertionError)
4. Enum documentation via wrap-doc annotation

Pattern for wrapping namespaced enums in .pxd files:
cpdef enum class Foo_MyEnum "Foo::MyEnum":
# wrap-attach:
# Foo
# wrap-as:
# MyEnum
A
B
C

This creates Foo.MyEnum in Python that maps to Foo::MyEnum in C++.

See tests/test_files/enums.pxd for the full example.
"""
if int(cython_version[0]) < 3:
return
target = os.path.join(test_files, "enums.pyx")
Expand All @@ -66,12 +89,26 @@ def test_enums():
include_dirs,
)

# Test 1: Enums with same name in different namespaces are both accessible
# Foo.MyEnum and Foo2.MyEnum are separate enum types
assert mod.Foo.MyEnum
assert mod.Foo.MyEnum.B
assert mod.Foo2.MyEnum
assert mod.Foo2.MyEnum.D

# Test 2: Enum documentation is preserved via wrap-doc
foo = mod.Foo()
my_enum = mod.Foo.MyEnum
assert "Testing Enum documentation." in my_enum.__doc__

# Test 3: Correct enum type is accepted
myenum_a = mod.Foo.MyEnum.A
myenum2_a = mod.Foo.MyEnum2.A
assert foo.enumToInt(myenum_a) == 1

# Test 4: Wrong enum type raises AssertionError (type-safe validation)
# Even though MyEnum2.A has the same numeric value as MyEnum.A,
# it's a different enum type and should be rejected
myenum2_a = mod.Foo.MyEnum2.A
with pytest.raises(AssertionError):
foo.enumToInt(myenum2_a)

Expand Down
41 changes: 38 additions & 3 deletions tests/test_files/enums.hpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
/**
* =============================================================================
* Example: C++ Enums in Different Namespaces
* =============================================================================
*
* This file demonstrates C++ code with enums that have the same name but exist
* in different scopes (class Foo and namespace Foo2). See enums.pxd for how to
* wrap these with autowrap to avoid naming conflicts in Python.
*
* C++ Structure:
* - Foo::MyEnum (scoped enum inside class Foo)
* - Foo::MyEnum2 (second enum inside class Foo)
* - Foo2::MyEnum (scoped enum inside namespace Foo2 - same name as Foo::MyEnum)
*
* =============================================================================
*/

// Class with nested scoped enums
class Foo
{
public:
// First enum - will be accessible as Foo.MyEnum in Python
enum class MyEnum
{
A,B,C
A, B, C
};

// Second enum in same class - will be accessible as Foo.MyEnum2 in Python
enum class MyEnum2
{
A,B,C
A, B, C
};

// Method that accepts MyEnum - demonstrates type-safe enum usage
// In Python, passing Foo2.MyEnum.A will raise AssertionError (wrong type)
int enumToInt(MyEnum e)
{
switch(e)
Expand All @@ -19,5 +41,18 @@ class Foo
case MyEnum::B : return 2;
case MyEnum::C : return 3;
}
return 0; // unreachable, but silences compiler warning
};
};

// Separate namespace with an enum of the same name as Foo::MyEnum
// This demonstrates the namespace collision problem that autowrap solves
namespace Foo2
{
// This enum has the same name "MyEnum" as Foo::MyEnum
// In Python, it will be accessible as Foo2.MyEnum (no conflict)
enum class MyEnum
{
A, C, D
};
};
};
87 changes: 83 additions & 4 deletions tests/test_files/enums.pxd
Original file line number Diff line number Diff line change
@@ -1,25 +1,104 @@
# cython: language_level=2
#
# =============================================================================
# Example: Wrapping C++ Enums in Different Namespaces
# =============================================================================
#
# This file demonstrates how to wrap C++ enums that have the same name but
# exist in different namespaces (e.g., Foo::MyEnum and Foo2::MyEnum).
#
# THE PROBLEM:
# When two C++ classes/namespaces define enums with the same name, Cython
# cannot distinguish them at the module level, causing naming conflicts.
#
# THE SOLUTION:
# Use a unique Cython identifier with a C++ name string to map to the
# actual C++ enum, then use wrap-as to expose it with the desired Python name.
#
# PATTERN:
# cpdef enum class <UniqueID> "<C++::FullName>":
# # wrap-attach:
# # <ClassName>
# # wrap-as:
# # <PythonName>
#
# EXAMPLE:
# For Foo::MyEnum and Foo2::MyEnum, declare them as:
# - Foo_MyEnum "Foo::MyEnum" -> exposed as Foo.MyEnum in Python
# - Foo2_MyEnum "Foo2::MyEnum" -> exposed as Foo2.MyEnum in Python
#
# IMPORTANT:
# - Use `cpdef enum class` (not `cpdef enum`) for scoped enums
# - Scoped enums generate Python Enum classes with proper type checking
# - The wrap-attach directive attaches the enum to the specified class
# - The wrap-as directive controls the Python-visible name
#
# RESULT IN PYTHON:
# foo = Foo()
# foo.enumToInt(Foo.MyEnum.A) # Works - correct enum type
# foo.enumToInt(Foo2.MyEnum.A) # Raises AssertionError - wrong enum type
#
# =============================================================================

cdef extern from "enums.hpp":
cdef cppclass Foo:
# Method accepts Foo_MyEnum (which maps to Foo::MyEnum in C++)
int enumToInt(Foo_MyEnum e)

cdef extern from "enums.hpp":
cdef cppclass Foo2:
# Foo2 has no methods, but we still wrap it to attach its enum
pass

int enumToInt(MyEnum e)

# -----------------------------------------------------------------------------
# Enums in the "Foo" namespace
# -----------------------------------------------------------------------------
cdef extern from "enums.hpp" namespace "Foo":

cpdef enum class MyEnum "Foo::MyEnum":
# Foo::MyEnum - attached to class Foo, exposed as Foo.MyEnum
cpdef enum class Foo_MyEnum "Foo::MyEnum":
# wrap-attach:
# Foo
#
# wrap-as:
# MyEnum
#
# wrap-doc:
# Testing Enum documentation.
A
B
C

cpdef enum class MyEnum2 "Foo::MyEnum2":
# Foo::MyEnum2 - a second enum in the same class
cpdef enum class Foo_MyEnum2 "Foo::MyEnum2":
# wrap-attach:
# Foo
#
# wrap-as:
# MyEnum2
A
B
C
C


# -----------------------------------------------------------------------------
# Enums in the "Foo2" namespace
# -----------------------------------------------------------------------------
cdef extern from "enums.hpp" namespace "Foo2":

# Foo2::MyEnum - same name as Foo::MyEnum but in different namespace
# Attached to Foo2 class, exposed as Foo2.MyEnum (no conflict with Foo.MyEnum)
cpdef enum class Foo2_MyEnum "Foo2::MyEnum":
# wrap-attach:
# Foo2
#
# wrap-as:
# MyEnum
#
# wrap-doc:
# This is a second enum in another namespace.
A
C
D