Skip to content
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

Remove open plugin, not needed with overloaded signatures on typeshed #7794

Closed
wants to merge 10 commits into from
Closed
78 changes: 44 additions & 34 deletions mypy/plugins/default.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from functools import partial
from typing import Callable, Optional, List
from typing import Callable, Optional, List, Tuple

from mypy import message_registry
from mypy.nodes import Expression, StrExpr, IntExpr, DictExpr, UnaryExpr
from mypy.nodes import Context, Expression, StrExpr, IntExpr, DictExpr, UnaryExpr
from mypy.plugin import (
Plugin, FunctionContext, MethodContext, MethodSigContext, AttributeContext, ClassDefContext,
CheckerPluginInterface,
Expand Down Expand Up @@ -107,55 +107,65 @@ def get_class_decorator_hook(self, fullname: str


def open_callback(ctx: FunctionContext) -> Type:
"""Infer a better return type for 'open'."""
return _analyze_open_signature(
"""Verify argument types for 'open'."""
_verify_open_signature(
arg_types=ctx.arg_types,
args=ctx.args,
arg_names=ctx.callee_arg_names,
mode_arg_index=1,
default_return_type=ctx.default_return_type,
text_only_arg_indices=(3, 4, 5),
api=ctx.api,
context=ctx.context,
)
return ctx.default_return_type


def path_open_callback(ctx: MethodContext) -> Type:
"""Infer a better return type for 'pathlib.Path.open'."""
return _analyze_open_signature(
"""Verify argument types for 'pathlib.Path.open'."""
_verify_open_signature(
arg_types=ctx.arg_types,
args=ctx.args,
arg_names=ctx.callee_arg_names,
mode_arg_index=0,
default_return_type=ctx.default_return_type,
text_only_arg_indices=(2, 3, 4),
api=ctx.api,
context=ctx.context,
)
return ctx.default_return_type


def _analyze_open_signature(arg_types: List[List[Type]],
args: List[List[Expression]],
mode_arg_index: int,
default_return_type: Type,
api: CheckerPluginInterface,
) -> Type:
"""A helper for analyzing any function that has approximately
the same signature as the builtin 'open(...)' function.

Currently, the only thing the caller can customize is the index
of the 'mode' argument. If the mode argument is omitted or is a
string literal, we refine the return type to either 'TextIO' or
'BinaryIO' as appropriate.
def _verify_open_signature(
arg_types: List[List[Type]],
args: List[List[Expression]],
arg_names: List[Optional[str]],
mode_arg_index: int,
text_only_arg_indices: Tuple[int, ...],
api: CheckerPluginInterface,
context: Context,
) -> None:
"""A helper for verifying any function that has approximately the same
signature as the builtin 'open(...)' function.

If mode is detected to be binary, verify that text-only arguments
(encoding, errors, newline) are None.
"""
mode = None
if not arg_types or len(arg_types[mode_arg_index]) != 1:
mode = 'r'
else:
mode_expr = args[mode_arg_index][0]
if isinstance(mode_expr, StrExpr):
mode = mode_expr.value
if mode is not None:
assert isinstance(default_return_type, Instance) # type: ignore
if 'b' in mode:
return api.named_generic_type('typing.BinaryIO', [])
else:
return api.named_generic_type('typing.TextIO', [])
return default_return_type
return

mode_str = try_getting_str_literals(
args[mode_arg_index][0], arg_types[mode_arg_index][0]
)
if mode_str is None or len(mode_str) != 1 or 'b' not in mode_str[0]:
# mode cannot be found, or is not binary
return

# Verify that text-only arguments are None if mode is binary
for arg_index in text_only_arg_indices:
if (len(arg_types[arg_index]) == 1
and not is_subtype(arg_types[arg_index][0], NoneType())):
api.fail("Binary mode doesn't take an argument for {}"
.format(arg_names[arg_index]),
context)


def contextmanager_callback(ctx: FunctionContext) -> Type:
Expand Down
49 changes: 37 additions & 12 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,32 @@ _program.py:3: note: Revealed type is 'typing.BinaryIO'
_program.py:5: note: Revealed type is 'typing.IO[Any]'

[case testOpenReturnTypeInferenceSpecialCases]
from typing_extensions import Literal
reveal_type(open())
reveal_type(open(mode='rb', file='x'))
reveal_type(open(file='x', mode='rb'))
mode = 'rb'
reveal_type(open(mode=mode, file='r'))
[out]
_testOpenReturnTypeInferenceSpecialCases.py:1: error: Too few arguments for "open"
_testOpenReturnTypeInferenceSpecialCases.py:1: note: Revealed type is 'typing.TextIO'
_testOpenReturnTypeInferenceSpecialCases.py:2: note: Revealed type is 'typing.BinaryIO'
reveal_type(open(mode=mode, file='r', errors='replace'))
open('x', mode='rb', errors='replace')
open('x', errors='replace', mode='rb')
errors: Literal['strict', 'ignore'] = 'ignore'
open('x', 'rb', -1, 'UTF-8', errors, '\n')
[out]
_testOpenReturnTypeInferenceSpecialCases.py:2: error: All overload variants of "open" require at least one argument
_testOpenReturnTypeInferenceSpecialCases.py:2: note: Possible overload variants:
_testOpenReturnTypeInferenceSpecialCases.py:2: note: def open(file: Union[str, bytes, int, _PathLike[Any]], mode: <union: 53 items> = ..., buffering: int = ..., encoding: Optional[str] = ..., errors: Optional[str] = ..., newline: Optional[str] = ..., closefd: bool = ..., opener: Optional[Callable[[str, int], int]] = ...) -> TextIO
_testOpenReturnTypeInferenceSpecialCases.py:2: note: def open(file: Union[str, bytes, int, _PathLike[Any]], mode: <union: 38 items>, buffering: int = ..., encoding: None = ..., errors: None = ..., newline: None = ..., closefd: bool = ..., opener: Optional[Callable[[str, int], int]] = ...) -> BinaryIO
_testOpenReturnTypeInferenceSpecialCases.py:2: note: def open(file: Union[str, bytes, int, _PathLike[Any]], mode: str, buffering: int = ..., encoding: Optional[str] = ..., errors: Optional[str] = ..., newline: Optional[str] = ..., closefd: bool = ..., opener: Optional[Callable[[str, int], int]] = ...) -> IO[Any]
_testOpenReturnTypeInferenceSpecialCases.py:2: note: Revealed type is 'Any'
_testOpenReturnTypeInferenceSpecialCases.py:3: note: Revealed type is 'typing.BinaryIO'
_testOpenReturnTypeInferenceSpecialCases.py:5: note: Revealed type is 'typing.IO[Any]'
_testOpenReturnTypeInferenceSpecialCases.py:4: note: Revealed type is 'typing.BinaryIO'
_testOpenReturnTypeInferenceSpecialCases.py:6: note: Revealed type is 'typing.IO[Any]'
_testOpenReturnTypeInferenceSpecialCases.py:7: error: Binary mode doesn't take an argument for errors
_testOpenReturnTypeInferenceSpecialCases.py:8: error: Binary mode doesn't take an argument for errors
_testOpenReturnTypeInferenceSpecialCases.py:10: error: Binary mode doesn't take an argument for encoding
_testOpenReturnTypeInferenceSpecialCases.py:10: error: Binary mode doesn't take an argument for errors
_testOpenReturnTypeInferenceSpecialCases.py:10: error: Binary mode doesn't take an argument for newline


[case testPathOpenReturnTypeInference]
from pathlib import Path
Expand All @@ -315,15 +330,25 @@ _program.py:7: note: Revealed type is 'typing.IO[Any]'

[case testPathOpenReturnTypeInferenceSpecialCases]
from pathlib import Path
from typing import Optional
from typing_extensions import Literal
p = Path("x")
reveal_type(p.open(mode='rb', errors='replace'))
reveal_type(p.open(errors='replace', mode='rb'))
p.open(mode='rb', errors='replace')
p.open(errors='replace', mode='rb')
mode = 'rb'
reveal_type(p.open(mode=mode, errors='replace'))
[out]
_program.py:3: note: Revealed type is 'typing.BinaryIO'
_program.py:4: note: Revealed type is 'typing.BinaryIO'
_program.py:6: note: Revealed type is 'typing.IO[Any]'
mode2: Literal['rb'] = 'rb'
p.open(mode=mode2, errors='replace')
errors: Optional[Literal['strict', 'ignore']] = None
p.open('rb', -1, 'UTF-8', errors, '\n')
[out]
_program.py:5: error: Binary mode doesn't take an argument for errors
_program.py:6: error: Binary mode doesn't take an argument for errors
_program.py:8: note: Revealed type is 'typing.IO[Any]'
_program.py:10: error: Binary mode doesn't take an argument for errors
_program.py:12: error: Binary mode doesn't take an argument for encoding
_program.py:12: error: Binary mode doesn't take an argument for errors
_program.py:12: error: Binary mode doesn't take an argument for newline

[case testGenericPatterns]
from typing import Pattern
Expand Down