diff options
Diffstat (limited to 'sources/shiboken6/shibokenmodule/files.dir/shibokensupport')
17 files changed, 1270 insertions, 966 deletions
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__feature__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__feature__.py deleted file mode 100644 index 0fa58d22f..000000000 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__feature__.py +++ /dev/null @@ -1,187 +0,0 @@ -############################################################################# -## -## Copyright (C) 2020 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# - -""" -__feature__.py - -This is the feature file for the Qt for Python project. There is some -similarity to Python's `__future__` file, but also some distinction. - -The normal usage is like - - from __feature__ import <feature_name> [, ...] - ... - -Alternatively, there is the `set_selection` function which uses select_id's -and takes an optional `mod_name` parameter. - -The select id `-1` has the spectial meaning "ignore this module". -""" - -import sys - -all_feature_names = [ - "snake_case", - "true_property", - "_feature_04", - "_feature_08", - "_feature_10", - "_feature_20", - "_feature_40", - "_feature_80", -] - -__all__ = ["all_feature_names", "set_selection", "info"] + all_feature_names - -snake_case = 0x01 -true_property = 0x02 -_feature_04 = 0x04 -_feature_08 = 0x08 -_feature_10 = 0x10 -_feature_20 = 0x20 -_feature_40 = 0x40 -_feature_80 = 0x80 - -# let's remove the dummies for the normal user -_really_all_feature_names = all_feature_names[:] -all_feature_names = list(_ for _ in all_feature_names if not _.startswith("_")) - -# Install an import hook that controls the `__feature__` import. -""" -Note: This are two imports. ->>> import dis ->>> def test(): -... from __feature__ import snake_case -... ->>> dis.dis(test) - 2 0 LOAD_CONST 1 (0) - 2 LOAD_CONST 2 (('snake_case',)) - 4 IMPORT_NAME 0 (__feature__) - 6 IMPORT_FROM 1 (snake_case) - 8 STORE_FAST 0 (snake_case) - 10 POP_TOP - 12 LOAD_CONST 0 (None) - 14 RETURN_VALUE -""" -# XXX build an improved C version? I guess not. -def _import(name, *args, **kwargs): - # PYSIDE-1368: The `__name__` attribute does not need to exist in all modules. - # PYSIDE-1398: sys._getframe(1) may not exist when embedding. - calling_frame = _cf = sys._getframe().f_back - importing_module = _cf.f_globals.get("__name__", "__main__") if _cf else "__main__" - existing = pyside_feature_dict.get(importing_module, 0) - - if name == "__feature__" and args[2]: - __init__() - - # This is an `import from` statement that corresponds to `IMPORT_NAME`. - # The following `IMPORT_FROM` will handle errors. (Confusing, ofc.) - flag = 0 - for feature in args[2]: - if feature in _really_all_feature_names: - flag |= globals()[feature] - else: - raise SyntaxError(f"PySide feature {feature} is not defined") - - flag |= existing & 255 if isinstance(existing, int) and existing >= 0 else 0 - pyside_feature_dict[importing_module] = flag - - if importing_module == "__main__": - # We need to add all modules here which should see __feature__. - pyside_feature_dict["rlcompleter"] = flag - - # Initialize feature (multiple times allowed) and clear cache. - sys.modules["PySide6.QtCore"].__init_feature__() - return sys.modules["__feature__"] - - if name.split(".")[0] == "PySide6": - # This is a module that imports PySide6. - flag = existing if isinstance(existing, int) else 0 - else: - # This is some other module. Ignore it in switching. - flag = -1 - pyside_feature_dict[importing_module] = flag - return original_import(name, *args, **kwargs) - -_is_initialized = False - -def __init__(): - global _is_initialized - if not _is_initialized: - # use _one_ recursive import... - import PySide6.QtCore - # Initialize all prior imported modules - for name in sys.modules: - pyside_feature_dict.setdefault(name, -1) - - -def set_selection(select_id, mod_name=None): - """ - Internal use: Set the feature directly by Id. - Id == -1: ignore this module in switching. - """ - mod_name = mod_name or sys._getframe(1).f_globals['__name__'] - __init__() - # Reset the features to the given id - flag = 0 - if isinstance(select_id, int): - flag = select_id & 255 - pyside_feature_dict[mod_name] = flag - sys.modules["PySide6.QtCore"].__init_feature__() - return _current_selection(flag) - - -def info(mod_name=None): - """ - Internal use: Return the current selection - """ - mod_name = mod_name or sys._getframe(1).f_globals['__name__'] - flag = pyside_feature_dict.get(mod_name, 0) - return _current_selection(flag) - - -def _current_selection(flag): - names = [] - if flag >= 0: - for idx, name in enumerate(_really_all_feature_names): - if (1 << idx) & flag: - names.append(name) - return names - -#eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py index 2d640cb89..e54bec75a 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py @@ -1,40 +1,4 @@ -############################################################################# -## -## Copyright (C) 2018 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only # this file intentionally left blank diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py new file mode 100644 index 000000000..7a0871ee7 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py @@ -0,0 +1,248 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +# flake8: noqa F:821 +# flake8: noqa F:401 + +""" +__feature__.py (renamed to feature.py) + +This is the feature file for the Qt for Python project. There is some +similarity to Python's `__future__` file, but also some distinction. + +The normal usage is like + + from __feature__ import <feature_name> [, ...] + ... + +Alternatively, there is the `set_selection` function which uses select_id's +and takes an optional `mod_name` parameter. + +The select id `-1` has the special meaning "ignore this module". +""" + +import inspect +import sys +from contextlib import contextmanager + +all_feature_names = [ + "snake_case", + "true_property", + "_feature_04", + "_feature_08", + "_feature_10", + "_feature_20", + "_feature_40", + "_feature_80", +] + +__all__ = ["all_feature_names", "info", "reset", "set_selection"] + all_feature_names + +snake_case = 0x01 +true_property = 0x02 +_feature_04 = 0x04 +_feature_08 = 0x08 +_feature_10 = 0x10 +_feature_20 = 0x20 +_feature_40 = 0x40 +_feature_80 = 0x80 + +# let's remove the dummies for the normal user +_really_all_feature_names = all_feature_names[:] +all_feature_names = list(_ for _ in all_feature_names if not _.startswith("_")) + +# Install an import hook that controls the `__feature__` import. +""" +Note: This are two imports. +>>> import dis +>>> def test(): +... from __feature__ import snake_case +... +>>> dis.dis(test) + 2 0 LOAD_CONST 1 (0) + 2 LOAD_CONST 2 (('snake_case',)) + 4 IMPORT_NAME 0 (__feature__) + 6 IMPORT_FROM 1 (snake_case) + 8 STORE_FAST 0 (snake_case) + 10 POP_TOP + 12 LOAD_CONST 0 (None) + 14 RETURN_VALUE +""" + +""" +The redirection of __import__ +----------------------------- + +This construction avoids irritating extra redirections in tracebacks. + +The normal `__import__` is replaced by C function `__feature_import__`. +`__feature_import__` calls this `feature_import` function first, to +see if a feature is requested. If this function does not handle it, it returns +None to indicate that a normal import should be performed, and +`__feature_import__` calls the original import `__orig_import__`. +All these variables are transparently kept in module `builtins`. +""" + + +def feature_import(name, *args, **kwargs): + # PYSIDE-1368: The `__name__` attribute does not need to exist in all modules. + # PYSIDE-1398: sys._getframe(1) may not exist when embedding. + # PYSIDE-1338: The "1" below is the redirection in loader.py . + # PYSIDE-1548: Ensure that features are not affected by other imports. + # PYSIDE-2029: Need to always switch. The cache was wrong interpreted. + calling_frame = _cf = sys._getframe(1).f_back + importing_module = _cf.f_globals.get("__name__", "__main__") if _cf else "__main__" + existing = pyside_feature_dict.get(importing_module, 0) + + if name == "__feature__" and args[2]: + __init__() + + # This is an `import from` statement that corresponds to `IMPORT_NAME`. + # The following `IMPORT_FROM` will handle errors. (Confusing, ofc.) + flag = get_select_id(args[2]) + + flag |= existing & 255 if isinstance(existing, int) and existing >= 0 else 0 + pyside_feature_dict[importing_module] = flag + + if importing_module == "__main__": + # We need to add all modules here which should see __feature__. + pyside_feature_dict["rlcompleter"] = flag + + # Initialize feature (multiple times allowed) and clear cache. + sys.modules["PySide6.QtCore"].__init_feature__() + return sys.modules["__feature__"] + # Redirect to the original import + return None + + +_is_initialized = False + + +def __init__(): + global _is_initialized + if not _is_initialized: + # use _one_ recursive import... + import PySide6.QtCore + # Initialize all prior imported modules + for name, module in sys.modules.items(): + if name not in pyside_feature_dict: + pyside_feature_dict[name] = 0 if _mod_uses_pyside(module) else -1 + _is_initialized = True + + +def feature_imported(module): + # PYSIDE-2029: Need to inspect imported modules for PySide usage. + """ + Set the module feature default after import. + + A module that uses PySide has a switching default of 0 = "no feature". + Otherwise the default is -1 = "ignore this module". + """ + + # PYSIDE-1368: The `__name__` attribute does not need to exist in all modules. + if hasattr(module, "__name__"): + name = module.__name__ + if name not in pyside_feature_dict: + pyside_feature_dict[name] = 0 if _mod_uses_pyside(module) else -1 + + +def _mod_uses_pyside(module): + """ + Find out if this module uses PySide. + + Simple approach: Search the source code for the string "PySide6". + Maybe we later support source-less modules by inspecting all code objects. + """ + try: + source = inspect.getsource(module) + except TypeError: + # this is a builtin module like sys + return False + except OSError: + # this is a module withot source file + return False + except SyntaxError: + # PYSIDE-2189: A UnicodeError happens in tokenize.py in find_cookie + # which is then creating a SyntaxError in inspect. + # This is undocumented and a Python error, seen in Python 3.10.2 on Windows, + # importing `pythoncom` of the win32 package. + return False + except Exception: + # PYSIDE-2393: pytest behaves weird when allowing any other error. + return False + return "PySide6" in source + + +def set_selection(select_id, mod_name=None): + """ + Internal use: Set the feature directly by Id. + Id == -1: ignore this module in switching. + """ + mod_name = mod_name or sys._getframe(1).f_globals['__name__'] + __init__() + # Reset the features to the given id + flag = 0 + if isinstance(select_id, int): + flag = select_id & 255 + pyside_feature_dict[mod_name] = flag + sys.modules["PySide6.QtCore"].__init_feature__() + return _current_selection(flag) + + +# The set_section(0) case seems to be unsafe. We will migrate to +# use the opaque feature.reset() call in all test cases. +def reset(): + set_selection(0) + pyside_feature_dict.clear() + _is_initialized = False + + +def info(mod_name=None): + """ + Internal use: Return the current selection + """ + mod_name = mod_name or sys._getframe(1).f_globals['__name__'] + flag = pyside_feature_dict.get(mod_name, 0) + return _current_selection(flag) + + +def _current_selection(flag): + names = [] + if flag >= 0: + for idx, name in enumerate(_really_all_feature_names): + if (1 << idx) & flag: + names.append(name) + return names + + +def get_select_id(feature_names): + flag = 0 + for feature in feature_names: + if feature in _really_all_feature_names: + flag |= globals()[feature] + else: + raise SyntaxError(f"PySide feature {feature} is not defined") + return flag + + +@contextmanager +def force_selection(select_id, mod_name): + """ + This function is for generating pyi files with features. + The selection id is set globally after performing the unswitched + import. + + """ + __init__() + saved_feature_dict = pyside_feature_dict.copy() + for name in pyside_feature_dict: + set_selection(0, name) + __import__(mod_name) + for name in pyside_feature_dict.copy(): + set_selection(select_id, name) + try: + yield + finally: + pyside_feature_dict.update(saved_feature_dict) + +#eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py index 396f6ca4c..f7190b12f 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py @@ -1,44 +1,5 @@ -# This Python file uses the following encoding: utf-8 -# It has been edited by fix-complaints.py . - -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ fix-complaints.py @@ -48,7 +9,6 @@ Run it once after copying a new version. It is idem-potent, unless you are changing messages (what I did, of course :-) . """ -import os import glob from pathlib import Path @@ -63,6 +23,7 @@ offending_words = { utf8_line = "# This Python file uses the following encoding: utf-8\n" marker_line = f"# It has been edited by {Path(__file__).name} .\n" + def patch_file(fname): with fname.open() as f: lines = f.readlines() @@ -80,6 +41,7 @@ def patch_file(fname): with open(fname, "w") as f: f.write("".join(lines)) + def doit(): dirname = Path(__file__).parent patched_files = [] @@ -90,6 +52,7 @@ def doit(): print("Working on", fname) patch_file(fname) + if __name__ == "__main__": doit() diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/shibokensupport.pyproject b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/shibokensupport.pyproject new file mode 100644 index 000000000..7147a4148 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/shibokensupport.pyproject @@ -0,0 +1,16 @@ +{ + "files": ["__init__.py", + "feature.py", + "fix-complaints.py", + "signature/__init__.py", + "signature/errorhandler.py", + "signature/importhandler.py", + "signature/layout.py", + "signature/lib/__init__.py", + "signature/lib/enum_sig.py", + "signature/lib/pyi_generator.py", + "signature/lib/tool.py", + "signature/loader.py", + "signature/mapping.py", + "signature/parser.py"] +} diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py index 7ae0a7f18..bebf56c7e 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py @@ -1,40 +1,4 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only __all__ = "get_signature layout mapping lib".split() diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py index 7af7b48b2..c2a19efef 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py @@ -1,41 +1,7 @@ -############################################################################# -## -## Copyright (C) 2020 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +# flake8: noqa E:721 """ errorhandler.py @@ -54,21 +20,33 @@ enough to produce a useful ValueError. This matter will be improved in a later version. """ -import sys +import collections.abc +import typing -from shibokensupport.signature import inspect from shibokensupport.signature import get_signature -from shibokensupport.signature.mapping import update_mapping, namespace +from shibokensupport.signature.mapping import namespace from textwrap import dedent def qt_isinstance(inst, the_type): if the_type == float: - return isinstance(inst, int) or isinstance(int, float) + # Qt thinks differently about int and float - simply keep it. + return isinstance(inst, int) or isinstance(inst, float) + if the_type.__module__ == "typing": + if the_type is typing.Any: + return True + if the_type.__origin__ is typing.Union: + return any(qt_isinstance(inst, _) for _ in the_type.__args__) + if the_type.__origin__ in (collections.abc.Sequence, + collections.abc.Iterable): + try: + return all(qt_isinstance(_, the_type.__args__[0]) for _ in inst) + except TypeError: + return False try: return isinstance(inst, the_type) except TypeError as e: - print("FIXME", e) + print(f"FIXME qt_isinstance({inst}, {the_type}):", e) return False @@ -82,13 +60,7 @@ def matched_type(args, sigs): if params[k].default is params[k].empty: # this is a necessary parameter, so it fails. continue - ok = True - for arg, param in zip(args, params): - ann = param.annotation - if qt_isinstance(arg, ann): - continue - ok = False - if ok: + if all(qt_isinstance(arg, param.annotation) for arg, param in zip(args, params)): return sig return None @@ -98,12 +70,15 @@ def seterror_argument(args, func_name, info): try: func = eval(func_name, namespace) except Exception as e: - msg = f"Internal error evaluating {func_name}: " + str(e) - return TypeError, msg + msg = f"Error evaluating `{func_name}`: {e}" + return type(e), msg if info and type(info) is str: err = TypeError if info == "<": msg = f"{func_name}(): not enough arguments" + elif info == "0": + msg = (f"{func_name}(): not enough arguments. " + "Note: keyword arguments are only supported for optional parameters.") elif info == ">": msg = f"{func_name}(): too many arguments" elif info.isalnum(): @@ -112,6 +87,12 @@ def seterror_argument(args, func_name, info): msg = f"{func_name}(): {info}" err = AttributeError return err, msg + if isinstance(info, Exception): + # PYSIDE-2230: Python 3.12 seems to always do normalization. + err = type(info) + info = info.args[0] + msg = f"{func_name}(): {info}" + return err, msg if info and type(info) is dict: msg = f"{func_name}(): unsupported keyword '{tuple(info)[0]}'" return AttributeError, msg @@ -151,6 +132,8 @@ def check_string_type(s): def make_helptext(func): existing_doc = func.__doc__ + if existing_doc is None and hasattr(func, "__dict__"): + existing_doc = func.__dict__.get("__doc__") sigs = get_signature(func) if not sigs: return existing_doc diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py index b37aefbb5..bae264294 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py @@ -1,41 +1,5 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ importhandler.py @@ -70,9 +34,10 @@ def finish_import(module): except Exception as e: name = e.__class__.__qualname__ print(72 * "*") - print(f"Error in deprecated.py, ignored:") + print("Error in deprecated.py, ignored:") print(f" {name}: {e}") + """ A note for people who might think this could be written in pure Python: @@ -98,4 +63,3 @@ module, it is *perhaps* possible to solve that. I tried for a day and then gave up, since the solution is anyway not too nice when __import__ must be overridden. """ -#eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py index c0460b332..0e781cbcb 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -1,41 +1,5 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ layout.py @@ -53,10 +17,12 @@ It also implements them in this file. The configurations are used literally as strings like "signature", "existence", etc. """ +import inspect +import typing + +from types import SimpleNamespace from textwrap import dedent -from shibokensupport.signature import inspect, typing from shibokensupport.signature.mapping import ellipsis -from shibokensupport.signature.lib.tool import SimpleNamespace class SignatureLayout(SimpleNamespace): @@ -113,6 +79,7 @@ class SignatureLayout(SimpleNamespace): The only allowed values are '{allowed_values}'. """)) + # The following names are used literally in this module. # This way, we avoid the dict hashing problem. signature = SignatureLayout() @@ -149,7 +116,7 @@ def define_nameless_parameter(): P = inspect.Parameter newname = "NamelessParameter" bases = P.__bases__ - body = dict(P.__dict__) # get rid of mappingproxy + body = dict(P.__dict__) # get rid of mappingproxy if "__slots__" in body: # __slots__ would create duplicates for name in body["__slots__"]: @@ -174,10 +141,10 @@ That information would be lost when we use the "..." convention. Note that the typing module has the remarkable expansion - Optional[T] is Variant[T, NoneType] + Optional[T] is Union[T, NoneType] We want to avoid that when generating the .pyi file. -This is done by a regex in generate_pyi.py . +This is done by a regex in pyi_generator.py . The following would work in Python 3, but this is a version-dependent hack that also won't work in Python 2 and would be _very_ complex. """ @@ -201,12 +168,13 @@ def make_signature_nameless(signature): signature.parameters[key].__class__ = NamelessParameter -_POSITIONAL_ONLY = inspect._POSITIONAL_ONLY -_POSITIONAL_OR_KEYWORD = inspect._POSITIONAL_OR_KEYWORD -_VAR_POSITIONAL = inspect._VAR_POSITIONAL -_KEYWORD_ONLY = inspect._KEYWORD_ONLY -_VAR_KEYWORD = inspect._VAR_KEYWORD -_empty = inspect._empty +_POSITIONAL_ONLY = inspect._POSITIONAL_ONLY # noqa E:201 +_POSITIONAL_OR_KEYWORD = inspect._POSITIONAL_OR_KEYWORD # noqa E:201 +_VAR_POSITIONAL = inspect._VAR_POSITIONAL # noqa E:201 +_KEYWORD_ONLY = inspect._KEYWORD_ONLY # noqa E:201 +_VAR_KEYWORD = inspect._VAR_KEYWORD # noqa E:201 +_empty = inspect._empty # noqa E:201 + def create_signature(props, key): if not props: @@ -217,9 +185,9 @@ def create_signature(props, key): return list(create_signature(elem, key) for elem in props["multi"]) if type(key) is tuple: - sig_kind, modifier = key + _, modifier = key else: - sig_kind, modifier = key, "signature" + _, modifier = key, "signature" layout = globals()[modifier] # lookup of the modifier in this module if not isinstance(layout, SignatureLayout): @@ -234,7 +202,7 @@ def create_signature(props, key): # parser. pass else: - if "self" in varnames[:1]: + if varnames and varnames[0] in ("self", "cls"): varnames = varnames[1:] # calculate the modifications @@ -254,7 +222,7 @@ def create_signature(props, key): elif name.startswith("*"): kind = _VAR_POSITIONAL ann = annotations.get(name, _empty) - if ann == "self": + if ann in ("self", "cls"): ann = _empty name = name.lstrip("*") defpos = idx - len(varnames) + len(defaults) @@ -268,8 +236,8 @@ def create_signature(props, key): if kind == _VAR_POSITIONAL: kind = _KEYWORD_ONLY sig = inspect.Signature(params, - return_annotation=annotations.get('return', _empty), - __validate_parameters__=False) + return_annotation=annotations.get('return', _empty), + __validate_parameters__=False) # the special case of nameless parameters if not layout.parameter_names: diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py index 2d640cb89..e54bec75a 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py @@ -1,40 +1,4 @@ -############################################################################# -## -## Copyright (C) 2018 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only # this file intentionally left blank diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py index 400c36de5..5650e2bc1 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py @@ -1,41 +1,5 @@ -############################################################################# -## -## Copyright (C) 2020 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ enum_sig.py @@ -47,12 +11,35 @@ It is not easy to adhere to this protocol, but in the end, it paid off by producing a lot of clarity. """ +import inspect import sys import types -from shibokensupport.signature import inspect from shibokensupport.signature import get_signature as get_sig +""" +PYSIDE-1599: Making sure that pyi files always are tested. + +A new problem popped up when supporting true properties: +When there exists an item named "property", then we cannot use +the builtin `property` as decorator, but need to prefix it with "builtins". + +We scan for such a name in a class, and if there should a property be +declared in the same class, we use `builtins.property` in the class and +all sub-classes. The same consideration holds for "overload". +""" + +_normal_functions = (types.BuiltinFunctionType, types.FunctionType) +if hasattr(sys, "pypy_version_info"): + # In PyPy, there are more types because our builtin functions + # are created differently in C++ and not in PyPy. + _normal_functions += (type(get_sig),) + + +def signal_check(thing): + return thing and type(thing) in (Signal, SignalInstance) + + class ExactEnumerator(object): """ ExactEnumerator enumerates all signatures in a module as they are. @@ -63,20 +50,19 @@ class ExactEnumerator(object): """ def __init__(self, formatter, result_type=dict): - global EnumMeta + global EnumMeta, Signal, SignalInstance try: # Lazy import - from PySide6.QtCore import Qt + from PySide6.QtCore import Qt, Signal, SignalInstance EnumMeta = type(Qt.Key) except ImportError: - EnumMeta = None + EnumMeta = Signal = SignalInstance = None self.fmt = formatter self.result_type = result_type self.fmt.level = 0 - self.fmt.after_enum = self.after_enum - self._after_enum = False self.fmt.is_method = self.is_method + self.collision_candidates = {"property", "overload"} def is_method(self): """ @@ -84,11 +70,11 @@ class ExactEnumerator(object): We check if it is a simple function. """ tp = type(self.func) - return tp not in (types.BuiltinFunctionType, types.FunctionType) + return tp not in _normal_functions - def after_enum(self): - ret = self._after_enum - self._after_enum = False + def section(self): + if hasattr(self.fmt, "section"): + self.fmt.section() def module(self, mod_name): __import__(mod_name) @@ -100,24 +86,31 @@ class ExactEnumerator(object): ret = self.result_type() self.fmt.class_name = None for class_name, klass in members: + self.collision_track = set() ret.update(self.klass(class_name, klass)) - if isinstance(klass, EnumMeta): - raise SystemError("implement enum instances at module level") + if len(members): + self.section() for func_name, func in functions: ret.update(self.function(func_name, func)) + if len(functions): + self.section() return ret def klass(self, class_name, klass): ret = self.result_type() + if ("._") in class_name: + # This happens when introspecting enum.Enum etc. Python 3.8.8 does not + # like this, but we want to remove that, anyway. + return ret if "<" in class_name: # This is happening in QtQuick for some reason: - ## class QSharedPointer<QQuickItemGrabResult >: + # class std::shared_ptr<QQuickItemGrabResult >: # We simply skip over this class. return ret bases_list = [] for base in klass.__bases__: - name = base.__name__ - if name not in ("object", "type"): + name = base.__qualname__ + if name not in ("object", "property", "type"): name = base.__module__ + "." + name bases_list.append(name) bases_str = ', '.join(bases_list) @@ -128,60 +121,126 @@ class ExactEnumerator(object): subclasses = [] functions = [] enums = [] + properties = [] + signals = [] + attributes = {} for thing_name, thing in class_members: - if inspect.isclass(thing): + if signal_check(thing): + signals.append((thing_name, thing)) + elif inspect.isclass(thing): + # If this is the only member of the class, it causes the stub + # to be printed empty without ..., as self.fmt.have_body will + # then be True. (Example: QtCore.QCborTag). Skip it to avoid + # this problem. + if thing_name == "_member_type_": + continue subclass_name = ".".join((class_name, thing_name)) subclasses.append((subclass_name, thing)) elif inspect.isroutine(thing): func_name = thing_name.split(".")[0] # remove ".overload" - signature = getattr(thing, "__signature__", None) - if signature is not None: - functions.append((func_name, thing)) + functions.append((func_name, thing)) elif type(type(thing)) is EnumMeta: # take the real enum name, not what is in the dict - enums.append((thing_name, type(thing).__qualname__, thing)) + if not thing_name.startswith("_"): + enums.append((thing_name, type(thing).__qualname__, thing)) + elif isinstance(thing, property): + properties.append((thing_name, thing)) + # Support attributes that have PySide types as values, + # but we skip the 'staticMetaObject' that needs + # to be defined at a QObject level. + elif "PySide" in str(type(thing)) and "QMetaObject" not in str(type(thing)): + if class_name not in attributes: + attributes[class_name] = {} + attributes[class_name][thing_name] = thing + + if thing_name in self.collision_candidates: + self.collision_track.add(thing_name) + init_signature = getattr(klass, "__signature__", None) - enums.sort(key=lambda tup: tup[1 : 3]) # sort by class then enum value - self.fmt.have_body = bool(subclasses or functions or enums or init_signature) + # sort by class then enum value + enums.sort(key=lambda tup: (tup[1], tup[2].value)) + + # We want to handle functions and properties together. + func_prop = sorted(functions + properties, key=lambda tup: tup[0]) + + # find out how many functions create a signature + sigs = list(_ for _ in functions if get_sig(_[1])) + self.fmt.have_body = bool(subclasses or sigs or properties or enums or # noqa W:504 + init_signature or signals or attributes) with self.fmt.klass(class_name, class_str): self.fmt.level += 1 self.fmt.class_name = class_name if hasattr(self.fmt, "enum"): # this is an optional feature + if len(enums): + self.section() for enum_name, enum_class_name, value in enums: - with self.fmt.enum(enum_class_name, enum_name, int(value)): + with self.fmt.enum(enum_class_name, enum_name, value.value): + pass + if hasattr(self.fmt, "signal"): + # this is an optional feature + if len(signals): + self.section() + for signal_name, signal in signals: + sig_class = type(signal) + sig_class_name = f"{sig_class.__qualname__}" + sig_str = str(signal) + with self.fmt.signal(sig_class_name, signal_name, sig_str): pass + if hasattr(self.fmt, "attribute"): + if len(attributes): + self.section() + for class_name, attrs in attributes.items(): + for attr_name, attr_value in attrs.items(): + with self.fmt.attribute(attr_name, attr_value): + pass + if len(subclasses): + self.section() for subclass_name, subclass in subclasses: - if klass == subclass: - # this is a side effect of the typing module for Python 2.7 - # via the "._gorg" property, which we can safely ignore. - print(f"Warning: {class_name} points to itself via {subclass_name}, skipped!") - continue + save = self.collision_track.copy() ret.update(self.klass(subclass_name, subclass)) + self.collision_track = save self.fmt.class_name = class_name + if len(subclasses): + self.section() ret.update(self.function("__init__", klass)) - for func_name, func in functions: + for func_name, func in func_prop: if func_name != "__init__": - ret.update(self.function(func_name, func)) + if isinstance(func, property): + ret.update(self.fproperty(func_name, func)) + else: + ret.update(self.function(func_name, func)) self.fmt.level -= 1 + if len(func_prop): + self.section() return ret @staticmethod def get_signature(func): - return func.__signature__ + return get_sig(func) - def function(self, func_name, func): + def function(self, func_name, func, decorator=None): self.func = func # for is_method() ret = self.result_type() - signature = self.get_signature(func) + if decorator in self.collision_track: + decorator = f"builtins.{decorator}" + signature = self.get_signature(func, decorator) if signature is not None: - with self.fmt.function(func_name, signature) as key: + with self.fmt.function(func_name, signature, decorator) as key: ret[key] = signature del self.func return ret + def fproperty(self, prop_name, prop): + ret = self.function(prop_name, prop.fget, type(prop).__qualname__) + if prop.fset: + ret.update(self.function(prop_name, prop.fset, f"{prop_name}.setter")) + if prop.fdel: + ret.update(self.function(prop_name, prop.fdel, f"{prop_name}.deleter")) + return ret + def stringify(signature): if isinstance(signature, list): @@ -221,6 +280,25 @@ class HintingEnumerator(ExactEnumerator): hinting stubs. Only default values are replaced by "...". """ - @staticmethod - def get_signature(func): - return get_sig(func, "hintingstub") + def __init__(self, *args, **kwds): + super().__init__(*args, **kwds) + # We need to provide default signatures for class properties. + cls_param = inspect.Parameter("cls", inspect._POSITIONAL_OR_KEYWORD) + set_param = inspect.Parameter("arg_1", inspect._POSITIONAL_OR_KEYWORD, annotation=object) + self.getter_sig = inspect.Signature([cls_param], return_annotation=object) + self.setter_sig = inspect.Signature([cls_param, set_param]) + self.deleter_sig = inspect.Signature([cls_param]) + + def get_signature(self, func, decorator=None): + # Class properties don't have signature support (yet). + # In that case, produce a fake one. + sig = get_sig(func, "hintingstub") + if not sig: + if decorator: + if decorator.endswith(".setter"): + sig = self.setter_sig + elif decorator.endswith(".deleter"): + sig = self.deleter_sig + else: + sig = self.getter_sig + return sig diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py new file mode 100644 index 000000000..ce12dd6c8 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -0,0 +1,334 @@ +LICENSE_TEXT = """ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +""" + +# flake8: noqa E:402 + +""" +pyi_generator.py + +This script generates .pyi files for arbitrary modules. +""" + +import argparse +import io +import logging +import os +import re +import sys +import typing + +from pathlib import Path +from contextlib import contextmanager +from textwrap import dedent + +from shibokensupport.signature.lib.enum_sig import HintingEnumerator +from shibokensupport.signature.lib.tool import build_brace_pattern + +# Can we use forward references? +USE_PEP563 = sys.version_info[:2] >= (3, 7) + +indent = " " * 4 + + +class Writer(object): + def __init__(self, outfile, *args): + self.outfile = outfile + self.history = [True, True] + + def print(self, *args, **kw): + # controlling too much blank lines + if self.outfile: + if args == () or args == ("",): + # We use that to skip too many blank lines: + if self.history[-2:] == [True, True]: + return + print("", file=self.outfile, **kw) + self.history.append(True) + else: + print(*args, file=self.outfile, **kw) + self.history.append(False) + + +class Formatter(Writer): + """ + Formatter is formatting the signature listing of an enumerator. + + It is written as context managers in order to avoid many callbacks. + The separation in formatter and enumerator is done to keep the + unrelated tasks of enumeration and formatting apart. + """ + def __init__(self, outfile, options, *args): + # XXX Find out which of these patches is still necessary! + self.options = options + Writer.__init__(self, outfile, *args) + # patching __repr__ to disable the __repr__ of typing.TypeVar: + """ + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + """ + def _typevar__repr__(self): + return f"typing.{self.__name__}" + # This is no longer necessary for modern typing versions. + # Ignore therefore if the repr is read-only and cannot be changed. + try: + typing.TypeVar.__repr__ = _typevar__repr__ + except TypeError: + pass + # Adding a pattern to substitute "Union[T, NoneType]" by "Optional[T]" + # I tried hard to replace typing.Optional by a simple override, but + # this became _way_ too much. + # See also the comment in layout.py . + brace_pat = build_brace_pattern(3, ",") + pattern = fr"\b Union \s* \[ \s* {brace_pat} \s*, \s* NoneType \s* \]" + replace = r"Optional[\1]" + optional_searcher = re.compile(pattern, flags=re.VERBOSE) + + def optional_replacer(source): + return optional_searcher.sub(replace, str(source)) + self.optional_replacer = optional_replacer + # self.level is maintained by enum_sig.py + # self.is_method() is true for non-plain functions. + + def section(self): + if self.level == 0: + self.print() + self.print() + + @contextmanager + def module(self, mod_name): + self.mod_name = mod_name + txt = f"""\ + # Module `{mod_name}` + + <<IMPORTS>> + """ + self.print(dedent(txt)) + yield + + @contextmanager + def klass(self, class_name, class_str): + spaces = indent * self.level + while "." in class_name: + class_name = class_name.split(".", 1)[-1] + class_str = class_str.split(".", 1)[-1] + if self.have_body: + self.print(f"{spaces}class {class_str}:") + else: + self.print(f"{spaces}class {class_str}: ...") + yield + + @contextmanager + def function(self, func_name, signature, decorator=None): + if func_name == "__init__": + self.print() + key = func_name + spaces = indent * self.level + if isinstance(signature, list): + for sig in signature: + self.print(f'{spaces}@overload') + self._function(func_name, sig, spaces) + else: + self._function(func_name, signature, spaces, decorator) + if func_name == "__init__": + self.print() + yield key + + def _function(self, func_name, signature, spaces, decorator=None): + if decorator: + # In case of a PyClassProperty the classmethod decorator is not used. + self.print(f'{spaces}@{decorator}') + elif self.is_method() and "self" not in signature.parameters: + kind = "class" if "cls" in signature.parameters else "static" + self.print(f'{spaces}@{kind}method') + signature = self.optional_replacer(signature) + self.print(f'{spaces}def {func_name}{signature}: ...') + + @contextmanager + def enum(self, class_name, enum_name, value): + spaces = indent * self.level + hexval = hex(value) + self.print(f"{spaces}{enum_name:25}: {class_name} = ... # {hexval}") + yield + + @contextmanager + def attribute(self, attr_name, attr_value): + spaces = indent * self.level + self.print(f"{spaces}{attr_name:25} = ... # type: {type(attr_value).__qualname__}") + yield + + @contextmanager + def signal(self, class_name, sig_name, sig_str): + spaces = indent * self.level + self.print(f"{spaces}{sig_name:25}: ClassVar[{class_name}] = ... # {sig_str}") + yield + + +def find_imports(text): + return [imp for imp in PySide6.__all__ if f"PySide6.{imp}." in text] + + +FROM_IMPORTS = [ + (None, ["builtins"]), + (None, ["os"]), + (None, ["enum"]), + ("collections.abc", ["Iterable"]), + ("typing", sorted(typing.__all__)), + ("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]), + ("shiboken6", ["Shiboken"]), + ] + + +def filter_from_imports(from_struct, text): + """ + Build a reduced new `from` structure (nfs) with found entries, only + """ + nfs = [] + for mod, imports in from_struct: + lis = [] + nfs.append((mod, lis)) + for each in imports: + # PYSIDE-1603: We search text that is a usage of the class `each`, + # but only if the class is not also defined here. + if (f"class {each}(") not in text: + if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text): + lis.append(each) + # Search if a type is present in the return statement + # of function declarations: '... -> here:' + if re.search(rf"->.*{each}.*:", text): + lis.append(each) + if not lis: + nfs.pop() + return nfs + + +def find_module(import_name, outpath, from_pyside): + """ + Find a module either directly by import, or use the full path, + add the path to sys.path and import then. + """ + if from_pyside: + # internal mode for generate_pyi.py + plainname = import_name.split(".")[-1] + outfilepath = Path(outpath) / f"{plainname}.pyi" + return import_name, plainname, outfilepath + # we are alone in external module mode + p = Path(import_name).resolve() + if not p.exists(): + raise ValueError(f"File {p} does not exist.") + if not outpath: + outpath = p.parent + # temporarily add the path and do the import + sys.path.insert(0, os.fspath(p.parent)) + plainname = p.name.split(".")[0] + __import__(plainname) + sys.path.pop(0) + return plainname, plainname, Path(outpath) / (plainname + ".pyi") + + +def generate_pyi(import_name, outpath, options): + """ + Generates a .pyi file. + """ + import_name, plainname, outfilepath = find_module(import_name, outpath, options._pyside_call) + top = __import__(import_name) + obj = getattr(top, plainname) if import_name != plainname else top + if not getattr(obj, "__file__", None) or Path(obj.__file__).is_dir(): + raise ModuleNotFoundError(f"We do not accept a namespace as module `{plainname}`") + + outfile = io.StringIO() + fmt = Formatter(outfile, options) + fmt.print(LICENSE_TEXT.strip()) + if USE_PEP563: + fmt.print("from __future__ import annotations") + fmt.print() + fmt.print(dedent(f'''\ + """ + This file contains the exact signatures for all functions in module + {import_name}, except for defaults which are replaced by "...". + """ + ''')) + HintingEnumerator(fmt).module(import_name) + fmt.print("# eof") + # Postprocess: resolve the imports + if options._pyside_call: + global PySide6 + import PySide6 + with outfilepath.open("w") as realfile: + wr = Writer(realfile) + outfile.seek(0) + while True: + line = outfile.readline() + if not line: + break + line = line.rstrip() + # we remove the "<<IMPORTS>>" marker and insert imports if needed + if line == "<<IMPORTS>>": + text = outfile.getvalue() + wr.print("import " + import_name) + for mod_name in find_imports(text): + imp = "PySide6." + mod_name + if imp != import_name: + wr.print("import " + imp) + wr.print() + for mod, imports in filter_from_imports(FROM_IMPORTS, text): + import_args = ', '.join(imports) + if mod is None: + # special case, a normal import + wr.print(f"import {import_args}") + else: + wr.print(f"from {mod} import {import_args}") + wr.print() + wr.print() + wr.print("NoneType: TypeAlias = type[None]") + wr.print() + else: + wr.print(line) + if not options.quiet: + options.logger.info(f"Generated: {outfilepath}") + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=dedent("""\ + pyi_generator.py + ---------------- + + This script generates the .pyi file for an arbitrary module. + You pass in the full path of a compiled, importable module. + pyi_generator will try to generate an interface "<module>.pyi". + """)) + parser.add_argument("module", + help="The full path name of an importable module binary (.pyd, .so)") # noqa E:128 + parser.add_argument("--quiet", action="store_true", help="Run quietly") + parser.add_argument("--outpath", + help="the output directory (default = location of module binary)") # noqa E:128 + options = parser.parse_args() + module = options.module + outpath = options.outpath + + qtest_env = os.environ.get("QTEST_ENVIRONMENT", "") + logging.basicConfig(level=logging.DEBUG if qtest_env else logging.INFO) + logger = logging.getLogger("pyi_generator") + + if outpath and not Path(outpath).exists(): + os.makedirs(outpath) + logger.info(f"+++ Created path {outpath}") + options._pyside_call = False + options.is_ci = qtest_env == "ci" + + options.logger = logger + generate_pyi(module, outpath, options=options) + + +if __name__ == "__main__": + main() +# eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py index 268142db8..979dcf4ce 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py @@ -1,41 +1,5 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """ tool.py @@ -45,30 +9,11 @@ On the function with_metaclass see the answer from Martijn Pieters on https://stackoverflow.com/questions/18513821/python-metaclass-understanding-the-with-metaclass """ +from inspect import currentframe from textwrap import dedent -class SimpleNamespace(object): - # From types.rst, because the builtin is implemented in Python 3, only. - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - keys = sorted(self.__dict__) - items = (f"{k}={self.__dict__[k]!r}" for k in keys) - items_str = ', '.join(items) - return f"{type(self).__name__}({items_str})" - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - -try: - from types import SimpleNamespace -except ImportError: - pass - - -def build_brace_pattern(level, separators=""): +def build_brace_pattern(level, separators): """ Build a brace pattern upto a given depth @@ -92,13 +37,15 @@ def build_brace_pattern(level, separators=""): avoiding this problem completely. It might be considered to switch to such an engine if the external module is not a problem. """ + assert type(separators) is str + def escape(txt): return "".join("\\" + c for c in txt) ro, rc = round_ = "()" so, sc = square = "[]" - co, cc = curly = "CD" # we insert "{}", later... - ao, ac = angle = "<>" + co, cc = curly = "CD" # noqa E:201 we insert "{}", later... + ao, ac = angle = "<>" # noqa E:201 q2, bs, q1 = '"', "\\", "'" allpat = round_ + square + curly + angle __ = " " @@ -132,8 +79,8 @@ def build_brace_pattern(level, separators=""): {indent} )* """) for idx in range(level): - pattern = pattern.format(replacer = repeated if idx < level-1 else no_braces_q, - indent = idx * " ", **locals()) + pattern = pattern.format(replacer=repeated if idx < level - 1 else no_braces_q, + indent=idx * " ", **locals()) return pattern.replace("C", "{").replace("D", "}") @@ -153,4 +100,11 @@ def with_metaclass(meta, *bases): return meta.__prepare__(name, bases) return type.__new__(metaclass, 'temporary_class', (), {}) + +# A handy tool that shows the current line number and indents. +def lno(level): + lineno = currentframe().f_back.f_lineno + spaces = level * " " + return f"{lineno}{spaces}" + # eof diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py index e685da166..fb4c9eeca 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py @@ -1,44 +1,8 @@ -# This Python file uses the following encoding: utf-8 -# It has been edited by fix-complaints.py . - -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +# flake8: noqa E:402 +# flake8: noqa F:401 """ loader.py @@ -64,119 +28,83 @@ import os import traceback import types -# On Python 2, we only have ImportError, which is way too coarse. -# When problems occour, please use Python 3, because it has the finer -# ModuleNotFoundError. - -try: - ModuleNotFoundError -except NameError: - ModuleNotFoundError = ImportError - -def _qualname(x): - return getattr(x, "__qualname__", x.__name__) - -# patching inspect's formatting to keep the word "typing": -def formatannotation(annotation, base_module=None): - # if getattr(annotation, '__module__', None) == 'typing': - # return repr(annotation).replace('typing.', '') - if isinstance(annotation, type): - name = _qualname(annotation) - if annotation.__module__ in ('builtins', base_module): - return name - return annotation.__module__ + '.' + name - return repr(annotation) - -# Note also that during the tests we have a different encoding that would -# break the Python license decorated files without an encoding line. # name used in signature.cpp def pyside_type_init(type_key, sig_strings): return parser.pyside_type_init(type_key, sig_strings) + # name used in signature.cpp def create_signature(props, key): return layout.create_signature(props, key) + # name used in signature.cpp def seterror_argument(args, func_name, info): return errorhandler.seterror_argument(args, func_name, info) + # name used in signature.cpp def make_helptext(func): return errorhandler.make_helptext(func) + # name used in signature.cpp def finish_import(module): return importhandler.finish_import(module) +# name used in signature.cpp +def feature_import(*args, **kwds): + # don't spend a stack level here for speed and compatibility + global feature_import + feature_import = feature.feature_import + return feature_import(*args, **kwds) + + +# name used in signature.cpp +def feature_imported(*args, **kwds): + # don't spend a stack level here for speed and compatibility + global feature_imported + feature_imported = feature.feature_imported + return feature_imported(*args, **kwds) + + +import builtins import signature_bootstrap -from shibokensupport import signature, __feature__ +from shibokensupport import signature, feature signature.get_signature = signature_bootstrap.get_signature # PYSIDE-1019: Publish the __feature__ dictionary. -__feature__.pyside_feature_dict = signature_bootstrap.pyside_feature_dict +feature.pyside_feature_dict = signature_bootstrap.pyside_feature_dict +builtins.__feature_import__ = signature_bootstrap.__feature_import__ del signature_bootstrap -def _get_modname(mod): - return mod.__spec__.name if getattr(mod, "__spec__", None) else mod.__name__ - -def _set_modname(mod, name): - if getattr(mod, "__spec__", None): - mod.__spec__.name = name - else: - mod.__name__ = name +is_pypy = hasattr(sys, "pypy_version_info") def put_into_package(package, module, override=None): # take the last component of the module name - name = (override if override else _get_modname(module)).rsplit(".", 1)[-1] - # allow access as {package}.typing + name = (override if override else module.__spec__.name).rsplit(".", 1)[-1] + # allow access as {package}.{name} if package: setattr(package, name, module) # put into sys.modules as a package to allow all import options - fullname = f"{_get_modname(package)}.{name}" if package else name - _set_modname(module, fullname) + fullname = f"{package.__spec__.name}.{name}" if package else name + module.__spec__.name = fullname # publish new dotted name in sys.modules sys.modules[fullname] = module -# Debug: used to inspect what each step loads -def list_modules(message): - ext_modules = {key:value for (key, value) in sys.modules.items() - if hasattr(value, "__file__")} - print("SYS.MODULES", message, len(sys.modules), len(ext_modules)) - for (name, module) in sorted(ext_modules.items()): - print(f" {name:23}", repr(module)[:70]) - - -orig_typing = True -import typing -import inspect -inspect.formatannotation = formatannotation - -# Fix the module names in typing if possible. This is important since -# the typing names should be I/O compatible, so that typing.Dict -# shows itself as "typing.Dict". -for name, obj in typing.__dict__.items(): - if hasattr(obj, "__module__"): - try: - obj.__module__ = "typing" - except (TypeError, AttributeError): - pass - -import shibokensupport -put_into_package(shibokensupport.signature, typing, "typing") -put_into_package(shibokensupport.signature, inspect, "inspect") - - def move_into_pyside_package(): + import shibokensupport import PySide6 try: import PySide6.support except ModuleNotFoundError: - PySide6.support = types.ModuleType("PySide6.support") - put_into_package(PySide6.support, __feature__) + # This can happen in the embedding case. + put_into_package(PySide6, shibokensupport, "support") + if not is_pypy: + put_into_package(PySide6.support, feature) put_into_package(PySide6.support, signature) put_into_package(PySide6.support.signature, mapping) put_into_package(PySide6.support.signature, errorhandler) @@ -185,9 +113,9 @@ def move_into_pyside_package(): put_into_package(PySide6.support.signature, parser) put_into_package(PySide6.support.signature, importhandler) put_into_package(PySide6.support.signature.lib, enum_sig) + put_into_package(PySide6.support.signature.lib, pyi_generator) + put_into_package(PySide6.support.signature.lib, tool) - put_into_package(None if orig_typing else PySide6.support.signature, typing) - put_into_package(PySide6.support.signature, inspect) from shibokensupport.signature import mapping from shibokensupport.signature import errorhandler @@ -196,20 +124,35 @@ from shibokensupport.signature import lib from shibokensupport.signature import parser from shibokensupport.signature import importhandler from shibokensupport.signature.lib import enum_sig +from shibokensupport.signature.lib import pyi_generator +from shibokensupport.signature.lib import tool + +import enum + +post_init = lambda: None # noqa E:731 default if "PySide6" in sys.modules: - # We publish everything under "PySide6.support.signature", again. + # We publish everything under "PySide6.support", again. move_into_pyside_package() - # PYSIDE-1019: Modify `__import__` to be `__feature__` aware. - # __feature__ is already in sys.modules, so this is actually no import + # PYSIDE-1502: Make sure that support can be imported. try: - import PySide6.support.__feature__ - sys.modules["__feature__"] = PySide6.support.__feature__ - PySide6.support.__feature__.original_import = __builtins__["__import__"] - __builtins__["__import__"] = PySide6.support.__feature__._import - # Maybe we should optimize that and change `__import__` from C, instead? + import PySide6.support except ModuleNotFoundError: - print("__feature__ could not be imported. " - "This is an unsolved PyInstaller problem.", file=sys.stderr) + print("PySide6.support could not be imported. " + "This is a serious configuration error.", file=sys.stderr) + raise + + def post_init(): + """ + This function needs to be called explicitly when all function pointers are set. + Doing this during import has bad side-effects when preloading the loader. + """ + # PYSIDE-1019: Modify `__import__` to be `__feature__` aware. + if not is_pypy: + # PYSIDE-535: Cannot enable __feature__ for various reasons. + import PySide6.support.feature + sys.modules["__feature__"] = PySide6.support.feature + builtins.__orig_import__ = builtins.__import__ + builtins.__import__ = builtins.__feature_import__ # end of file diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 2f4104945..944a928e6 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -1,41 +1,7 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +# flake8: noqa E:203 """ mapping.py @@ -47,23 +13,24 @@ The PySide modules are not loaded in advance, but only after they appear in sys.modules. This minimizes the loading overhead. """ -import sys -import struct import os +import struct +import sys +import typing + from pathlib import Path +from typing import TypeVar, Generic +from _imp import is_builtin -from shibokensupport.signature import typing -from shibokensupport.signature.typing import TypeVar, Generic -from shibokensupport.signature.lib.tool import with_metaclass class ellipsis(object): def __repr__(self): return "..." + ellipsis = ellipsis() -Point = typing.Tuple[float, float] +Point = typing.Tuple[int, int] Variant = typing.Any -ModelIndexList = typing.List[int] QImageCleanupFunction = typing.Callable # unfortunately, typing.Optional[t] expands to typing.Union[t, NoneType] @@ -75,7 +42,7 @@ _S = TypeVar("_S") MultiMap = typing.DefaultDict[str, typing.List[str]] # ulong_max is only 32 bit on windows. -ulong_max = 2*sys.maxsize+1 if len(struct.pack("L", 1)) != 4 else 0xffffffff +ulong_max = 2 * sys.maxsize + 1 if len(struct.pack("L", 1)) != 4 else 0xffffffff ushort_max = 0xffff GL_COLOR_BUFFER_BIT = 0x00004000 @@ -111,6 +78,7 @@ class _NotCalled(str): text = self if self.endswith(")") else self + "()" return eval(text, namespace) + USE_PEP563 = False # Note: we cannot know if this feature has been imported. # Otherwise it would be "sys.version_info[:2] >= (3, 7)". @@ -123,6 +91,7 @@ USE_PEP563 = False class Virtual(_NotCalled): pass + # Other types I simply could not find. class Missing(_NotCalled): # The string must be quoted, because the object does not exist. @@ -135,6 +104,7 @@ class Missing(_NotCalled): class Invalid(_NotCalled): pass + # Helper types class Default(_NotCalled): pass @@ -143,6 +113,7 @@ class Default(_NotCalled): class Instance(_NotCalled): pass + # Parameterized primitive variables class _Parameterized(object): def __init__(self, type): @@ -152,15 +123,18 @@ class _Parameterized(object): def __repr__(self): return f"{type(self).__name__}({self.type.__name__})" + # Mark the primitive variables to be moved into the result. class ResultVariable(_Parameterized): pass + # Mark the primitive variables to become Sequence, Iterable or List # (decided in the parser). class ArrayLikeVariable(_Parameterized): pass + StringList = ArrayLikeVariable(str) @@ -179,7 +153,7 @@ class Reloader(object): if getattr(mod, "__file__", None) and not Path(mod.__file__).is_dir(): ending = Path(mod.__file__).suffix return ending not in (".py", ".pyc", ".pyo", ".pyi") - return False + return bool(hasattr(mod, "__name__") and is_builtin(mod.__name__)) def update(self): """ @@ -217,23 +191,29 @@ def check_module(mod): mod_name = mod.__name__ raise ImportError(f"Module '{mod_name}' is not a binary module!") + update_mapping = Reloader().update type_map = {} namespace = globals() # our module's __dict__ type_map.update({ "...": ellipsis, + "Any": typing.Any, "bool": bool, "char": int, - "char*": str, - "char*const": str, "double": float, "float": float, "int": int, "List": ArrayLikeVariable, + "Optional": typing.Optional, "long": int, + "long long": int, + "nullptr": None, "PyCallable": typing.Callable, "PyObject": object, + "PyObject*": object, + "PyArrayObject": ArrayLikeVariable, # numpy + "PyPathLike": typing.Union[str, bytes, os.PathLike[str]], "PySequence": typing.Iterable, # important for numpy "PyTypeObject": type, "QChar": str, @@ -242,16 +222,26 @@ type_map.update({ "qint32": int, "qint64": int, "qint8": int, + "int16_t": int, + "int32_t": int, + "int64_t": int, + "int8_t": int, + "intptr_t": int, + "uintptr_t": int, "qintptr": int, "qsizetype": int, + "QFunctionPointer": int, "QList": ArrayLikeVariable, "qlonglong": int, "QMap": typing.Dict, + "QMultiHash": typing.Dict, + "QMultiMap": typing.Dict, "QPair": typing.Tuple, "qptrdiff": int, "qreal": float, "QSet": typing.Set, "QString": str, + "QLatin1String": str, "QStringView": str, "QStringList": StringList, "quint16": int, @@ -259,6 +249,11 @@ type_map.update({ "quint32": int, "quint64": int, "quint8": int, + "uint16_t": int, + "uint32_t": int, + "uint64_t": int, + "uint8_t": int, + "Union": typing.Union, "quintptr": int, "qulonglong": int, "QVariant": Variant, @@ -270,7 +265,10 @@ type_map.update({ "signed long": int, "std.list": typing.List, "std.map": typing.Dict, + "std.nullptr_t": NoneType, "std.pair": typing.Tuple, + "std.string": str, + "std.wstring": str, "std.vector": typing.List, "str": str, "true": True, @@ -280,17 +278,17 @@ type_map.update({ "uint": int, "ulong": int, "ULONG_MAX": ulong_max, - "unsigned char": int, # 5.9 + "UINT64_MAX": 0xffffffff, + "unsigned char": int, # 5.9 "unsigned char*": str, "unsigned int": int, - "unsigned long int": int, # 5.6, RHEL 6.6 + "unsigned long int": int, # 5.6, RHEL 6.6 "unsigned long long": int, "unsigned long": int, - "unsigned short int": int, # 5.6, RHEL 6.6 + "unsigned short int": int, # 5.6, RHEL 6.6 "unsigned short": int, - "Unspecified": None, "ushort": int, - "void": int, # be more specific? + "void": int, # be more specific? "WId": WId, "zero(bytes)": b"", "zero(Char)": 0, @@ -299,7 +297,12 @@ type_map.update({ "zero(object)": None, "zero(str)": "", "zero(typing.Any)": None, - }) + "zero(Any)": None, + # This can be refined by importing numpy.typing optionally, but better than nothing. + "numpy.ndarray": typing.List[typing.Any], + "std.array[int, 4]": typing.List[int], + "std.array[float, 4]": typing.List[float] +}) type_map.update({ # Handling variables declared as array: @@ -311,21 +314,29 @@ type_map.update({ "array long long*" : ArrayLikeVariable(int), "array long*" : ArrayLikeVariable(int), "array short*" : ArrayLikeVariable(int), - "array signed char*" : bytes, - "array unsigned char*" : bytes, + "array signed char*" : typing.Union[bytes, bytearray, memoryview], + "array unsigned char*" : typing.Union[bytes, bytearray, memoryview], "array unsigned int*" : ArrayLikeVariable(int), "array unsigned short*" : ArrayLikeVariable(int), - }) + # PYSIDE-1646: New macOS primitive types + "array int8_t*" : ArrayLikeVariable(int), + "array uint8_t*" : ArrayLikeVariable(int), + "array int16_t*" : ArrayLikeVariable(int), + "array uint16_t*" : ArrayLikeVariable(int), + "array int32_t*" : ArrayLikeVariable(int), + "array uint32_t*" : ArrayLikeVariable(int), + "array intptr_t*" : ArrayLikeVariable(int), +}) type_map.update({ # Special cases: - "char*" : bytes, - "QChar*" : bytes, + "char*" : typing.Union[bytes, bytearray, memoryview], + "QChar*" : typing.Union[bytes, bytearray, memoryview], "quint32*" : int, # only for QRandomGenerator "quint8*" : bytearray, # only for QCborStreamReader and QCborValue - "uchar*" : bytes, - "unsigned char*": bytes, - }) + "uchar*" : typing.Union[bytes, bytearray, memoryview], + "unsigned char*": typing.Union[bytes, bytearray, memoryview], +}) type_map.update({ # Handling variables that are returned, eventually as Tuples: @@ -339,6 +350,7 @@ type_map.update({ "qint32*" : ResultVariable(int), "qint64*" : ResultVariable(int), "qreal*" : ResultVariable(float), + "qsizetype*" : ResultVariable(int), "QString*" : ResultVariable(str), "qintptr*" : ResultVariable(int), "quintptr*" : ResultVariable(int), @@ -346,11 +358,26 @@ type_map.update({ "uint*" : ResultVariable(int), "unsigned int*" : ResultVariable(int), "QStringList*" : ResultVariable(StringList), - }) +}) + + +type_map.update({ + # Hack, until improving the parser: + "[typing.Any]" : [typing.Any], + "[typing.Any,typing.Any]" : [typing.Any, typing.Any], + "None" : None, +}) + # PYSIDE-1328: We need to handle "self" explicitly. type_map.update({ - "self" : "self", + "self" : "self", + "cls" : "cls", +}) + +# PYSIDE-1538: We need to treat "std::optional" accordingly. +type_map.update({ + "std.optional": typing.Optional, }) @@ -363,6 +390,7 @@ def init_Shiboken(): }) return locals() + def init_minimal(): type_map.update({ "MinBool": bool, @@ -375,16 +403,18 @@ def init_sample(): type_map.update({ "char": int, "char**": typing.List[str], + "const char*": str, "Complex": complex, "double": float, + "ByteArray&": typing.Union[bytes, bytearray, memoryview], "Foo.HANDLE": int, "HANDLE": int, "Null": None, - "nullptr": None, "ObjectType.Identifier": Missing("sample.ObjectType.Identifier"), "OddBool": bool, "PStr": str, "PyDate": datetime.date, + "PyBuffer": typing.Union[bytes, bytearray, memoryview], "sample.bool": bool, "sample.char": int, "sample.double": float, @@ -395,6 +425,7 @@ def init_sample(): "sample.Photon.TemplateBase[Photon.IdentityType]": sample.Photon.ValueIdentity, "sample.Point": Point, "sample.PStr": str, + "SampleNamespace.InValue.ZeroIn": 0, "sample.unsigned char": int, "std.size_t": int, "std.string": str, @@ -419,6 +450,7 @@ def init_smart(): # This missing type should be defined in module smart. We cannot set it to Missing() # because it is a container type. Therefore, we supply a surrogate: global SharedPtr + class SharedPtr(Generic[_S]): __module__ = "smart" smart.SharedPtr = SharedPtr @@ -430,9 +462,10 @@ def init_smart(): # The PySide Part def init_PySide6_QtCore(): - from PySide6.QtCore import Qt, QUrl, QDir - from PySide6.QtCore import QRect, QSize, QPoint, QLocale, QByteArray - from PySide6.QtCore import QMarginsF # 5.9 + from PySide6.QtCore import Qt, QUrl, QDir, QKeyCombination + from PySide6.QtCore import QRect, QRectF, QSize, QPoint, QLocale, QByteArray + from PySide6.QtCore import QMarginsF # 5.9 + from PySide6.QtCore import SignalInstance try: # seems to be not generated by 5.9 ATM. from PySide6.QtCore import Connection @@ -442,56 +475,69 @@ def init_PySide6_QtCore(): "' '": " ", "'%'": "%", "'g'": "g", - "4294967295UL": 4294967295, # 5.6, RHEL 6.6 + "4294967295UL": 4294967295, # 5.6, RHEL 6.6 "CheckIndexOption.NoOption": Instance( - "PySide6.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 + "PySide6.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 "DescriptorType(-1)": int, # Native handle of QSocketDescriptor "false": False, "list of QAbstractAnimation": typing.List[PySide6.QtCore.QAbstractAnimation], "long long": int, "size_t": int, - "NULL": None, # 5.6, MSVC - "nullptr": None, # 5.9 + "NULL": None, # 5.6, MSVC + "nullptr": None, # 5.9 + "PyBuffer": typing.Union[bytes, bytearray, memoryview], "PyByteArray": bytearray, - "PyBytes": bytes, + "PyBytes": typing.Union[bytes, bytearray, memoryview], + "PyTuple": typing.Tuple, "QDeadlineTimer(QDeadlineTimer.Forever)": Instance("PySide6.QtCore.QDeadlineTimer"), "PySide6.QtCore.QUrl.ComponentFormattingOptions": - PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? + PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? "PyUnicode": typing.Text, + "QByteArrayView": QByteArray, "Q_NULLPTR": None, + "QCalendar.Unspecified": PySide6.QtCore.QCalendar.Unspecified, + "QCborTag(-1)": ulong_max, "QDir.Filters(AllEntries | NoDotAndDotDot)": Instance( "QDir.Filters(QDir.AllEntries | QDir.NoDotAndDotDot)"), "QDir.SortFlags(Name | IgnoreCase)": Instance( "QDir.SortFlags(QDir.Name | QDir.IgnoreCase)"), - "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? + "QEvent.Type.None": None, + "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? "QGenericArgument()": ellipsis, "QGenericArgument(0)": ellipsis, - "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC - "QGenericArgument(nullptr)": ellipsis, # 5.10 + "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC + "QGenericArgument(nullptr)": ellipsis, # 5.10 "QGenericArgument(Q_NULLPTR)": ellipsis, "QJsonObject": typing.Dict[str, PySide6.QtCore.QJsonValue], - "QModelIndex()": Invalid("PySide6.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! - "QModelIndexList": ModelIndexList, - "QModelIndexList": ModelIndexList, + "QModelIndex()": Invalid("PySide6.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! + "QModelIndexList": typing.List[PySide6.QtCore.QModelIndex], + "PySideSignalInstance": SignalInstance, "QString()": "", + "Flag.Default": Instance("PySide6.QtCore.QStringConverterBase.Flags"), "QStringList()": [], "QStringRef": str, "QStringRef": str, - "Qt.HANDLE": int, # be more explicit with some constants? + "Qt.HANDLE": int, # be more explicit with some constants? "QUrl.FormattingOptions(PrettyDecoded)": Instance( "QUrl.FormattingOptions(QUrl.PrettyDecoded)"), "QVariant()": Invalid(Variant), - "QVariant.Type": type, # not so sure here... - "QVariantMap": typing.Dict[str, Variant], + "QVariant.Type": type, # not so sure here... "QVariantMap": typing.Dict[str, Variant], + "std.chrono.seconds{5}" : ellipsis, }) try: type_map.update({ - "PySide6.QtCore.QMetaObject.Connection": PySide6.QtCore.Connection, # wrong! + "PySide6.QtCore.QMetaObject.Connection": PySide6.QtCore.Connection, # wrong! }) except AttributeError: # this does not exist on 5.9 ATM. pass + + # special case - char* can either be 'bytes' or 'str'. The default is 'bytes'. + # Here we manually set it to map to 'str'. + type_map.update({("PySide6.QtCore.QObject.setProperty", "char*"): str}) + type_map.update({("PySide6.QtCore.QObject.property", "char*"): str}) + return locals() @@ -506,37 +552,48 @@ def init_PySide6_QtConcurrent(): def init_PySide6_QtGui(): - from PySide6.QtGui import QPageLayout, QPageSize # 5.12 macOS + from PySide6.QtGui import QPageLayout, QPageSize # 5.12 macOS type_map.update({ "0.0f": 0.0, "1.0f": 1.0, "GL_COLOR_BUFFER_BIT": GL_COLOR_BUFFER_BIT, "GL_NEAREST": GL_NEAREST, "int32_t": int, - "QPixmap()": Default("PySide6.QtGui.QPixmap"), # can't create without qApp - "QPlatformSurface*": int, # a handle - "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? + "HBITMAP": int, + "HICON": int, + "HMONITOR": int, + "HRGN": int, + "QPixmap()": Default("PySide6.QtGui.QPixmap"), # can't create without qApp + "QPlatformSurface*": int, # a handle + "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? "uint32_t": int, "uint8_t": int, "USHRT_MAX": ushort_max, }) + + # special case - char* can either be 'bytes' or 'str'. The default is 'bytes'. + # Here we manually set it to map to 'str'. + type_map.update({("PySide6.QtGui.QPixmap.save", "char*"): str}) + return locals() def init_PySide6_QtWidgets(): - from PySide6.QtWidgets import QWidget, QMessageBox, QStyleOption, QStyleHintReturn, QStyleOptionComplex - from PySide6.QtWidgets import QGraphicsItem, QStyleOptionGraphicsItem # 5.9 + from PySide6.QtWidgets import (QWidget, QMessageBox, QStyleOption, + QStyleHintReturn, QStyleOptionComplex, + QGraphicsItem, QStyleOptionGraphicsItem) type_map.update({ "QMessageBox.StandardButtons(Yes | No)": Instance( "QMessageBox.StandardButtons(QMessageBox.Yes | QMessageBox.No)"), "QWidget.RenderFlags(DrawWindowBackground | DrawChildren)": Instance( "QWidget.RenderFlags(QWidget.DrawWindowBackground | QWidget.DrawChildren)"), - "SH_Default": QStyleHintReturn.SH_Default, - "SO_Complex": QStyleOptionComplex.SO_Complex, - "SO_Default": QStyleOption.SO_Default, "static_cast<Qt.MatchFlags>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( "Qt.MatchFlags(Qt.MatchExactly | Qt.MatchCaseSensitive)"), - "Type": PySide6.QtWidgets.QListWidgetItem.Type, + "static_cast<Qt.MatchFlag>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( + "Qt.MatchFlag(Qt.MatchExactly | Qt.MatchCaseSensitive)"), + "QListWidgetItem.ItemType.Type": PySide6.QtWidgets.QListWidgetItem.Type, + "QTableWidgetItem.ItemType.Type": PySide6.QtWidgets.QTableWidgetItem.Type, + "QTreeWidgetItem.ItemType.Type": PySide6.QtWidgets.QTreeWidgetItem.Type, }) return locals() @@ -544,14 +601,14 @@ def init_PySide6_QtWidgets(): def init_PySide6_QtSql(): from PySide6.QtSql import QSqlDatabase type_map.update({ - "QLatin1String(defaultConnection)": QSqlDatabase.defaultConnection, - "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here... + "QLatin1StringView(QSqlDatabase.defaultConnection)": QSqlDatabase.defaultConnection, + "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here... }) return locals() def init_PySide6_QtNetwork(): - from PySide6.QtNetwork import QNetworkRequest + from PySide6.QtNetwork import QNetworkRequest, QHostAddress best_structure = typing.OrderedDict if getattr(typing, "OrderedDict", None) else typing.Dict type_map.update({ "QMultiMap[PySide6.QtNetwork.QSsl.AlternativeNameEntryType, QString]": @@ -565,32 +622,11 @@ def init_PySide6_QtNetwork(): return locals() -def init_PySide6_QtXmlPatterns(): - from PySide6.QtXmlPatterns import QXmlName - type_map.update({ - "QXmlName.NamespaceCode": Missing("PySide6.QtXmlPatterns.QXmlName.NamespaceCode"), - "QXmlName.PrefixCode": Missing("PySide6.QtXmlPatterns.QXmlName.PrefixCode"), - }) - return locals() - - -def init_PySide6_QtMultimedia(): - import PySide6.QtMultimediaWidgets - # Check if foreign import is valid. See mapping.py in shiboken6. - check_module(PySide6.QtMultimediaWidgets) - type_map.update({ - "QGraphicsVideoItem": PySide6.QtMultimediaWidgets.QGraphicsVideoItem, - "qint64": int, - "QVideoWidget": PySide6.QtMultimediaWidgets.QVideoWidget, - }) - return locals() - - def init_PySide6_QtOpenGL(): type_map.update({ "GLbitfield": int, "GLenum": int, - "GLfloat": float, # 5.6, MSVC 15 + "GLfloat": float, # 5.6, MSVC 15 "GLint": int, "GLuint": int, }) @@ -599,8 +635,7 @@ def init_PySide6_QtOpenGL(): def init_PySide6_QtQml(): type_map.update({ - "QJSValueList()": [], - "QVariantHash()": typing.Dict[str, Variant], # from 5.9 + "VolatileBool": PySide6.QtQml.VolatileBool, }) return locals() @@ -609,35 +644,47 @@ def init_PySide6_QtQuick(): type_map.update({ "PySide6.QtQuick.QSharedPointer[PySide6.QtQuick.QQuickItemGrabResult]": PySide6.QtQuick.QQuickItemGrabResult, - "UnsignedShortType": int, + "QSGGeometry.Type.UnsignedShortType": int, }) return locals() -def init_PySide6_QtScript(): +def init_PySide6_QtTest(): + from PySide6.QtCore import SignalInstance type_map.update({ - "QScriptValueList()": [], + "PySideSignalInstance": SignalInstance, + "PySide6.QtTest.QTest.PySideQTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, + "PySide6.QtTest.QTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, }) return locals() -def init_PySide6_QtTest(): +# from 5.12, macOS +def init_PySide6_QtDataVisualization(): + from PySide6.QtDataVisualization import (QBarDataItem, QSurfaceDataItem) + QBarDataRow = typing.List[QBarDataItem] + QBarDataArray = typing.List[QBarDataRow] + QSurfaceDataRow = typing.List[QSurfaceDataItem] + QSurfaceDataArray = typing.List[QSurfaceDataRow] type_map.update({ - "PySide6.QtTest.QTest.PySideQTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, - "PySide6.QtTest.QTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, + "100.0f": 100.0, + "QBarDataArray": QBarDataArray, + "QBarDataArray*": QBarDataArray, + "QSurfaceDataArray": QSurfaceDataArray, + "QSurfaceDataArray*": QSurfaceDataArray, }) return locals() -# from 5.6, MSVC -def init_PySide6_QtWinExtras(): + +def init_PySide6_QtBluetooth(): type_map.update({ - "QList< QWinJumpListItem* >()": [], + "QVariant*": object, }) return locals() -# from 5.12, macOS -def init_PySide6_QtDataVisualization(): - from PySide6.QtDataVisualization import (QBarDataItem, QSurfaceDataItem) + +def init_PySide6_QtGraphs(): + from PySide6.QtGraphs import (QBarDataItem, QSurfaceDataItem) QBarDataRow = typing.List[QBarDataItem] QBarDataArray = typing.List[QBarDataRow] QSurfaceDataRow = typing.List[QSurfaceDataItem] @@ -652,9 +699,20 @@ def init_PySide6_QtDataVisualization(): return locals() +def init_PySide6_QtHttpServer(): + type_map.update({ + "qMakePair(1u, 1u)": (1, 1), + }) + return locals() + + def init_testbinding(): type_map.update({ "testbinding.PySideCPP2.TestObjectWithoutNamespace": testbinding.TestObjectWithoutNamespace, + "testbinding.FlagsNamespace.Options": testbinding.Option, + "FlagsNamespace.Option.NoOptions": 0, + "StdIntList": typing.List[int], + 'Str("")': str(""), }) return locals() diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py index d737c2a54..9b48ab442 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -1,52 +1,19 @@ -############################################################################# -## -## Copyright (C) 2019 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import sys +import ast +import enum +import keyword +import os import re +import sys +import typing import warnings -import types -import keyword -import functools + +from types import SimpleNamespace from shibokensupport.signature.mapping import (type_map, update_mapping, - namespace, typing, _NotCalled, ResultVariable, ArrayLikeVariable) -from shibokensupport.signature.lib.tool import (SimpleNamespace, - build_brace_pattern) + namespace, _NotCalled, ResultVariable, ArrayLikeVariable) # noqa E:128 +from shibokensupport.signature.lib.tool import build_brace_pattern _DEBUG = False LIST_KEYWORDS = False @@ -71,6 +38,65 @@ guesses, we provide an entry in 'type_map' that resolves it. In effect, 'type_map' maps text to real Python objects. """ + +def _get_flag_enum_option(): + from shiboken6 import (__version_info__ as ver, # noqa F:401 + __minimum_python_version__ as pyminver, + __maximum_python_version__ as pymaxver) + + # PYSIDE-1735: Use the new Enums per default if version is >= 6.4 + # This decides between delivered vs. dev versions. + # When 6.4 is out, the switching mode will be gone. + flag = ver[:2] >= (6, 4) + envname = "PYSIDE6_OPTION_PYTHON_ENUM" + sysname = envname.lower() + opt = os.environ.get(envname) + if opt: + opt = opt.lower() + if opt in ("yes", "on", "true"): + flag = True + elif opt in ("no", "off", "false"): + flag = False + else: + # instead of a simple int() conversion, let's allow for "0xf" or "0b1111" + try: + flag = ast.literal_eval(opt) + except Exception: + flag = False # turn a forbidden option into an error + elif hasattr(sys, sysname): + opt2 = flag = getattr(sys, sysname) + if not isinstance(flag, int): + flag = False # turn a forbidden option into an error + p = f"\n *** Python is at version {'.'.join(map(str, pyminver or (0,)))} now." + q = f"\n *** PySide is at version {'.'.join(map(str, ver[:2]))} now." + # _PepUnicode_AsString: Fix a broken promise + if pyminver and pyminver >= (3, 10): + warnings.warn(f"{p} _PepUnicode_AsString can now be replaced by PyUnicode_AsUTF8! ***") + # PYSIDE-1960: Emit a warning when we may remove bufferprocs_py37.(cpp|h) + if pyminver and pyminver >= (3, 11): + warnings.warn(f"{p} The files bufferprocs_py37.(cpp|h) should be removed ASAP! ***") + # PYSIDE-1735: Emit a warning when we should maybe evict forgiveness mode + if ver[:2] >= (7, 0): + warnings.warn(f"{q} Please drop Enum forgiveness mode in `mangled_type_getattro` ***") + # PYSIDE-2404: Emit a warning when we should drop uppercase offset constants + if ver[:2] >= (7, 0): + warnings.warn(f"{q} Please drop uppercase type offsets in `copyOffsetEnumStream` ***") + # normalize the sys attribute + setattr(sys, sysname, flag) + os.environ[envname] = str(flag) + if int(flag) == 0: + raise RuntimeError(f"Old Enums are no longer supported. int({opt or opt2}) evaluates to 0)") + return flag + + +class EnumSelect(enum.Enum): + # PYSIDE-1735: Here we could save object.value expressions by using IntEnum. + # But it is nice to use just an Enum for selecting Enum version. + OLD = 1 + NEW = 2 + SELECTION = NEW if _get_flag_enum_option() else OLD + + def dprint(*args, **kw): if _DEBUG: import pprint @@ -81,6 +107,7 @@ def dprint(*args, **kw): _cache = {} + def _parse_arglist(argstr): # The following is a split re. The string is broken into pieces which are # between the recognized strings. Because the re has groups, both the @@ -103,19 +130,21 @@ def _parse_line(line): ( -> (?P<returntype> .*) )? # the optional return type $ """ - ret = SimpleNamespace(**re.match(line_re, line, re.VERBOSE).groupdict()) + matches = re.match(line_re, line, re.VERBOSE) + if not matches: + raise SystemError("Error parsing line:", repr(line)) + ret = SimpleNamespace(**matches.groupdict()) # PYSIDE-1095: Handle arbitrary default expressions argstr = ret.arglist.replace("->", ".deref.") arglist = _parse_arglist(argstr) args = [] for idx, arg in enumerate(arglist): tokens = arg.split(":") - if len(tokens) < 2: - if idx == 0 and tokens[0] == "self": - tokens = 2 * tokens # "self: self" - else: - # This should never happen again (but who knows?) - raise SystemError(f'Invalid argument "{arg}" in "{line}".') + if len(tokens) < 2 and idx == 0 and tokens[0] in ("self", "cls"): + tokens = 2 * tokens # "self: self" + if len(tokens) != 2: + # This should never happen again (but who knows?) + raise SystemError(f'Invalid argument "{arg}" in "{line}".') name, ann = tokens if name in keyword.kwlist: if LIST_KEYWORDS: @@ -138,15 +167,57 @@ def _parse_line(line): return vars(ret) +def _using_snake_case(): + # Note that this function should stay here where we use snake_case. + if "PySide6.QtCore" not in sys.modules: + return False + from PySide6.QtCore import QDir + return hasattr(QDir, "cd_up") + + +def _handle_instance_fixup(thing): + """ + Default expressions using instance methods like + (...,device=QPointingDevice.primaryPointingDevice()) + need extra handling for snake_case. These are: + QPointingDevice.primaryPointingDevice() + QInputDevice.primaryKeyboard() + QKeyCombination.fromCombined(0) + QSslConfiguration.defaultConfiguration() + """ + match = re.search(r"\w+\(", thing) + if not match: + return thing + start, stop = match.start(), match.end() - 1 + pre, func, args = thing[:start], thing[start:stop], thing[stop:] + if func[0].isupper() or func.startswith("gl") and func[2:3].isupper(): + return thing + # Now convert this string to snake case. + snake_func = "" + for idx, char in enumerate(func): + if char.isupper(): + if idx and func[idx - 1].isupper(): + # two upper chars are forbidden + return thing + snake_func += f"_{char.lower()}" + else: + snake_func += char + return f"{pre}{snake_func}{args}" + + def make_good_value(thing, valtype): + # PYSIDE-1019: Handle instance calls (which are really seldom) + if "(" in thing and _using_snake_case(): + thing = _handle_instance_fixup(thing) try: if thing.endswith("()"): thing = f'Default("{thing[:-2]}")' else: - ret = eval(thing, namespace) + # PYSIDE-1735: Use explicit globals and locals because of a bug in VsCode + ret = eval(thing, globals(), namespace) if valtype and repr(ret).startswith("<"): thing = f'Instance("{thing}")' - return eval(thing, namespace) + return eval(thing, globals(), namespace) except Exception: pass @@ -170,12 +241,14 @@ def try_to_guess(thing, valtype): return ret return None + def get_name(thing): if isinstance(thing, type): return getattr(thing, "__qualname__", thing.__name__) else: return thing.__name__ + def _resolve_value(thing, valtype, line): if thing in ("0", "None") and valtype: if valtype.startswith("PySide6.") or valtype.startswith("typing."): @@ -194,7 +267,7 @@ def _resolve_value(thing, valtype, line): if res is not None: type_map[thing] = res return res - warnings.warn(f"""pyside_type_init: + warnings.warn(f"""pyside_type_init:_resolve_value UNRECOGNIZED: {thing!r} OFFENDING LINE: {line!r} @@ -218,40 +291,44 @@ def _resolve_arraytype(thing, line): def to_string(thing): + # This function returns a string that creates the same object. + # It is absolutely crucial that str(eval(thing)) == str(thing), + # i.e. it must be an idempotent mapping. if isinstance(thing, str): return thing - if hasattr(thing, "__name__"): - dot = "." in str(thing) + if hasattr(thing, "__name__") and thing.__module__ != "typing": + m = thing.__module__ + dot = "." in str(thing) or m not in (thing.__qualname__, "builtins") name = get_name(thing) - return thing.__module__ + "." + name if dot else name + ret = m + "." + name if dot else name + assert (eval(ret, globals(), namespace)) + return ret # Note: This captures things from the typing module: return str(thing) matrix_pattern = "PySide6.QtGui.QGenericMatrix" + def handle_matrix(arg): - n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(","))) + n, m, typstr = tuple(map(lambda x: x.strip(), arg.split(","))) assert typstr == "float" result = f"PySide6.QtGui.QMatrix{n}x{m}" - return eval(result, namespace) - + return eval(result, globals(), namespace) -debugging_aid = """ -from inspect import currentframe - -def lno(level): - lineno = currentframe().f_back.f_lineno - spaces = level * " " - return f"{lineno}{spaces}" -""" +def _resolve_type(thing, line, level, var_handler, func_name=None): + # manual set of 'str' instead of 'bytes' + if func_name: + new_thing = (func_name, thing) + if new_thing in type_map: + return type_map[new_thing] -def _resolve_type(thing, line, level, var_handler): # Capture total replacements, first. Happens in # "PySide6.QtCore.QCborStreamReader.StringResult[PySide6.QtCore.QByteArray]" if thing in type_map: return type_map[thing] + # Now the nested structures are handled. if "[" in thing: # handle primitive arrays @@ -262,20 +339,28 @@ def _resolve_type(thing, line, level, var_handler): # Special case: Handle the generic matrices. if contr == matrix_pattern: return handle_matrix(thing) - contr = var_handler(_resolve_type(contr, line, level+1, var_handler)) + contr = var_handler(_resolve_type(contr, line, level + 1, var_handler)) if isinstance(contr, _NotCalled): raise SystemError("Container types must exist:", repr(contr)) contr = to_string(contr) pieces = [] for part in _parse_arglist(thing): - part = var_handler(_resolve_type(part, line, level+1, var_handler)) + part = var_handler(_resolve_type(part, line, level + 1, var_handler)) if isinstance(part, _NotCalled): # fix the tag (i.e. "Missing") by repr part = repr(part) pieces.append(to_string(part)) thing = ", ".join(pieces) result = f"{contr}[{thing}]" - return eval(result, namespace) + # PYSIDE-1538: Make sure that the eval does not crash. + try: + return eval(result, globals(), namespace) + except Exception: + warnings.warn(f"""pyside_type_init:_resolve_type + + UNRECOGNIZED: {result!r} + OFFENDING LINE: {line!r} + """, RuntimeWarning) return _resolve_value(thing, None, line) @@ -328,16 +413,20 @@ def calculate_props(line): name, ann = tup[:2] if ann == "...": name = "*args" if name.startswith("arg_") else "*" + name - # copy the pathed fields back + # copy the patched fields back ann = 'nullptr' # maps to None tup = name, ann arglist[idx] = tup - annotations[name] = _resolve_type(ann, line, 0, handle_argvar) + annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname) if len(tup) == 3: default = _resolve_value(tup[2], ann, line) _defaults.append(default) defaults = tuple(_defaults) returntype = parsed.returntype + if isinstance(returntype, str) and returntype.startswith("("): + # PYSIDE-1588: Simplify the handling of returned tuples for now. + # Later we might create named tuples, instead. + returntype = "Tuple" # PYSIDE-1383: We need to handle `None` explicitly. annotations["return"] = (_resolve_type(returntype, line, 0, handle_retvar) if returntype is not None else None) @@ -345,9 +434,9 @@ def calculate_props(line): props.defaults = defaults props.kwdefaults = {} props.annotations = annotations - props.varnames = varnames = tuple(tup[0] for tup in arglist) + props.varnames = tuple(tup[0] for tup in arglist) funcname = parsed.funcname - shortname = funcname[funcname.rindex(".")+1:] + shortname = funcname[funcname.rindex(".") + 1:] props.name = shortname props.multi = parsed.multi fix_variables(props, line) @@ -378,7 +467,9 @@ def fix_variables(props, line): if not isinstance(ann, ResultVariable): continue # We move the variable to the end and remove it. - retvars.append(ann.type) + # PYSIDE-1409: If the variable was the first arg, we move it to the front. + # XXX This algorithm should probably be replaced by more introspection. + retvars.insert(0 if idx == 0 else len(retvars), ann.type) deletions.append(idx) del annos[name] for idx in reversed(deletions): @@ -391,7 +482,6 @@ def fix_variables(props, line): else: diff -= 1 if retvars: - rvs = [] retvars = list(handle_retvar(rv) if isinstance(rv, ArrayLikeVariable) else rv for rv in retvars) if len(retvars) == 1: @@ -399,7 +489,7 @@ def fix_variables(props, line): else: retvars_str = ", ".join(map(to_string, retvars)) typestr = f"typing.Tuple[{retvars_str}]" - returntype = eval(typestr, namespace) + returntype = eval(typestr, globals(), namespace) props.annotations["return"] = returntype props.varnames = tuple(varnames) props.defaults = tuple(defaults) diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json index 4cbec4824..0f05aea8b 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json @@ -3,7 +3,7 @@ "Name": "Python", "QDocModule": "QtForPython", "QtUsage": "Used for Qt for Python in the signature extension.", - "Description": "Qt for Python is an add-on for Python. The signature packages of PySide uses certain copied and adapted source files (backport_inspect.py, typing27.py). See the folder sources/shiboken6/files.dir/shibokensupport/signature .", + "Description": "Qt for Python is an add-on for Python. The signature packages of PySide uses certain copied and adapted source files. See the folder sources/shiboken6/files.dir/shibokensupport .", "Homepage": "http://www.python.org/", "Version": "3.7.0", "LicenseId": "Python-2.0", |