diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2d78adee3e3d..bd697d5c3dbb 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -333,6 +333,13 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> isinstance(callee_type, CallableType) and callee_type.implicit): self.msg.untyped_function_call(callee_type, e) + + if (isinstance(callee_type, CallableType) + and not callee_type.is_type_obj() + and callee_type.name == "_"): + self.msg.underscore_function_call(e) + return AnyType(TypeOfAny.from_error) + # Figure out the full name of the callee for plugin lookup. object_type = None member = None diff --git a/mypy/messages.py b/mypy/messages.py index 39241925aa25..018b0c4631bd 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -701,6 +701,9 @@ def does_not_return_value(self, callee_type: Optional[Type], context: Context) - else: self.fail('Function does not return a value', context, code=codes.FUNC_RETURNS_VALUE) + def underscore_function_call(self, context: Context) -> None: + self.fail('Calling function named "_" is not allowed', context) + def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an rvalue.""" if typ.source is None: diff --git a/mypy/semanal.py b/mypy/semanal.py index 21af9322452a..124ac4fcc327 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -666,6 +666,8 @@ def f(): ... # Error: 'f' redefined """ if isinstance(new, Decorator): new = new.func + if isinstance(previous, (FuncDef, Decorator)) and new.name == previous.name == "_": + return True if isinstance(previous, (FuncDef, Var, Decorator)) and new.is_conditional: new.original_def = previous return True @@ -810,7 +812,7 @@ def handle_missing_overload_decorators(self, else: self.fail("The implementation for an overloaded function " "must come last", defn.items[idx]) - else: + elif defn.name != "_": for idx in non_overload_indexes[1:]: self.name_already_defined(defn.name, defn.items[idx], defn.items[0]) if defn.impl: diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 17b36051446e..c945d9c68aee 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2751,12 +2751,6 @@ def foo() -> None: pass _().method() # E: "_" has no attribute "method" -[case testUnusedTargetNotDef] -def foo() -> None: - def _() -> int: - pass - _() + '' # E: Unsupported operand types for + ("int" and "str") - [case testUnusedTargetForLoop] def f() -> None: a = [(0, '', 0)] diff --git a/test-data/unit/check-redefine.test b/test-data/unit/check-redefine.test index 4ac56edb24ef..d2229dba470f 100644 --- a/test-data/unit/check-redefine.test +++ b/test-data/unit/check-redefine.test @@ -483,3 +483,89 @@ try: except Exception as typing: pass [builtins fixtures/exception.pyi] + +[case testRedefiningUnderscoreFunctionIsntAnError] +def _(arg): + pass + +def _(arg): + pass + +[case testTypeErrorsInUnderscoreFunctionsReported] +def _(arg: str): + x = arg + 1 # E: Unsupported left operand type for + ("str") + +def _(arg: int) -> int: + return 'a' # E: Incompatible return value type (got "str", expected "int") + +[case testCallingUnderscoreFunctionIsNotAllowed] +def _(arg: str) -> None: + pass + +def _(arg: int) -> int: + return arg + +_('a') # E: Calling function named "_" is not allowed + +y = _(5) # E: Calling function named "_" is not allowed + +[case testFunctionStillTypeCheckedWhenAliasedAsUnderscoreDuringImport] +from a import f as _ + +_(1) # E: Argument 1 to "f" has incompatible type "int"; expected "str" +reveal_type(_('a')) # N: Revealed type is "builtins.str" + +[file a.py] +def f(arg: str) -> str: + return arg + +[case testCallToFunctionStillTypeCheckedWhenAssignedToUnderscoreVariable] +from a import g +_ = g + +_('a') # E: Argument 1 has incompatible type "str"; expected "int" +reveal_type(_(1)) # N: Revealed type is "builtins.int" + +[file a.py] +def g(arg: int) -> int: + return arg + +[case testRedefiningUnderscoreFunctionWithDecoratorWithUnderscoreFunctionsNextToEachOther] +def dec(f): + return f + +@dec +def _(arg): + pass + +@dec +def _(arg): + pass + +[case testRedefiningUnderscoreFunctionWithDecoratorInDifferentPlaces] +def dec(f): + return f + +def dec2(f): + return f + +@dec +def _(arg): + pass + +def f(arg): + pass + +@dec2 +def _(arg): + pass + +[case testOverwritingImportedFunctionThatWasAliasedAsUnderscore] +from a import f as _ + +def _(arg: str) -> str: # E: Name "_" already defined (possibly by an import) + return arg + +[file a.py] +def f(s: str) -> str: + return s diff --git a/test-data/unit/check-singledispatch.test b/test-data/unit/check-singledispatch.test index 47714b8310d0..2757173f3ae7 100644 --- a/test-data/unit/check-singledispatch.test +++ b/test-data/unit/check-singledispatch.test @@ -16,7 +16,7 @@ fun(1) # E: Argument 1 to "fun" has incompatible type "int"; expected "A" # probably won't be required after singledispatch is special cased [builtins fixtures/args.pyi] -[case testMultipleUnderscoreFunctionsIsntError-xfail] +[case testMultipleUnderscoreFunctionsIsntError] from functools import singledispatch @singledispatch