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..40ba173ce6cd 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] = 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 9daa8528e7f6..71130d191bfa 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 c796ac90215d..6d89fa72d833 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 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 44e6b66c02e6..48459dd8941a 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -936,7 +936,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