diff --git a/CHANGES.current b/CHANGES.current index f9177f1a612..d3c025363d6 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -7,6 +7,27 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/ Version 4.0.0 (in progress) =========================== +2017-06-23: m7thon + [Python] fix and improve default argument handling: + + 1. Fix negative octals. Currently not handled correctly by `-py3` + (unusual case, but incorrect). + 2. Fix arguments of type "octal + something" (e.g. `0640 | 04`). + Currently drops everything after the first octal. Nasty! + 3. Fix bool arguments "0 + something" (e.g. `0 | 1`) are always + "False" (unusual case, but incorrect). + 4. Remove special handling of "TRUE" and "FALSE" from + `convertValue` since there's no reason these have to match + "true" and "false". + 5. Remove the Python 2 vs. Python 3 distinction based on the + `-py3` flag. Now the same python code is produced for default + arguments for Python 2 and Python 3. For this, octal default + arguments, e.g. 0644, are now wrapped as `int('644', 8)`. This + is required, as Python 2 and Python 3 have incompatible syntax + for octal literals. + + Fixes #707 + 2017-06-19: wsfulton [Python] Fix handling of rich comparisons when wrapping overloaded operators: diff --git a/Examples/test-suite/default_args.i b/Examples/test-suite/default_args.i index 8828bfe0400..28e64d40e98 100644 --- a/Examples/test-suite/default_args.i +++ b/Examples/test-suite/default_args.i @@ -18,14 +18,20 @@ #include // All kinds of numbers: hex, octal (which pose special problems to Python), negative... - void trickyvalue1(int first, int pos = -1) {} - void trickyvalue2(int first, unsigned rgb = 0xabcdef) {} - void trickyvalue3(int first, int mode = 0644) {} + + class TrickyInPython { + public: + int value_m1(int first, int pos = -1) { return pos; } + unsigned value_0xabcdef(int first, unsigned rgb = 0xabcdef) { return rgb; } + int value_0644(int first, int mode = 0644) { return mode; } + int value_perm(int first, int mode = 0640 | 0004) { return mode; } + int value_m01(int first, int val = -01) { return val; } + bool booltest2(bool x = 0 | 1) { return x; } + }; void doublevalue1(int first, double num = 0.0e-1) {} void doublevalue2(int first, double num = -0.0E2) {} - // Long long arguments are not handled at Python level currently but still work. void seek(long long offset = 0LL) {} void seek2(unsigned long long offset = 0ULL) {} void seek3(long offset = 0L) {} diff --git a/Examples/test-suite/python/default_args_runme.py b/Examples/test-suite/python/default_args_runme.py index b7c4cb508ab..ddaf2cd4f20 100644 --- a/Examples/test-suite/python/default_args_runme.py +++ b/Examples/test-suite/python/default_args_runme.py @@ -115,15 +115,39 @@ def run(module_name): if 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) + tricky_failure = False + tricky = default_args.TrickyInPython() + if tricky.value_m1(10) != -1: + print "trickyvalue_m1 failed" + tricky_failure = True + if tricky.value_m1(10, 10) != 10: + print "trickyvalue_m1 failed" + tricky_failure = True + if tricky.value_0xabcdef(10) != 0xabcdef: + print "trickyvalue_0xabcdef failed" + tricky_failure = True + if tricky.value_0644(10) != 420: + print "trickyvalue_0644 failed" + tricky_failure = True + if tricky.value_perm(10) != 420: + print "trickyvalue_perm failed" + tricky_failure = True + if tricky.value_m01(10) != -1: + print "trickyvalue_m01 failed" + tricky_failure = True + if not tricky.booltest2(): + print "booltest2 failed" + tricky_failure = True + + if tricky_failure: + raise RuntimeError + default_args.seek() default_args.seek(10) + if not default_args.booltest(): + raise RuntimeError("booltest failed") + if default_args.slightly_off_square(10) != 102: raise RuntimeError diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index 802c36cc7d1..a55c8ab09e5 100644 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -2030,6 +2030,66 @@ class PYTHON:public Language { return doc; } + /* ------------------------------------------------------------ + * convertIntegerValue() + * + * Check if string v is an integer and can be represented in + * Python. If so, return an appropriate Python representation, + * otherwise (or if we are unsure), return NIL. + * ------------------------------------------------------------ */ + String *convertIntegerValue(String *v, SwigType *resolved_type) { + const char *const s = Char(v); + char *end; + String *result = NIL; + + // Check if this is an integer number in any base. + long value = strtol(s, &end, 0); + if (errno == ERANGE || end == s) + return NIL; + if (*end != '\0') { + // If there is a suffix after the number, we can safely ignore any + // combination of "l" and "u", but not anything else. + for (char *p = end; *p != '\0'; ++p) { + switch (*p) { + case 'l': + case 'L': + case 'u': + case 'U': + break; + default: + return NIL; + } + } + } + // So now we are certain that we are indeed dealing with an integer + // that has a representation as long given by value. + + if (Cmp(resolved_type, "bool") == 0) + // Allow integers as the default value for a bool parameter. + return NewString(value ? "True" : "False"); + + if (value == 0) + return NewString(SwigType_ispointer(resolved_type) ? "None" : "0"); + + // v may still be octal or hexadecimal: + const char *p = s; + if (*p == '+' || *p == '-') + ++p; + if (*p == '0' && *(p+1) != 'x' && *(p+1) != 'X') { + // This must have been an octal number. This is the only case we + // cannot use in Python directly, since Python 2 and 3 use non- + // compatible representations. + result = NewString(*s == '-' ? "int('-" : "int('"); + String *octal_string = NewStringWithSize(p, (int) (end - p)); + Append(result, octal_string); + Append(result, "', 8)"); + Delete(octal_string); + return result; + } + result = *end == '\0' ? v : NewStringWithSize(s, (int) (end - s)); + return result; + } + /* ------------------------------------------------------------ * convertDoubleValue() * @@ -2078,111 +2138,24 @@ class PYTHON:public Language { * convertValue() * * Check if string v can be a Python value literal or a - * constant. Return NIL if it isn't. + * constant. Return an equivalent Python representation, + * or NIL if it isn't, or we are unsure. * ------------------------------------------------------------ */ String *convertValue(String *v, SwigType *type) { const char *const s = Char(v); - char *end; String *result = NIL; - bool fail = false; - SwigType *resolved_type = 0; - - // Check if this is a number in any base. - long value = strtol(s, &end, 0); - (void) value; - if (end != s) { - if (errno == ERANGE) { - // There was an overflow, we could try representing the value as Python - // long integer literal, but for now don't bother with it. - fail = true; - } else { - if (*end != '\0') { - // If there is a suffix after the number, we can safely ignore any - // combination of "l" and "u", but not anything else (again, stuff like - // "LL" could be handled, but we don't bother to do it currently). - bool seen_long = false; - for (char * p = end; *p != '\0'; ++p) { - switch (*p) { - case 'l': - case 'L': - // Bail out on "LL". - if (seen_long) { - fail = true; - break; - } - seen_long = true; - break; - - case 'u': - case 'U': - if (value < 0) - fail = true; - break; - - default: - // Except that our suffix could actually be the fractional part of - // a floating point number, so we still have to check for this. - result = convertDoubleValue(v); - } - } - } - - if (!fail) { - // Allow integers as the default value for a bool parameter. - resolved_type = SwigType_typedef_resolve_all(type); - if (Cmp(resolved_type, "bool") == 0) { - result = NewString(value ? "True" : "False"); - } else { - // Deal with the values starting with 0 first as they can be octal or - // hexadecimal numbers or even pointers. - if (s[0] == '0') { - if (Len(v) == 1) { - // This is just a lone 0, but it needs to be represented differently - // in Python depending on whether it's a zero or a null pointer. - if (SwigType_ispointer(resolved_type)) - result = NewString("None"); - else - result = v; - } else if (s[1] == 'x' || s[1] == 'X') { - // This must have been a hex number, we can use it directly in Python, - // so nothing to do here. - } else { - // This must have been an octal number, we have to change its prefix - // to be "0o" in Python 3 only (and as long as we still support Python - // 2.5, this can't be done unconditionally). - if (py3) { - if (end - s > 1) { - result = NewString("0o"); - Append(result, NewStringWithSize(s + 1, (int)(end - s - 1))); - } - } - } - } + SwigType *resolved_type = SwigType_typedef_resolve_all(type); - // Avoid unnecessary string allocation in the common case when we don't - // need to remove any suffix. - if (!result) - result = *end == '\0' ? v : NewStringWithSize(s, (int)(end - s)); - } - } - } - } - - // Check if this is a floating point number (notice that it wasn't - // necessarily parsed as a long above, consider e.g. ".123"). - if (!fail && !result) { + result = convertIntegerValue(v, resolved_type); + if (!result) { result = convertDoubleValue(v); if (!result) { - if (Strcmp(v, "true") == 0 || Strcmp(v, "TRUE") == 0) + if (Strcmp(v, "true") == 0) result = NewString("True"); - else if (Strcmp(v, "false") == 0 || Strcmp(v, "FALSE") == 0) + else if (Strcmp(v, "false") == 0) result = NewString("False"); - else if (Strcmp(v, "NULL") == 0 || Strcmp(v, "nullptr") == 0) { - if (!resolved_type) - resolved_type = SwigType_typedef_resolve_all(type); + else if (Strcmp(v, "NULL") == 0 || Strcmp(v, "nullptr") == 0) result = SwigType_ispointer(resolved_type) ? NewString("None") : NewString("0"); - } - // This could also be an enum type, default value of which could be // representable in Python if it doesn't include any scope (which could, // but currently is not, translated).