From 47adb22df8e797e57789cd6e35013aef436efa28 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 19 Sep 2022 14:28:31 +0200 Subject: [PATCH 1/2] Add error-code for truthy-function --- docs/source/error_code_list2.rst | 13 +++++++++++++ mypy/errorcodes.py | 5 +++++ mypy/message_registry.py | 2 +- test-data/unit/check-errorcodes.test | 18 ++++++++++-------- test-data/unit/check-incremental.test | 7 +++++-- test-data/unit/check-inline-config.test | 14 ++++++++++---- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 3938669edafc..cac19e705361 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -258,6 +258,19 @@ except that attempting to invoke an undefined method (e.g. ``__len__``) results while attempting to evaluate an object in boolean context without a concrete implementation results in a truthy value. +Check that function isn't used in boolean context [truthy-function] +------------------------------------------------------------------- + +Functions will always evaluate to true in boolean contexts. + +.. code-block:: python + + def f(): + ... + + if f: # Error: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] + pass + .. _ignore-without-code: Check that ``# type: ignore`` include an error code [ignore-without-code] diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 511808fa7888..684c2fb236d2 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -139,6 +139,11 @@ def __str__(self) -> str: "General", default_enabled=False, ) +TRUTHY_FUNCTION: Final = ErrorCode( + "truthy-function", + "Warn about function that always evaluate to true in boolean contexts", + "General", +) NAME_MATCH: Final = ErrorCode( "name-match", "Check that type definition has consistent naming", "General" ) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 6dfc11be5c12..e2893dfa0506 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -145,7 +145,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: code=codes.TRUTHY_BOOL, ) FUNCTION_ALWAYS_TRUE: Final = ErrorMessage( - "Function {} could always be true in boolean context", code=codes.TRUTHY_BOOL + "Function {} could always be true in boolean context", code=codes.TRUTHY_FUNCTION ) NOT_CALLABLE: Final = "{} not callable" TYPE_MUST_BE_USED: Final = "Value of type {} must be used" diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 24684c84b76d..10c3c02985e0 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -841,19 +841,21 @@ if bad_union: # E: "__main__.bad_union" has type "Union[Foo, object]" of which if not bad_union: # E: "__main__.bad_union" has type "object" which does not implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] pass -def f(): - pass -if f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-bool] - pass -if not f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-bool] - pass -conditional_result = 'foo' if f else 'bar' # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-bool] - lst: List[int] = [] if lst: pass [builtins fixtures/list.pyi] +[case testTruthyFunctions] +# flags: --strict-optional +def f(): + pass +if f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] + pass +if not f: # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] + pass +conditional_result = 'foo' if f else 'bar' # E: Function "Callable[[], Any]" could always be true in boolean context [truthy-function] + [case testNoOverloadImplementation] from typing import overload diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index e4ab52e860a2..4dd21377bc4a 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6016,12 +6016,15 @@ tmp/m.py:2: error: Argument "age" to "foo" has incompatible type "str"; expected [case testDisableEnableErrorCodesIncremental] # flags: --disable-error-code truthy-bool # flags2: --enable-error-code truthy-bool -def foo() -> int: ... +class Foo: + pass + +foo = Foo() if foo: ... [out] [out2] -main:4: error: Function "Callable[[], int]" could always be true in boolean context +main:7: error: "__main__.foo" has type "Foo" which does not implement __bool__ or __len__ so it could always be true in boolean context [case testModuleAsProtocolImplementationSerialize] import m diff --git a/test-data/unit/check-inline-config.test b/test-data/unit/check-inline-config.test index 3c318d89789a..1b2085e33e91 100644 --- a/test-data/unit/check-inline-config.test +++ b/test-data/unit/check-inline-config.test @@ -166,9 +166,11 @@ main:1: error: Setting "strict" not supported in inline configuration: specify i [case testInlineErrorCodes] # flags: --strict-optional # mypy: enable-error-code="ignore-without-code,truthy-bool" +class Foo: + pass -def foo() -> int: ... -if foo: ... # E: Function "Callable[[], int]" could always be true in boolean context +foo = Foo() +if foo: ... # E: "__main__.foo" has type "Foo" which does not implement __bool__ or __len__ so it could always be true in boolean context 42 + "no" # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) [case testInlineErrorCodesOverrideConfig] @@ -178,8 +180,10 @@ import tests.bar import tests.baz [file foo.py] # mypy: disable-error-code="truthy-bool" +class Foo: + pass -def foo() -> int: ... +foo = Foo() if foo: ... 42 + "no" # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) @@ -193,8 +197,10 @@ if foo: ... # E: Function "Callable[[], int]" could always be true in boolean c [file tests/baz.py] # mypy: disable-error-code="truthy-bool" +class Foo: + pass -def foo() -> int: ... +foo = Foo() if foo: ... 42 + "no" # type: ignore From 74efdc7aa2ca677d96af54f33da3c8f1a7063df1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 19 Sep 2022 14:55:01 +0200 Subject: [PATCH 2/2] Fix tests --- mypy/errorcodes.py | 2 +- test-data/unit/check-python38.test | 2 +- test-data/unit/check-unreachable-code.test | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 684c2fb236d2..40ba173ce6cd 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -139,7 +139,7 @@ def __str__(self) -> str: "General", default_enabled=False, ) -TRUTHY_FUNCTION: Final = ErrorCode( +TRUTHY_FUNCTION: Final[ErrorCode] = ErrorCode( "truthy-function", "Warn about function that always evaluate to true in boolean contexts", "General", diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index 49b7d6c9c2e7..1922192c2877 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -310,7 +310,7 @@ def f(x: int = (c := 4)) -> int: z2: NT # E: Variable "NT" is not valid as a type \ # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases - if Alias := int: + if Alias := int: # E: Function "Type[int]" could always be true in boolean context z3: Alias # E: Variable "Alias" is not valid as a type \ # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 64736e55e2dd..e7dce5899988 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -928,7 +928,8 @@ class Case1: return False and self.missing() # E: Right operand of "and" is never evaluated def test2(self) -> bool: - return not self.property_decorator_missing and self.missing() # E: Right operand of "and" is never evaluated + return not self.property_decorator_missing and self.missing() # E: Function "Callable[[], bool]" could always be true in boolean context \ + # E: Right operand of "and" is never evaluated def property_decorator_missing(self) -> bool: return True