aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py')
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py250
1 files changed, 170 insertions, 80 deletions
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
index d737c2a54..9b48ab442 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py
@@ -1,52 +1,19 @@
-#############################################################################
-##
-## Copyright (C) 2019 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-import sys
+import ast
+import enum
+import keyword
+import os
import re
+import sys
+import typing
import warnings
-import types
-import keyword
-import functools
+
+from types import SimpleNamespace
from shibokensupport.signature.mapping import (type_map, update_mapping,
- namespace, typing, _NotCalled, ResultVariable, ArrayLikeVariable)
-from shibokensupport.signature.lib.tool import (SimpleNamespace,
- build_brace_pattern)
+ namespace, _NotCalled, ResultVariable, ArrayLikeVariable) # noqa E:128
+from shibokensupport.signature.lib.tool import build_brace_pattern
_DEBUG = False
LIST_KEYWORDS = False
@@ -71,6 +38,65 @@ guesses, we provide an entry in 'type_map' that resolves it.
In effect, 'type_map' maps text to real Python objects.
"""
+
+def _get_flag_enum_option():
+ from shiboken6 import (__version_info__ as ver, # noqa F:401
+ __minimum_python_version__ as pyminver,
+ __maximum_python_version__ as pymaxver)
+
+ # PYSIDE-1735: Use the new Enums per default if version is >= 6.4
+ # This decides between delivered vs. dev versions.
+ # When 6.4 is out, the switching mode will be gone.
+ flag = ver[:2] >= (6, 4)
+ envname = "PYSIDE6_OPTION_PYTHON_ENUM"
+ sysname = envname.lower()
+ opt = os.environ.get(envname)
+ if opt:
+ opt = opt.lower()
+ if opt in ("yes", "on", "true"):
+ flag = True
+ elif opt in ("no", "off", "false"):
+ flag = False
+ else:
+ # instead of a simple int() conversion, let's allow for "0xf" or "0b1111"
+ try:
+ flag = ast.literal_eval(opt)
+ except Exception:
+ flag = False # turn a forbidden option into an error
+ elif hasattr(sys, sysname):
+ opt2 = flag = getattr(sys, sysname)
+ if not isinstance(flag, int):
+ flag = False # turn a forbidden option into an error
+ p = f"\n *** Python is at version {'.'.join(map(str, pyminver or (0,)))} now."
+ q = f"\n *** PySide is at version {'.'.join(map(str, ver[:2]))} now."
+ # _PepUnicode_AsString: Fix a broken promise
+ if pyminver and pyminver >= (3, 10):
+ warnings.warn(f"{p} _PepUnicode_AsString can now be replaced by PyUnicode_AsUTF8! ***")
+ # PYSIDE-1960: Emit a warning when we may remove bufferprocs_py37.(cpp|h)
+ if pyminver and pyminver >= (3, 11):
+ warnings.warn(f"{p} The files bufferprocs_py37.(cpp|h) should be removed ASAP! ***")
+ # PYSIDE-1735: Emit a warning when we should maybe evict forgiveness mode
+ if ver[:2] >= (7, 0):
+ warnings.warn(f"{q} Please drop Enum forgiveness mode in `mangled_type_getattro` ***")
+ # PYSIDE-2404: Emit a warning when we should drop uppercase offset constants
+ if ver[:2] >= (7, 0):
+ warnings.warn(f"{q} Please drop uppercase type offsets in `copyOffsetEnumStream` ***")
+ # normalize the sys attribute
+ setattr(sys, sysname, flag)
+ os.environ[envname] = str(flag)
+ if int(flag) == 0:
+ raise RuntimeError(f"Old Enums are no longer supported. int({opt or opt2}) evaluates to 0)")
+ return flag
+
+
+class EnumSelect(enum.Enum):
+ # PYSIDE-1735: Here we could save object.value expressions by using IntEnum.
+ # But it is nice to use just an Enum for selecting Enum version.
+ OLD = 1
+ NEW = 2
+ SELECTION = NEW if _get_flag_enum_option() else OLD
+
+
def dprint(*args, **kw):
if _DEBUG:
import pprint
@@ -81,6 +107,7 @@ def dprint(*args, **kw):
_cache = {}
+
def _parse_arglist(argstr):
# The following is a split re. The string is broken into pieces which are
# between the recognized strings. Because the re has groups, both the
@@ -103,19 +130,21 @@ def _parse_line(line):
( -> (?P<returntype> .*) )? # the optional return type
$
"""
- ret = SimpleNamespace(**re.match(line_re, line, re.VERBOSE).groupdict())
+ matches = re.match(line_re, line, re.VERBOSE)
+ if not matches:
+ raise SystemError("Error parsing line:", repr(line))
+ ret = SimpleNamespace(**matches.groupdict())
# PYSIDE-1095: Handle arbitrary default expressions
argstr = ret.arglist.replace("->", ".deref.")
arglist = _parse_arglist(argstr)
args = []
for idx, arg in enumerate(arglist):
tokens = arg.split(":")
- if len(tokens) < 2:
- if idx == 0 and tokens[0] == "self":
- tokens = 2 * tokens # "self: self"
- else:
- # This should never happen again (but who knows?)
- raise SystemError(f'Invalid argument "{arg}" in "{line}".')
+ if len(tokens) < 2 and idx == 0 and tokens[0] in ("self", "cls"):
+ tokens = 2 * tokens # "self: self"
+ if len(tokens) != 2:
+ # This should never happen again (but who knows?)
+ raise SystemError(f'Invalid argument "{arg}" in "{line}".')
name, ann = tokens
if name in keyword.kwlist:
if LIST_KEYWORDS:
@@ -138,15 +167,57 @@ def _parse_line(line):
return vars(ret)
+def _using_snake_case():
+ # Note that this function should stay here where we use snake_case.
+ if "PySide6.QtCore" not in sys.modules:
+ return False
+ from PySide6.QtCore import QDir
+ return hasattr(QDir, "cd_up")
+
+
+def _handle_instance_fixup(thing):
+ """
+ Default expressions using instance methods like
+ (...,device=QPointingDevice.primaryPointingDevice())
+ need extra handling for snake_case. These are:
+ QPointingDevice.primaryPointingDevice()
+ QInputDevice.primaryKeyboard()
+ QKeyCombination.fromCombined(0)
+ QSslConfiguration.defaultConfiguration()
+ """
+ match = re.search(r"\w+\(", thing)
+ if not match:
+ return thing
+ start, stop = match.start(), match.end() - 1
+ pre, func, args = thing[:start], thing[start:stop], thing[stop:]
+ if func[0].isupper() or func.startswith("gl") and func[2:3].isupper():
+ return thing
+ # Now convert this string to snake case.
+ snake_func = ""
+ for idx, char in enumerate(func):
+ if char.isupper():
+ if idx and func[idx - 1].isupper():
+ # two upper chars are forbidden
+ return thing
+ snake_func += f"_{char.lower()}"
+ else:
+ snake_func += char
+ return f"{pre}{snake_func}{args}"
+
+
def make_good_value(thing, valtype):
+ # PYSIDE-1019: Handle instance calls (which are really seldom)
+ if "(" in thing and _using_snake_case():
+ thing = _handle_instance_fixup(thing)
try:
if thing.endswith("()"):
thing = f'Default("{thing[:-2]}")'
else:
- ret = eval(thing, namespace)
+ # PYSIDE-1735: Use explicit globals and locals because of a bug in VsCode
+ ret = eval(thing, globals(), namespace)
if valtype and repr(ret).startswith("<"):
thing = f'Instance("{thing}")'
- return eval(thing, namespace)
+ return eval(thing, globals(), namespace)
except Exception:
pass
@@ -170,12 +241,14 @@ def try_to_guess(thing, valtype):
return ret
return None
+
def get_name(thing):
if isinstance(thing, type):
return getattr(thing, "__qualname__", thing.__name__)
else:
return thing.__name__
+
def _resolve_value(thing, valtype, line):
if thing in ("0", "None") and valtype:
if valtype.startswith("PySide6.") or valtype.startswith("typing."):
@@ -194,7 +267,7 @@ def _resolve_value(thing, valtype, line):
if res is not None:
type_map[thing] = res
return res
- warnings.warn(f"""pyside_type_init:
+ warnings.warn(f"""pyside_type_init:_resolve_value
UNRECOGNIZED: {thing!r}
OFFENDING LINE: {line!r}
@@ -218,40 +291,44 @@ def _resolve_arraytype(thing, line):
def to_string(thing):
+ # This function returns a string that creates the same object.
+ # It is absolutely crucial that str(eval(thing)) == str(thing),
+ # i.e. it must be an idempotent mapping.
if isinstance(thing, str):
return thing
- if hasattr(thing, "__name__"):
- dot = "." in str(thing)
+ if hasattr(thing, "__name__") and thing.__module__ != "typing":
+ m = thing.__module__
+ dot = "." in str(thing) or m not in (thing.__qualname__, "builtins")
name = get_name(thing)
- return thing.__module__ + "." + name if dot else name
+ ret = m + "." + name if dot else name
+ assert (eval(ret, globals(), namespace))
+ return ret
# Note: This captures things from the typing module:
return str(thing)
matrix_pattern = "PySide6.QtGui.QGenericMatrix"
+
def handle_matrix(arg):
- n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(",")))
+ n, m, typstr = tuple(map(lambda x: x.strip(), arg.split(",")))
assert typstr == "float"
result = f"PySide6.QtGui.QMatrix{n}x{m}"
- return eval(result, namespace)
-
+ return eval(result, globals(), namespace)
-debugging_aid = """
-from inspect import currentframe
-
-def lno(level):
- lineno = currentframe().f_back.f_lineno
- spaces = level * " "
- return f"{lineno}{spaces}"
-"""
+def _resolve_type(thing, line, level, var_handler, func_name=None):
+ # manual set of 'str' instead of 'bytes'
+ if func_name:
+ new_thing = (func_name, thing)
+ if new_thing in type_map:
+ return type_map[new_thing]
-def _resolve_type(thing, line, level, var_handler):
# Capture total replacements, first. Happens in
# "PySide6.QtCore.QCborStreamReader.StringResult[PySide6.QtCore.QByteArray]"
if thing in type_map:
return type_map[thing]
+
# Now the nested structures are handled.
if "[" in thing:
# handle primitive arrays
@@ -262,20 +339,28 @@ def _resolve_type(thing, line, level, var_handler):
# Special case: Handle the generic matrices.
if contr == matrix_pattern:
return handle_matrix(thing)
- contr = var_handler(_resolve_type(contr, line, level+1, var_handler))
+ contr = var_handler(_resolve_type(contr, line, level + 1, var_handler))
if isinstance(contr, _NotCalled):
raise SystemError("Container types must exist:", repr(contr))
contr = to_string(contr)
pieces = []
for part in _parse_arglist(thing):
- part = var_handler(_resolve_type(part, line, level+1, var_handler))
+ part = var_handler(_resolve_type(part, line, level + 1, var_handler))
if isinstance(part, _NotCalled):
# fix the tag (i.e. "Missing") by repr
part = repr(part)
pieces.append(to_string(part))
thing = ", ".join(pieces)
result = f"{contr}[{thing}]"
- return eval(result, namespace)
+ # PYSIDE-1538: Make sure that the eval does not crash.
+ try:
+ return eval(result, globals(), namespace)
+ except Exception:
+ warnings.warn(f"""pyside_type_init:_resolve_type
+
+ UNRECOGNIZED: {result!r}
+ OFFENDING LINE: {line!r}
+ """, RuntimeWarning)
return _resolve_value(thing, None, line)
@@ -328,16 +413,20 @@ def calculate_props(line):
name, ann = tup[:2]
if ann == "...":
name = "*args" if name.startswith("arg_") else "*" + name
- # copy the pathed fields back
+ # copy the patched fields back
ann = 'nullptr' # maps to None
tup = name, ann
arglist[idx] = tup
- annotations[name] = _resolve_type(ann, line, 0, handle_argvar)
+ annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname)
if len(tup) == 3:
default = _resolve_value(tup[2], ann, line)
_defaults.append(default)
defaults = tuple(_defaults)
returntype = parsed.returntype
+ if isinstance(returntype, str) and returntype.startswith("("):
+ # PYSIDE-1588: Simplify the handling of returned tuples for now.
+ # Later we might create named tuples, instead.
+ returntype = "Tuple"
# PYSIDE-1383: We need to handle `None` explicitly.
annotations["return"] = (_resolve_type(returntype, line, 0, handle_retvar)
if returntype is not None else None)
@@ -345,9 +434,9 @@ def calculate_props(line):
props.defaults = defaults
props.kwdefaults = {}
props.annotations = annotations
- props.varnames = varnames = tuple(tup[0] for tup in arglist)
+ props.varnames = tuple(tup[0] for tup in arglist)
funcname = parsed.funcname
- shortname = funcname[funcname.rindex(".")+1:]
+ shortname = funcname[funcname.rindex(".") + 1:]
props.name = shortname
props.multi = parsed.multi
fix_variables(props, line)
@@ -378,7 +467,9 @@ def fix_variables(props, line):
if not isinstance(ann, ResultVariable):
continue
# We move the variable to the end and remove it.
- retvars.append(ann.type)
+ # PYSIDE-1409: If the variable was the first arg, we move it to the front.
+ # XXX This algorithm should probably be replaced by more introspection.
+ retvars.insert(0 if idx == 0 else len(retvars), ann.type)
deletions.append(idx)
del annos[name]
for idx in reversed(deletions):
@@ -391,7 +482,6 @@ def fix_variables(props, line):
else:
diff -= 1
if retvars:
- rvs = []
retvars = list(handle_retvar(rv) if isinstance(rv, ArrayLikeVariable) else rv
for rv in retvars)
if len(retvars) == 1:
@@ -399,7 +489,7 @@ def fix_variables(props, line):
else:
retvars_str = ", ".join(map(to_string, retvars))
typestr = f"typing.Tuple[{retvars_str}]"
- returntype = eval(typestr, namespace)
+ returntype = eval(typestr, globals(), namespace)
props.annotations["return"] = returntype
props.varnames = tuple(varnames)
props.defaults = tuple(defaults)