Skip to content

Remove python2 logic from checkstrformat.py #13248

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2585,15 +2585,10 @@ def visit_op_expr(self, e: OpExpr) -> Type:
# Expressions of form [...] * e get special type inference.
return self.check_list_multiply(e)
if e.op == "%":
pyversion = self.chk.options.python_version
if pyversion[0] == 3:
if isinstance(e.left, BytesExpr) and pyversion[1] >= 5:
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
if isinstance(e.left, StrExpr):
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
elif pyversion[0] == 2:
if isinstance(e.left, (StrExpr, BytesExpr, UnicodeExpr)):
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
if isinstance(e.left, BytesExpr) and self.chk.options.python_version >= (3, 5):
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
if isinstance(e.left, StrExpr):
return self.strfrm_checker.check_str_interpolation(e.left, e.right)
left_type = self.accept(e.left)

proper_left_type = get_proper_type(left_type)
Expand Down
111 changes: 40 additions & 71 deletions mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
StrExpr,
TempNode,
TupleExpr,
UnicodeExpr,
)
from mypy.types import (
AnyType,
Expand All @@ -65,7 +64,7 @@
from mypy.subtypes import is_subtype
from mypy.typeops import custom_special_method

FormatStringExpr: _TypeAlias = Union[StrExpr, BytesExpr, UnicodeExpr]
FormatStringExpr: _TypeAlias = Union[StrExpr, BytesExpr]
Checkers: _TypeAlias = Tuple[Callable[[Expression], None], Callable[[Type], bool]]
MatchMap: _TypeAlias = Dict[Tuple[int, int], Match[str]] # span -> match

Expand Down Expand Up @@ -331,9 +330,6 @@ def __init__(
self.chk = chk
self.exprchk = exprchk
self.msg = msg
# This flag is used to track Python 2 corner case where for example
# '%s, %d' % (u'abc', 42) returns u'abc, 42' (i.e. unicode, not a string).
self.unicode_upcast = False

def check_str_format_call(self, call: CallExpr, format_value: str) -> None:
"""Perform more precise checks for str.format() calls when possible.
Expand Down Expand Up @@ -402,7 +398,7 @@ def check_specs_in_format_call(
expected_type: Optional[Type] = AnyType(TypeOfAny.special_form)
else:
assert isinstance(call.callee, MemberExpr)
if isinstance(call.callee.expr, (StrExpr, UnicodeExpr)):
if isinstance(call.callee.expr, StrExpr):
format_str = call.callee.expr
else:
format_str = StrExpr(format_value)
Expand Down Expand Up @@ -453,17 +449,16 @@ def perform_special_format_checks(
if len(c_typ.value) != 1:
self.msg.requires_int_or_char(call, format_call=True)
if (not spec.conv_type or spec.conv_type == "s") and not spec.conversion:
if self.chk.options.python_version >= (3, 0):
if has_type_component(actual_type, "builtins.bytes") and not custom_special_method(
actual_type, "__str__"
):
self.msg.fail(
'On Python 3 formatting "b\'abc\'" with "{}" '
'produces "b\'abc\'", not "abc"; '
'use "{!r}" if this is desired behavior',
call,
code=codes.STR_BYTES_PY3,
)
if has_type_component(actual_type, "builtins.bytes") and not custom_special_method(
actual_type, "__str__"
):
self.msg.fail(
'On Python 3 formatting "b\'abc\'" with "{}" '
'produces "b\'abc\'", not "abc"; '
'use "{!r}" if this is desired behavior',
call,
code=codes.STR_BYTES_PY3,
)
if spec.flags:
numeric_types = UnionType(
[self.named_type("builtins.int"), self.named_type("builtins.float")]
Expand Down Expand Up @@ -706,15 +701,14 @@ def check_str_interpolation(self, expr: FormatStringExpr, replacements: Expressi
self.exprchk.accept(expr)
specifiers = parse_conversion_specifiers(expr.value)
has_mapping_keys = self.analyze_conversion_specifiers(specifiers, expr)
if isinstance(expr, BytesExpr) and (3, 0) <= self.chk.options.python_version < (3, 5):
if isinstance(expr, BytesExpr) and self.chk.options.python_version < (3, 5):
self.msg.fail(
"Bytes formatting is only supported in Python 3.5 and later",
replacements,
code=codes.STRING_FORMATTING,
)
return AnyType(TypeOfAny.from_error)

self.unicode_upcast = False
if has_mapping_keys is None:
pass # Error was reported
elif has_mapping_keys:
Expand All @@ -724,11 +718,7 @@ def check_str_interpolation(self, expr: FormatStringExpr, replacements: Expressi

if isinstance(expr, BytesExpr):
return self.named_type("builtins.bytes")
elif isinstance(expr, UnicodeExpr):
return self.named_type("builtins.unicode")
elif isinstance(expr, StrExpr):
if self.unicode_upcast:
return self.named_type("builtins.unicode")
return self.named_type("builtins.str")
else:
assert False
Expand Down Expand Up @@ -815,11 +805,11 @@ def check_mapping_str_interpolation(
) -> None:
"""Check % string interpolation with names specifiers '%(name)s' % {'name': 'John'}."""
if isinstance(replacements, DictExpr) and all(
isinstance(k, (StrExpr, BytesExpr, UnicodeExpr)) for k, v in replacements.items
isinstance(k, (StrExpr, BytesExpr)) for k, v in replacements.items
):
mapping: Dict[str, Type] = {}
for k, v in replacements.items:
if self.chk.options.python_version >= (3, 0) and isinstance(expr, BytesExpr):
if isinstance(expr, BytesExpr):
# Special case: for bytes formatting keys must be bytes.
if not isinstance(k, BytesExpr):
self.msg.fail(
Expand Down Expand Up @@ -870,21 +860,14 @@ def check_mapping_str_interpolation(
def build_dict_type(self, expr: FormatStringExpr) -> Type:
"""Build expected mapping type for right operand in % formatting."""
any_type = AnyType(TypeOfAny.special_form)
if self.chk.options.python_version >= (3, 0):
if isinstance(expr, BytesExpr):
bytes_type = self.chk.named_generic_type("builtins.bytes", [])
return self.chk.named_generic_type("typing.Mapping", [bytes_type, any_type])
elif isinstance(expr, StrExpr):
str_type = self.chk.named_generic_type("builtins.str", [])
return self.chk.named_generic_type("typing.Mapping", [str_type, any_type])
else:
assert False, "There should not be UnicodeExpr on Python 3"
else:
if isinstance(expr, BytesExpr):
bytes_type = self.chk.named_generic_type("builtins.bytes", [])
return self.chk.named_generic_type("typing.Mapping", [bytes_type, any_type])
elif isinstance(expr, StrExpr):
str_type = self.chk.named_generic_type("builtins.str", [])
unicode_type = self.chk.named_generic_type("builtins.unicode", [])
str_map = self.chk.named_generic_type("typing.Mapping", [str_type, any_type])
unicode_map = self.chk.named_generic_type("typing.Mapping", [unicode_type, any_type])
return UnionType.make_union([str_map, unicode_map])
return self.chk.named_generic_type("typing.Mapping", [str_type, any_type])
else:
assert False, "There should not be UnicodeExpr on Python 3"

def build_replacement_checkers(
self, specifiers: List[ConversionSpecifier], context: Context, expr: FormatStringExpr
Expand Down Expand Up @@ -979,29 +962,24 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
"""Additional special cases for %s in bytes vs string context."""
if isinstance(expr, StrExpr):
# Couple special cases for string formatting.
if self.chk.options.python_version >= (3, 0):
if has_type_component(typ, "builtins.bytes"):
self.msg.fail(
'On Python 3 formatting "b\'abc\'" with "%s" '
'produces "b\'abc\'", not "abc"; '
'use "%r" if this is desired behavior',
context,
code=codes.STR_BYTES_PY3,
)
return False
if self.chk.options.python_version < (3, 0):
if has_type_component(typ, "builtins.unicode"):
self.unicode_upcast = True
if has_type_component(typ, "builtins.bytes"):
self.msg.fail(
'On Python 3 formatting "b\'abc\'" with "%s" '
'produces "b\'abc\'", not "abc"; '
'use "%r" if this is desired behavior',
context,
code=codes.STR_BYTES_PY3,
)
return False
if isinstance(expr, BytesExpr):
# A special case for bytes formatting: b'%s' actually requires bytes on Python 3.
if self.chk.options.python_version >= (3, 0):
if has_type_component(typ, "builtins.str"):
self.msg.fail(
"On Python 3 b'%s' requires bytes, not string",
context,
code=codes.STRING_FORMATTING,
)
return False
if has_type_component(typ, "builtins.str"):
self.msg.fail(
"On Python 3 b'%s' requires bytes, not string",
context,
code=codes.STRING_FORMATTING,
)
return False
return True

def checkers_for_c_type(
Expand All @@ -1016,7 +994,7 @@ def checkers_for_c_type(

def check_type(type: Type) -> bool:
assert expected_type is not None
if self.chk.options.python_version >= (3, 0) and isinstance(format_expr, BytesExpr):
if isinstance(format_expr, BytesExpr):
err_msg = '"%c" requires an integer in range(256) or a single byte'
else:
err_msg = '"%c" requires int or char'
Expand All @@ -1037,13 +1015,11 @@ def check_expr(expr: Expression) -> None:
if check_type(type):
# Python 3 doesn't support b'%c' % str
if (
self.chk.options.python_version >= (3, 0)
and isinstance(format_expr, BytesExpr)
isinstance(format_expr, BytesExpr)
and isinstance(expr, BytesExpr)
and len(expr.value) != 1
):
self.msg.requires_int_or_single_byte(context)
# In Python 2, b'%c' is the same as '%c'
elif isinstance(expr, (StrExpr, BytesExpr)) and len(expr.value) != 1:
self.msg.requires_int_or_char(context)

Expand Down Expand Up @@ -1079,13 +1055,6 @@ def conversion_type(
return None
return self.named_type("builtins.bytes")
elif p == "a":
if self.chk.options.python_version < (3, 0):
self.msg.fail(
'Format character "a" is only supported in Python 3',
context,
code=codes.STRING_FORMATTING,
)
return None
# TODO: return type object?
return AnyType(TypeOfAny.special_form)
elif p in ["s", "r"]:
Expand Down