Skip to content

[FEAT or BUG] @overload and default value matches #18653

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
ego-thales opened this issue Feb 10, 2025 · 7 comments
Closed

[FEAT or BUG] @overload and default value matches #18653

ego-thales opened this issue Feb 10, 2025 · 7 comments

Comments

@ego-thales
Copy link

Hello everyone,

In the following example, zero-argument call matches the first overload (with Literal[True]), despite the default value being False.

@overload
def f(x: Literal[True] = ...) -> int: ...
@overload
def f(x: Literal[False] = ...) -> str: ...
def f(x: bool = False) -> int | str: return 0 if x else ""
    
# Expected: OK / error / OK
f() + ""       # error: Unsupported operand types for + ("int" and "str")  [operator]
f(True) + ""   # error: Unsupported operand types for + ("int" and "str")  [operator]
f(False) + ""  # OK

Is this a bug or desired behaviour? The easy fix is the following (remove default value from the Literal[True] overload since it cannot be targetted.

@overload
def f(x: Literal[True]) -> int: ...
@overload
def f(x: Literal[False] = ...) -> str: ...
def f(x: bool = False) -> int | str: return 0 if x else ""
    

# Expected: OK / error / OK
f() + ""       # OK
f(True) + ""   # error: Unsupported operand types for + ("int" and "str")  [operator]
f(False) + ""  # OK

The problem with this fix, is that introduces dependance in the default value set in the actual definition of f. Indeed, if I were to change it from False to True, then I would have to rewrite the overloads.
I think it would be neat if the match took the default value into consideration, and I don't know if the fact that it is not here is a bug or a "non-feature".

What do you think about this?

Thanks for your time!
Élie

@ego-thales ego-thales added the bug mypy got something wrong label Feb 10, 2025
@A5rocks
Copy link
Collaborator

A5rocks commented Feb 10, 2025

It's not a bug, mypy very much ignores defaults.

@A5rocks A5rocks added feature topic-overloads and removed bug mypy got something wrong labels Feb 10, 2025
@sterliakov
Copy link
Collaborator

Duplicate of #18581, but this example manifests in obviously incorrect typechecking without definition-site errors.

@A5rocks
Copy link
Collaborator

A5rocks commented Feb 10, 2025

Duplicate of #18581, but this example manifests in obviously incorrect typechecking without definition-site errors.

Are you sure? I'm under the impression this issue is mostly about selecting the right overload based on defaults, which mirrors issue 3737 (but is different) and is a feature request.

@sterliakov
Copy link
Collaborator

sterliakov commented Feb 10, 2025

Are you sure?

Yes, almost definitely: leaving a couple of corner cases behind the scenes (there's a bit of variadic args/kwargs magic that doesn't apply here), mypy picks the first matching overload, and here the first one matches. If you swap the definitions, you should get the opposite behavior. Two overloads are unsafely overlapping due to common zero-values call signature with incompatible return types, [overloads-overlap] is designed to prevent exactly this from happening.

And there's nothing generic here to relate to 3737 - what am I missing?..

@ilevkivskyi
Copy link
Member

Mypy never looks at overload implementation. Overloads are simply tried in order of appearance until one matches, you can flip order to achieve what you want.

As for selecting the overload depending on the default value, this is a non-starter (it may look simple, but it is actually really hard to implement in general), also it may be quite surprising when a matching overload is skipped for some reason.

@ilevkivskyi ilevkivskyi closed this as not planned Won't fix, can't repro, duplicate, stale Feb 10, 2025
@ego-thales
Copy link
Author

ego-thales commented Feb 11, 2025

Thanks for your answers!

I didn't know about [overload-overlap], I'm surprised mypy didn't raise it in my case. Is it only up with --strict? Or maybe it does not properly handle default value signatures?

@A5rocks
Copy link
Collaborator

A5rocks commented Feb 12, 2025

I didn't know about [overload-overlap], I'm surprised mypy didn't raise it in my case. Is it only up with --strict? Or maybe it does not properly handle default value signatures?

[overload-overlap] is very forgiving! It's ultimately more on the linter side, so coming up with a set of rules that penalizes wrong code without penalizing correct code is ... kinda hard. (honestly I don't know the concrete conditions on what it's preventing beyond just general "this looks bad".)

I don't think it's worth adding code to check "are there two 0 arg overloads" because that would have to be quite a bit of extra implementation (current code just uses standard callable subtyping with a special subtype check but obviously the "standard callable subtyping" is what's passing this)

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

No branches or pull requests

4 participants