forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add type checking plugin support for functions (python#3299)
* Add type checking plugin support for functions The plugins allow implementing special-case logic for inferring the return type of certain functions with tricky signatures such as `open` in Python 3. Include plugins for `open` and `contextlib.contextmanager`. Some design considerations: - The plugins have direct access to mypy internals. The idea is that most plugins will be included with mypy so mypy maintainers can update the plugins as needed. - User-maintained plugins are currently not supported but could be added in the future. However, the intention is to not have a stable plugin API, at least initially. User-maintained plugins would have to track mypy internal API changes. Later on, we may decide to provide a more stable API if there seems to be a significant need. The preferred way would still be to keep plugins in the mypy repo. * Add test case for additional special cases * Fix handling of arguments other than simple positional ones Also add comments and some defensive checks.
- Loading branch information
1 parent
a494197
commit 53879ef
Showing
4 changed files
with
176 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Plugins that implement special type checking rules for individual functions. | ||
The plugins infer better types for tricky functions such as "open". | ||
""" | ||
|
||
from typing import Tuple, Dict, Callable, List | ||
|
||
from mypy.nodes import Expression, StrExpr | ||
from mypy.types import Type, Instance, CallableType | ||
|
||
|
||
# A callback that infers the return type of a function with a special signature. | ||
# | ||
# A no-op callback would just return the inferred return type, but a useful callback | ||
# at least sometimes can infer a more precise type. | ||
PluginCallback = Callable[ | ||
[ | ||
List[List[Type]], # List of types caller provides for each formal argument | ||
List[List[Expression]], # Actual argument expressions for each formal argument | ||
Type, # Return type for call inferred using the regular signature | ||
Callable[[str, List[Type]], Type] # Callable for constructing a named instance type | ||
], | ||
Type # Return type inferred by the callback | ||
] | ||
|
||
|
||
def get_function_plugin_callbacks(python_version: Tuple[int, int]) -> Dict[str, PluginCallback]: | ||
"""Return all available function plugins for a given Python version.""" | ||
if python_version[0] == 3: | ||
return { | ||
'builtins.open': open_callback, | ||
'contextlib.contextmanager': contextmanager_callback, | ||
} | ||
else: | ||
return { | ||
'contextlib.contextmanager': contextmanager_callback, | ||
} | ||
|
||
|
||
def open_callback( | ||
arg_types: List[List[Type]], | ||
args: List[List[Expression]], | ||
inferred_return_type: Type, | ||
named_generic_type: Callable[[str, List[Type]], Type]) -> Type: | ||
"""Infer a better return type for 'open'. | ||
Infer IO[str] or IO[bytes] as the return value if the mode argument is not | ||
given or is a literal. | ||
""" | ||
mode = None | ||
if not arg_types or len(arg_types[1]) != 1: | ||
mode = 'r' | ||
elif isinstance(args[1][0], StrExpr): | ||
mode = args[1][0].value | ||
if mode is not None: | ||
assert isinstance(inferred_return_type, Instance) | ||
if 'b' in mode: | ||
arg = named_generic_type('builtins.bytes', []) | ||
else: | ||
arg = named_generic_type('builtins.str', []) | ||
return Instance(inferred_return_type.type, [arg]) | ||
return inferred_return_type | ||
|
||
|
||
def contextmanager_callback( | ||
arg_types: List[List[Type]], | ||
args: List[List[Expression]], | ||
inferred_return_type: Type, | ||
named_generic_type: Callable[[str, List[Type]], Type]) -> Type: | ||
"""Infer a better return type for 'contextlib.contextmanager'.""" | ||
# Be defensive, just in case. | ||
if arg_types and len(arg_types[0]) == 1: | ||
arg_type = arg_types[0][0] | ||
if isinstance(arg_type, CallableType) and isinstance(inferred_return_type, CallableType): | ||
# The stub signature doesn't preserve information about arguments so | ||
# add them back here. | ||
return inferred_return_type.copy_modified( | ||
arg_types=arg_type.arg_types, | ||
arg_kinds=arg_type.arg_kinds, | ||
arg_names=arg_type.arg_names) | ||
return inferred_return_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters