Skip to content

Allow redefinition of underscore functions #10811

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

Merged
merged 7 commits into from
Jul 15, 2021
Merged
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
7 changes: 7 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 0 additions & 6 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
86 changes: 86 additions & 0 deletions test-data/unit/check-redefine.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion test-data/unit/check-singledispatch.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down