Skip to content

Commit

Permalink
upgrade helpful-string
Browse files Browse the repository at this point in the history
  • Loading branch information
KotlinIsland committed Jul 21, 2024
1 parent 547f363 commit 0113f61
Show file tree
Hide file tree
Showing 7 changed files with 34 additions and 8 deletions.
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}"

0 comments on commit 0113f61

Please sign in to comment.