From 10154888ded753c2fa3fe2876fe9ae25404efe52 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 21 Jul 2020 00:53:45 +0300 Subject: [PATCH 1/5] make tkinter.Event generic and add missing type hints to bind methods --- stdlib/3/tkinter/__init__.pyi | 98 ++++++++++++++++++++++++++++++----- stdlib/3/tkinter/ttk.pyi | 16 +++++- 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/stdlib/3/tkinter/__init__.pyi b/stdlib/3/tkinter/__init__.pyi index 9b1e3d1296ec..f935b3a64480 100644 --- a/stdlib/3/tkinter/__init__.pyi +++ b/stdlib/3/tkinter/__init__.pyi @@ -2,7 +2,8 @@ import sys from enum import Enum from tkinter.constants import * # noqa: F403 from types import TracebackType -from typing import Any, Callable, Dict, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union, overload +from typing_extensions import Literal TclError: Any wantobjects: Any @@ -52,7 +53,10 @@ if sys.version_info >= (3, 6): VirtualEvent: str = ... Visibility: str = ... -class Event: +# Events considered covariant because you should never assign to event.widget. +_W = TypeVar("_W", covariant=True, bound="Misc") + +class Event(Generic[_W]): serial: int num: int focus: bool @@ -73,7 +77,7 @@ class Event: type: EventType else: type: str - widget: Misc + widget: _W delta: int def NoDefaultRoot(): ... @@ -119,6 +123,8 @@ getdouble: Any def getboolean(s): ... +# This class is the base class of all widgets. Don't use BaseWidget or Widget +# for that because Tk doesn't inherit from Widget or BaseWidget. class Misc: def destroy(self): ... def deletecommand(self, name): ... @@ -222,12 +228,42 @@ class Misc: def update(self): ... def update_idletasks(self): ... def bindtags(self, tagList: Optional[Any] = ...): ... - def bind(self, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ... - def unbind(self, sequence, funcid: Optional[Any] = ...): ... - def bind_all(self, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ... - def unbind_all(self, sequence): ... - def bind_class(self, className, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ... - def unbind_class(self, className, sequence): ... + # bind with isinstance(func, str) doesn't return anything, but all other binds do. + # + # The order of overloads matters for a call like bind(''), which should return str. + @overload + def bind( + self, + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def bind(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + # There's no way to know what type of widget bind_all and bind_class + # callbacks will get, so those are Misc. + @overload + def bind_all( + self, + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def bind_all(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + @overload + def bind_class( + self, + className: str, + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def bind_class(self, className: str, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + def unbind(self, sequence: str, funcid: Optional[str] = ...) -> None: ... + def unbind_all(self, sequence: str) -> None: ... + def unbind_class(self, className: str, sequence: str) -> None: ... def mainloop(self, n: int = ...): ... def quit(self): ... def nametowidget(self, name): ... @@ -419,7 +455,20 @@ class BaseWidget(Misc): def __init__(self, master, widgetName, cnf=..., kw=..., extra=...): ... def destroy(self): ... -class Widget(BaseWidget, Pack, Place, Grid): ... +# This class represents any widget except Toplevel or Tk. +class Widget(BaseWidget, Pack, Place, Grid): + # Allow bind callbacks to take e.g. Event[Label] instead of Event[Misc]. + # Tk and Toplevel get notified for their child widgets' events, but other + # widgets don't. + @overload + def bind( + self: _W, + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[_W]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def bind(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... class Toplevel(BaseWidget, Wm): def __init__(self, master: Optional[Any] = ..., cnf=..., **kw): ... @@ -440,8 +489,19 @@ class Canvas(Widget, XView, YView): def addtag_overlapping(self, newtag, x1, y1, x2, y2): ... def addtag_withtag(self, newtag, tagOrId): ... def bbox(self, *args): ... - def tag_unbind(self, tagOrId, sequence, funcid: Optional[Any] = ...): ... - def tag_bind(self, tagOrId, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ... + @overload + def tag_bind( + self, + tagOrId: Union[str, int], + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def tag_bind( + self, tagOrId: Union[str, int], sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ..., + ) -> None: ... + def tag_unbind(self, tagOrId: Union[str, int], sequence: str, funcid: Optional[str] = ...) -> None: ... def canvasx(self, screenx, gridspacing: Optional[Any] = ...): ... def canvasy(self, screeny, gridspacing: Optional[Any] = ...): ... def coords(self, *args): ... @@ -660,8 +720,18 @@ class Text(Widget, XView, YView): ): ... def see(self, index): ... def tag_add(self, tagName, index1, *args): ... - def tag_unbind(self, tagName, sequence, funcid: Optional[Any] = ...): ... - def tag_bind(self, tagName, sequence, func, add: Optional[Any] = ...): ... + # tag_bind stuff is very similar to Canvas + @overload + def tag_bind( + self, + tagName: str, + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]] = ..., + add: Optional[bool] = ..., + ) -> str: ... + @overload + def tag_bind(self, tagName: str, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...,) -> None: ... + def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ... def tag_cget(self, tagName, option): ... def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ... tag_config: Any diff --git a/stdlib/3/tkinter/ttk.pyi b/stdlib/3/tkinter/ttk.pyi index 4d7afe1da86c..023c4c8cd6dd 100644 --- a/stdlib/3/tkinter/ttk.pyi +++ b/stdlib/3/tkinter/ttk.pyi @@ -1,6 +1,8 @@ import sys import tkinter -from typing import Any, List, Optional +from tkinter import Event +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload +from typing_extensions import Literal def tclobjs_to_py(adict): ... def setup_master(master: Optional[Any] = ...): ... @@ -145,7 +147,17 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): def selection_remove(self, items): ... def selection_toggle(self, items): ... def set(self, item, column: Optional[Any] = ..., value: Optional[Any] = ...): ... - def tag_bind(self, tagname, sequence: Optional[Any] = ..., callback: Optional[Any] = ...): ... + # There's no tag_unbind() or 'add' argument for whatever reason. + # Also, it's 'callback' instead of 'func' here. + @overload + def tag_bind( + self, + tagname: str, + sequence: Optional[str] = ..., + callback: Optional[Callable[[Event[Treeview]], Optional[Literal["break"]]]] = ..., + ) -> str: ... + @overload + def tag_bind(self, tagname: str, sequence: Optional[str] = ..., callback: str = ...,) -> None: ... def tag_configure(self, tagname, option: Optional[Any] = ..., **kw): ... def tag_has(self, tagname, item: Optional[Any] = ...): ... From 32f9f29ec7248a0c7816ecc975d009dde465d3a9 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 21 Jul 2020 15:45:33 +0300 Subject: [PATCH 2/5] fix binds with isinstance(func, str) --- stdlib/3/tkinter/__init__.pyi | 31 ++++++++++++++++++++----------- stdlib/3/tkinter/ttk.pyi | 4 +++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/stdlib/3/tkinter/__init__.pyi b/stdlib/3/tkinter/__init__.pyi index f935b3a64480..22cb262db0ee 100644 --- a/stdlib/3/tkinter/__init__.pyi +++ b/stdlib/3/tkinter/__init__.pyi @@ -228,9 +228,8 @@ class Misc: def update(self): ... def update_idletasks(self): ... def bindtags(self, tagList: Optional[Any] = ...): ... - # bind with isinstance(func, str) doesn't return anything, but all other binds do. - # - # The order of overloads matters for a call like bind(''), which should return str. + # bind with isinstance(func, str) doesn't return anything, but all other + # binds do. The default value of func is not str. @overload def bind( self, @@ -239,7 +238,9 @@ class Misc: add: Optional[bool] = ..., ) -> str: ... @overload - def bind(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + def bind(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def bind(self, *, func: str, add: Optional[bool] = ...) -> None: ... # There's no way to know what type of widget bind_all and bind_class # callbacks will get, so those are Misc. @overload @@ -250,7 +251,9 @@ class Misc: add: Optional[bool] = ..., ) -> str: ... @overload - def bind_all(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + def bind_all(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def bind_all(self, *, func: str, add: Optional[bool] = ...) -> None: ... @overload def bind_class( self, @@ -260,7 +263,9 @@ class Misc: add: Optional[bool] = ..., ) -> str: ... @overload - def bind_class(self, className: str, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + def bind_class(self, className: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def bind_class(self, className: str, *, func: str, add: Optional[bool] = ...) -> None: ... def unbind(self, sequence: str, funcid: Optional[str] = ...) -> None: ... def unbind_all(self, sequence: str) -> None: ... def unbind_class(self, className: str, sequence: str) -> None: ... @@ -468,7 +473,9 @@ class Widget(BaseWidget, Pack, Place, Grid): add: Optional[bool] = ..., ) -> str: ... @overload - def bind(self, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...) -> None: ... + def bind(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def bind(self, *, func: str, add: Optional[bool] = ...) -> None: ... class Toplevel(BaseWidget, Wm): def __init__(self, master: Optional[Any] = ..., cnf=..., **kw): ... @@ -498,9 +505,9 @@ class Canvas(Widget, XView, YView): add: Optional[bool] = ..., ) -> str: ... @overload - def tag_bind( - self, tagOrId: Union[str, int], sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ..., - ) -> None: ... + def tag_bind(self, tagOrId: Union[str, int], sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def tag_bind(self, tagOrId: Union[str, int], *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagOrId: Union[str, int], sequence: str, funcid: Optional[str] = ...) -> None: ... def canvasx(self, screenx, gridspacing: Optional[Any] = ...): ... def canvasy(self, screeny, gridspacing: Optional[Any] = ...): ... @@ -730,7 +737,9 @@ class Text(Widget, XView, YView): add: Optional[bool] = ..., ) -> str: ... @overload - def tag_bind(self, tagName: str, sequence: Optional[str] = ..., func: str = ..., add: Optional[bool] = ...,) -> None: ... + def tag_bind(self, tagName: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def tag_bind(self, tagName: str, *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ... def tag_cget(self, tagName, option): ... def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ... diff --git a/stdlib/3/tkinter/ttk.pyi b/stdlib/3/tkinter/ttk.pyi index 023c4c8cd6dd..4cd08dfa052e 100644 --- a/stdlib/3/tkinter/ttk.pyi +++ b/stdlib/3/tkinter/ttk.pyi @@ -157,7 +157,9 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): callback: Optional[Callable[[Event[Treeview]], Optional[Literal["break"]]]] = ..., ) -> str: ... @overload - def tag_bind(self, tagname: str, sequence: Optional[str] = ..., callback: str = ...,) -> None: ... + def tag_bind(self, tagname: str, sequence: Optional[str], callback: str) -> None: ... + @overload + def tag_bind(self, tagname: str, *, callback: str) -> None: ... def tag_configure(self, tagname, option: Optional[Any] = ..., **kw): ... def tag_has(self, tagname, item: Optional[Any] = ...): ... From 4f173868e65b13fb341685adaccca151a4cecbd6 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 21 Jul 2020 16:01:30 +0300 Subject: [PATCH 3/5] tag_bind methods don't provide defaults for sequence and func, except in ttk --- stdlib/3/tkinter/__init__.pyi | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/stdlib/3/tkinter/__init__.pyi b/stdlib/3/tkinter/__init__.pyi index 22cb262db0ee..609c46746eae 100644 --- a/stdlib/3/tkinter/__init__.pyi +++ b/stdlib/3/tkinter/__init__.pyi @@ -500,14 +500,12 @@ class Canvas(Widget, XView, YView): def tag_bind( self, tagOrId: Union[str, int], - sequence: Optional[str] = ..., - func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]] = ..., + sequence: Optional[str], + func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]], add: Optional[bool] = ..., ) -> str: ... @overload def tag_bind(self, tagOrId: Union[str, int], sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... - @overload - def tag_bind(self, tagOrId: Union[str, int], *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagOrId: Union[str, int], sequence: str, funcid: Optional[str] = ...) -> None: ... def canvasx(self, screenx, gridspacing: Optional[Any] = ...): ... def canvasy(self, screeny, gridspacing: Optional[Any] = ...): ... @@ -732,14 +730,12 @@ class Text(Widget, XView, YView): def tag_bind( self, tagName: str, - sequence: Optional[str] = ..., - func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]] = ..., + sequence: Optional[str], + func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]], add: Optional[bool] = ..., ) -> str: ... @overload def tag_bind(self, tagName: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... - @overload - def tag_bind(self, tagName: str, *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ... def tag_cget(self, tagName, option): ... def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ... From e7d1326263bd250697d02112d8d3254423bcaa5a Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 21 Jul 2020 16:04:53 +0300 Subject: [PATCH 4/5] Revert "tag_bind methods don't provide defaults for sequence and func, except in ttk" This reverts commit 4f173868e65b13fb341685adaccca151a4cecbd6. --- stdlib/3/tkinter/__init__.pyi | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/stdlib/3/tkinter/__init__.pyi b/stdlib/3/tkinter/__init__.pyi index 609c46746eae..22cb262db0ee 100644 --- a/stdlib/3/tkinter/__init__.pyi +++ b/stdlib/3/tkinter/__init__.pyi @@ -500,12 +500,14 @@ class Canvas(Widget, XView, YView): def tag_bind( self, tagOrId: Union[str, int], - sequence: Optional[str], - func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]], + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]] = ..., add: Optional[bool] = ..., ) -> str: ... @overload def tag_bind(self, tagOrId: Union[str, int], sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def tag_bind(self, tagOrId: Union[str, int], *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagOrId: Union[str, int], sequence: str, funcid: Optional[str] = ...) -> None: ... def canvasx(self, screenx, gridspacing: Optional[Any] = ...): ... def canvasy(self, screeny, gridspacing: Optional[Any] = ...): ... @@ -730,12 +732,14 @@ class Text(Widget, XView, YView): def tag_bind( self, tagName: str, - sequence: Optional[str], - func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]], + sequence: Optional[str] = ..., + func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]] = ..., add: Optional[bool] = ..., ) -> str: ... @overload def tag_bind(self, tagName: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... + @overload + def tag_bind(self, tagName: str, *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ... def tag_cget(self, tagName, option): ... def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ... From 2c1d61d452e1904d4250155d3e48128dd9657c83 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 21 Jul 2020 16:06:13 +0300 Subject: [PATCH 5/5] Text.tag_bind methods doesn't provide defaults for sequence and func --- stdlib/3/tkinter/__init__.pyi | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stdlib/3/tkinter/__init__.pyi b/stdlib/3/tkinter/__init__.pyi index 22cb262db0ee..8f03107c984a 100644 --- a/stdlib/3/tkinter/__init__.pyi +++ b/stdlib/3/tkinter/__init__.pyi @@ -732,14 +732,12 @@ class Text(Widget, XView, YView): def tag_bind( self, tagName: str, - sequence: Optional[str] = ..., - func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]] = ..., + sequence: Optional[str], + func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]], add: Optional[bool] = ..., ) -> str: ... @overload def tag_bind(self, tagName: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ... - @overload - def tag_bind(self, tagName: str, *, func: str, add: Optional[bool] = ...) -> None: ... def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ... def tag_cget(self, tagName, option): ... def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ...