Skip to content

Let "Literal" accept another Literal type variable #10026

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

Open
xuhdev opened this issue Feb 4, 2021 · 6 comments
Open

Let "Literal" accept another Literal type variable #10026

xuhdev opened this issue Feb 4, 2021 · 6 comments

Comments

@xuhdev
Copy link

xuhdev commented Feb 4, 2021

Feature

Currently Literal doesn't accept another Literal or Final[Literal]. For example:

from typing_extensions import Final, Literal

s: Final[Literal['ccc']] = 'ccc'
s1: Literal[s] = s

outputs:

a.py:4: error: Parameter 1 of Literal[...] is invalid
a.py:4: error: Variable "a.s" is not valid as a type
a.py:4: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
Found 2 errors in 1 file (checked 1 source file)

Pitch

This is particularly useful when there are some defined constants and people refer to those constants rather than the actual value. For example, some of these socket constants: https://docs.python.org/3/library/socket.html#socket.BDADDR_ANY

@xuhdev xuhdev added the feature label Feb 4, 2021
@Zac-HD
Copy link
Contributor

Zac-HD commented Sep 28, 2021

I'm not sure this is a bug: the PEP notes that "", and mypy is perfectly happy if you omit the Final:

from typing import Literal

var = Literal[Literal[1]] = 1

And in Literal[s], s is a name, not a literal value.

@ZeeD
Copy link

ZeeD commented Sep 28, 2021

while this may be not a bug, I thik this design decision limits the usefulness of Literals - and Finals
let's say I define a pair of constants

from typing import Final, Literal

MY_CONST_A: Final = 1
MY_CONST_B: Final = 2

from now on I would like to avoid using directly the values 1 and 2 and instead refer to the constants MY_CONST_A and MY_CONST_B.

let's say I want to define a function that can return either of that consts, what the return type should be?
or similarly, I want to set the type of a value that can be set to one of the two values; what is it's type?

def foo() -> Literal[MY_CONST_A, MY_CONST_B]:
    return MY_CONST_A if ... else MY_CONST_B

is not supported by mypy, and I need to set Literal[1,2] as return type to make the signature work

@erictraut
Copy link

This proposal would require an update to PEP 586, so this discussion should probably be moved to the typing-sig forum rather than mypy's issue tracker. (For full transparency, I'm one of the primary contributors to pyright, Microsoft's Python type checker.)

If we were to adopt a change like this, I think we'd need to place some additional constraints on the constants. Not only would they need to be marked Final, but they'd also need to be assigned an expression that is unambiguously a literal value — in other words, expressions that are allowed today within a Literal. That excludes more complex expressions.

For example, I would propose that none of these constants should be allowed in a Literal.

MY_CONST_C: Final = my_func()
MY_CONST_D: Final = 1 + 2
MY_CONST_E: Final = 1 if a else 2
MY_CONST_F: Final = (1, 2, 3)[0]
MY_CONST_G: Final = ...

The problem with more complex expressions is that different type checkers can infer different types. If we constrain it to literal values only, there's no ambiguity or inconsistency between type checkers.

@DevilXD
Copy link

DevilXD commented Dec 16, 2021

I'm not sure if it's helpful or not, but PEP 586 already mentions this:

Literal may also be parameterized by other literal types, or type aliases to other literal types. For example, the following is legal:

ReadOnlyMode         = Literal["r", "r+"]
WriteAndTruncateMode = Literal["w", "w+", "wt", "w+t"]
WriteNoTruncateMode  = Literal["r+", "r+t"]
AppendMode           = Literal["a", "a+", "at", "a+t"]

AllModes = Literal[ReadOnlyMode, WriteAndTruncateMode,
                   WriteNoTruncateMode, AppendMode]

This feature is again intended to help make using and reusing literal types more ergonomic.

That being said, I just ran into this issue while trying to specify a variable type as Literal[logging.NOTSET, logging.DEBUG], caused most likely by those being inferred by MyPy as int rather than Literal[0] and Literal[10] respectively. Regarding the type being int instead of Literal, I've proposed a typeshed PR: python/typeshed#6610

Having that PR accepted, MyPy would still be expected to accept Literal type aliases like so:

NOTSET: Literal[0]
DEBUG: Literal[10]

# currently valid
logging_level: Literal[Literal[0], Literal[10]]
# currently invalid
logging_level: Literal[NOTSET, DEBUG]

@karolyjozsa
Copy link

What if we take a different approach, and the Type parameterization is extended with Literals? I.e. if we could get this working?

MY_CONST_A: Final = 1
MY_CONST_B: Final = 2
def foo() -> Type[MY_CONST_A] | Type[MY_CONST_B]:
    return MY_CONST_A if ... else MY_CONST_B

@christiansegercrantz
Copy link

This still seems like a topic. I have the following scenario:

from typing import Final, Literal

TOTAL: Final = "total"

Lit = Literal["sub_sum_1", "sub_sum_2", TOTAL]

val: Lit = "sub_sum_1"  # error: Parameter 3 of Literal[...] is invalid  [valid-type]

val_list: list[Lit] = ["sub_sum_1", "sub_sum_2", TOTAL]  # error: Parameter 3 of Literal[...] is invalid  [valid-type]

Any update on the matter?

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

8 participants