Skip to content

Literal type math #11990

Open
Open

Description

@sobolevn

I am working on feature where reveal_type(1 + 2) will reveal Literal[3].

Types that I want to support:

  • int
  • str
  • bool

And these operations:

_SUPPORTED_LITERAL_OPERATIONS: Final = {
    int: ('+', '-', '*', '//'),  # `/` returns `float`
    str: ('+',),
    bool: ('and', 'or'),
}

So, True or False is revealed as Literal[True].

I will finish some test later today and send a PR in the morning.

Снимок экрана 2022-01-14 в 0 44 36

Activity

  1. erictraut commented on Jan 13, 2022

    @erictraut

    I presume this is intended to work generally for any expression that involves these types, not only expressions that are used as arguments to reveal_type calls? In other words, do you expect this to work?

    a: int
    a = 1 + 2
    reveal_type(a) # Literal[3]

    You might want to consider adding 'not' to the list of supported operators for bool and '%' and the unary form of '+' and '-' to the list of operations for 'int'.

    For int operations, you'll need to decide what to do about numeric overflows and divide-by-zero conditions. Probably OK to fall back on int.

  2. erictraut commented on Jan 14, 2022

    @erictraut

    Out of curiosity, are you planning to support unions?

    def func1(a: Literal[1, 2], b: Literal[0, 4], c: Literal[3, 4]):
        reveal_type(a * b + c)  # Literal[3, 4, 7, 8, 11, 12]?
    
    def func2():
        greeting = "Hi " + ("Steve" if random() > 0.5 else "Amy")
        reveal_type(greeting) # Literal["Hi Steve", "Hi Amy"]?
  3. erictraut commented on Jan 14, 2022

    @erictraut

    One challenge to consider... If you're evaluating types in a loop, you need to be careful about literal math.

    def func1(loop_count: int):
        x = 1
        reveal_type(x) # Literal[1]
        x = x + 1
        reveal_type(x) # Literal[2]
    
        for _ in range(loop_count):
            x = x + 1
            reveal_type(x) # int
  4. erictraut commented on Jan 14, 2022

    @erictraut

    I think this is a good feature idea, so I implemented it in pyright. If you haven't already completed your PR for mypy, perhaps I can save you some time by sharing the unit tests I wrote.

  5. AlexWaygood commented on Jan 14, 2022

    @AlexWaygood
    Member

    Could * potentially be a supported operation for strings? 'foo' * 2 seems pretty similar to 'foo' + 'foo'.

  6. GBeauregard commented on Jan 14, 2022

    @GBeauregard

    Out of curiosity, are you planning to support unions?

    def func1(a: Literal[1, 2], b: Literal[0, 4], c: Literal[3, 4]):
        reveal_type(a * b + c)  # Literal[3, 4, 7, 8, 11, 12]?
    
    def func2():
        greeting = "Hi " + ("Steve" if random() > 0.5 else "Amy")
        reveal_type(greeting) # Literal["Hi Steve", "Hi Amy"]?

    You of course need to trust the code you run your LSP on, but what is the handling for bad input?

    Intentionally trying to OOM:

    from typing import Literal
    
    fifty_primes = Literal[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229]
    
    def func(
        a: fifty_primes,
        b: fifty_primes,
        c: fifty_primes,
        d: fifty_primes,
        e: fifty_primes,
        f: fifty_primes,
        g: fifty_primes,
        h: fifty_primes,
        i: fifty_primes,
        j: fifty_primes,
        k: fifty_primes,
        l: fifty_primes,
    ):
        reveal_type(a * b * c * d * e * f * g * h * i * j * k * l)

    I'm not sure how possible it is to accidentally create such a situation though.

  7. sobolevn commented on Jan 14, 2022

    @sobolevn
    MemberAuthor

    You might want to consider adding 'not' to the list of supported operators for bool and '%' and the unary form of '+' and '-' to the list of operations for 'int'.

    Thanks! I haven't touched unary operations right now. But, they are on the list!

    For int operations, you'll need to decide what to do about numeric overflows and divide-by-zero conditions. Probably OK to fall back on int.

    Yes, this is a plan. In case of any problems / errors we escape from Literal special path to ordinary check_op

    I think this is a good feature idea, so I implemented it in pyright. If you haven't already completed your PR for mypy, perhaps I can save you some time by sharing the unit tests I wrote.

    Thank you, @erictraut!

    One challenge to consider... If you're evaluating types in a loop, you need to be careful about literal math.

    Yes, looks like a good unit-test.

    Out of curiosity, are you planning to support unions?

    They are not supported right now. Probably in the future 🙂

    Could * potentially be a supported operation for strings? 'foo' * 2 seems pretty similar to 'foo' + 'foo'.

    Yes, I also tought about that. But, right now the logic is that type never changes for both operands. str * int produces str which is the corner case. I will try it!

  8. sobolevn commented on Jan 14, 2022

    @sobolevn
    MemberAuthor

    TODO:

    • UnaryOp
    • OperatorAssignmentStmt?
    • More operators for int: like ^, |, **, etc
    • 'a' * 2
    • Union literals
    • Better 1 // 0 error message, we can totally catch that case and warn our users
    • a: Final = 'a'; reveal_type(f'{a}b') should reveal Literal['ab']?, probably this can be a separate feature
  9. GBeauregard commented on Jan 14, 2022

    @GBeauregard

    Strings explode a bit more easily in similar fashion if they're added a few times.

    Intended to OOM:

    from typing import Literal
    mode = Literal["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","z","y","z"]
    
    def func(
        a: mode, b: mode, c: mode, d: mode, e: mode, f: mode, g: mode, h: mode, i: mode
    ):
        reveal_type(a + b + c + d + e + f + g + h + i)

    EDIT: This is far too much unneeded work, but I was thinking you can probably handle these cases without bailing to int or str if you avoid eager evaluation and instead put the corresponding problem into a SMT solver when you need to type check against a literal. 😄

  10. sobolevn commented on Jan 14, 2022

    @sobolevn
    MemberAuthor

    @GBeauregard we don't support union type at the moment, but this is going to be a great corner-case in the future! 🎉

  11. erictraut commented on Jan 14, 2022

    @erictraut

    @GBeauregard, that test case is evil! I love it. :)

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