diff --git a/.travis.yml b/.travis.yml index 2062a35da..ab76d3b27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ dist: xenial sudo: false language: python python: + - 3.9 - 3.8 - 3.7 - 3.6 diff --git a/AUTHORS.md b/AUTHORS.md index 5dbf1e1ce..85812a47d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -73,4 +73,5 @@ - ([@rmadsen-ks](https://github.com/rmadsen-ks)) - ([@stonebig](https://github.com/stonebig)) - ([@testrunner123](https://github.com/testrunner123)) +- ([@DanBarzilian](https://github.com/DanBarzilian)) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4016450..e884341bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,25 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## [Unreleased][] -### Added +## [2.5.2](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.2) - 2021-02-05 -### Changed +Bugfix release. ### Fixed +- Fix `object[]` parameters taking precedence when should not in overload resolution +- Empty parameter names (as can be generated from F#) do not cause crashes + +## [2.5.1](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.1) - 2020-06-18 + +Bugfix release. + +### Fixed + +- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash +- Fix incorrect dereference in params array handling -## [2.5.0][] - 2020-06-14 +## [2.5.0](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.0) - 2020-06-14 This version improves performance on benchmarks significantly compared to 2.3. @@ -65,7 +75,7 @@ This version improves performance on benchmarks significantly compared to 2.3. - Fixed issue with params methods that are not passed an array. - Use UTF8 to encode strings passed to `PyRun_String` on Python 3 -## [2.4.0][] - 2019-05-15 +## [2.4.0](https://github.com/pythonnet/pythonnet/releases/tag/v2.4.0) - 2019-05-15 ### Added diff --git a/appveyor.yml b/appveyor.yml index b58b72372..6268798af 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,8 +2,8 @@ version: '{branch}-{build}' build: off image: - - Visual Studio 2017 - + - Visual Studio 2019 + platform: - x86 - x64 @@ -15,6 +15,8 @@ environment: CODECOV_ENV: PYTHON_VERSION, PLATFORM matrix: + - PYTHON_VERSION: 3.9 + BUILD_OPTS: --xplat - PYTHON_VERSION: 3.8 BUILD_OPTS: --xplat - PYTHON_VERSION: 3.7 @@ -23,14 +25,14 @@ environment: BUILD_OPTS: --xplat - PYTHON_VERSION: 3.5 BUILD_OPTS: --xplat - - PYTHON_VERSION: 2.7 + - PYTHON_VERSION: 2.7 BUILD_OPTS: --xplat + - PYTHON_VERSION: 3.9 - PYTHON_VERSION: 3.8 - PYTHON_VERSION: 3.7 - PYTHON_VERSION: 3.6 - PYTHON_VERSION: 3.5 - PYTHON_VERSION: 2.7 - init: # Update Environment Variables based on matrix/platform - set PY_VER=%PYTHON_VERSION:.=% diff --git a/requirements.txt b/requirements.txt index 29c2e4566..78570cb95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Requirements for both Travis and AppVeyor -pytest==3.2.5 +pytest coverage psutil @@ -7,6 +7,5 @@ psutil codecov # Platform specific requirements -# pip; sys_platform == 'win32' -wheel; sys_platform == 'win32' -pycparser; sys_platform != 'win32' +wheel +pycparser diff --git a/setup.py b/setup.py index 216620211..9dd4ec990 100644 --- a/setup.py +++ b/setup.py @@ -13,10 +13,10 @@ import subprocess import sys import sysconfig -from distutils import spawn -from distutils.command import install, build, build_ext, install_data, install_lib from setuptools import Extension, setup +from distutils import spawn +from distutils.command import install, build, build_ext, install_data, install_lib try: from wheel import bdist_wheel @@ -629,7 +629,7 @@ def run(self): setup( name="pythonnet", - version="2.5.0", + version="2.5.2", description=".Net and Mono integration for Python", url="https://pythonnet.github.io/", license="MIT", @@ -652,6 +652,7 @@ def run(self): "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index dd057b144..2b96e0daf 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -25,4 +25,4 @@ // Version Information. Keeping it simple. May need to revisit for Nuget // See: https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/ // AssemblyVersion can only be numeric -[assembly: AssemblyVersion("2.5.0")] +[assembly: AssemblyVersion("2.5.2")] diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs index 377be66d1..e8ef1ae77 100644 --- a/src/clrmodule/ClrModule.cs +++ b/src/clrmodule/ClrModule.cs @@ -53,7 +53,7 @@ public static void initclr() { #if USE_PYTHON_RUNTIME_VERSION // Has no effect until SNK works. Keep updated anyways. - Version = new Version("2.5.0"), + Version = new Version("2.5.2"), #endif CultureInfo = CultureInfo.InvariantCulture }; diff --git a/src/clrmodule/clrmodule.15.csproj b/src/clrmodule/clrmodule.15.csproj index 7fc9c2dda..eba365b76 100644 --- a/src/clrmodule/clrmodule.15.csproj +++ b/src/clrmodule/clrmodule.15.csproj @@ -9,7 +9,7 @@ clrmodule clrmodule clrmodule - 2.5.0 + 2.5.2 false false false diff --git a/src/console/Console.15.csproj b/src/console/Console.15.csproj index 3c51caa31..4397feb7b 100644 --- a/src/console/Console.15.csproj +++ b/src/console/Console.15.csproj @@ -8,7 +8,7 @@ nPython Python.Runtime nPython - 2.5.0 + 2.5.2 false false false diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index eb6957656..8fed4dc99 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -10,7 +10,7 @@ Python.EmbeddingTest Python.EmbeddingTest Python.EmbeddingTest - 2.5.0 + 2.5.2 false false false diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index d753a5dff..a9a86536f 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -8,7 +8,7 @@ Python.Runtime Python.Runtime pythonnet - 2.5.0 + 2.5.2 true false Python.NET diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 75f5e2fab..9f8acff92 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -1,4 +1,4 @@ - + Debug @@ -165,6 +165,7 @@ + diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 144bac9d3..43ec6ea72 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -266,6 +266,7 @@ public static IntPtr tp_repr(IntPtr ob) //otherwise use the standard object.__repr__(inst) IntPtr args = Runtime.PyTuple_New(1); + Runtime.XIncref(ob); Runtime.PyTuple_SetItem(args, 0, ob); IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); diff --git a/src/runtime/interop39.cs b/src/runtime/interop39.cs new file mode 100644 index 000000000..e042cb57d --- /dev/null +++ b/src/runtime/interop39.cs @@ -0,0 +1,244 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + + +#if PYTHON39 +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Text; + +namespace Python.Runtime +{ + + [StructLayout(LayoutKind.Sequential)] + internal static class TypeOffset + { + static TypeOffset() + { + Type type = typeof(TypeOffset); + FieldInfo[] fi = type.GetFields(); + int size = IntPtr.Size; + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, i * size); + } + } + + public static int magic() + { + return ob_size; + } + + // Auto-generated from PyHeapTypeObject in Python.h + public static int ob_refcnt = 0; + public static int ob_type = 0; + public static int ob_size = 0; + public static int tp_name = 0; + public static int tp_basicsize = 0; + public static int tp_itemsize = 0; + public static int tp_dealloc = 0; + public static int tp_vectorcall_offset = 0; + public static int tp_getattr = 0; + public static int tp_setattr = 0; + public static int tp_as_async = 0; + public static int tp_repr = 0; + public static int tp_as_number = 0; + public static int tp_as_sequence = 0; + public static int tp_as_mapping = 0; + public static int tp_hash = 0; + public static int tp_call = 0; + public static int tp_str = 0; + public static int tp_getattro = 0; + public static int tp_setattro = 0; + public static int tp_as_buffer = 0; + public static int tp_flags = 0; + public static int tp_doc = 0; + public static int tp_traverse = 0; + public static int tp_clear = 0; + public static int tp_richcompare = 0; + public static int tp_weaklistoffset = 0; + public static int tp_iter = 0; + public static int tp_iternext = 0; + public static int tp_methods = 0; + public static int tp_members = 0; + public static int tp_getset = 0; + public static int tp_base = 0; + public static int tp_dict = 0; + public static int tp_descr_get = 0; + public static int tp_descr_set = 0; + public static int tp_dictoffset = 0; + public static int tp_init = 0; + public static int tp_alloc = 0; + public static int tp_new = 0; + public static int tp_free = 0; + public static int tp_is_gc = 0; + public static int tp_bases = 0; + public static int tp_mro = 0; + public static int tp_cache = 0; + public static int tp_subclasses = 0; + public static int tp_weaklist = 0; + public static int tp_del = 0; + public static int tp_version_tag = 0; + public static int tp_finalize = 0; + public static int tp_vectorcall = 0; + public static int am_await = 0; + public static int am_aiter = 0; + public static int am_anext = 0; + public static int nb_add = 0; + public static int nb_subtract = 0; + public static int nb_multiply = 0; + public static int nb_remainder = 0; + public static int nb_divmod = 0; + public static int nb_power = 0; + public static int nb_negative = 0; + public static int nb_positive = 0; + public static int nb_absolute = 0; + public static int nb_bool = 0; + public static int nb_invert = 0; + public static int nb_lshift = 0; + public static int nb_rshift = 0; + public static int nb_and = 0; + public static int nb_xor = 0; + public static int nb_or = 0; + public static int nb_int = 0; + public static int nb_reserved = 0; + public static int nb_float = 0; + public static int nb_inplace_add = 0; + public static int nb_inplace_subtract = 0; + public static int nb_inplace_multiply = 0; + public static int nb_inplace_remainder = 0; + public static int nb_inplace_power = 0; + public static int nb_inplace_lshift = 0; + public static int nb_inplace_rshift = 0; + public static int nb_inplace_and = 0; + public static int nb_inplace_xor = 0; + public static int nb_inplace_or = 0; + public static int nb_floor_divide = 0; + public static int nb_true_divide = 0; + public static int nb_inplace_floor_divide = 0; + public static int nb_inplace_true_divide = 0; + public static int nb_index = 0; + public static int nb_matrix_multiply = 0; + public static int nb_inplace_matrix_multiply = 0; + public static int mp_length = 0; + public static int mp_subscript = 0; + public static int mp_ass_subscript = 0; + public static int sq_length = 0; + public static int sq_concat = 0; + public static int sq_repeat = 0; + public static int sq_item = 0; + public static int was_sq_slice = 0; + public static int sq_ass_item = 0; + public static int was_sq_ass_slice = 0; + public static int sq_contains = 0; + public static int sq_inplace_concat = 0; + public static int sq_inplace_repeat = 0; + public static int bf_getbuffer = 0; + public static int bf_releasebuffer = 0; + public static int name = 0; + public static int ht_slots = 0; + public static int qualname = 0; + public static int ht_cached_keys = 0; + public static int ht_module = 0; + + /* here are optional user slots, followed by the members. */ + public static int members = 0; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PyNumberMethods + { + public IntPtr nb_add; + public IntPtr nb_subtract; + public IntPtr nb_multiply; + public IntPtr nb_remainder; + public IntPtr nb_divmod; + public IntPtr nb_power; + public IntPtr nb_negative; + public IntPtr nb_positive; + public IntPtr nb_absolute; + public IntPtr nb_bool; + public IntPtr nb_invert; + public IntPtr nb_lshift; + public IntPtr nb_rshift; + public IntPtr nb_and; + public IntPtr nb_xor; + public IntPtr nb_or; + public IntPtr nb_int; + public IntPtr nb_reserved; + public IntPtr nb_float; + public IntPtr nb_inplace_add; + public IntPtr nb_inplace_subtract; + public IntPtr nb_inplace_multiply; + public IntPtr nb_inplace_remainder; + public IntPtr nb_inplace_power; + public IntPtr nb_inplace_lshift; + public IntPtr nb_inplace_rshift; + public IntPtr nb_inplace_and; + public IntPtr nb_inplace_xor; + public IntPtr nb_inplace_or; + public IntPtr nb_floor_divide; + public IntPtr nb_true_divide; + public IntPtr nb_inplace_floor_divide; + public IntPtr nb_inplace_true_divide; + public IntPtr nb_index; + public IntPtr nb_matrix_multiply; + public IntPtr nb_inplace_matrix_multiply; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PySequenceMethods + { + public IntPtr sq_length; + public IntPtr sq_concat; + public IntPtr sq_repeat; + public IntPtr sq_item; + public IntPtr was_sq_slice; + public IntPtr sq_ass_item; + public IntPtr was_sq_ass_slice; + public IntPtr sq_contains; + public IntPtr sq_inplace_concat; + public IntPtr sq_inplace_repeat; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PyMappingMethods + { + public IntPtr mp_length; + public IntPtr mp_subscript; + public IntPtr mp_ass_subscript; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PyAsyncMethods + { + public IntPtr am_await; + public IntPtr am_aiter; + public IntPtr am_anext; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PyBufferProcs + { + public IntPtr bf_getbuffer; + public IntPtr bf_releasebuffer; + } + + internal static partial class SlotTypes + { + public static readonly Type[] Types = { + typeof(PyNumberMethods), + typeof(PySequenceMethods), + typeof(PyMappingMethods), + typeof(PyAsyncMethods), + typeof(PyBufferProcs), + }; + } + +} +#endif + diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index b74f21754..077297a90 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -203,6 +203,16 @@ internal static int ArgPrecedence(Type t) return 3000; } + if (t.IsArray) + { + Type e = t.GetElementType(); + if (e == objectType) + { + return 2500; + } + return 100 + ArgPrecedence(e); + } + TypeCode tc = Type.GetTypeCode(t); // TODO: Clean up switch (tc) @@ -250,16 +260,6 @@ internal static int ArgPrecedence(Type t) return 40; } - if (t.IsArray) - { - Type e = t.GetElementType(); - if (e == objectType) - { - return 2500; - } - return 100 + ArgPrecedence(e); - } - return 2000; } @@ -372,7 +372,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out bool isNewReference) { isNewReference = false; - IntPtr op; + IntPtr op; // for a params method, we may have a sequence or single/multiple items // here we look to see if the item at the paramIndex is there or not // and then if it is a sequence itself. @@ -390,10 +390,6 @@ static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out { isNewReference = true; op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount); - if (item != IntPtr.Zero) - { - Runtime.XDecref(item); - } } } else @@ -431,7 +427,7 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++) { var parameter = pi[paramIndex]; - bool hasNamedParam = kwargDict.ContainsKey(parameter.Name); + bool hasNamedParam = parameter.Name != null ? kwargDict.ContainsKey(parameter.Name) : false; bool isNewReference = false; if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart))) diff --git a/src/runtime/resources/clr.py b/src/runtime/resources/clr.py index 78fae0d3a..18b4ba93f 100644 --- a/src/runtime/resources/clr.py +++ b/src/runtime/resources/clr.py @@ -2,7 +2,7 @@ Code in this module gets loaded into the main clr module. """ -__version__ = "2.5.0" +__version__ = "2.5.1" class clrproperty(object): diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 8cf24ba70..8db9c3d13 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -68,8 +68,11 @@ public class Runtime #elif PYTHON38 internal const string _pyversion = "3.8"; internal const string _pyver = "38"; +#elif PYTHON39 + internal const string _pyversion = "3.9"; + internal const string _pyver = "39"; #else -#error You must define one of PYTHON34 to PYTHON38 or PYTHON27 +#error You must define one of PYTHON34 to PYTHON39 or PYTHON27 #endif #if MONO_LINUX || MONO_OSX // Linux/macOS use dotted version string diff --git a/src/testing/Python.Test.15.csproj b/src/testing/Python.Test.15.csproj index 0e19adf91..75a26bd1c 100644 --- a/src/testing/Python.Test.15.csproj +++ b/src/testing/Python.Test.15.csproj @@ -7,7 +7,7 @@ Python.Test Python.Test Python.Test - 2.5.0 + 2.5.2 bin\ false $(OutputPath)\$(AssemblyName).xml diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs index 91836b727..9a4c408d6 100644 --- a/src/testing/methodtest.cs +++ b/src/testing/methodtest.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Runtime.InteropServices; namespace Python.Test @@ -84,7 +85,7 @@ public Type[] TestNullArrayConversion(Type[] v) public static string[] TestStringParamsArg(params string[] args) { - return args; + return args.Concat(new []{"tail"}).ToArray(); } public static object[] TestObjectParamsArg(params object[] args) diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 69f1b5e72..8456ddc9f 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -208,17 +208,20 @@ def test_null_array_conversion(): def test_string_params_args(): """Test use of string params.""" result = MethodTest.TestStringParamsArg('one', 'two', 'three') - assert result.Length == 3 - assert len(result) == 3, result + assert result.Length == 4 + assert len(result) == 4, result assert result[0] == 'one' assert result[1] == 'two' assert result[2] == 'three' + # ensures params string[] overload takes precedence over params object[] + assert result[3] == 'tail' result = MethodTest.TestStringParamsArg(['one', 'two', 'three']) - assert len(result) == 3 + assert len(result) == 4 assert result[0] == 'one' assert result[1] == 'two' assert result[2] == 'three' + assert result[3] == 'tail' def test_object_params_args(): diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py index 902296229..aed646dd4 100644 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -21,6 +21,11 @@ import sysconfig import subprocess +if sys.version_info.major > 2: + from io import StringIO +else: + from StringIO import StringIO + from pycparser import c_ast, c_parser _log = logging.getLogger() @@ -48,22 +53,25 @@ class AstParser(object): """Walk an AST and determine the members of all structs""" def __init__(self): - self.__typedefs = {} - self.__typedecls = {} - self.__structs = {} - self.__struct_stack = [] - self.__struct_members_stack = [] - self.__ptr_decl_depth = 0 - self.__struct_members = {} + self._typedefs = {} + self._typedecls = {} + self._structs = {} + self._struct_stack = [] + self._struct_members_stack = [] + self._ptr_decl_depth = 0 + self._struct_members = {} + self._decl_names = {} def get_struct_members(self, name): """return a list of (name, type) of struct members""" - if name in self.__typedefs: - node = self.__get_leaf_node(self.__typedefs[name]) - name = node.name - if name not in self.__struct_members: - raise Exception("Unknown struct '%s'" % name) - return self.__struct_members[name] + defs = self._typedefs.get(name) + if defs is None: + return None + node = self._get_leaf_node(defs) + name = node.name + if name is None: + name = defs.declname + return self._struct_members.get(name) def visit(self, node): if isinstance(node, c_ast.FileAST): @@ -88,26 +96,27 @@ def visit_ast(self, ast): self.visit(node) def visit_typedef(self, typedef): - self.__typedefs[typedef.name] = typedef.type + self._typedefs[typedef.name] = typedef.type self.visit(typedef.type) def visit_typedecl(self, typedecl): + self._decl_names[typedecl.type] = typedecl.declname self.visit(typedecl.type) def visit_struct(self, struct): - self.__structs[self.__get_struct_name(struct)] = struct if struct.decls: + self._structs[self._get_struct_name(struct)] = struct # recurse into the struct - self.__struct_stack.insert(0, struct) + self._struct_stack.insert(0, struct) for decl in struct.decls: - self.__struct_members_stack.insert(0, decl.name) + self._struct_members_stack.insert(0, decl.name) self.visit(decl) - self.__struct_members_stack.pop(0) - self.__struct_stack.pop(0) - elif self.__ptr_decl_depth: + self._struct_members_stack.pop(0) + self._struct_stack.pop(0) + elif self._ptr_decl_depth: # the struct is empty, but add it as a member to the current # struct as the current member maybe a pointer to it. - self.__add_struct_member(struct.name) + self._add_struct_member(struct.name) def visit_decl(self, decl): self.visit(decl.type) @@ -116,51 +125,72 @@ def visit_funcdecl(self, funcdecl): self.visit(funcdecl.type) def visit_ptrdecl(self, ptrdecl): - self.__ptr_decl_depth += 1 + self._ptr_decl_depth += 1 self.visit(ptrdecl.type) - self.__ptr_decl_depth -= 1 + self._ptr_decl_depth -= 1 def visit_identifier(self, identifier): type_name = " ".join(identifier.names) - self.__add_struct_member(type_name) + self._add_struct_member(type_name) - def __add_struct_member(self, type_name): - if not (self.__struct_stack and self.__struct_members_stack): + def _add_struct_member(self, type_name): + if not (self._struct_stack and self._struct_members_stack): return # add member to current struct - current_struct = self.__struct_stack[0] - member_name = self.__struct_members_stack[0] - struct_members = self.__struct_members.setdefault( - self.__get_struct_name(current_struct), []) + current_struct = self._struct_stack[0] + member_name = self._struct_members_stack[0] + struct_members = self._struct_members.setdefault( + self._get_struct_name(current_struct), []) # get the node associated with this type node = None - if type_name in self.__typedefs: - node = self.__get_leaf_node(self.__typedefs[type_name]) - elif type_name in self.__structs: - node = self.__structs[type_name] + if type_name in self._typedefs: + node = self._get_leaf_node(self._typedefs[type_name]) + # If the struct was only declared when the typedef was created, its member + # information will not have been recorded and we have to look it up in the + # structs + if isinstance(node, c_ast.Struct) and node.decls is None: + if node.name in self._structs: + node = self._structs[node.name] + elif type_name in self._structs: + node = self._structs[type_name] # If it's a struct (and not a pointer to a struct) expand # it into the current struct definition - if not self.__ptr_decl_depth and isinstance(node, c_ast.Struct): + if not self._ptr_decl_depth and isinstance(node, c_ast.Struct): for decl in node.decls or []: - self.__struct_members_stack.insert(0, decl.name) + self._struct_members_stack.insert(0, decl.name) self.visit(decl) - self.__struct_members_stack.pop(0) + self._struct_members_stack.pop(0) else: # otherwise add it as a single member struct_members.append((member_name, type_name)) - def __get_leaf_node(self, node): + def _get_leaf_node(self, node): if isinstance(node, c_ast.Typedef): - return self.__get_leaf_node(node.type) + return self._get_leaf_node(node.type) if isinstance(node, c_ast.TypeDecl): - return self.__get_leaf_node(node.type) + return self._get_leaf_node(node.type) return node - def __get_struct_name(self, node): - return node.name or "_struct_%d" % id(node) + def _get_struct_name(self, node): + return node.name or self._decl_names.get(node) or "_struct_%d" % id(node) + + +class Writer(object): + + def __init__(self): + self._stream = StringIO() + + def append(self, indent=0, code=""): + self._stream.write("%s%s\n" % (indent * " ", code)) + + def extend(self, s): + self._stream.write(s) + + def to_string(self): + return self._stream.getvalue() def preprocess_python_headers(): @@ -188,6 +218,7 @@ def preprocess_python_headers(): defines.extend([ "-D", "__inline=inline", "-D", "__ptr32=", + "-D", "__ptr64=", "-D", "__declspec(x)=", ]) @@ -211,9 +242,8 @@ def preprocess_python_headers(): return "\n".join(lines) -def gen_interop_code(members): - """Generate the TypeOffset C# class""" +def gen_interop_head(writer): defines = [ "PYTHON{0}{1}".format(PY_MAJOR, PY_MINOR) ] @@ -243,8 +273,23 @@ def gen_interop_code(members): namespace Python.Runtime { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - internal class TypeOffset +""" % (filename, defines_str) + writer.extend(class_definition) + + +def gen_interop_tail(writer): + tail = """} +#endif +""" + writer.extend(tail) + + +def gen_heap_type_members(parser, writer): + """Generate the TypeOffset C# class""" + members = parser.get_struct_members("PyHeapTypeObject") + class_definition = """ + [StructLayout(LayoutKind.Sequential)] + internal static class TypeOffset { static TypeOffset() { @@ -263,7 +308,7 @@ def gen_interop_code(members): } // Auto-generated from PyHeapTypeObject in Python.h -""" % (filename, defines_str) +""" # All the members are sizeof(void*) so we don't need to do any # extra work to determine the size based on the type. @@ -275,11 +320,36 @@ def gen_interop_code(members): /* here are optional user slots, followed by the members. */ public static int members = 0; } -} -#endif """ - return class_definition + writer.extend(class_definition) + + +def gen_structure_code(parser, writer, type_name, indent): + members = parser.get_struct_members(type_name) + if members is None: + return False + out = writer.append + out(indent, "[StructLayout(LayoutKind.Sequential)]") + out(indent, "internal struct %s" % type_name) + out(indent, "{") + for name, tpy in members: + out(indent + 1, "public IntPtr %s;" % name) + out(indent, "}") + out() + return True + + +def gen_supported_slot_record(writer, types, indent): + out = writer.append + out(indent, "internal static partial class SlotTypes") + out(indent, "{") + out(indent + 1, "public static readonly Type[] Types = {") + for name in types: + out(indent + 2, "typeof(%s)," % name) + out(indent + 1, "};") + out(indent, "}") + out() def main(): @@ -292,10 +362,29 @@ def main(): ast_parser = AstParser() ast_parser.visit(ast) + writer = Writer() # generate the C# code - members = ast_parser.get_struct_members("PyHeapTypeObject") - interop_cs = gen_interop_code(members) + gen_interop_head(writer) + + gen_heap_type_members(ast_parser, writer) + slots_types = [ + "PyNumberMethods", + "PySequenceMethods", + "PyMappingMethods", + "PyAsyncMethods", + "PyBufferProcs", + ] + supported_types = [] + indent = 1 + for type_name in slots_types: + if not gen_structure_code(ast_parser, writer, type_name, indent): + continue + supported_types.append(type_name) + gen_supported_slot_record(writer, supported_types, indent) + + gen_interop_tail(writer) + interop_cs = writer.to_string() if len(sys.argv) > 1: with open(sys.argv[1], "w") as fh: fh.write(interop_cs)