diff options
author | Christian Tismer <tismer@stackless.com> | 2019-02-01 11:12:43 +0100 |
---|---|---|
committer | Christian Tismer <tismer@stackless.com> | 2019-02-05 14:17:48 +0000 |
commit | 3748e8fa9830e97466f12104501d4acf104260a5 (patch) | |
tree | 67dc037767dce0f87568b4a27beac69010742026 /sources | |
parent | 295061d00be3dedf60d8adc97bd14ff46931ef4c (diff) |
Type Hints: Handle Container Types Correctly
An error was reported that we had a bug in this .pyi line:
def parseErrors(self) -> PySide2.QtScxml.QScxmlError: ...
which actually had to be a list like
def parseErrors(self) -> typing.List[PySide2.QtScxml.QScxmlError]: ...
A deeper look revealed that we had very many other examples,
also in the argument lists, were we did not handle containers
properly. (For example, 90 times in QtCore!)
This is a leftover from the type error messages which
were generated in C++, and never really understood.
This is now a clean rewrite of the C++ part to expose all
information and an extension of the Python parser that
systematically uses the container types from the typing
module.
The implementation became a bit more complex because of
Python 2: We need to provide our own copy of the public
typing module, because it is not safe to assume that this module
can be loaded. Importing it from support.signature gave a
problem because we now need to find the names of instances
to produce List[...], which needed to be implemented in the
loader.
Implemented the "Pair" type now as a native generic type.
This is more correct than the former implementation and
shorter in the .pyi files.
Additionally, an own definition of "Char" was provided for
brevity. This was not important to implement and could also
be done with "int", but it is helpful for the future to know how
to implement own types (and it was fun).
Task-number: PYSIDE-921
Task-number: PYSIDE-795
Change-Id: I0e565b38d7b4fff39dd606950f9f8d97c45acbf5
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'sources')
5 files changed, 132 insertions, 31 deletions
diff --git a/sources/pyside2/PySide2/support/signature/mapping.py b/sources/pyside2/PySide2/support/signature/mapping.py index 96afd3c7a..d56d97454 100644 --- a/sources/pyside2/PySide2/support/signature/mapping.py +++ b/sources/pyside2/PySide2/support/signature/mapping.py @@ -98,7 +98,6 @@ def init_QtCore(): "unsigned long long": int, "unsigned short": int, "QStringList": StringList, - "QList": list, "QChar": Char, "signed char": Char, "QVariant": Variant, @@ -106,11 +105,8 @@ def init_QtCore(): "QStringRef": str, "QString()": "", "QModelIndexList": ModelIndexList, - "QPair": Pair, "unsigned char": Char, - "QSet": set, # seems _not_ to work - "QVector": list, - "QJsonObject": dict, # seems to work + "QJsonObject": typing.Dict[str, PySide2.QtCore.QJsonValue], "QStringList()": [], "ULONG_MAX": ulong_max, "quintptr": int, @@ -120,12 +116,12 @@ def init_QtCore(): "qptrdiff": int, "true": True, "Qt.HANDLE": int, # be more explicit with some consts? - "list of QAbstractState": list, # how to use typing.List when we don't have QAbstractState? - "list of QAbstractAnimation": list, # dto. + "list of QAbstractState": typing.List[PySide2.QtCore.QAbstractState], + "list of QAbstractAnimation": typing.List[PySide2.QtCore.QAbstractAnimation], "QVariant()": Invalid(Variant), - "QMap": dict, + "QMap": typing.Dict, "PySide2.QtCore.bool": bool, - "QHash": dict, + "QHash": typing.Dict, "PySide2.QtCore.QChar": Char, "PySide2.QtCore.qreal": float, "PySide2.QtCore.float": float, @@ -159,7 +155,6 @@ def init_QtCore(): "signed long": int, "PySide2.QtCore.int": int, "PySide2.QtCore.char": StringList, # A 'char **' is a list of strings. - "char[]": StringList, # 5.9 "unsigned long int": int, # 5.6, RHEL 6.6 "unsigned short int": int, # 5.6, RHEL 6.6 "4294967295UL": 4294967295, # 5.6, RHEL 6.6 @@ -169,7 +164,6 @@ def init_QtCore(): "nullptr": None, # 5.9 "uint64_t": int, # 5.9 "PySide2.QtCore.uint32_t": int, # 5.9 - "float[][]": FloatMatrix, # 5.9 "PySide2.QtCore.unsigned int": int, # 5.9 Ubuntu "PySide2.QtCore.long long": int, # 5.9, MSVC 15 "QGenericArgument(nullptr)": ellipsis, # 5.10 @@ -202,7 +196,7 @@ def init_QtCore(): "zero(PySide2.QtCore.QEvent.Type)": None, "CheckIndexOption.NoOption": Instance( "PySide2.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 - "QVariantMap": dict, + "QVariantMap": typing.Dict[str, Variant], "PySide2.QtCore.QCborStreamReader.StringResult": typing.AnyStr, "PySide2.QtCore.double": float, }) @@ -249,8 +243,6 @@ def init_QtGui(): def init_QtWidgets(): from PySide2.QtWidgets import QWidget, QMessageBox, QStyleOption, QStyleHintReturn, QStyleOptionComplex from PySide2.QtWidgets import QGraphicsItem, QStyleOptionGraphicsItem # 5.9 - GraphicsItemList = typing.List[QGraphicsItem] - StyleOptionGraphicsItemList = typing.List[QStyleOptionGraphicsItem] type_map.update({ "QMessageBox.StandardButtons(Yes | No)": Instance( "QMessageBox.StandardButtons(QMessageBox.Yes | QMessageBox.No)"), @@ -265,8 +257,6 @@ def init_QtWidgets(): "SO_Default": QStyleOption.SO_Default, "SH_Default": QStyleHintReturn.SH_Default, "SO_Complex": QStyleOptionComplex.SO_Complex, - "QGraphicsItem[]": GraphicsItemList, - "QStyleOptionGraphicsItem[]": StyleOptionGraphicsItemList, "zero(PySide2.QtWidgets.QWidget)": None, "zero(PySide2.QtWidgets.QGraphicsItem)": None, "zero(PySide2.QtCore.QEvent)": None, @@ -286,7 +276,7 @@ def init_QtSql(): from PySide2.QtSql import QSqlDatabase type_map.update({ "QLatin1String(defaultConnection)": QSqlDatabase.defaultConnection, - "QVariant.Invalid": Invalid("PySide2.QtCore.QVariant"), # not sure what I should create, here... + "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here... }) return locals() @@ -342,7 +332,7 @@ def init_QtQml(): "QJSValueList()": [], "PySide2.QtQml.bool volatile": bool, # from 5.9 - "QVariantHash()": {}, + "QVariantHash()": typing.Dict[str, Variant], # XXX sorted? "zero(PySide2.QtQml.QQmlContext)": None, "zero(PySide2.QtQml.QQmlEngine)": None, }) diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index d15f3a951..56f53f304 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -4573,7 +4573,11 @@ void CppGenerator::writeSignatureInfo(QTextStream &s, const AbstractMetaFunction QStringList args; const AbstractMetaArgumentList &arguments = f->arguments(); for (const AbstractMetaArgument *arg : arguments) { + AbstractMetaType *argType = getTypeWithoutContainer(arg->type()); QString strArg = resolveRetOrArgType(arg->type()); + // PYSIDE-921: Handle container returntypes correctly. + if (argType != arg->type()) + strArg += QLatin1Char('[') + resolveRetOrArgType(argType) + QLatin1Char(']'); if (!arg->defaultValueExpression().isEmpty()) { strArg += QLatin1Char('='); QString e = arg->defaultValueExpression(); @@ -4587,10 +4591,12 @@ void CppGenerator::writeSignatureInfo(QTextStream &s, const AbstractMetaFunction // mark the multiple signatures as such, to make it easier to generate different code if (multiple) s << idx-- << ':'; - // now calculate the return type. s << funcName << '(' << args.join(QLatin1Char(',')) << ')'; AbstractMetaType *returnType = getTypeWithoutContainer(f->type()); - if (returnType) + // PYSIDE-921: Handle container returntypes correctly. + if (returnType != f->type()) + s << "->" << resolveRetOrArgType(f->type()) << '[' << resolveRetOrArgType(returnType) << ']'; + else if (returnType) s << "->" << resolveRetOrArgType(returnType); s << endl; } diff --git a/sources/shiboken2/shibokenmodule/support/signature/loader.py b/sources/shiboken2/shibokenmodule/support/signature/loader.py index 8c7739d9a..40ad95bdd 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/loader.py +++ b/sources/shiboken2/shibokenmodule/support/signature/loader.py @@ -148,6 +148,15 @@ with ensure_import_support(): namespace = inspect.__dict__ from support.signature import typing27 as typing typing.__name__ = "typing" + # Fix the module names in typing if possible. This is important since + # the typing names should be I/O compatible, so that typing.Dict + # shows itself as "typing.Dict". + for name, obj in typing.__dict__.items(): + if hasattr(obj, "__module__"): + try: + obj.__module__ = "typing" + except (TypeError, AttributeError): + pass from support.signature import backport_inspect as inspect _doc = inspect.__doc__ inspect.__dict__.update(namespace) diff --git a/sources/shiboken2/shibokenmodule/support/signature/mapping.py b/sources/shiboken2/shibokenmodule/support/signature/mapping.py index 40db43729..d4c630aca 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/mapping.py +++ b/sources/shiboken2/shibokenmodule/support/signature/mapping.py @@ -55,12 +55,13 @@ import os import pkgutil from signature_loader import typing +from signature_loader.typing import TypeVar, Generic class ellipsis(object): def __repr__(self): return "..." + ellipsis = ellipsis() -Char = typing.Union[str, int] # how do I model the limitation to 1 char? StringList = typing.List[str] IntList = typing.List[int] Point = typing.Tuple[float, float] @@ -69,10 +70,72 @@ IntMatrix = typing.List[IntList] Variant = typing.Any ModelIndexList = typing.List[int] QImageCleanupFunction = typing.Callable -FloatList = typing.List[float] -FloatMatrix = typing.List[FloatList] -# Pair could be more specific, but we loose the info in the generator. -Pair = typing.Tuple[typing.Any, typing.Any] + +# First time installing our own Pair type into typing. +T = TypeVar('T') +S = TypeVar('S') + +class Pair(Generic[T, S]): + __module__ = "typing" + +typing.Pair = Pair + + +# Building our own Char type, which is much nicer than +# Char = typing.Union[str, int] # how do I model the limitation to 1 char? + +# 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', (), {}) + +class _CharMeta(type): + def __repr__(self): + return '%s.%s' % (self.__module__, self.__name__) + + +class Char(with_metaclass(_CharMeta)): + """ + From http://doc.qt.io/qt-5/qchar.html : + + In Qt, Unicode characters are 16-bit entities without any markup or + structure. This class represents such an entity. It is lightweight, + so it can be used everywhere. Most compilers treat it like an + unsigned short. + + Here, we provide a simple implementation just to avoid long aliases. + """ + __module__ = "typing" + + def __init__(self, code): + if isinstance(code, int): + self.code = code & 0xffff + else: + self.code = ord(code) + + def __add__(self, other): + return chr(self.code) + other + + def __radd__(self, other): + return other + chr(self.code) + + def __repr__(self): + return "typing.Char({})".format(self.code) + +typing.Char = Char + + MultiMap = typing.DefaultDict[str, typing.List[str]] # ulong_max is only 32 bit on windows. @@ -205,6 +268,13 @@ update_mapping = Reloader().update type_map = {} namespace = globals() # our module's __dict__ +type_map.update({ + "QList": typing.List, + "QVector": typing.List, + "QSet": typing.Set, + "QPair": Pair, + }) + def init_Shiboken(): type_map.update({ @@ -225,18 +295,16 @@ def init_minimal(): def init_sample(): import datetime type_map.update({ + "double": float, "sample.int": int, "Complex": complex, "sample.OddBool": bool, "sample.bool": bool, "sample.PStr": str, - "double[]": FloatList, "OddBool": bool, "PStr": str, + "char": Char, "sample.char": Char, - "double[][]": FloatMatrix, - "int[]": IntList, - "int[][]": IntMatrix, "sample.Point": Point, "sample.ObjectType": object, "std.string": str, @@ -257,7 +325,6 @@ def init_sample(): "zero(sample.bool)": False, "PyDate": datetime.date, "ZeroIn": 0, - "Point[]": PointList, }) return locals() diff --git a/sources/shiboken2/shibokenmodule/support/signature/parser.py b/sources/shiboken2/shibokenmodule/support/signature/parser.py index 2f6e6e3f5..3b09dd4f7 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/parser.py +++ b/sources/shiboken2/shibokenmodule/support/signature/parser.py @@ -45,7 +45,8 @@ import warnings import types import keyword import functools -from signature_loader.mapping import type_map, update_mapping, namespace +from signature_loader.mapping import (type_map, update_mapping, namespace, + typing, Missing) _DEBUG = False LIST_KEYWORDS = False @@ -191,7 +192,35 @@ def _resolve_value(thing, valtype, line): """.format(thing, line), RuntimeWarning) return thing +def _resolve_arraytype(thing, line): + thing = thing[:-2] + if thing.endswith("[]"): + thing = _resolve_arraytype(thing, line) + # this mapping is in shiboken + thing = "QList[" + thing + "]" + return thing + +def to_string(thing): + if isinstance(thing, str): + return thing + if hasattr(thing, "__name__"): + dot = "." in str(type(thing)) + return thing.__module__ + "." + thing.__name__ if dot else thing.__name__ + # Note: This captures things from the typing module: + return str(thing) + def _resolve_type(thing, line): + if thing.endswith("[]"): + thing = _resolve_arraytype(thing, line) + if "[" in thing: + # Handle a container return type. (see PYSIDE-921 in cppgenerator.cpp) + contr, thing = re.match(r"(.*?)\[(.*?)\]$", thing).groups() + contr = to_string(_resolve_type(contr, line)) + thing = to_string(_resolve_type(thing, line)) + result = "{contr}[{thing}]".format(**locals()) + if not isinstance(thing, Missing): + result = eval(result, namespace) + return result return _resolve_value(thing, None, line) def calculate_props(line): |