diff --git a/docs/testing/mypy.md b/docs/testing/mypy.md index f6db236929..583661861c 100644 --- a/docs/testing/mypy.md +++ b/docs/testing/mypy.md @@ -148,6 +148,42 @@ because a list can have many elements, which would make the output too large. Similarly in dicts, one key's type and the corresponding value's type are printed. So `{1: 'a', 2: 'b', 3: 'c'}` will be printed as `{int: str, ...}`. +## Using @overload to accurately describe variations + +Sometimes, a function's type is most precisely expressed as a few +possibilites, and which possibility can be determined by looking at +the arguments. You can express that idea in a way mypy understands +using `@overload`. For example, `check_list` returns a `Validator` +function that verifies that an object is a list, raising an exception +if it isn't. + +It supports being passed a `sub_validator`, which will verify that +each element in the list has a given type as well. One can express +the idea "If `sub_validator` validates that something is a `ResultT`, +`check_list(sub_validator)` validators that something is a +`List[ResultT]` as follows: + +~~~ py +@overload +def check_list(sub_validator: None, length: Optional[int]=None) -> Validator[List[object]]: + ... +@overload +def check_list(sub_validator: Validator[ResultT], + length: Optional[int]=None) -> Validator[List[ResultT]]: + ... +def check_list(sub_validator: Optional[Validator[ResultT]]=None, + length: Optional[int]=None) -> Validator[List[ResultT]]: +~~~ + +The first overload expresses the types for the case where no +`sub_validator` is passed, in which case all we know is that it +returns a `Validator[List[object]]`; whereas the second defines the +type logic for the case where we are passed a `sub_validator`. + +See the [mypy overloading documentation][mypy-overloads] for more details. + +[mypy-overloads]: https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading + ## Troubleshooting advice All of our linters, including mypy, are designed to only check files diff --git a/zerver/lib/request.py b/zerver/lib/request.py index d8247ba5f1..7823460d6f 100644 --- a/zerver/lib/request.py +++ b/zerver/lib/request.py @@ -137,7 +137,10 @@ class _REQ(Generic[ResultT]): # functions using has_request_variables. In reality, REQ returns an # instance of class _REQ to enable the decorator to scan the parameter # list for _REQ objects and patch the parameters as the true types. - +# +# See also this documentation to learn how @overload helps here. +# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations +# # Overload 1: converter @overload def REQ( diff --git a/zerver/lib/validator.py b/zerver/lib/validator.py index 924ebae4db..fa89a9ceb5 100644 --- a/zerver/lib/validator.py +++ b/zerver/lib/validator.py @@ -186,6 +186,7 @@ def check_none_or(sub_validator: Validator[ResultT]) -> Validator[ResultT]: return sub_validator(var_name, val) return f +# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations @overload def check_list(sub_validator: None, length: Optional[int]=None) -> Validator[List[object]]: ... @@ -220,6 +221,7 @@ def check_list(sub_validator: Optional[Validator[ResultT]]=None, length: Optiona return cast(List[ResultT], val) return f +# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations @overload def check_dict(required_keys: Iterable[Tuple[str, Validator[object]]]=[], optional_keys: Iterable[Tuple[str, Validator[object]]]=[],