Fix Python default argument handing broken since swig-3.0.3

Default values are no longer generated as Python code by default.
They must be explicitly turned on using the "python:defaultargs" feature.

Closes #294
Closes #296

The problems in these two issues when "python:defaultargs" is turned
on still need to be fixed and should be addressed in separate patches.
The important thing is the default code generation is now fixed.
This commit is contained in:
William S Fulton 2015-01-08 23:33:47 +00:00
commit 38ba81811e
8 changed files with 139 additions and 69 deletions

View file

@ -5,6 +5,16 @@ See the RELEASENOTES file for a summary of changes in each release.
Version 3.0.4 (in progress) Version 3.0.4 (in progress)
=========================== ===========================
2015-01-08: wsfulton
[Python] Fix #294 #296 - Regression introduced in SWIG-3.0.3 when
wrapping functions with default arguments. Now any method with default
arguments obtains the default arguments from C++ instead of generating
Python code with the default arguments.
The "python:defaultargs" feature has been introduced for users to
optionally generate Python methods with the default arguments instead
the default *args.
2015-01-08: olly 2015-01-08: olly
Allow C++11 "explicit constexpr". Fixes github issue#284 reported Allow C++11 "explicit constexpr". Fixes github issue#284 reported
by Paweł Tomulik. Also handle "constexpr explicit" and "constexpr by Paweł Tomulik. Also handle "constexpr explicit" and "constexpr

View file

@ -14,7 +14,9 @@
#include <string> #include <string>
// All kinds of numbers: hex, octal (which pose special problems to Python), negative... // All kinds of numbers: hex, octal (which pose special problems to Python), negative...
void lots_of_args(int pos = -1, unsigned rgb = 0xabcdef, int mode = 0644) { } void trickyvalue1(int first, int pos = -1) {}
void trickyvalue2(int first, unsigned rgb = 0xabcdef) {}
void trickyvalue3(int first, int mode = 0644) {}
// Long long arguments are not handled at Python level currently but still work. // Long long arguments are not handled at Python level currently but still work.
void seek(long long offset = 0LL) {} void seek(long long offset = 0LL) {}

View file

@ -58,6 +58,7 @@ CPP_TEST_CASES += \
primitive_types \ primitive_types \
python_abstractbase \ python_abstractbase \
python_append \ python_append \
python_default_args \
python_director \ python_director \
python_nondynamic \ python_nondynamic \
python_overload_simple_cast \ python_overload_simple_cast \

View file

@ -1,67 +1,91 @@
import default_args # Note that this test is also used by python_default_args_runme.py hence the use of __main__ and the run function
ec = default_args.EnumClass() def run(module_name):
if not ec.blah(): default_args = __import__(module_name)
raise RuntimeError,"EnumClass::blah() default arguments don't work" print "running...."
ec = default_args.EnumClass()
if not ec.blah():
raise RuntimeError,"EnumClass::blah() default arguments don't work"
if default_args.Statics_staticMethod() != 60: de = default_args.DerivedEnumClass()
raise RuntimeError de.accelerate()
de.accelerate(default_args.EnumClass.SLOW)
if default_args.cfunc1(1) != 2:
raise RuntimeError
if default_args.cfunc2(1) != 3: if default_args.Statics_staticMethod() != 60:
raise RuntimeError raise RuntimeError
if default_args.cfunc3(1) != 4: if default_args.cfunc1(1) != 2:
raise RuntimeError raise RuntimeError
if default_args.cfunc2(1) != 3:
raise RuntimeError
if default_args.cfunc3(1) != 4:
raise RuntimeError
f = default_args.Foo() f = default_args.Foo()
f.newname() f.newname()
f.newname(1) f.newname(1)
try: try:
f = default_args.Foo(1) f = default_args.Foo(1)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::Foo ignore is not working" if error: raise RuntimeError,"Foo::Foo ignore is not working"
try: try:
f = default_args.Foo(1,2) f = default_args.Foo(1,2)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::Foo ignore is not working" if error: raise RuntimeError,"Foo::Foo ignore is not working"
try: try:
f = default_args.Foo(1,2,3) f = default_args.Foo(1,2,3)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::Foo ignore is not working" if error: raise RuntimeError,"Foo::Foo ignore is not working"
try: try:
m = f.meth(1) m = f.meth(1)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::meth ignore is not working" if error: raise RuntimeError,"Foo::meth ignore is not working"
try: try:
m = f.meth(1,2) m = f.meth(1,2)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::meth ignore is not working" if error: raise RuntimeError,"Foo::meth ignore is not working"
try: try:
m = f.meth(1,2,3) m = f.meth(1,2,3)
error = 1 error = 1
except: except:
error = 0 error = 0
if error: raise RuntimeError,"Foo::meth ignore is not working" if error: raise RuntimeError,"Foo::meth ignore is not working"
if default_args.Klass.inc(100, default_args.Klass(22)).val != 122:
raise RuntimeError, "Klass::inc failed"
if default_args.Klass.inc(100).val != 99:
raise RuntimeError, "Klass::inc failed"
if default_args.Klass.inc().val != 0:
raise RuntimeError, "Klass::inc failed"
default_args.trickyvalue1(10); default_args.trickyvalue1(10, 10)
default_args.trickyvalue2(10); default_args.trickyvalue2(10, 10)
default_args.trickyvalue3(10); default_args.trickyvalue3(10, 10)
default_args.seek(); default_args.seek(10)
if __name__=="__main__":
run('default_args')

View file

@ -0,0 +1,3 @@
# Test %feature("python:defaultargs") using the test code in default_args_runme.py (which does not use the feature)
import default_args_runme
default_args_runme.run('python_default_args')

View file

@ -0,0 +1,10 @@
%module python_default_args
%pythondefaultargs;
// Turn off the feature for the tricky cases that can't be handled
%nopythondefaultargs seek;
%nopythondefaultargs Space::Klass::inc;
%nopythondefaultargs DerivedEnumClass::accelerate;
%include "default_args.i"

View file

@ -185,6 +185,16 @@ These methods "may be called" if needed.
#define %clearpythonappend %feature("pythonappend","") #define %clearpythonappend %feature("pythonappend","")
/* ------------------------------------------------------------------------- */
/*
Python default argument handling (for non-builtin)
*/
#define %pythondefaultargs %feature("python:defaultargs")
#define %nopythondefaultargs %feature("python:defaultargs", "0")
#define %clearpythondefaultargs %feature("python:defaultargs", "")
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* /*

View file

@ -1856,27 +1856,37 @@ public:
* at C++ code level where they can always be handled. * at C++ code level where they can always be handled.
* ------------------------------------------------------------ */ * ------------------------------------------------------------ */
bool is_representable_as_pyargs(Node *n) { bool is_representable_as_pyargs(Node *n) {
ParmList *plist = CopyParmList(Getattr(n, "parms")); bool is_representable = true;
Parm *p;
Parm *pnext;
for (p = plist; p; p = pnext) { if (Getattr(n, "sym:overloaded")) {
String *tm = Getattr(p, "tmap:in"); if (GetFlag(n, "feature:python:defaultargs")) {
if (tm) { ParmList *plist = CopyParmList(Getattr(n, "parms"));
pnext = Getattr(p, "tmap:in:next"); Parm *p;
if (checkAttribute(p, "tmap:in:numinputs", "0")) { Parm *pnext;
continue;
for (p = plist; p; p = pnext) {
String *tm = Getattr(p, "tmap:in");
if (tm) {
pnext = Getattr(p, "tmap:in:next");
if (checkAttribute(p, "tmap:in:numinputs", "0")) {
continue;
}
} else {
pnext = nextSibling(p);
}
if (String *value = Getattr(p, "value")) {
String *type = Getattr(p, "type");
if (!convertValue(value, type)) {
is_representable = false;
break;
}
}
} }
} else { } else {
pnext = nextSibling(p); is_representable = false;
}
if (String *value = Getattr(p, "value")) {
String *type = Getattr(p, "type");
if (!convertValue(value, type))
return false;
} }
} }
return true; return is_representable;
} }