diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index ca0db74b32bf..4869e9be2735 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index dc5e5098630b..2a4688be7b3e 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -38,7 +38,6 @@ StrExpr, TempNode, TupleExpr, - UnicodeExpr, ) from mypy.types import ( AnyType, @@ -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 @@ -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. @@ -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) @@ -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")] @@ -706,7 +701,7 @@ 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, @@ -714,7 +709,6 @@ def check_str_interpolation(self, expr: FormatStringExpr, replacements: Expressi ) return AnyType(TypeOfAny.from_error) - self.unicode_upcast = False if has_mapping_keys is None: pass # Error was reported elif has_mapping_keys: @@ -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 @@ -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( @@ -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 @@ -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( @@ -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' @@ -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) @@ -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"]: