Skip to content

helpful-string improvements #603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- Enable stub mode within `TYPE_CHECKING` branches (#702)
- Infer from overloads - add default value in impl (#697)
- Warn for missing returns with explicit `Any` return types (#715)
- Added `--helpful-string-allow-none` to allow `None` with `helpful-string` (#717)
- Enabled `helpful-string` by default (#717)
### Fixes
- positional arguments on overloads break super (#697)
- positional arguments on overloads duplicate unions (#697)
Expand Down
18 changes: 12 additions & 6 deletions mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
TypedDictType,
TypeOfAny,
TypeStrVisitor,
TypeVarLikeType,
TypeVarTupleType,
TypeVarType,
UnionType,
Expand Down Expand Up @@ -442,6 +443,13 @@ def check_specs_in_format_call(
def helpful_check(self, actual_type: ProperType, context: Context) -> bool:
if isinstance(actual_type, (TupleType, TypedDictType, LiteralType)):
return True
if isinstance(actual_type, TypeVarType):
if actual_type.values:
for value in actual_type.values:
self.helpful_check(get_proper_type(value), context)
return False
while isinstance(actual_type, TypeVarLikeType):
actual_type = actual_type.upper_bound
bad_builtin = False
if isinstance(actual_type, Instance):
if "dataclass" in actual_type.type.metadata:
Expand All @@ -464,13 +472,11 @@ def helpful_check(self, actual_type: ProperType, context: Context) -> bool:
if base.module_name == "builtins":
return True
type_string = actual_type.accept(TypeStrVisitor(options=self.chk.options))
if (
custom_special_method(actual_type, "__format__")
or custom_special_method(actual_type, "__str__")
or custom_special_method(actual_type, "__repr__")
):
if custom_special_method(actual_type, ("__format__", "__str__", "__repr__")):
return True
if bad_builtin or isinstance(actual_type, NoneType):
if bad_builtin or (
not self.msg.options.helpful_string_allow_none and isinstance(actual_type, NoneType)
):
self.msg.fail(
f'The string for "{type_string}" isn\'t helpful in a user-facing or semantic string',
context,
Expand Down
2 changes: 1 addition & 1 deletion mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def __hash__(self) -> int:
"str-format", "Check that string formatting/interpolation is type-safe", "General"
)
HELPFUL_STRING: Final = ErrorCode(
"helpful-string", "Check that string conversions are useful", "General", default_enabled=False
"helpful-string", "Check that string conversions are useful", "General"
)
STR_BYTES_PY3: Final = ErrorCode(
"str-bytes-safe", "Warn about implicit coercions related to bytes and string types", "General"
Expand Down
7 changes: 7 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,12 @@ def add_invertible_flag(
"You probably want to set this on a module override",
group=based_group,
)
add_invertible_flag(
"--helpful-string-allow-none",
default=False,
help="Allow Nones to appear in f-strings",
group=based_group,
)
add_invertible_flag(
"--ide", default=False, help="Best default for IDE integration.", group=based_group
)
Expand Down Expand Up @@ -1351,6 +1357,7 @@ def add_invertible_flag(
"callable-functiontype",
"possible-function",
"bad-cast",
"helpful-string",
}
if mypy.options._based
else set()
Expand Down
2 changes: 2 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class BuildType:
"extra_checks",
"follow_imports_for_stubs",
"follow_imports",
"helpful_string_allow_none",
"ignore_errors",
"ignore_missing_imports",
"ignore_missing_py_typed",
Expand Down Expand Up @@ -175,6 +176,7 @@ def __init__(self) -> None:
self.bare_literals = True
self.ignore_missing_py_typed = False
self.ide = False
self.helpful_string_allow_none = False

# disallow_any options
self.disallow_any_generics = flip_if_not_based(True)
Expand Down
6 changes: 5 additions & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,11 +1078,13 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> list[TypeVarLikeType]:
return [t] if self.include_all else []


def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool:
def custom_special_method(typ: Type, name: str | Iterable[str], check_all: bool = False) -> bool:
"""Does this type have a custom special method such as __format__() or __eq__()?

If check_all is True ensure all items of a union have a custom method, not just some.
"""
if not isinstance(name, str):
return any(custom_special_method(typ, n, check_all) for n in name)
typ = get_proper_type(typ)
if isinstance(typ, Instance):
method = typ.type.get(name)
Expand All @@ -1104,6 +1106,8 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool
if isinstance(typ, AnyType):
# Avoid false positives in uncertain cases.
return True
if isinstance(typ, TypeVarLikeType):
return custom_special_method(typ.upper_bound, name, check_all)
# TODO: support other types (see ExpressionChecker.has_member())?
return False

Expand Down
5 changes: 5 additions & 0 deletions test-data/unit/check-based-format.test
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,8 @@ class D:
data: D
f"{data}"
[builtins fixtures/primitives.pyi]


[case testHelpfulStringAllowNone]
# flags: --helpful-string-allow-none
f"{None}"
Loading