Skip to content

Commit c979af9

Browse files
Extend check for format string specifiers and added more unit tests
1 parent cfc6b17 commit c979af9

File tree

3 files changed

+42
-24
lines changed

3 files changed

+42
-24
lines changed

mypy/checkstrformat.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
StrExpr,
4545
TempNode,
4646
TupleExpr,
47+
Var,
4748
)
4849
from mypy.parse import parse
4950
from mypy.subtypes import is_subtype
@@ -452,24 +453,27 @@ def perform_special_format_checks(
452453
code=codes.STRING_FORMATTING,
453454
)
454455

455-
a_type = get_proper_type(actual_type)
456-
if isinstance(a_type, NoneType):
456+
if isinstance(get_proper_type(actual_type), NoneType):
457457
# Perform type check of alignment specifiers on None
458-
if spec.format_spec and any(c in spec.format_spec for c in "<>^"):
459-
specifierIndex = -1
460-
for i in range(len("<>^")):
461-
if spec.format_spec[i] in "<>^":
462-
specifierIndex = i
463-
if specifierIndex > -1:
464-
self.msg.fail(
465-
(
466-
f"Alignment format specifier "
467-
f'"{spec.format_spec[specifierIndex]}" '
468-
f"is not supported for None"
469-
),
470-
call,
471-
code=codes.STRING_FORMATTING,
472-
)
458+
# If spec.format_spec is None then we use "" instead of avoid crashing
459+
specifier_char = None
460+
if spec.non_standard_format_spec == True and isinstance(call.args[-1], StrExpr):
461+
arg = call.args[-1].value
462+
specifier_char = next((c for c in (arg or "") if c in "<>^"), None)
463+
elif isinstance(spec.format_spec, str):
464+
specifier_char = next((c for c in (spec.format_spec or "") if c in "<>^"), None)
465+
466+
if specifier_char:
467+
self.msg.fail(
468+
(
469+
f"Alignment format specifier "
470+
f'"{specifier_char}" '
471+
f"is not supported for None"
472+
),
473+
call,
474+
code=codes.STRING_FORMATTING,
475+
)
476+
473477

474478
def find_replacements_in_call(self, call: CallExpr, keys: list[str]) -> list[Expression]:
475479
"""Find replacement expression for every specifier in str.format() call.

test-data/unit/check-formatting.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,34 @@ b'%c' % (123)
266266

267267

268268
[case testFormatCallNoneAlignment]
269+
from typing import Optional
270+
269271
'{:<1}'.format(None) # E: Alignment format specifier "<" is not supported for None
270272
'{:>1}'.format(None) # E: Alignment format specifier ">" is not supported for None
271273
'{:^1}'.format(None) # E: Alignment format specifier "^" is not supported for None
272274

273275
'{:<10}'.format('16') # OK
274276
'{:>10}'.format('16') # OK
275277
'{:^10}'.format('16') # OK
278+
279+
'{!s:<5}'.format(None) # OK
280+
'{!s:>5}'.format(None) # OK
281+
'{!s:^5}'.format(None) # OK
282+
283+
f"{None!s:<5}" # OK
284+
f"{None!s:>5}" # OK
285+
f"{None!s:^5}" # OK
286+
287+
288+
f"{None:<5}" # E: Alignment format specifier "<" is not supported for None
289+
f"{None:>5}" # E: Alignment format specifier ">" is not supported for None
290+
f"{None:^5}" # E: Alignment format specifier "^" is not supported for None
291+
292+
my_var: Optional[str] = None
293+
"{:<2}".format(my_var) # E: Alignment format specifier "<" is not supported for None
294+
my_var = "test"
295+
"{:>2}".format(my_var) # OK
296+
276297
[builtins fixtures/primitives.pyi]
277298

278299
[case testFormatCallParseErrors]

test.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)