diff options
Diffstat (limited to 'sources/shiboken6/shibokenmodule/files.dir/shibokensupport')
17 files changed, 3030 insertions, 0 deletions
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py new file mode 100644 index 000000000..e54bec75a --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..f7190b12f --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/fix-complaints.py @@ -0,0 +1,59 @@ +# 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 + +This module fixes the buildbot messages of external python files. +Run it once after copying a new version. It is idem-potent, unless +you are changing messages (what I did, of course :-) . +""" + +import glob +from pathlib import Path + +patched_file_patterns = "backport_inspect.py typing27.py python_minilib_*.py" + +offending_words = { + "behavio""ur": "behavior", + "at""least": "at_least", + "reali""sed": "realized", +} + +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() + dup = lines[:] + for idx, line in enumerate(lines): + for word, repl in offending_words.items(): + if word in line: + lines[idx] = line.replace(word, repl) + print(f"line:{line!r} {word!r}->{repl!r}") + if lines[0].strip() != utf8_line.strip(): + lines[:0] = [utf8_line, "\n"] + if lines[1] != marker_line: + lines[1:1] = marker_line + if lines != dup: + with open(fname, "w") as f: + f.write("".join(lines)) + + +def doit(): + dirname = Path(__file__).parent + patched_files = [] + for name in patched_file_patterns.split(): + pattern = dirname / name + patched_files += glob.glob(pattern) + for fname in patched_files: + print("Working on", fname) + patch_file(fname) + + +if __name__ == "__main__": + doit() + +# end of file 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/PSF-3.7.0.txt b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/PSF-3.7.0.txt new file mode 100644 index 000000000..be42010dd --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/PSF-3.7.0.txt @@ -0,0 +1,43 @@ +PSF LICENSE AGREEMENT FOR PYTHON 3.7.0 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.7.0 software in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby + grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + analyze, test, perform and/or display publicly, prepare derivative works, + distribute, and otherwise use Python 3.7.0 alone or in any derivative + version, provided, however, that PSF's License Agreement and PSF's notice of + copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights + Reserved" are retained in Python 3.7.0 alone or in any derivative version + prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or + incorporates Python 3.7.0 or any part thereof, and wants to make the + derivative work available to others as provided herein, then Licensee hereby + agrees to include in any such work a brief summary of the changes made to Python + 3.7.0. + +4. PSF is making Python 3.7.0 available to Licensee on an "AS IS" basis. + PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF + EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR + WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE + USE OF PYTHON 3.7.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.0 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.0, OR ANY DERIVATIVE + THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach of + its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship + of agency, partnership, or joint venture between PSF and Licensee. This License + Agreement does not grant permission to use PSF trademarks or trade name in a + trademark sense to endorse or promote products or services of Licensee, or any + third party. + +8. By copying, installing or otherwise using Python 3.7.0, Licensee agrees + to be bound by the terms and conditions of this License Agreement. diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py new file mode 100644 index 000000000..bebf56c7e --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..c2a19efef --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py @@ -0,0 +1,150 @@ +# 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 + +This module handles the TypeError messages which were previously +produced by the generated C code. + +This version is at least consistent with the signatures, which +are created by the same module. + +Experimentally, we are trying to guess those errors which are +just the wrong number of elements in an iterator. +At the moment, it is unclear whether the information given is +enough to produce a useful ValueError. + +This matter will be improved in a later version. +""" + +import collections.abc +import typing + +from shibokensupport.signature import get_signature +from shibokensupport.signature.mapping import namespace +from textwrap import dedent + + +def qt_isinstance(inst, the_type): + if the_type == 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(f"FIXME qt_isinstance({inst}, {the_type}):", e) + return False + + +def matched_type(args, sigs): + for sig in sigs: + params = list(sig.parameters.values()) + if len(args) > len(params): + continue + if len(args) < len(params): + k = len(args) + if params[k].default is params[k].empty: + # this is a necessary parameter, so it fails. + continue + if all(qt_isinstance(arg, param.annotation) for arg, param in zip(args, params)): + return sig + return None + + +def seterror_argument(args, func_name, info): + func = None + try: + func = eval(func_name, namespace) + except Exception as e: + 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(): + msg = f"{func_name}(): got multiple values for keyword argument '{info}'" + else: + 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 + sigs = get_signature(func, "typeerror") + if not sigs: + msg = f"{func_name}({args}) is wrong (missing signature)" + return TypeError, msg + if type(sigs) != list: + sigs = [sigs] + if type(args) != tuple: + args = (args,) + # temp! + found = matched_type(args, sigs) + if found: + msg = dedent(f""" + {func_name!r} called with wrong argument values: + {func_name}{args} + Found signature: + {func_name}{found} + """).strip() + return ValueError, msg + type_str = ", ".join(type(arg).__name__ for arg in args) + msg = dedent(f""" + {func_name!r} called with wrong argument types: + {func_name}({type_str}) + Supported signatures: + """).strip() + for sig in sigs: + msg += f"\n {func_name}{sig}" + # We don't raise the error here, to avoid the loader in the traceback. + return TypeError, msg + + +def check_string_type(s): + return isinstance(s, str) + + +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 + if type(sigs) != list: + sigs = [sigs] + try: + func_name = func.__name__ + except AttributeError: + func_name = func.__func__.__name__ + sigtext = "\n".join(func_name + str(sig) for sig in sigs) + msg = f"{sigtext}\n\n{existing_doc}" if check_string_type(existing_doc) else sigtext + return msg + +# end of file diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py new file mode 100644 index 000000000..bae264294 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/importhandler.py @@ -0,0 +1,65 @@ +# 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 + +This module handles special actions after the import of PySide modules. +The reason for this was the wish to replace some deprecated functions +by a Python implementation that gives a warning. + +It provides a framework to safely call functions outside of files.dir, +because the implementation of deprecated functions should be visible +to the users (in the hope they don't use it any longer <wink>). + +As a first approach, the function finish_import redirects to +PySide6/support/deprecated.py . There can come other extensions as well. +""" + +try: + from PySide6.support import deprecated + have_deprecated = True +except ImportError: + have_deprecated = False + + +# called by loader.py from signature.cpp +def finish_import(module): + if have_deprecated and module.__name__.startswith("PySide6."): + try: + name = "fix_for_" + module.__name__.split(".")[1] + func = getattr(deprecated, name, None) + if func: + func(module) + except Exception as e: + name = e.__class__.__qualname__ + print(72 * "*") + print("Error in deprecated.py, ignored:") + print(f" {name}: {e}") + + +""" +A note for people who might think this could be written in pure Python: + +Sure, by an explicit import of the modules to patch, this is no problem. +But in the general case, a module should only be imported on user +request and not because we want to patch it. So I started over. + +I then tried to do it on demand by redirection of the __import__ function. +Things worked quite nicely as it seemed, but at second view this solution +was much less appealing. + +Reason: +If someone executes as the first PySide statement + + from PySide6 import QtGui + +then this import is already running. We can see the other imports like the +diverse initializations and QtCore, because it is triggered by import of +QtGui. But the QtGui import can not be seen at all! + +With a lot of effort, sys.setprofile() and stack inspection with the inspect +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. +""" diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py new file mode 100644 index 000000000..0e781cbcb --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -0,0 +1,247 @@ +# 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 + +The signature module now has the capability to configure +differently formatted versions of signatures. The default +layout is known from the "__signature__" attribute. + +The function "get_signature(ob, modifier=None)" produces the same +signatures by default. By passing different modifiers, you +can select different layouts. + +This module configures the different layouts which can be used. +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.mapping import ellipsis + + +class SignatureLayout(SimpleNamespace): + """ + Configure a signature. + + The layout of signatures can have different layouts which are + controlled by keyword arguments: + + definition=True Determines if self will generated. + defaults=True + ellipsis=False Replaces defaults by "...". + return_annotation=True + parameter_names=True False removes names before ":". + """ + allowed_keys = SimpleNamespace(definition=True, + defaults=True, + ellipsis=False, + return_annotation=True, + parameter_names=True) + allowed_values = True, False + + def __init__(self, **kwds): + args = SimpleNamespace(**self.allowed_keys.__dict__) + args.__dict__.update(kwds) + self.__dict__.update(args.__dict__) + err_keys = list(set(self.__dict__) - set(self.allowed_keys.__dict__)) + if err_keys: + self._attributeerror(err_keys) + err_values = list(set(self.__dict__.values()) - set(self.allowed_values)) + if err_values: + self._valueerror(err_values) + + def __setattr__(self, key, value): + if key not in self.allowed_keys.__dict__: + self._attributeerror([key]) + if value not in self.allowed_values: + self._valueerror([value]) + self.__dict__[key] = value + + def _attributeerror(self, err_keys): + err_keys = ", ".join(err_keys) + allowed_keys = ", ".join(self.allowed_keys.__dict__.keys()) + raise AttributeError(dedent(f"""\ + Not allowed: '{err_keys}'. + The only allowed keywords are '{allowed_keys}'. + """)) + + def _valueerror(self, err_values): + err_values = ", ".join(map(str, err_values)) + allowed_values = ", ".join(map(str, self.allowed_values)) + raise ValueError(dedent(f"""\ + Not allowed: '{err_values}'. + 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() + +existence = SignatureLayout(definition=False, + defaults=False, + return_annotation=False, + parameter_names=False) + +hintingstub = SignatureLayout(ellipsis=True) + +typeerror = SignatureLayout(definition=False, + return_annotation=False, + parameter_names=False) + + +def define_nameless_parameter(): + """ + Create Nameless Parameters + + A nameless parameter has a reduced string representation. + This is done by cloning the parameter type and overwriting its + __str__ method. The inner structure is still a valid parameter. + """ + def __str__(self): + # for Python 2, we must change self to be an instance of P + klass = self.__class__ + self.__class__ = P + txt = P.__str__(self) + self.__class__ = klass + txt = txt[txt.index(":") + 1:].strip() if ":" in txt else txt + return txt + + P = inspect.Parameter + newname = "NamelessParameter" + bases = P.__bases__ + body = dict(P.__dict__) # get rid of mappingproxy + if "__slots__" in body: + # __slots__ would create duplicates + for name in body["__slots__"]: + del body[name] + body["__str__"] = __str__ + return type(newname, bases, body) + + +NamelessParameter = define_nameless_parameter() + +""" +Note on the "Optional" feature: + +When an annotation has a default value that is None, then the +type has to be wrapped into "typing.Optional". + +Note that only the None value creates an Optional expression, +because the None leaves the domain of the variable. +Defaults like integer values are ignored: They stay in the domain. + +That information would be lost when we use the "..." convention. + +Note that the typing module has the remarkable expansion + + Optional[T] is Union[T, NoneType] + +We want to avoid that when generating the .pyi file. +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. +""" +# import sys +# if sys.version_info[0] == 3: +# class hugo(list):pass +# typing._normalize_alias["hugo"] = "Optional" +# Optional = typing._alias(hugo, typing.T, inst=False) +# else: +# Optional = typing.Optional + + +def make_signature_nameless(signature): + """ + Make a Signature Nameless + + We use an existing signature and change the type of its parameters. + The signature looks different, but is totally intact. + """ + for key in signature.parameters.keys(): + signature.parameters[key].__class__ = NamelessParameter + + +_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: + # empty signatures string + return + if isinstance(props["multi"], list): + # multi sig: call recursively + return list(create_signature(elem, key) + for elem in props["multi"]) + if type(key) is tuple: + _, modifier = key + else: + _, modifier = key, "signature" + + layout = globals()[modifier] # lookup of the modifier in this module + if not isinstance(layout, SignatureLayout): + raise SystemError("Modifiers must be names of a SignatureLayout " + "instance") + + # this is the basic layout of a signature + varnames = props["varnames"] + if layout.definition: + # PYSIDE-1328: We no longer use info from the sig_kind which is + # more complex for multiple signatures. We now get `self` from the + # parser. + pass + else: + if varnames and varnames[0] in ("self", "cls"): + varnames = varnames[1:] + + # calculate the modifications + defaults = props["defaults"][:] + if not layout.defaults: + defaults = () + annotations = props["annotations"].copy() + if not layout.return_annotation and "return" in annotations: + del annotations["return"] + + # Build a signature. + kind = inspect._POSITIONAL_OR_KEYWORD + params = [] + for idx, name in enumerate(varnames): + if name.startswith("**"): + kind = _VAR_KEYWORD + elif name.startswith("*"): + kind = _VAR_POSITIONAL + ann = annotations.get(name, _empty) + if ann in ("self", "cls"): + ann = _empty + name = name.lstrip("*") + defpos = idx - len(varnames) + len(defaults) + default = defaults[defpos] if defpos >= 0 else _empty + if default is None: + ann = typing.Optional[ann] + if default is not _empty and layout.ellipsis: + default = ellipsis + param = inspect.Parameter(name, kind, annotation=ann, default=default) + params.append(param) + if kind == _VAR_POSITIONAL: + kind = _KEYWORD_ONLY + sig = inspect.Signature(params, + return_annotation=annotations.get('return', _empty), + __validate_parameters__=False) + + # the special case of nameless parameters + if not layout.parameter_names: + make_signature_nameless(sig) + return sig + +# end of file diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py new file mode 100644 index 000000000..e54bec75a --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/__init__.py @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..5650e2bc1 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py @@ -0,0 +1,304 @@ +# 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 + +Enumerate all signatures of a class. + +This module separates the enumeration process from the formatting. +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 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. + + This class is used for generating complete listings of all signatures. + An appropriate formatter should be supplied, if printable output + is desired. + """ + + def __init__(self, formatter, result_type=dict): + global EnumMeta, Signal, SignalInstance + try: + # Lazy import + from PySide6.QtCore import Qt, Signal, SignalInstance + EnumMeta = type(Qt.Key) + except ImportError: + EnumMeta = Signal = SignalInstance = None + + self.fmt = formatter + self.result_type = result_type + self.fmt.level = 0 + self.fmt.is_method = self.is_method + self.collision_candidates = {"property", "overload"} + + def is_method(self): + """ + Is this function a method? + We check if it is a simple function. + """ + tp = type(self.func) + return tp not in _normal_functions + + def section(self): + if hasattr(self.fmt, "section"): + self.fmt.section() + + def module(self, mod_name): + __import__(mod_name) + self.fmt.mod_name = mod_name + with self.fmt.module(mod_name): + module = sys.modules[mod_name] + members = inspect.getmembers(module, inspect.isclass) + functions = inspect.getmembers(module, inspect.isroutine) + 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 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 std::shared_ptr<QQuickItemGrabResult >: + # We simply skip over this class. + return ret + bases_list = [] + for base in klass.__bases__: + name = base.__qualname__ + if name not in ("object", "property", "type"): + name = base.__module__ + "." + name + bases_list.append(name) + bases_str = ', '.join(bases_list) + class_str = f"{class_name}({bases_str})" + # class_members = inspect.getmembers(klass) + # gives us also the inherited things. + class_members = sorted(list(klass.__dict__.items())) + subclasses = [] + functions = [] + enums = [] + properties = [] + signals = [] + attributes = {} + + for thing_name, thing in class_members: + 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" + functions.append((func_name, thing)) + elif type(type(thing)) is EnumMeta: + # take the real enum name, not what is in the dict + 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) + # 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, 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: + 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 func_prop: + if func_name != "__init__": + 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 get_sig(func) + + def function(self, func_name, func, decorator=None): + self.func = func # for is_method() + ret = self.result_type() + 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, 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): + # remove duplicates which still sometimes occour: + ret = set(stringify(sig) for sig in signature) + return sorted(ret) if len(ret) > 1 else list(ret)[0] + return tuple(str(pv) for pv in signature.parameters.values()) + + +class SimplifyingEnumerator(ExactEnumerator): + """ + SimplifyingEnumerator enumerates all signatures in a module filtered. + + There are no default values, no variable + names and no self parameter. Only types are present after simplification. + The functions 'next' resp. '__next__' are removed + to make the output identical for Python 2 and 3. + An appropriate formatter should be supplied, if printable output + is desired. + """ + + def function(self, func_name, func): + ret = self.result_type() + signature = get_sig(func, 'existence') + sig = stringify(signature) if signature is not None else None + if sig is not None and func_name not in ("next", "__next__", "__div__"): + with self.fmt.function(func_name, sig) as key: + ret[key] = sig + return ret + + +class HintingEnumerator(ExactEnumerator): + """ + HintingEnumerator enumerates all signatures in a module slightly changed. + + This class is used for generating complete listings of all signatures for + hinting stubs. Only default values are replaced by "...". + """ + + 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 new file mode 100644 index 000000000..979dcf4ce --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py @@ -0,0 +1,110 @@ +# 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 + +Some useful stuff, see below. +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 + + +def build_brace_pattern(level, separators): + """ + Build a brace pattern upto a given depth + + The brace pattern parses any pattern with round, square, curly, or angle + brackets. Inside those brackets, any characters are allowed. + + The structure is quite simple and is recursively repeated as needed. + When separators are given, the match stops at that separator. + + Reason to use this instead of some Python function: + The resulting regex is _very_ fast! + + A faster replacement would be written in C, but this solution is + sufficient when the nesting level is not too large. + + Because of the recursive nature of the pattern, the size grows by a factor + of 4 at every level, as does the creation time. Up to a level of 6, this + is below 10 ms. + + There are other regex engines available which allow recursive patterns, + 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" # noqa E:201 we insert "{}", later... + ao, ac = angle = "<>" # noqa E:201 + q2, bs, q1 = '"', "\\", "'" + allpat = round_ + square + curly + angle + __ = " " + ro, rc, so, sc, co, cc, ao, ac, separators, q2, bs, q1, allpat = map( + escape, (ro, rc, so, sc, co, cc, ao, ac, separators, q2, bs, q1, allpat)) + + no_brace_sep_q = fr"[^{allpat}{separators}{q2}{bs}{q1}]" + no_quot2 = fr"(?: [^{q2}{bs}] | {bs}. )*" + no_quot1 = fr"(?: [^{q1}{bs}] | {bs}. )*" + pattern = dedent(r""" + ( + (?: {__} {no_brace_sep_q} + | {q2} {no_quot2} {q2} + | {q1} {no_quot1} {q1} + | {ro} {replacer} {rc} + | {so} {replacer} {sc} + | {co} {replacer} {cc} + | {ao} {replacer} {ac} + )+ + ) + """) + no_braces_q = f"[^{allpat}{q2}{bs}{q1}]*" + repeated = dedent(r""" + {indent} (?: {__} {no_braces_q} + {indent} | {q2} {no_quot2} {q2} + {indent} | {q1} {no_quot1} {q1} + {indent} | {ro} {replacer} {rc} + {indent} | {so} {replacer} {sc} + {indent} | {co} {replacer} {cc} + {indent} | {ao} {replacer} {ac} + {indent} )* + """) + for idx in range(level): + pattern = pattern.format(replacer=repeated if idx < level - 1 else no_braces_q, + indent=idx * " ", **locals()) + return pattern.replace("C", "{").replace("D", "}") + + +# Copied from the six module: +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_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 new file mode 100644 index 000000000..fb4c9eeca --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/loader.py @@ -0,0 +1,158 @@ +# 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 + +The loader has to load the signature module completely at startup, +to make sure that the functions are available when needed. +This is meanwhile necessary to make the '__doc__' attribute work correctly. + +It does not mean that everything is initialized in advance. Only the modules +are loaded completely after 'import PySide6'. + +This version uses both a normal directory, but has also an embedded ZIP file +as a fallback solution. The ZIP file is generated by 'embedding_generator.py' +and embedded into 'signature.cpp' as "embed/signature.inc". + +Meanwhile, the ZIP file grew so much, that MSVC had problems +with it's 64k string limit, so we had to break the string up. +See 'zipped_string_sequence' in signature.cpp. +""" + +import sys +import os +import traceback +import types + + +# 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 +signature.get_signature = signature_bootstrap.get_signature +# PYSIDE-1019: Publish the __feature__ dictionary. +feature.pyside_feature_dict = signature_bootstrap.pyside_feature_dict +builtins.__feature_import__ = signature_bootstrap.__feature_import__ +del signature_bootstrap + +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 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"{package.__spec__.name}.{name}" if package else name + module.__spec__.name = fullname + # publish new dotted name in sys.modules + sys.modules[fullname] = module + + +def move_into_pyside_package(): + import shibokensupport + import PySide6 + try: + import PySide6.support + except ModuleNotFoundError: + # 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) + put_into_package(PySide6.support.signature, layout) + put_into_package(PySide6.support.signature, lib) + 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) + + +from shibokensupport.signature import mapping +from shibokensupport.signature import errorhandler +from shibokensupport.signature import layout +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", again. + move_into_pyside_package() + # PYSIDE-1502: Make sure that support can be imported. + try: + import PySide6.support + except ModuleNotFoundError: + 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 new file mode 100644 index 000000000..944a928e6 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -0,0 +1,719 @@ +# 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 + +This module has the mapping from the pyside C-modules view of signatures +to the Python representation. + +The PySide modules are not loaded in advance, but only after they appear +in sys.modules. This minimizes the loading overhead. +""" + +import os +import struct +import sys +import typing + +from pathlib import Path +from typing import TypeVar, Generic +from _imp import is_builtin + + +class ellipsis(object): + def __repr__(self): + return "..." + + +ellipsis = ellipsis() +Point = typing.Tuple[int, int] +Variant = typing.Any +QImageCleanupFunction = typing.Callable + +# unfortunately, typing.Optional[t] expands to typing.Union[t, NoneType] +# Until we can force it to create Optional[t] again, we use this. +NoneType = type(None) + +_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 +ushort_max = 0xffff + +GL_COLOR_BUFFER_BIT = 0x00004000 +GL_NEAREST = 0x2600 + +WId = int + +# from 5.9 +GL_TEXTURE_2D = 0x0DE1 +GL_RGBA = 0x1908 + + +class _NotCalled(str): + """ + Wrap some text with semantics + + This class is wrapped around text in order to avoid calling it. + There are three reasons for this: + + - some instances cannot be created since they are abstract, + - some can only be created after qApp was created, + - some have an ugly __repr__ with angle brackets in it. + + By using derived classes, good looking instances can be created + which can be used to generate source code or .pyi files. When the + real object is needed, the wrapper can simply be called. + """ + def __repr__(self): + return f"{type(self).__name__}({self})" + + def __call__(self): + from shibokensupport.signature.mapping import __dict__ as namespace + 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)". +# We *can* eventually inspect sys.modules and look if +# the calling module has this future statement set, +# but should we do that? + + +# Some types are abstract. They just show their name. +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. + def __repr__(self): + if USE_PEP563: + return _NotCalled.__repr__(self) + return f'{type(self).__name__}("{self}")' + + +class Invalid(_NotCalled): + pass + + +# Helper types +class Default(_NotCalled): + pass + + +class Instance(_NotCalled): + pass + + +# Parameterized primitive variables +class _Parameterized(object): + def __init__(self, type): + self.type = type + self.__name__ = self.__class__.__name__ + + 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) + + +class Reloader(object): + """ + Reloder class + + This is a singleton class which provides the update function for the + shiboken and PySide classes. + """ + def __init__(self): + self.sys_module_count = 0 + + @staticmethod + def module_valid(mod): + 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 bool(hasattr(mod, "__name__") and is_builtin(mod.__name__)) + + def update(self): + """ + 'update' imports all binary modules which are already in sys.modules. + The reason is to follow all user imports without introducing new ones. + This function is called by pyside_type_init to adapt imports + when the number of imported modules has changed. + """ + if self.sys_module_count == len(sys.modules): + return + self.sys_module_count = len(sys.modules) + g = globals() + # PYSIDE-1009: Try to recognize unknown modules in errorhandler.py + candidates = list(mod_name for mod_name in sys.modules.copy() + if self.module_valid(sys.modules[mod_name])) + for mod_name in candidates: + # 'top' is PySide6 when we do 'import PySide.QtCore' + # or Shiboken if we do 'import Shiboken'. + # Convince yourself that these two lines below have the same + # global effect as "import Shiboken" or "import PySide6.QtCore". + top = __import__(mod_name) + g[top.__name__] = top + proc_name = "init_" + mod_name.replace(".", "_") + if proc_name in g: + # Modules are in place, we can update the type_map. + g.update(g.pop(proc_name)()) + + +def check_module(mod): + # During a build, there exist the modules already as directories, + # although the '*.so' was not yet created. This causes a problem + # in Python 3, because it accepts folders as namespace modules + # without enforcing an '__init__.py'. + if not Reloader.module_valid(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, + "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, + "QHash": typing.Dict, + "qint16": int, + "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, + "quint32": int, + "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, + "QVector": typing.List, + "QSharedPointer": typing.Tuple, + "real": float, + "short": int, + "signed char": int, + "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, + "Tuple": typing.Tuple, + "uchar": int, + "uchar*": str, + "uint": int, + "ulong": int, + "ULONG_MAX": ulong_max, + "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 long": int, + "unsigned long": int, + "unsigned short int": int, # 5.6, RHEL 6.6 + "unsigned short": int, + "ushort": int, + "void": int, # be more specific? + "WId": WId, + "zero(bytes)": b"", + "zero(Char)": 0, + "zero(float)": 0, + "zero(int)": 0, + "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: + "array double*" : ArrayLikeVariable(float), + "array float*" : ArrayLikeVariable(float), + "array GLint*" : ArrayLikeVariable(int), + "array GLuint*" : ArrayLikeVariable(int), + "array int*" : ArrayLikeVariable(int), + "array long long*" : ArrayLikeVariable(int), + "array long*" : ArrayLikeVariable(int), + "array short*" : ArrayLikeVariable(int), + "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*" : typing.Union[bytes, bytearray, memoryview], + "QChar*" : typing.Union[bytes, bytearray, memoryview], + "quint32*" : int, # only for QRandomGenerator + "quint8*" : bytearray, # only for QCborStreamReader and QCborValue + "uchar*" : typing.Union[bytes, bytearray, memoryview], + "unsigned char*": typing.Union[bytes, bytearray, memoryview], +}) + +type_map.update({ + # Handling variables that are returned, eventually as Tuples: + "PySide6.QtQml.atomic[bool]": ResultVariable(bool), # QmlIncubationController::incubateWhile() + "bool*" : ResultVariable(bool), + "float*" : ResultVariable(float), + "int*" : ResultVariable(int), + "long long*" : ResultVariable(int), + "long*" : ResultVariable(int), + "PStr*" : ResultVariable(str), # module sample + "qint32*" : ResultVariable(int), + "qint64*" : ResultVariable(int), + "qreal*" : ResultVariable(float), + "qsizetype*" : ResultVariable(int), + "QString*" : ResultVariable(str), + "qintptr*" : ResultVariable(int), + "quintptr*" : ResultVariable(int), + "quint16*" : ResultVariable(int), + "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", + "cls" : "cls", +}) + +# PYSIDE-1538: We need to treat "std::optional" accordingly. +type_map.update({ + "std.optional": typing.Optional, + }) + + +# The Shiboken Part +def init_Shiboken(): + type_map.update({ + "PyType": type, + "shiboken6.bool": bool, + "size_t": int, + }) + return locals() + + +def init_minimal(): + type_map.update({ + "MinBool": bool, + }) + return locals() + + +def init_sample(): + import datetime + 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, + "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, + "sample.int": int, + "sample.ObjectType": object, + "sample.OddBool": bool, + "sample.Photon.TemplateBase[Photon.DuplicatorType]": sample.Photon.ValueDuplicator, + "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, + "ZeroIn": 0, + 'Str("<unk")': "<unk", + 'Str("<unknown>")': "<unknown>", + 'Str("nown>")': "nown>", + }) + return locals() + + +def init_other(): + import numbers + type_map.update({ + "other.ExtendsNoImplicitConversion": Missing("other.ExtendsNoImplicitConversion"), + "other.Number": numbers.Number, + }) + return locals() + + +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 + type_map.update({ + "smart.Smart.Integer2": int, + }) + return locals() + + +# The PySide Part +def init_PySide6_QtCore(): + 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 + except ImportError: + pass + type_map.update({ + "' '": " ", + "'%'": "%", + "'g'": "g", + "4294967295UL": 4294967295, # 5.6, RHEL 6.6 + "CheckIndexOption.NoOption": Instance( + "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 + "PyBuffer": typing.Union[bytes, bytearray, memoryview], + "PyByteArray": bytearray, + "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??? + "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)"), + "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(Q_NULLPTR)": ellipsis, + "QJsonObject": typing.Dict[str, PySide6.QtCore.QJsonValue], + "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? + "QUrl.FormattingOptions(PrettyDecoded)": Instance( + "QUrl.FormattingOptions(QUrl.PrettyDecoded)"), + "QVariant()": Invalid(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! + }) + 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() + + +def init_PySide6_QtConcurrent(): + type_map.update({ + "PySide6.QtCore.QFuture[QString]": + PySide6.QtConcurrent.QFutureQString, + "PySide6.QtCore.QFuture[void]": + PySide6.QtConcurrent.QFutureVoid, + }) + return locals() + + +def init_PySide6_QtGui(): + 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, + "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, + 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)"), + "static_cast<Qt.MatchFlags>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( + "Qt.MatchFlags(Qt.MatchExactly | Qt.MatchCaseSensitive)"), + "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() + + +def init_PySide6_QtSql(): + from PySide6.QtSql import QSqlDatabase + type_map.update({ + "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, QHostAddress + best_structure = typing.OrderedDict if getattr(typing, "OrderedDict", None) else typing.Dict + type_map.update({ + "QMultiMap[PySide6.QtNetwork.QSsl.AlternativeNameEntryType, QString]": + best_structure[PySide6.QtNetwork.QSsl.AlternativeNameEntryType, typing.List[str]], + "DefaultTransferTimeoutConstant": + QNetworkRequest.TransferTimeoutConstant, + "QNetworkRequest.DefaultTransferTimeoutConstant": + QNetworkRequest.TransferTimeoutConstant, + }) + del best_structure + return locals() + + +def init_PySide6_QtOpenGL(): + type_map.update({ + "GLbitfield": int, + "GLenum": int, + "GLfloat": float, # 5.6, MSVC 15 + "GLint": int, + "GLuint": int, + }) + return locals() + + +def init_PySide6_QtQml(): + type_map.update({ + "VolatileBool": PySide6.QtQml.VolatileBool, + }) + return locals() + + +def init_PySide6_QtQuick(): + type_map.update({ + "PySide6.QtQuick.QSharedPointer[PySide6.QtQuick.QQuickItemGrabResult]": + PySide6.QtQuick.QQuickItemGrabResult, + "QSGGeometry.Type.UnsignedShortType": int, + }) + return locals() + + +def init_PySide6_QtTest(): + from PySide6.QtCore import SignalInstance + type_map.update({ + "PySideSignalInstance": SignalInstance, + "PySide6.QtTest.QTest.PySideQTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, + "PySide6.QtTest.QTouchEventSequence": PySide6.QtTest.QTest.QTouchEventSequence, + }) + return locals() + + +# 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({ + "100.0f": 100.0, + "QBarDataArray": QBarDataArray, + "QBarDataArray*": QBarDataArray, + "QSurfaceDataArray": QSurfaceDataArray, + "QSurfaceDataArray*": QSurfaceDataArray, + }) + return locals() + + +def init_PySide6_QtBluetooth(): + type_map.update({ + "QVariant*": object, + }) + return locals() + + +def init_PySide6_QtGraphs(): + from PySide6.QtGraphs import (QBarDataItem, QSurfaceDataItem) + QBarDataRow = typing.List[QBarDataItem] + QBarDataArray = typing.List[QBarDataRow] + QSurfaceDataRow = typing.List[QSurfaceDataItem] + QSurfaceDataArray = typing.List[QSurfaceDataRow] + type_map.update({ + "100.0f": 100.0, + "QBarDataArray": QBarDataArray, + "QBarDataArray*": QBarDataArray, + "QSurfaceDataArray": QSurfaceDataArray, + "QSurfaceDataArray*": QSurfaceDataArray, + }) + 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() + +# end of file diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py new file mode 100644 index 000000000..9b48ab442 --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -0,0 +1,552 @@ +# 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 ast +import enum +import keyword +import os +import re +import sys +import typing +import warnings + +from types import SimpleNamespace +from shibokensupport.signature.mapping import (type_map, update_mapping, + namespace, _NotCalled, ResultVariable, ArrayLikeVariable) # noqa E:128 +from shibokensupport.signature.lib.tool import build_brace_pattern + +_DEBUG = False +LIST_KEYWORDS = False + +""" +parser.py + +This module parses the signature text and creates properties for the +signature objects. + +PySide has a new function 'CppGenerator::writeSignatureInfo()' +that extracts the gathered information about the function arguments +and defaults as good as it can. But what PySide generates is still +very C-ish and has many constants that Python doesn't understand. + +The function 'try_to_guess()' below understands a lot of PySide's +peculiar way to assume local context. If it is able to do the guess, +then the result is inserted into the dict, so the search happens +not again. For everything that is not covered by these automatic +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 + for arg in args: + pprint.pprint(arg) + sys.stdout.flush() + + +_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 + # strings and the separators are returned, where the strings are not + # interesting at all: They are just the commata. + key = "_parse_arglist" + if key not in _cache: + regex = build_brace_pattern(level=3, separators=",") + _cache[key] = re.compile(regex, flags=re.VERBOSE) + split = _cache[key].split + # Note: this list is interspersed with "," and surrounded by "" + return [x.strip() for x in split(argstr) if x.strip() not in ("", ",")] + + +def _parse_line(line): + line_re = r""" + ((?P<multi> ([0-9]+)) : )? # the optional multi-index + (?P<funcname> \w+(\.\w+)*) # the function name + \( (?P<arglist> .*?) \) # the argument list + ( -> (?P<returntype> .*) )? # the optional return type + $ + """ + 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 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: + print("KEYWORD", ret) + name = name + "_" + if "=" in ann: + ann, default = ann.split("=", 1) + tup = name, ann, default + else: + tup = name, ann + args.append(tup) + ret.arglist = args + multi = ret.multi + if multi is not None: + ret.multi = int(multi) + funcname = ret.funcname + parts = funcname.split(".") + if parts[-1] in keyword.kwlist: + ret.funcname = funcname + "_" + 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: + # 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, globals(), namespace) + except Exception: + pass + + +def try_to_guess(thing, valtype): + if "." not in thing and "(" not in thing: + text = f"{valtype}.{thing}" + ret = make_good_value(text, valtype) + if ret is not None: + return ret + typewords = valtype.split(".") + valwords = thing.split(".") + braceless = valwords[0] # Yes, not -1. Relevant is the overlapped word. + if "(" in braceless: + braceless = braceless[:braceless.index("(")] + for idx, w in enumerate(typewords): + if w == braceless: + text = ".".join(typewords[:idx] + valwords) + ret = make_good_value(text, valtype) + if ret is not None: + 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."): + return None + map = type_map[valtype] + # typing.Any: '_SpecialForm' object has no attribute '__name__' + name = get_name(map) if hasattr(map, "__name__") else str(map) + thing = f"zero({name})" + if thing in type_map: + return type_map[thing] + res = make_good_value(thing, valtype) + if res is not None: + type_map[thing] = res + return res + res = try_to_guess(thing, valtype) if valtype else None + if res is not None: + type_map[thing] = res + return res + warnings.warn(f"""pyside_type_init:_resolve_value + + UNRECOGNIZED: {thing!r} + OFFENDING LINE: {line!r} + """, RuntimeWarning) + return thing + + +def _resolve_arraytype(thing, line): + search = re.search(r"\[(\d*)\]$", thing) + thing = thing[:search.start()] + if thing.endswith("]"): + thing = _resolve_arraytype(thing, line) + if search.group(1): + # concrete array, use a tuple + nelem = int(search.group(1)) + thing = ", ".join([thing] * nelem) + thing = "Tuple[" + thing + "]" + else: + thing = "QList[" + thing + "]" + return thing + + +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__") and thing.__module__ != "typing": + m = thing.__module__ + dot = "." in str(thing) or m not in (thing.__qualname__, "builtins") + name = get_name(thing) + 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(","))) + assert typstr == "float" + result = f"PySide6.QtGui.QMatrix{n}x{m}" + return eval(result, globals(), namespace) + + +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] + + # 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 + if re.search(r"\[\d*\]$", thing): + thing = _resolve_arraytype(thing, line) + # Handle a container return type. (see PYSIDE-921 in cppgenerator.cpp) + contr, thing = re.match(r"(.*?)\[(.*?)\]$", thing).groups() + # 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)) + 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)) + 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}]" + # 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) + + +def _handle_generic(obj, repl): + """ + Assign repl if obj is an ArrayLikeVariable + + This is a neat trick. Example: + + obj repl result + ---------------------- -------- --------- + ArrayLikeVariable List List + ArrayLikeVariable(str) List List[str] + ArrayLikeVariable Sequence Sequence + ArrayLikeVariable(str) Sequence Sequence[str] + """ + if isinstance(obj, ArrayLikeVariable): + return repl[obj.type] + if isinstance(obj, type) and issubclass(obj, ArrayLikeVariable): + # was "if obj is ArrayLikeVariable" + return repl + return obj + + +def handle_argvar(obj): + """ + Decide how array-like variables are resolved in arguments + + Currently, the best approximation is types.Sequence. + We want to change that to types.Iterable in the near future. + """ + return _handle_generic(obj, typing.Sequence) + + +def handle_retvar(obj): + """ + Decide how array-like variables are resolved in results + + This will probably stay typing.List forever. + """ + return _handle_generic(obj, typing.List) + + +def calculate_props(line): + parsed = SimpleNamespace(**_parse_line(line.strip())) + arglist = parsed.arglist + annotations = {} + _defaults = [] + for idx, tup in enumerate(arglist): + name, ann = tup[:2] + if ann == "...": + name = "*args" if name.startswith("arg_") else "*" + name + # 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, 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) + props = SimpleNamespace() + props.defaults = defaults + props.kwdefaults = {} + props.annotations = annotations + props.varnames = tuple(tup[0] for tup in arglist) + funcname = parsed.funcname + shortname = funcname[funcname.rindex(".") + 1:] + props.name = shortname + props.multi = parsed.multi + fix_variables(props, line) + return vars(props) + + +def fix_variables(props, line): + annos = props.annotations + if not any(isinstance(ann, (ResultVariable, ArrayLikeVariable)) + for ann in annos.values()): + return + retvar = annos.get("return", None) + if retvar and isinstance(retvar, (ResultVariable, ArrayLikeVariable)): + # Special case: a ResultVariable which is the result will always be an array! + annos["return"] = retvar = typing.List[retvar.type] + varnames = list(props.varnames) + defaults = list(props.defaults) + diff = len(varnames) - len(defaults) + + safe_annos = annos.copy() + retvars = [retvar] if retvar else [] + deletions = [] + for idx, name in enumerate(varnames): + ann = safe_annos[name] + if isinstance(ann, ArrayLikeVariable): + ann = typing.Sequence[ann.type] + annos[name] = ann + if not isinstance(ann, ResultVariable): + continue + # We move the variable to the end and remove it. + # 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): + # varnames: 0 1 2 3 4 5 6 7 + # defaults: 0 1 2 3 4 + # diff: 3 + del varnames[idx] + if idx >= diff: + del defaults[idx - diff] + else: + diff -= 1 + if retvars: + retvars = list(handle_retvar(rv) if isinstance(rv, ArrayLikeVariable) else rv + for rv in retvars) + if len(retvars) == 1: + returntype = retvars[0] + else: + retvars_str = ", ".join(map(to_string, retvars)) + typestr = f"typing.Tuple[{retvars_str}]" + returntype = eval(typestr, globals(), namespace) + props.annotations["return"] = returntype + props.varnames = tuple(varnames) + props.defaults = tuple(defaults) + + +def fixup_multilines(lines): + """ + Multilines can collapse when certain distinctions between C++ types + vanish after mapping to Python. + This function fixes this by re-computing multiline-ness. + """ + res = [] + multi_lines = [] + for line in lines: + multi = re.match(r"([0-9]+):", line) + if multi: + idx, rest = int(multi.group(1)), line[multi.end():] + multi_lines.append(rest) + if idx > 0: + continue + # remove duplicates + multi_lines = sorted(set(multi_lines)) + # renumber or return a single line + nmulti = len(multi_lines) + if nmulti > 1: + for idx, line in enumerate(multi_lines): + res.append(f"{nmulti-idx-1}:{line}") + else: + res.append(multi_lines[0]) + multi_lines = [] + else: + res.append(line) + return res + + +def pyside_type_init(type_key, sig_strings): + dprint() + dprint(f"Initialization of type key '{type_key}'") + update_mapping() + lines = fixup_multilines(sig_strings) + ret = {} + multi_props = [] + for line in lines: + props = calculate_props(line) + shortname = props["name"] + multi = props["multi"] + if multi is None: + ret[shortname] = props + dprint(props) + else: + multi_props.append(props) + if multi > 0: + continue + multi_props = {"multi": multi_props} + ret[shortname] = multi_props + dprint(multi_props) + multi_props = [] + return ret + +# end of file diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json new file mode 100644 index 000000000..0f05aea8b --- /dev/null +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "python", + "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. See the folder sources/shiboken6/files.dir/shibokensupport .", + "Homepage": "http://www.python.org/", + "Version": "3.7.0", + "LicenseId": "Python-2.0", + "License": "Python License 2.0", + "LicenseFile": "PSF-3.7.0.txt", + "Copyright": "© Copyright 2001-2018, Python Software Foundation." +} |