Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
77 changes: 38 additions & 39 deletions mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from mypy.parse import parse

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


Expand Down Expand Up @@ -98,23 +98,8 @@ def compile_new_format_re(custom_spec: bool) -> Pattern[str]:
DUMMY_FIELD_NAME: Final = "__dummy_name__"

# Format types supported by str.format() for builtin classes.
SUPPORTED_TYPES_NEW: Final = {
"b",
"c",
"d",
"e",
"E",
"f",
"F",
"g",
"G",
"n",
"o",
"s",
"x",
"X",
"%",
}
SUPPORTED_TYPES_NEW: Final = {"b", "c", "d", "e", "E", "f", "F",
"g", "G", "n", "o", "s", "x", "X", "%"}

# Types that require either int or float.
NUMERIC_TYPES_OLD: Final = {"d", "i", "o", "u", "x", "X", "e", "E", "f", "F", "g", "G"}
Expand Down Expand Up @@ -813,22 +798,22 @@ def checkers_for_star(self, context: Context) -> Checkers:
"""
expected = self.named_type('builtins.int')

def check_type(type: Type) -> None:
def check_type(type: Type) -> bool:
expected = self.named_type('builtins.int')
self.chk.check_subtype(type, expected, context, '* wants int',
code=codes.STRING_FORMATTING)
return self.chk.check_subtype(type, expected, context, '* wants int',
code=codes.STRING_FORMATTING)

def check_expr(expr: Expression) -> None:
type = self.accept(expr, expected)
check_type(type)

return check_expr, check_type

def check_placeholder_type(self, typ: Type, expected_type: Type, context: Context) -> None:
self.chk.check_subtype(typ, expected_type, context,
message_registry.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION,
'expression has type', 'placeholder has type',
code=codes.STRING_FORMATTING)
def check_placeholder_type(self, typ: Type, expected_type: Type, context: Context) -> bool:
return self.chk.check_subtype(typ, expected_type, context,
message_registry.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION,
'expression has type', 'placeholder has type',
code=codes.STRING_FORMATTING)

def checkers_for_regular_type(self, type: str,
context: Context,
Expand All @@ -840,19 +825,20 @@ def checkers_for_regular_type(self, type: str,
if expected_type is None:
return None

def check_type(typ: Type) -> None:
def check_type(typ: Type) -> bool:
assert expected_type is not None
self.check_placeholder_type(typ, expected_type, context)
if type == 's':
self.check_s_special_cases(expr, typ, context)
ret = self.check_placeholder_type(typ, expected_type, context)
if ret and type == 's':
ret = self.check_s_special_cases(expr, typ, context)
return ret

def check_expr(expr: Expression) -> None:
type = self.accept(expr, expected_type)
check_type(type)

return check_expr, check_type

def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Context) -> None:
def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Context) -> bool:
"""Additional special cases for %s in bytes vs string context."""
if isinstance(expr, StrExpr):
# Couple special cases for string formatting.
Expand All @@ -862,6 +848,7 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
"On Python 3 '%s' % b'abc' produces \"b'abc'\", not 'abc'; "
"use '%r' % b'abc' 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
Expand All @@ -871,6 +858,8 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
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(self, type: str,
context: Context,
Expand All @@ -882,20 +871,30 @@ def checkers_for_c_type(self, type: str,
if expected_type is None:
return None

def check_type(type: Type) -> None:
def check_type(type: Type) -> bool:
assert expected_type is not None
self.check_placeholder_type(type, expected_type, context)
if self.chk.options.python_version >= (3, 0) and 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'
return self.chk.check_subtype(type, expected_type, context, err_msg,
'expression has type',
code=codes.STRING_FORMATTING)

def check_expr(expr: Expression) -> None:
"""int, or str with length 1"""
type = self.accept(expr, expected_type)
# TODO: Use the same the error message when incompatible types match %c
# Python 3 doesn't support b'%c' % str
if not (self.chk.options.python_version >= (3, 0)
and isinstance(format_expr, BytesExpr)):
if isinstance(expr, (StrExpr, BytesExpr)) and len(expr.value) != 1:
# We need further check with expr to make sure that
# it has exact one char or one single byte.
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)
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)
check_type(type)

return check_expr, check_type

Expand Down
6 changes: 6 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,12 @@ def string_interpolation_with_star_and_key(self, context: Context) -> None:
self.fail('String interpolation contains both stars and mapping keys', context,
code=codes.STRING_FORMATTING)

def requires_int_or_single_byte(self, context: Context,
format_call: bool = False) -> None:
self.fail('"{}c" requires an integer in range(256) or a single byte'
.format(':' if format_call else '%'),
context, code=codes.STRING_FORMATTING)

def requires_int_or_char(self, context: Context,
format_call: bool = False) -> None:
self.fail('"{}c" requires int or char'.format(':' if format_call else '%'),
Expand Down
30 changes: 19 additions & 11 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1281,41 +1281,49 @@ b'%a' % 3
[case testStringInterPolationCPython2]
# flags: --py2 --no-strict-optional
'%c' % 1
'%c' % 1.0 # E: Incompatible types in string interpolation (expression has type "float", placeholder has type "Union[int, str]")
'%c' % 1.0 # E: "%c" requires int or char (expression has type "float")
'%c' % 's'
'%c' % '' # E: "%c" requires int or char
'%c' % '' # E: "%c" requires int or char
'%c' % 'ab' # E: "%c" requires int or char
'%c' % b'a'
'%c' % b'' # E: "%c" requires int or char
'%c' % b'ab' # E: "%c" requires int or char
[builtins_py2 fixtures/python2.pyi]

[case testStringInterpolationC]
# flags: --python-version 3.6
'%c' % 1
'%c' % 1.0 # E: Incompatible types in string interpolation (expression has type "float", placeholder has type "Union[int, str]")
'%c' % 1.0 # E: "%c" requires int or char (expression has type "float")
'%c' % 's'
'%c' % '' # E: "%c" requires int or char
'%c' % '' # E: "%c" requires int or char
'%c' % 'ab' # E: "%c" requires int or char
'%c' % b'a' # E: Incompatible types in string interpolation (expression has type "bytes", placeholder has type "Union[int, str]")
'%c' % b'a' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'' # E: "%c" requires int or char (expression has type "bytes")
'%c' % b'ab' # E: "%c" requires int or char (expression has type "bytes")
[builtins fixtures/primitives.pyi]

[case testBytesInterPolationCPython2]
# flags: --py2 --no-strict-optional
b'%c' % 1
b'%c' % 1.0 # E: Incompatible types in string interpolation (expression has type "float", placeholder has type "Union[int, str]")
b'%c' % 1.0 # E: "%c" requires int or char (expression has type "float")
b'%c' % 's'
b'%c' % '' # E: "%c" requires int or char
b'%c' % '' # E: "%c" requires int or char
b'%c' % 'ab' # E: "%c" requires int or char
b'%c' % b'a'
b'%c' % b'' # E: "%c" requires int or char
b'%c' % b'aa' # E: "%c" requires int or char
[builtins_py2 fixtures/python2.pyi]

[case testBytesInterpolationC]
# flags: --python-version 3.6
b'%c' % 1
b'%c' % 1.0 # E: Incompatible types in string interpolation (expression has type "float", placeholder has type "Union[int, bytes]")
b'%c' % 's' # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, bytes]")
b'%c' % '' # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, bytes]")
b'%c' % 'ab' # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, bytes]")
b'%c' % 1.0 # E: "%c" requires an integer in range(256) or a single byte (expression has type "float")
b'%c' % 's' # E: "%c" requires an integer in range(256) or a single byte (expression has type "str")
b'%c' % '' # E: "%c" requires an integer in range(256) or a single byte (expression has type "str")
b'%c' % 'ab' # E: "%c" requires an integer in range(256) or a single byte (expression has type "str")
b'%c' % b'a'
b'%c' % b'' # E: "%c" requires an integer in range(256) or a single byte
b'%c' % b'aa' # E: "%c" requires an integer in range(256) or a single byte
[builtins fixtures/primitives.pyi]

[case testStringInterpolationMappingTypes]
Expand Down