-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix pyqtSlot
result
parameter type and Callable
generic
#102
Conversation
PyQt5-stubs/QtCore.pyi
Outdated
@@ -9264,7 +9264,7 @@ def oct_(s: QTextStream) -> QTextStream: ... | |||
def bin_(s: QTextStream) -> QTextStream: ... | |||
def Q_RETURN_ARG(type: typing.Any) -> QGenericReturnArgument: ... | |||
def Q_ARG(type: typing.Any, data: typing.Any) -> QGenericArgument: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Optional[str] = ...) -> typing.Callable[[FuncT], FuncT]: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[object] = ...) -> typing.Callable[[FuncT], FuncT]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you feel about the typing.Type[object]
. I starting with Type
because I was trying to relate it to the function return type but... I didn't get that working. Maybe this should just be object
/Any
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could FuncT
be made generic? I think this would work
T = TypeVar("T")
FuncT = typing.TypeVar('FuncT', bound=typing.Callable[..., T])
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[T] = ...) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
However, looking at the docs:
result – the type of the result and may be a Python type object or a string that specifies a C++ type
Not sure how the C++ string would interact with this. Maybe
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Union[typing.Type[T], str] = ...) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed that types
has the same comment in the docs about "may be a Python type object or a string", so maybe we can be more specific than typing.Any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean "can't be more specific"? When I read this I thought you made a comment then backed away from it. I did make an effort to relate the result
parameter to the return type of the typing.Callable
parameter and return but failed. I could make another attempt for you to look at.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No those were two different comments. I was suggesting that we change result
to use a generic. Then I noticed that types
is documented in a similar fashion and remarked that we could maybe do better than its current typing.Any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the types
, they can be any types including user defined and they don't necessarily even inherit from type what with metatypes, right? (plus str
) So the only thing I can think of is to try to relate it with the parameters of the wrapped callable, but mypy doesn't do that yet. There's some related PEP but I can't find it at the moment. Do you have something specific in mind here?
I'll try what you shared though it looks familiar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you're probably right that types
is as good as mypy currently allows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's yours along with the existing and a Protocol
attempt.
https://mypy-play.net/?mypy=latest&python=3.8&gist=314620351fccbbfd297833d345b40a6d
import typing
FuncT = typing.TypeVar('FuncT', bound=typing.Callable[..., typing.Any])
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Any = ..., revision: int = ...) -> typing.Callable[[FuncT], FuncT]: ...
T = typing.TypeVar("T")
FuncT2 = typing.TypeVar('FuncT2', bound=typing.Callable[..., T])
def pyqtSlot2(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[T] = ...) -> typing.Callable[[FuncT2[T]], FuncT2[T]]: ...
T_covariant = typing.TypeVar("T_covariant", covariant=True)
class P(typing.Protocol, typing.Generic[T_covariant]):
def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> T_covariant: ...
def pyqtSlot3(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[T_covariant] = ...) -> typing.Callable[[P[T_covariant]], P[T_covariant]]: ...
def f(s: str, i: int) -> float: ...
reveal_type(pyqtSlot(result=float)(f))
reveal_type(pyqtSlot2(result=float)(f))
reveal_type(pyqtSlot3(result=float)(f))
main.py:11: error: Type variable "FuncT2" used with arguments
main.py:25: note: Revealed type is 'def (s: builtins.str, i: builtins.int) -> builtins.float'
main.py:26: error: Value of type variable "FuncT2" of function cannot be "Callable[[str, int], float]"
main.py:26: note: Revealed type is 'def (s: builtins.str, i: builtins.int) -> builtins.float'
main.py:27: note: Revealed type is 'main.P[builtins.float*]'
main.py:27: error: Argument 1 has incompatible type "Callable[[str, int], float]"; expected "P[float]"
Found 3 errors in 1 file (checked 1 source file)
I don't think we get to make a generic typevar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we need to make FuncT
a TypeVar?
import typing
S = typing.TypeVar("S")
T = typing.TypeVar("T")
FuncT = typing.Callable[..., S]
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[T] = ...) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
def f(s: str, i: int) -> float: ...
reveal_type(pyqtSlot(result=float)(f))
main.py:13: note: Revealed type is 'def (*Any, **Any) -> builtins.float*'
def f(s: str, i: int) -> float: ...
reveal_type(pyqtSlot(result=bool)(f))
main.py:13: note: Revealed type is 'def (*Any, **Any) -> builtins.bool*'
main.py:13: error: Argument 1 has incompatible type "Callable[[str, int], float]"; expected "Callable[..., bool]"
Found 1 error in 1 file (checked 1 source file)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You win (I think), thanks.
PyQt5-stubs/QtCore.pyi
Outdated
@@ -9264,7 +9264,7 @@ def oct_(s: QTextStream) -> QTextStream: ... | |||
def bin_(s: QTextStream) -> QTextStream: ... | |||
def Q_RETURN_ARG(type: typing.Any) -> QGenericReturnArgument: ... | |||
def Q_ARG(type: typing.Any, data: typing.Any) -> QGenericArgument: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Optional[str] = ...) -> typing.Callable[[FuncT], FuncT]: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[object] = ...) -> typing.Callable[[FuncT], FuncT]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could FuncT
be made generic? I think this would work
T = TypeVar("T")
FuncT = typing.TypeVar('FuncT', bound=typing.Callable[..., T])
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[T] = ...) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
However, looking at the docs:
result – the type of the result and may be a Python type object or a string that specifies a C++ type
Not sure how the C++ string would interact with this. Maybe
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Union[typing.Type[T], str] = ...) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
PyQt5-stubs/QtCore.pyi
Outdated
@@ -9264,7 +9264,7 @@ def oct_(s: QTextStream) -> QTextStream: ... | |||
def bin_(s: QTextStream) -> QTextStream: ... | |||
def Q_RETURN_ARG(type: typing.Any) -> QGenericReturnArgument: ... | |||
def Q_ARG(type: typing.Any, data: typing.Any) -> QGenericArgument: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Optional[str] = ...) -> typing.Callable[[FuncT], FuncT]: ... | |||
def pyqtSlot(*types: typing.Any, name: typing.Optional[str] = ..., result: typing.Type[object] = ...) -> typing.Callable[[FuncT], FuncT]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed that types
has the same comment in the docs about "may be a Python type object or a string", so maybe we can be more specific than typing.Any
Do you think we should try to do more here now? |
Did you see my comment about making |
Sorry not trying to drag this PR on and on again, but I think we need this for the overloads: @typing.overload
def pyqtSlot(*types: typing.Any) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, result: typing.Union[typing.Type[T], str]) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, result: typing.Union[typing.Type[T], str]) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, result: typing.Union[typing.Type[T], str], revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, result: typing.Union[typing.Type[T], str], revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ... What I changed:
* Note: This contradicts the documentation here where the later arguments are in ever-nesting brackets. The documentation implies that you can't use >>> pyqtSlot(int, name=None)
Side note: It looks like This is further contradicted
We should probably mention this to Phil too. This is a mess 🤦 |
You can't use https://mypy-play.net/?mypy=latest&python=3.8&flags=strict&gist=9608229557029541b9a90553bce77414 import typing
T = typing.TypeVar("T")
FuncT = typing.Callable[..., T]
@typing.overload
def pyqtSlot(*types: typing.Any) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, result: typing.Union[typing.Type[T], str]) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, result: typing.Union[typing.Type[T], str]) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, result: typing.Union[typing.Type[T], str], revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@typing.overload
def pyqtSlot(*types: typing.Any, name: str, result: typing.Union[typing.Type[T], str], revision: int) -> typing.Callable[[FuncT[T]], FuncT[T]]: ...
@pyqtSlot(str, result='int')
def func_str(s: str) -> int:
return 42
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we probably also want to update the changelog too
|
fix `pyqtSlot` `result` parameter type and `Callable` generic
No description provided.