Skip to content

Commit 35cf276

Browse files
Merge pull request #5 from VallinZ/AddAlignmentSpecifierLogic
Extend check on None for str.format()
2 parents c536cab + d46f562 commit 35cf276

File tree

2 files changed

+42
-17
lines changed

2 files changed

+42
-17
lines changed

mypy/checkstrformat.py

+21-17
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ def check_specs_in_format_call(
337337
338338
The core logic for format checking is implemented in this method.
339339
"""
340+
340341
assert all(s.key for s in specs), "Keys must be auto-generated first!"
341342
replacements = self.find_replacements_in_call(call, [cast(str, s.key) for s in specs])
342343
assert len(replacements) == len(specs)
@@ -448,24 +449,27 @@ def perform_special_format_checks(
448449
call,
449450
code=codes.STRING_FORMATTING,
450451
)
451-
a_type = get_proper_type(actual_type)
452-
if isinstance(a_type, NoneType):
452+
453+
if isinstance(get_proper_type(actual_type), NoneType):
453454
# Perform type check of alignment specifiers on None
454-
if spec.format_spec and any(c in spec.format_spec for c in "<>^"):
455-
specifierIndex = -1
456-
for i in range(len("<>^")):
457-
if spec.format_spec[i] in "<>^":
458-
specifierIndex = i
459-
if specifierIndex > -1:
460-
self.msg.fail(
461-
(
462-
f"Alignment format specifier "
463-
f'"{spec.format_spec[specifierIndex]}" '
464-
f"is not supported for None"
465-
),
466-
call,
467-
code=codes.STRING_FORMATTING,
468-
)
455+
# If spec.format_spec is None then we use "" instead of avoid crashing
456+
specifier_char = None
457+
if spec.non_standard_format_spec and isinstance(call.args[-1], StrExpr):
458+
arg = call.args[-1].value
459+
specifier_char = next((c for c in (arg or "") if c in "<>^"), None)
460+
elif isinstance(spec.format_spec, str):
461+
specifier_char = next((c for c in (spec.format_spec or "") if c in "<>^"), None)
462+
463+
if specifier_char:
464+
self.msg.fail(
465+
(
466+
f"Alignment format specifier "
467+
f'"{specifier_char}" '
468+
f"is not supported for None"
469+
),
470+
call,
471+
code=codes.STRING_FORMATTING,
472+
)
469473

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

test-data/unit/check-formatting.test

+21
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]

0 commit comments

Comments
 (0)