From ffef40ed375d5dda1ad4638a349748e23febda1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ch=C5=82odnicki?= Date: Tue, 16 Feb 2021 21:45:10 +0100 Subject: [PATCH] Require a value when calling resolve function of a Promise (#1583) Make the value passed to ResolveFunc non-optional. This allows to skip some if-checks when handling resolved value from a "fulfill" function. This also aligns the behavior with a recent change in Typescript [1]. And, for consistency, also make the argument to Promise.[resolve|on_main_thread|on_async_thread] non-optional. [1] https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#resolves-parameters-are-no-longer-optional-in-promises --- plugin/core/open.py | 6 +++--- plugin/core/promise.py | 32 ++++++++++++++++---------------- plugin/core/sessions.py | 16 ++++++++-------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/plugin/core/open.py b/plugin/core/open.py index 1b52bcfba..3f8740d73 100644 --- a/plugin/core/open.py +++ b/plugin/core/open.py @@ -58,11 +58,11 @@ def center_selection(v: Optional[sublime.View]) -> None: def open_file_and_center_async(window: sublime.Window, file_path: str, r: Optional[RangeLsp], flag: int = 0, - group: int = -1) -> Promise: + group: int = -1) -> Promise[None]: """Open a file asynchronously and center the range, worker thread version.""" - return Promise.on_main_thread() \ + return Promise.on_main_thread(None) \ .then(lambda _: open_file_and_center(window, file_path, r, flag, group)) \ - .then(Promise.on_async_thread) + .then(lambda _: Promise.on_async_thread(None)) def open_externally(uri: str, take_focus: bool) -> bool: diff --git a/plugin/core/promise.py b/plugin/core/promise.py index 6ff45ae9a..6151e707c 100644 --- a/plugin/core/promise.py +++ b/plugin/core/promise.py @@ -4,16 +4,17 @@ import threading T = TypeVar('T') +TExecutor = TypeVar('TExecutor') T_contra = TypeVar('T_contra', contravariant=True) TResult = TypeVar('TResult') class ResolveFunc(Protocol[T_contra]): - def __call__(self, value: Union[T_contra, 'Promise[T_contra]'] = None) -> None: + def __call__(self, value: Union[T_contra, 'Promise[T_contra]']) -> None: ... -FullfillFunc = Callable[[T], Union[TResult, 'Promise[TResult]', None]] +FullfillFunc = Callable[[T], Union[TResult, 'Promise[TResult]']] ExecutorFunc = Callable[[ResolveFunc[T]], None] PackagedTask = Tuple['Promise[T]', ResolveFunc[T]] @@ -64,7 +65,7 @@ def process_value(value): """ @classmethod - def resolve(cls, resolve_value: T = None) -> 'Promise[T]': + def resolve(cls, resolve_value: T) -> 'Promise[T]': """Immediately resolves a Promise. Convenience function for creating a Promise that gets immediately @@ -79,29 +80,29 @@ def executor_func(resolve_fn: ResolveFunc[T]) -> None: return cls(executor_func) @classmethod - def on_main_thread(cls, value: T = None) -> 'Promise[T]': + def on_main_thread(cls, value: T) -> 'Promise[T]': """Return a promise that resolves on the main thread.""" return Promise(lambda resolve: sublime.set_timeout(lambda: resolve(value))) @classmethod - def on_async_thread(cls, value: T = None) -> 'Promise[T]': + def on_async_thread(cls, value: T) -> 'Promise[T]': """Return a promise that resolves on the worker thread.""" return Promise(lambda resolve: sublime.set_timeout_async(lambda: resolve(value))) @classmethod def packaged_task(cls) -> PackagedTask[T]: - class Executor: + class Executor(Generic[TExecutor]): __slots__ = ("resolver",) def __init__(self) -> None: - self.resolver = None # type: Optional[ResolveFunc[T]] + self.resolver = None # type: Optional[ResolveFunc[TExecutor]] - def __call__(self, resolver: ResolveFunc[T]) -> None: + def __call__(self, resolver: ResolveFunc[TExecutor]) -> None: self.resolver = resolver - executor = Executor() + executor = Executor() # type: Executor[T] promise = cls(executor) assert callable(executor.resolver) return promise, executor.resolver @@ -121,14 +122,14 @@ def all(cls, promises: List['Promise[T]']) -> 'Promise[List[T]]': def executor(resolve: ResolveFunc[List[T]]) -> None: was_resolved = False - def recheck_resolve_status(_: Optional[T]) -> None: + def recheck_resolve_status(_: T) -> None: nonlocal was_resolved # We're being called from a Promise that is holding a lock so don't try to use # any methods that would try to acquire it. if not was_resolved and all(p.resolved for p in promises): was_resolved = True values = [p.value for p in promises] - resolve(values) # type: ignore + resolve(values) for p in promises: assert isinstance(p, Promise) @@ -146,7 +147,6 @@ def __init__(self, executor_func: ExecutorFunc[T]) -> None: It gets passed a "resolve" function. The "resolve" function, when called, resolves the Promise with the value passed to it. """ - self.value = None # type: Optional[T] self.resolved = False self.mutex = threading.Lock() self.callbacks = [] # type: List[ResolveFunc[T]] @@ -157,7 +157,7 @@ def __repr__(self) -> str: return 'Promise({})'.format(self.value) return 'Promise()' - def then(self, onfullfilled: FullfillFunc[Optional[T], TResult]) -> 'Promise[TResult]': + def then(self, onfullfilled: FullfillFunc[T, TResult]) -> 'Promise[TResult]': """Create a new promise and chain it with this promise. When this promise gets resolved, the callback will be called with the @@ -168,7 +168,7 @@ def then(self, onfullfilled: FullfillFunc[Optional[T], TResult]) -> 'Promise[TRe Arguments: onfullfilled: The callback to call when this promise gets resolved. """ - def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: Optional[T]) -> None: + def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: T) -> None: """A wrapper called when this promise resolves. Arguments: @@ -179,7 +179,7 @@ def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: Optional[T # If returned value is a promise then this promise needs to be # resolved with the value of returned promise. if isinstance(result, Promise): - result.then(resolve_fn) + result.then(lambda value: resolve_fn(value)) else: resolve_fn(result) @@ -221,6 +221,6 @@ def _is_resolved(self) -> bool: with self.mutex: return self.resolved - def _get_value(self) -> Optional[T]: + def _get_value(self) -> T: with self.mutex: return self.value diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 36ebb69af..67081f7d6 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -985,7 +985,7 @@ def execute_command(self, command: ExecuteCommandParams, progress: bool) -> Prom if self._plugin: task = Promise.packaged_task() # type: PackagedTask[None] promise, resolve = task - if self._plugin.on_pre_server_command(command, resolve): + if self._plugin.on_pre_server_command(command, lambda: resolve(None)): return promise # TODO: Our Promise class should be able to handle errors/exceptions return Promise( @@ -1020,15 +1020,15 @@ def _maybe_resolve_code_action(self, code_action: CodeAction) -> Promise[Union[C return self.send_request_task(request) return Promise.resolve(code_action) - def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) -> Promise: + def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) -> Promise[None]: if not code_action: - return Promise.resolve() + return Promise.resolve(None) if isinstance(code_action, Error): # TODO: our promise must be able to handle exceptions (or, wait until we can use coroutines) self.window.status_message("Failed to apply code action: {}".format(code_action)) - return Promise.resolve() + return Promise.resolve(None) edit = code_action.get("edit") - promise = self._apply_workspace_edit_async(edit) if edit else Promise.resolve() + promise = self._apply_workspace_edit_async(edit) if edit else Promise.resolve(None) command = code_action.get("command") if isinstance(command, dict): execute_command = { @@ -1038,15 +1038,15 @@ def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) return promise.then(lambda _: self.execute_command(execute_command, False)) return promise - def _apply_workspace_edit_async(self, edit: Any) -> Promise: + def _apply_workspace_edit_async(self, edit: Any) -> Promise[None]: """ Apply workspace edits, and return a promise that resolves on the async thread again after the edits have been applied. """ changes = parse_workspace_edit(edit) - return Promise.on_main_thread() \ + return Promise.on_main_thread(None) \ .then(lambda _: apply_workspace_edit(self.window, changes)) \ - .then(Promise.on_async_thread) + .then(lambda _: Promise.on_async_thread(None)) # --- server request handlers --------------------------------------------------------------------------------------