Search code examples
pythonc++swig

SWIG - OverflowError when methods have enum typed arguments


I have a C++ code base which uses SWIG to generate Python 3 interfaces.

I had a problem where I couldn't convert enum values into large enough types. This was solved with good help.

Now I have a new problem that relates to the other one. Methods that take the corrected enum values as arguments are throwing an OverflowError when the largest enum values are passed in as an argument.

I was wondering if there is a generic way to solve this using the typemap function (like the way with the enum objects). To identify all methods that take the enum constants as arguments. Or do I need to define an in typemap for each of the enum types?

What I have tried, and works, is including this in the .i file:

%include "typemaps.i"
%apply unsigned long long { doom::Bar::FooPresence };

But it would be great if there was a "catch all" kind of thing, like with the constcode typemap.

The code necessary to reproduce the current behavior:

bar.h


namespace doom
{
class Bar
{
public:
    struct FooIdent
    {
        enum Ident
        {
            UnknownFoo = 0,
            KnownFoo = 1,
            MainFoo = 2,
            SecondaryFoo = 3
        };
    };

    enum FooPresence
    {
        Boo = 0x0,
        Foo1 = 0x8000000000ULL,
        Foo2 = 0x4000000000ULL,
        Foo3 = 0x2000000000ULL,
        FooWithA1 = 0x1000000000ULL,
        FooWithA2 = 0x0800000000ULL,
        FooWithA3 = 0x0400000000ULL,
        FooWithA4 = 0x0200000000ULL,
        FooWithB1 = 0x0100000000ULL,
        FooWithB2 = 0x0080000000,
        FooWithB3 = 0x0040000000
    };

    Bar();

    void setVesODee( int ves, doom::Bar::FooPresence pr );
    void setVesOGoo( int goo, doom::Bar::FooIdent::Ident ide );
    int doSomething();

private:
    int m_vdee;
    int m_vgoo;
};
} // namespace doom

bar.cpp

#include "bar.h"
#include <iostream>

namespace doom
{

Bar::Bar()
{
    m_vdee = 0;
    m_vgoo = 0;
}

void Bar::setVesODee( int ves, doom::Bar::FooPresence pr ) {
    m_vdee = static_cast< doom::Bar::FooPresence >( ves & pr );
}

void Bar::setVesOGoo( int goo, doom::Bar::FooIdent::Ident ide ) {
    m_vgoo = static_cast< doom::Bar::FooIdent::Ident >( goo & ide );
}

int Bar::doSomething() {
    return m_vgoo + m_vdee;
}

} // namespace doom


int main() {
    doom::Bar b = doom::Bar();

    b.setVesODee(3, doom::Bar::FooWithB2);
    b.setVesOGoo(4, doom::Bar::FooIdent::MainFoo);

    int c = b.doSomething();
    std::cout << c << std::endl;

    return 0;
}

bar.i

%module bar

%feature ("flatnested");

%{
#define SWIG

#include "bar.h"

#define SWIG_PYTHON_STRICT_BYTE_CHAR
%}


%typemap(constcode) int %{
    SWIG_Python_SetConstant(d, "$symname", PyLong_FromLongLong(static_cast<long long>($1)));
%}

%rename("Bar_%s", %$isnested) "";
%include "bar.h"

test_bar.py

import bar

b = bar.Bar()
b.setVesODee(10, bar.Bar.Foo1)

build-and-test.sh

#!/bin/bash
set -e

echo "Cleanup..."
rm -rf __pycache__
rm -f _bar.so bar.o bar_wrap.*

echo "Building..."
swig -python -c++ -py3 -debug-tmsearch bar.i
g++ -fPIC -c $(pkg-config --cflags --libs python3) bar.cpp bar_wrap.cxx
g++ -shared -o _bar.so bar.o bar_wrap.o

echo "Testing..."
python3 test_bar.py

output

Traceback (most recent call last):
  File "test_bar.py", line 15, in <module>
    b.setVesODee(10, bar.Bar.Foo1)
  File "/workspace/ctmp/bar.py", line 83, in setVesODee
    return _bar.Bar_setVesODee(self, ves, pr)
OverflowError: in method 'Bar_setVesODee', argument 3 of type 'doom::Bar::FooPresence'

Solution

  • SWIGTYPE is the default type match when a type-specific match is not found. You can use the following to apply to all enumerations:

    %apply unsigned long long { enum SWIGTYPE };
    

    See 13.3.3 Default typemap matching rules in the SWIG documentation.

    Example:

    test.i

    %module test
    
    %typemap(constcode) int %{SWIG_Python_SetConstant(d, "$1",PyLong_FromLongLong(static_cast<long long>($1)));%}
    %apply unsigned long long { enum SWIGTYPE };
    
    %{
    #include <iostream>
    %}
    
    %inline %{
    enum FooPresence : unsigned long long
    {
        Foo1 = 0x8000000000ULL,
    };
    
    void func(FooPresence x) {
        std::cout << std::hex << static_cast<unsigned long long>(x) << std::dec << std::endl;
    }
    
    %}
    

    Demo:

    >>> import test
    >>> test.func(test.Foo1)
    8000000000