Skip to content
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

Implementation of overloaded function should be automatically typed #3160

Closed
pkch opened this issue Apr 12, 2017 · 4 comments
Closed

Implementation of overloaded function should be automatically typed #3160

pkch opened this issue Apr 12, 2017 · 4 comments

Comments

@pkch
Copy link
Contributor

pkch commented Apr 12, 2017

Without type annotation, implementation of overloaded functions would assume all arguments are of type Any, and so will fail to properly type check the body (that's assuming one has --checked-untyped-defs flag on - otherwise, the body is ignored completely).

But annotating the implementation of the overloaded function is annoying:

T = TypeVar('T')
class MyList(Generic[T]):
    items: List[T]

    @overload
    def __getitem__(self, index: int) -> T:
        pass

    @overload
    def __getitem__(self, index: slice) -> Sequence[T]:
        pass

    def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]:
        if isinstance(index, int):
            ...  # Return a T here
        elif isinstance(index, slice):
            ...  # Return a sequence of Ts here
        else:
            assert False

In addition, even with this annotation, precise type checking is impossible. For example, if the code returns a T instead of Sequence[T] by mistake when index is a slice, it will pass type check.

I think mypy should automatically type check the implementation several times, once for each of the possible overloaded variants, and each time assume the argument and return types as provided in that variant. It should also prohibit manual type annotation of implementation of overloaded functions to avoid since it would then be worse than redundant.

@pkch pkch mentioned this issue Apr 12, 2017
@sixolet
Copy link
Collaborator

sixolet commented Apr 12, 2017

Unfortunately, we can't cover situations where the implementation takes in (*args, **kwargs) in this manner at all -- it's too dynamic. And that's not completely uncommon.

@gvanrossum
Copy link
Member

I think we should close this until we have more experience here.

@pkch
Copy link
Contributor Author

pkch commented Apr 12, 2017

@sixolet Nothing is lost in the case of *args, **kwargs: currently any type checking mypy does with overloaded implementation assumes all arguments are Any.

@gvanrossum
Copy link
Member

We considered this and decided against it. There would be too many special cases to consider (e.g. overloading on different arg counts).

facebook-github-bot pushed a commit to facebook/pyre-check that referenced this issue Oct 25, 2018
Summary:
The typechecker needs to ignore the existence of a signature if that signature is overloaded with typing.overload decorated defines. In other words, when selecting signatures, the overloaded define (containing the actual implementation of the function) should never be selected.

typing.overload designates a definition that is purely for the typechecker's benefit, and is ignored at runtime because it is immediately overwritten with a definition that contains the actual implementation. Calling a function decorated with typing.overload throws at runtime:
```
>>> import typing
>>> typing.overload
... def foo(x):
...     return 1
...
>>> foo(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/fbcode/gcc-5-glibc-2.23/lib/python3.6/typing.py", line 1591, in _overload_dummy
    "You should not call an overloaded function. "
NotImplementedError: You should not call an overloaded function. A series of The controller you requested could not be found. functions outside a stub module should always be followed by an implementation that is not The controller you requested could not be found..
```

Therefore, the purpose of typing.overload defines is simply to clarify the type signature when the return annotation should vary depending on the parameter annotations (but maybe not in a way specifiable with type vars):

```
typing.overload
def foo(x: int) -> str: ...

typing.overload
def foo(x:str) -> int:...

def foo(x: Union[int, str]) -> Union[int, str]
```

Currently, when pyre sees the above set of signatures and then tries to resolve a call to `foo(x)` where `x` is an `int`, we look at all overloads equally and can select the `Union[int, str] -> Union[int, str]` signature and end up assuming the return type of `foo(x)` is `Union[int, str]` rather than the correct and more specific `str`.

If `typing.overload` definitions exist, we should be selecting only from those, and not from the implementation signature.

**NodeAPI Bug (T35334236)**
```
    overload
    staticmethod
    def get(roots):
        # type: (TRoot) -> NodeGetCity[NodeCity]
        pass

    overload  # noqa
    staticmethod
    def get(roots):
        # type: (TRoots) -> NodeGetCity[List[NodeCity]]
        pass

    staticmethod  # noqa
    def get(roots):
        # type: (Union[TRoot, TRoots]) -> NodeGetCity
        """
        Get the node object/s corresponding to the roots.
        param roots: Node|fbid, or iterable<Node|fbid>.
        return A Query object that will return a Node if a Node or NodeId
                was passed as roots otherwise will return a list of Nodes.
        """
        return NodeGetCity(roots, nodeapi_ext.NodeErrorControllerType.ENFORCE)
```

Pyre selects the third method, which has a return type of NodeGetCity with an unknown type parameter (which inherits from awaitable), so awaiting on this returns an unknown type. Pyre *should* realize that the overloaded methods are there to refine the type signature of get depending on the input type. Merging with a pre-existing task to handle overloads this way.

**More context:**
Pep:
https://docs.python.org/3/library/typing.html#typing.overload
Discussion:
python/typing#253
python/mypy#3160
python/typing#14

**Next steps - not urgent (T35517446):**
1. When typechecking a define, if it is decorated with an typing.overload and there does not exist a matching definition *not* decorated with typing.overload. (overload functions throw when called at runtime)
2. When typechecking a define, if there exist other same name defines decorated with typing.overload, throw if overloading functions do not comprehensively cover the type signature of this overloaded function

Reviewed By: dkgi

Differential Revision: D10494018

fbshipit-source-id: 48973008d94cc539198ec13552b8108412413f2a
mthuurne added a commit to boxingbeetle/softfab that referenced this issue May 14, 2019
PEP 484 doesn't require an overloaded function implementation to be
annotated, but mypy will not check it otherwise.

python/mypy#3160

Note that in dispatchlib I replaced overload by type restriction,
since that was a simpler way of expressing the same rules.
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

No branches or pull requests

3 participants