Skip to content

Decorator on __init__ causes loss of type info #5398

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

Closed
campkeith opened this issue Jul 27, 2018 · 6 comments · Fixed by #6645 · May be fixed by softagram/mypy#2
Closed

Decorator on __init__ causes loss of type info #5398

campkeith opened this issue Jul 27, 2018 · 6 comments · Fixed by #6645 · May be fixed by softagram/mypy#2
Labels
bug mypy got something wrong priority-0-high

Comments

@campkeith
Copy link

Here's a simple example:

from __future__ import annotations
from typing import Callable

def decorator(method: Callable[[Foo], None]) -> Callable[[Foo], None]:
    return method

class Foo():
    @decorator
    def __init__(self):
        pass

reveal_type(Foo.__init__)

The output of mypy is:

% mypy --version
mypy 0.620
% mypy foo.py
foo.py:13: error: Revealed type is 'Any'

This seems to be specific to methods as similar type inferences work fine for functions outside of the class scope.

@gvanrossum
Copy link
Member

Try again after adding -> None to the __init__ signature.

@campkeith
Copy link
Author

Adding annotations to __init__ does not change the result; here's the updated code:

from __future__ import annotations

from typing import Callable

def decorator(method: Callable[[Foo], None]) -> Callable[[Foo], None]:
    return method

class Foo():
    @decorator
    def __init__(self: Foo) -> None:
        pass

reveal_type(Foo.__init__)

I get the same result (the revealed type is Any). If I change the name of the method, the type inference works correctly, so this issue seems to be specific to __init__.

@gvanrossum gvanrossum changed the title Method decorator causes loss of type info Decorator on __init__ causes loss of type info Jul 29, 2018
@gvanrossum
Copy link
Member

__init__ is special in a number of ways, but this still seems wrong.

@gvanrossum gvanrossum added the bug mypy got something wrong label Jul 29, 2018
@ilevkivskyi
Copy link
Member

I just stumbled at this again. Even precisely typed decorator on __init__ not just erases types, it completely erases the signature of __init__. For example:

from typing import Any, Callable, TypeVar

_C = TypeVar("_C", bound=Callable[..., None])

def decorator(f: _C) -> _C:
    return f
    
class C:
    @decorator
    def __init__(self, x: int) -> None:
        ...
        
C("whatever", "works", "now") + 42

@JukkaL
Copy link
Collaborator

JukkaL commented Sep 18, 2018

Increased priority to "high" since this behavior is very unexpected and can hide bugs.

@TV4Fun
Copy link
Contributor

TV4Fun commented Oct 13, 2018

It looks like #1706 is a special case of this. FYI, I am working on a PR to fix this. It seems like the problem is that quite a few places check whether a node represents a callable function by checking if it is an instance of FuncBase, which Decorator is not. We don't want to make Decorator inherit from FuncBase, because whether it is callable or not depends on the type returned by the decorator, and we don't want to assume that it is, so my thought was to override __instancecheck__ in FuncBase to specifically check and return true if the decorated function is callable. There I still some issues I need to work out with my implementation—my current version has some false positives that I am trying to track down, but if anyone would like to offer input on what I'm doing, my code is at https://github.com/TV4Fun/mypy/tree/fix_decorators

benclifford added a commit to Parsl/parsl that referenced this issue Apr 15, 2019
This allows mypy to understand the type signatures of functions
decorated with @typeguard.typechecked, allowing the type signatures
on such functions to be used by both enforce and mypy.

Prior to this commit, adding @TypeChecked removed the ability of mypy to
type-check calls to decorated functions, as it did not understand that
the type of such a decorated function is the same as the original
function. The added stub makes this declaration.

The test-suite mypy calls are modified to use that stub file.

The immediate motivation for this is to keep mypy passing when adding a
type annotation for parsl.load; that type annotation is also added in
this commit.

This commit fixes some of problem described in the commit message for
d7d9a25 about breaking mypy type checking for end user Configs in some
places, but by no means all of it.

However there is a mypy bug, python/mypy#5398,
where decorated __init__ methods are not properly type-checked by mypy,
which means that mypy typechecking is still missing on any decorated
__init__ methods - which unfortunately is most of the user-facing type
checked code.

At time of writing, there are PRs open to fix this in mypy, so there
is hope; and that does not impede this PR being merged.
benclifford added a commit to Parsl/parsl that referenced this issue Apr 24, 2019
This allows mypy to understand the type signatures of functions
decorated with @typeguard.typechecked, allowing the type signatures
on such functions to be used by both enforce and mypy.

Prior to this commit, adding @TypeChecked removed the ability of mypy to
type-check calls to decorated functions, as it did not understand that
the type of such a decorated function is the same as the original
function. The added stub makes this declaration.

The test-suite mypy calls are modified to use that stub file.

The immediate motivation for this is to keep mypy passing when adding a
type annotation for parsl.load; that type annotation is also added in
this commit.

This commit fixes some of problem described in the commit message for
d7d9a25 about breaking mypy type checking for end user Configs in some
places, but by no means all of it.

However there is a mypy bug, python/mypy#5398,
where decorated __init__ methods are not properly type-checked by mypy,
which means that mypy typechecking is still missing on any decorated
__init__ methods - which unfortunately is most of the user-facing type
checked code.

At time of writing, there are PRs open to fix this in mypy, so there
is hope; and that does not impede this PR being merged.
ilevkivskyi added a commit that referenced this issue Apr 27, 2019
Fixes #1706
Fixes #5398

Previously, decorated constructors either resulted in silent `Any` types for class objects (in case of a decorated `__init__()`) or incorrect signatures (in case of a decorated `__new__()`).

This PR adds support for decorated constructors, implementation is straightforward.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-0-high
Projects
None yet
5 participants