镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

(🐞) No error when overload implementation has incompatible return type #12401

Open

Description

@KotlinIsland
from typing import overload

@overload
def foo(a: int) -> int: ...

@overload
def foo(a: float) -> int: ...

def foo(a: object) -> object:  # no error
    return object()

playground

While the strictest of solutions would be to enforce the return type be an intersection of the overload return types, that would result in being overly strict to the point of being unusable. I suggest enforcing the return type to be a union of the overloads, this is not typesafe, but is much safer than not checking it at all.

Activity

  1. JelleZijlstra commented on Mar 21, 2022

    @JelleZijlstra
    Member

    Not sure why this should be an error. object is a supertype of int, and it's generally not possible for mypy to check that overload implementations are correct.

  2. added and removed
    bugmypy got something wrong
    on Mar 21, 2022
  3. KotlinIsland commented on Mar 21, 2022

    @KotlinIsland
    ContributorAuthor

    @JelleZijlstra It's not to do with the implementation, object isn't a subtype of int, it's the same as the following scenario:

    class A:
        def foo(self) -> int: ...
    
    class B(A):
        def foo(self) -> object: ...

    In the OP only subtypes of int would be valid, eg int or bool.

  4. KotlinIsland commented on Mar 21, 2022

    @KotlinIsland
    ContributorAuthor

    I think this is intentional, as there is no way to correctly annotate an overload like the following due to overload signatures being intersections in the return type.

    The following:

    from typing import overload
    
    @overload
    def foo(a: int) -> int: ...
    
    @overload
    def foo(a: str) -> str: ...
    
    def foo(
        a: int | str  # correct
    ) -> int | str: ...  # incorrect, should be intersection of int and str

    It is at least, much more close to being correct, and I would expect mypy to enforce this tighter(unionized) type.

    That being said, the true nature of overloads is far more complicated than just unionizing the inputs and intersecting the outputs, their final form is a complex conditional type with no currently existing method of describing or checking them.

  5. DetachHead commented on Mar 21, 2022

    @DetachHead
    Contributor

    i believe this is related to microsoft/TypeScript#47669. specifically, the issue is that the function implementation is not checked to be assignable to its type (the overloads).

    in typescript, function interfaces/intersections can be used to create overloads on arrow functions as they do not have their own dedicated overload syntax, however this results in so many false positives that it's pretty much unusable.

    take this example:

    // Type '(value: string | number) => string | number' is not assignable to type '{ (value: number): number; (value: string): string; }'.
    //   Type 'string | number' is not assignable to type 'number'.
    //     Type 'string' is not assignable to type 'number'
    const foo: {
        (value: number): number
        (value: string): string
    } = (value) => value

    there are two workarounds for this, both of which are pretty annoying:

    const foo: {
        (value: number): number
        (value: string): string
    } = (value: number | string) => value as number & string

    or more accurately (which almost defeats the point of the overload in the first place):

    const foo: {
        (value: number): number
        (value: string): string
    } = <T extends number|string>(value: T): T => value

    so i'm skeptical of making overloads work this way. while it is stricter, it makes overloads pretty much unusable in most cases (in typescript it's not as much of a problem because you can get around it with generics and conditional types for some of the more complicated overloads)

    i think @KotlinIsland's idea of enforcing that the impl's return type is at least a union of the return types in the overloads is a decent compromise tho

  6. KotlinIsland commented on Mar 21, 2022

    @KotlinIsland
    ContributorAuthor

    or more accurately (which defeats the point of the overload in the first place):

    const foo: {
        (value: number): number
        (value: string): string
    } = <T extends number|string>(value: T): T => value

    This is almost identical to a constrained TypeVar!

    T = TypeVar("T", int, str)
    
    def foo(value: T) -> T:
        return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions