Skip to content

Commit 0113f61

Browse files
committed
upgrade helpful-string
1 parent 547f363 commit 0113f61

File tree

7 files changed

+34
-8
lines changed

7 files changed

+34
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
- Enable stub mode within `TYPE_CHECKING` branches (#702)
99
- Infer from overloads - add default value in impl (#697)
1010
- Warn for missing returns with explicit `Any` return types (#715)
11+
- Added `--helpful-string-allow-none` to allow `None` with `helpful-string` (#717)
12+
- Enabled `helpful-string` by default (#717)
1113
### Fixes
1214
- positional arguments on overloads break super (#697)
1315
- positional arguments on overloads duplicate unions (#697)

mypy/checkstrformat.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
TypedDictType,
5252
TypeOfAny,
5353
TypeStrVisitor,
54+
TypeVarLikeType,
5455
TypeVarTupleType,
5556
TypeVarType,
5657
UnionType,
@@ -442,6 +443,13 @@ def check_specs_in_format_call(
442443
def helpful_check(self, actual_type: ProperType, context: Context) -> bool:
443444
if isinstance(actual_type, (TupleType, TypedDictType, LiteralType)):
444445
return True
446+
if isinstance(actual_type, TypeVarType):
447+
if actual_type.values:
448+
for value in actual_type.values:
449+
self.helpful_check(get_proper_type(value), context)
450+
return False
451+
while isinstance(actual_type, TypeVarLikeType):
452+
actual_type = actual_type.upper_bound
445453
bad_builtin = False
446454
if isinstance(actual_type, Instance):
447455
if "dataclass" in actual_type.type.metadata:
@@ -464,13 +472,11 @@ def helpful_check(self, actual_type: ProperType, context: Context) -> bool:
464472
if base.module_name == "builtins":
465473
return True
466474
type_string = actual_type.accept(TypeStrVisitor(options=self.chk.options))
467-
if (
468-
custom_special_method(actual_type, "__format__")
469-
or custom_special_method(actual_type, "__str__")
470-
or custom_special_method(actual_type, "__repr__")
471-
):
475+
if custom_special_method(actual_type, ("__format__", "__str__", "__repr__")):
472476
return True
473-
if bad_builtin or isinstance(actual_type, NoneType):
477+
if bad_builtin or (
478+
not self.msg.options.helpful_string_allow_none and isinstance(actual_type, NoneType)
479+
):
474480
self.msg.fail(
475481
f'The string for "{type_string}" isn\'t helpful in a user-facing or semantic string',
476482
context,

mypy/errorcodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def __hash__(self) -> int:
130130
"str-format", "Check that string formatting/interpolation is type-safe", "General"
131131
)
132132
HELPFUL_STRING: Final = ErrorCode(
133-
"helpful-string", "Check that string conversions are useful", "General", default_enabled=False
133+
"helpful-string", "Check that string conversions are useful", "General"
134134
)
135135
STR_BYTES_PY3: Final = ErrorCode(
136136
"str-bytes-safe", "Warn about implicit coercions related to bytes and string types", "General"

mypy/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,12 @@ def add_invertible_flag(
619619
"You probably want to set this on a module override",
620620
group=based_group,
621621
)
622+
add_invertible_flag(
623+
"--helpful-string-allow-none",
624+
default=False,
625+
help="Allow Nones to appear in f-strings",
626+
group=based_group,
627+
)
622628
add_invertible_flag(
623629
"--ide", default=False, help="Best default for IDE integration.", group=based_group
624630
)
@@ -1351,6 +1357,7 @@ def add_invertible_flag(
13511357
"callable-functiontype",
13521358
"possible-function",
13531359
"bad-cast",
1360+
"helpful-string",
13541361
}
13551362
if mypy.options._based
13561363
else set()

mypy/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class BuildType:
4343
"extra_checks",
4444
"follow_imports_for_stubs",
4545
"follow_imports",
46+
"helpful_string_allow_none",
4647
"ignore_errors",
4748
"ignore_missing_imports",
4849
"ignore_missing_py_typed",
@@ -175,6 +176,7 @@ def __init__(self) -> None:
175176
self.bare_literals = True
176177
self.ignore_missing_py_typed = False
177178
self.ide = False
179+
self.helpful_string_allow_none = False
178180

179181
# disallow_any options
180182
self.disallow_any_generics = flip_if_not_based(True)

mypy/typeops.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1078,11 +1078,13 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> list[TypeVarLikeType]:
10781078
return [t] if self.include_all else []
10791079

10801080

1081-
def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool:
1081+
def custom_special_method(typ: Type, name: str | Iterable[str], check_all: bool = False) -> bool:
10821082
"""Does this type have a custom special method such as __format__() or __eq__()?
10831083
10841084
If check_all is True ensure all items of a union have a custom method, not just some.
10851085
"""
1086+
if not isinstance(name, str):
1087+
return any(custom_special_method(typ, n, check_all) for n in name)
10861088
typ = get_proper_type(typ)
10871089
if isinstance(typ, Instance):
10881090
method = typ.type.get(name)
@@ -1104,6 +1106,8 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool
11041106
if isinstance(typ, AnyType):
11051107
# Avoid false positives in uncertain cases.
11061108
return True
1109+
if isinstance(typ, TypeVarLikeType):
1110+
return custom_special_method(typ.upper_bound, name, check_all)
11071111
# TODO: support other types (see ExpressionChecker.has_member())?
11081112
return False
11091113

test-data/unit/check-based-format.test

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,8 @@ class D:
129129
data: D
130130
f"{data}"
131131
[builtins fixtures/primitives.pyi]
132+
133+
134+
[case testHelpfulStringAllowNone]
135+
# flags: --helpful-string-allow-none
136+
f"{None}"

0 commit comments

Comments
 (0)