Skip to content

Commit

Permalink
Remove python2 logic from checkstrformat.py (#13248)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Jul 27, 2022
1 parent 87fcd07 commit 1fa71c4
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 80 deletions.
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

0 comments on commit 1fa71c4

Please sign in to comment.