Skip to content

Fix decorators on class __init__ methods #5798

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
wants to merge 2 commits into from

Conversation

TV4Fun
Copy link
Contributor

@TV4Fun TV4Fun commented Oct 18, 2018

This fixes a few issues around decorators, specifically resolves #5398 and #1706. Not sure if this is exactly the way you want to fix this. Probably better would be to get rid of the Decorator class altogether, but this provides a workable fix. My reasoning on it is this:

  • Many places check whether a Node is callable by checking whether it is an instance of FuncBase.
  • Replace these with a get_callable method, which will return a FuncBase if it makes sense to construct one for the passed in Node, otherwise return None.
  • This method currently only checks if a Node is a FuncBase or a callable Decorator, but could conceivably be expanded to cover other cases with minimal additional work.
  • In the case of a Decorator return an instance of the CallableDecorator class, which inherits from FuncItem iff the decorated function is still callable.
    This works, but creates problems in a few places in the codebase that explicitly assume that an instance of FuncBase is not decorated. I have fixed the instances of this I could find, and all of the existing tests pass, but we probably want to do more testing on this to be sure.

Stray observations:

  • It would be nice if we didn't have to treat property and similar descriptor-based decorators as special cases and could work with them more directly. Possibly by having their implementations in typeshed be Generics with appropriate types. This would probably require more robust type annotation than we currently support though.
  • It may make sense to either rename Var.is_staticmethod and Var.is_classmethod to Var.is_static and Var.is_class or rename FuncBase.is_static and FuncBase.is_class to FuncBase.is_staticmethod and FuncBase.is_classmethod. They duplicate the same functionality, and this would help remove some unneeded type checks in a few places.
  • I experimented with having properties just return their property type, rather than being treated as a special type of callable. This made a few things nicer, in particular fixing the false positive in testInferListInitializedToEmptyInClassBodyAndOverriden, but created problems in other places. This may be worth investigating further.
  • This change may make it easier to implement properties with different get/set types as suggested in What to do about setters of a different type than their property? #3004, I will need to investigate further.
  • This change probably means that there is a lot of code related to handling Decorators that is no longer needed, but I am not familiar enough with the codebase to say that for sure.
  • This fix requires that the decorator is explicitly annotated to return a Callable. It seems like this should be something we could determine through type inference, but I'm not sure how easy that would be to do.

I look forward to your criticisms.

commit 95cafcd
Author: root <jcroteau@liveramp.com>
Date:   Wed Oct 17 19:03:27 2018 -0700

    Fix broken tests, add new tests, and remove things added for debugging

commit c845501
Author: Joel Croteau <jcroteau@gmail.com>
Date:   Mon Oct 15 16:07:46 2018 -0700

    Fix some test failures

commit 32f05cb
Author: Joel Croteau <jcroteau@gmail.com>
Date:   Mon Oct 15 11:52:52 2018 -0700

    Fix false positive and add some type annotations

commit cbaab5b
Author: root <jcroteau@liveramp.com>
Date:   Thu Oct 11 17:31:33 2018 -0700

    Here, have some code
Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just started reviewing, but there is a problem with the proposed approach.

@ilevkivskyi, I see your point about how overriding `__instancecheck__`
would create problems with debugging and `mypyc`, so I have implemented
an alternate solution. I have created a `get_callable` method to check
whether a node is callable and, in the case of a decorator, return an
instance of a separate `CallableDecorator` class that inherits from
`FuncItem`. I had originally thought to just have an `is_callable` method
that would test whether a `Node` was either an instance of `FuncBase`
or a callable `Decorator`, but this created problems with type resolution.
This solution is not exactly ideal either, but I think will create fewer
problems. I agree that the best solution would be to remove the `Decorator`
class altogether, but this would be a fairly major refactor. The problem
is that we do need a `Decorator` class in early passes before we have
type information, and we use the `Node.accept` method to add type information
to an instance, we don't have a simple way to replace a node with another
node of a different type. Let me know what you think of this approach.

One other stray observation:
* This fix requires that the decorator is explicitly annotated to return
  a `Callable`. It seems like this should be something we could determine
  through type inference, but I'm not sure how easy that would be to do.
Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks better. I have a suggestion about possible strategy in a comment below. Here are two additional "large scale" suggestions/comments:

  • Please update the PR description.
  • It looks like you make many changes that are not necessary for the actual fix. If you do so, please add a test case for every change (a test that currently fails, but passes with your change).

"""A wrapper around a Decorator that allows it to be treated as a callable function"""
def __init__(self, decorator: Decorator) -> None:
super().__init__(decorator.func.arguments, decorator.func.body,
cast('mypy.types.CallableType', decorator.type))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better but I think this can be made even simpler. I don't see why this class is necessary, moreover instead of one class that I don't like (Decorator), we will have two. Instead of trying to solve everything, why not start from a simple approach that fixes the original issues, and then generalize it to a reasonable extent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason I do it this way is that FuncBase and FuncItem are both abstract, so I can't instantiate them directly, and I don't know if it makes sense to return a FuncDef, since that's not really what this is. I think it makes sense to have a callable decorator be represented as a FuncBase, so that code that is looking for something callable can treat it the same as it would any other callable. The alternative is to create a lot of special cases to handle Decorators, which doesn't seem like it serves the long-term interest of eliminating the Decorator class altogether.

if second_type is None:
method = get_callable(second.node)
if method:
second_type = self.function_type(method)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why you need this change, but please take into account that there are three places in checker.py that check Liskov: one here (for "horizontal" overriding in multiple inheritance), one for variables, and one for methods etc. (There is some code duplication, I know our code is not perfect.) So please double check if you need to update other two places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On closer inspection, I think you are correct that this change is unnecessary, so removing it.

@ilevkivskyi
Copy link
Member

@TV4Fun Sorry, but you will need to find another reviewer for this PR.

@ilevkivskyi ilevkivskyi reopened this Oct 27, 2018
@ilevkivskyi
Copy link
Member

(Didn't mean to close, hit a wrong button.)

@ilevkivskyi
Copy link
Member

Superseded by #6645

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decorator on __init__ causes loss of type info
2 participants