Skip to content

Commit

Permalink
Fix string formatting for string enums (#16555)
Browse files Browse the repository at this point in the history
Fixes #7563

Inside `check_str_format_call` method, it checks if expression of
`format` method call is an Enum member and it takes Literal value of
that Enum member to check the `format` call arguments, if so.
  • Loading branch information
roberfi authored Apr 8, 2024
1 parent 8019010 commit 732d98e
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
12 changes: 11 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,17 @@ def check_str_format_call(self, e: CallExpr) -> None:
if isinstance(e.callee.expr, StrExpr):
format_value = e.callee.expr.value
elif self.chk.has_type(e.callee.expr):
base_typ = try_getting_literal(self.chk.lookup_type(e.callee.expr))
typ = get_proper_type(self.chk.lookup_type(e.callee.expr))
if (
isinstance(typ, Instance)
and typ.type.is_enum
and isinstance(typ.last_known_value, LiteralType)
and isinstance(typ.last_known_value.value, str)
):
value_type = typ.type.names[typ.last_known_value.value].type
if isinstance(value_type, Type):
typ = get_proper_type(value_type)
base_typ = try_getting_literal(typ)
if isinstance(base_typ, LiteralType) and isinstance(base_typ.value, str):
format_value = base_typ.value
if format_value is not None:
Expand Down
42 changes: 42 additions & 0 deletions test-data/unit/check-formatting.test
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,45 @@ class S:
'{:%}'.format(0.001)
[builtins fixtures/primitives.pyi]
[typing fixtures/typing-medium.pyi]

[case testEnumWithStringToFormatValue]
from enum import Enum

class Responses(str, Enum):
TEMPLATED = 'insert {} here'
TEMPLATED_WITH_KW = 'insert {value} here'
NORMAL = 'something'

Responses.TEMPLATED.format(42)
Responses.TEMPLATED_WITH_KW.format(value=42)
Responses.TEMPLATED.format() # E: Cannot find replacement for positional format specifier 0
Responses.TEMPLATED_WITH_KW.format() # E: Cannot find replacement for named format specifier "value"
Responses.NORMAL.format(42) # E: Not all arguments converted during string formatting
Responses.NORMAL.format(value=42) # E: Not all arguments converted during string formatting
[builtins fixtures/primitives.pyi]

[case testNonStringEnumToFormatValue]
from enum import Enum

class Responses(Enum):
TEMPLATED = 'insert {value} here'

Responses.TEMPLATED.format(value=42) # E: "Responses" has no attribute "format"
[builtins fixtures/primitives.pyi]

[case testStrEnumWithStringToFormatValue]
# flags: --python-version 3.11
from enum import StrEnum

class Responses(StrEnum):
TEMPLATED = 'insert {} here'
TEMPLATED_WITH_KW = 'insert {value} here'
NORMAL = 'something'

Responses.TEMPLATED.format(42)
Responses.TEMPLATED_WITH_KW.format(value=42)
Responses.TEMPLATED.format() # E: Cannot find replacement for positional format specifier 0
Responses.TEMPLATED_WITH_KW.format() # E: Cannot find replacement for named format specifier "value"
Responses.NORMAL.format(42) # E: Not all arguments converted during string formatting
Responses.NORMAL.format(value=42) # E: Not all arguments converted during string formatting
[builtins fixtures/primitives.pyi]

0 comments on commit 732d98e

Please sign in to comment.