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

"bool" cannot be assigned to type "Literal[False]" in calling an overloaded function from an overloaded function #5230

Open
ringohoffman opened this issue Jun 4, 2023 · 4 comments
Labels
addressed in next version Issue is fixed and will appear in next published version enhancement request New feature or request

Comments

@ringohoffman
Copy link

ringohoffman commented Jun 4, 2023

Describe the bug
When I have 2 classmethods overloaded on a bool, when I call one from the other, it says that bool cannot be assigned to Literal[X] where X is the 2nd overload signature (Literal[False]) below.

To Reproduce

from typing import Tuple, Union, overload

from typing_extensions import Literal


class MyClass:
    @overload
    @classmethod
    def from_something(cls, return_two: Literal[True]) -> Tuple[int, int]:
        ...

    @overload
    @classmethod
    def from_something(cls, return_two: Literal[False]) -> int:
        ...

    @classmethod
    def from_something(cls, return_two: bool) -> Union[int, Tuple[int, int]]:
        # something something
        return cls.from_something_else(return_two)  # "bool" cannot be assigned to type "Literal[False]"

    @overload
    @classmethod
    def from_something_else(cls, return_two: Literal[True]) -> Tuple[int, int]:
        ...

    @overload
    @classmethod
    def from_something_else(cls, return_two: Literal[False]) -> int:
        ...

    @classmethod
    def from_something_else(cls, return_two: bool) -> Union[int, Tuple[int, int]]:
        if return_two:
            return 1, 2
        return 1

Expected behavior
I do not expect to see this error message.

VS Code extension or command-line
Pylance for VS Code v2023.5.50

@ringohoffman ringohoffman changed the title "bool" cannot be assigned to type "Literal[False]" in nested overload "bool" cannot be assigned to type "Literal[False]" in calling an overloaded function from an overloaded function Jun 4, 2023
@erictraut
Copy link
Collaborator

erictraut commented Jun 4, 2023

Yes, this is correct. You have defined two overloads, and they accept only Literal[True] and Literal[False]. If you want the function to accept bool, you would need to create a third overload that accepts a bool.

    @overload
    @classmethod
    def from_something_else(cls, return_two: bool) -> int | tuple[int, int]:
        ...

Pyright doesn't expand bool into Literal[True, False] in this case. That's an intentional decision, as expansion of this sort would be computationally expensive in a hot path within the type analyzer.

@erictraut erictraut added the as designed Not a bug, working as intended label Jun 4, 2023
ringohoffman added a commit to ringohoffman/transformers that referenced this issue Jun 4, 2023
type checkers won't infer Union[Literal[True], Literal[False]] in this case, so we need to manually define it microsoft/pyright#5230
@santibreo
Copy link

santibreo commented Jul 30, 2024

Yes, this is correct. You have defined two overloads, and they accept only Literal[True] and Literal[False]. If you want the function to accept bool, you would need to create a third overload that accepts a bool.

    @overload
    @classmethod
    def from_something_else(cls, return_two: bool) -> int | tuple[int, int]:
        ...

Pyright doesn't expand bool into Literal[True, False] in this case. That's an intentional decision, as expansion of this sort would be computationally expensive in a hot path within the type analyzer.

I am working in the following example file, and I feel like I am missing something:

from typing import Literal, overload


@overload
def f(a: Literal[True]) -> int: ...

@overload
def f(a: Literal[False]) -> str: ...

#  @overload
#  def f(a: bool) -> int | str: ...

def f(a: bool) -> int | str:
    return 1 if a else '1'

f(a=True)
f(a=False)


def g(a: bool = True) -> int:
    return int(f(a=a))

If i execute pyright on this file I get the following 2 errors:

  ...:18:16 - error: No overloads for "f" match the provided arguments (reportCallIssue)
  ...:18:20 - error: Argument of type "bool" cannot be assigned to parameter "a" of type "Literal[False]" in function "f"
    "bool" is incompatible with type "Literal[False]" (reportArgumentType)
2 errors, 0 warnings, 0 informations

But if I uncomment commented lines everything is fine.

I do not understand:

  1. Why calling directly f with bool types outside a function does not give errors but calling it inside a function does.
  2. Why the "final" signature of function f has to be defined as an @overload to make pyright happy.

Could you provide any help on this?.

Thank you in advance.

PD: I am using pyright 1.1.373 on Windows laptop (just in case)

@erictraut
Copy link
Collaborator

Why calling directly f with bool types outside a function does not give errors but calling it inside a function does.

Calling it inside versus outside a function is irrelevant. What is relevant is whether you're calling it with an argument that has type Literal[True], Literal[False] or bool. Those are three distinct types.

from random import random
x = random() > 0.5
reveal_type(x) # bool

f(a=x) # Error
f(a=True) # No error

Why the "final" signature of function f has to be defined as an @overload to make pyright happy.

When a call expression is evaluated by a type checker and the callee is overloaded, only the overload signatures are considered by a type checker. The implementation is ignored in this case. For details, refer to the overload description in the official Python typing spec.

Not surprisingly, the other major Python type checkers including mypy behave the same with your code sample.

@erictraut erictraut reopened this Jan 26, 2025
@erictraut erictraut added enhancement request New feature or request addressed in next version Issue is fixed and will appear in next published version and removed as designed Not a bug, working as intended labels Jan 26, 2025
@erictraut
Copy link
Collaborator

I recently submitted a draft update to the Python typing spec that clarifies the type checker behaviors for argument type expansion when evaluating calls to overloaded functions. The draft spec includes expansion of bool into Literal[False] and Literal[True]. This allows the code above to type check without errors.

This draft update hasn't yet been approved by the full Typing Council, but I think there's a good chance it will be in the near future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version enhancement request New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants