@davidfstr
Not being familar with as const, I’m not sure what you’re trying to do in the example provided.
TypeScript’s as const infers the narrowest-possible type in some sense and is added to expressions, not type annotations.
Let’s back up for a moment and consider the following Python code:
foo = {
"a": 1,
"b": 2,
}
Right now, Mypy and Pyright infer foo’s type to be dict[str, int]. However, in many cases (though not all!) I’d want foo to be typed more narrowly as
TypedDict[{
"a": Literal[1],
"b": Literal[2],
}]
with the TypedDict being closed as mentioned in PEP 764 – Inline typed dictionaries | peps.python.org . I think up until here we agree.
Now, in PEP 764: Inlined typed dictionaries - #82 by davidfstr you proposed using a TypedDict[...] (... being the literal ellipsis) as type annotation to have the type checker do this more narrow type inference. E.g. if I understand your proposal correctly
foo: TypedDict[...] = {
"a": 1,
"b": 2,
}
would produce the TypedDict[{ "a": …, "b": … }] type mentioned above.
Now, what I was getting at was that sometimes we use dictionaries within larger expressions, the larger expression being e.g. another dictionary, a list, a tuple, some function call, … How would we trigger narrow type inference for the dictionary “subexpression”? For a nested dictionary you said in your most recent comment that
bar: TypedDict[...] = {
"foo": {
"a": 1,
"b": 2,
}
}
should give a nested TypedDict type, i.e. TypedDict[{ “foo”: TypedDict[{ "a": …, "b": … }] }].
However, I don’t think this is flexible enough for two reasons:
- I might not want to apply narrow type inference to the entire dictionary but only to the
foo part, with the top-level type staying a dict[str, something].
- It doesn’t generalize well to other composite expressions, in which the top level structure is not a dictionary.
To illustrate the second point, consider the following two examples:
# Example 1
foo = (
"hello",
{
"a": 1,
"b": 2,
}
) # currently inferred as Tuple[Literal["hello"], dict[str, int]] but we would want Tuple[Literal["hello"], TypedDict[{ … }]]
# Example 2
def bar[T](val: T) => list[T]:
return [val]
def func():
return bar({
"a": 1,
"b": 2,
}) # inner dictionary is currently inferred as dict[str, int], so func()'s return type is list[dict[str, int]]. However, we would want list[TypedDict[{ … }]].
In both cases, there are of course workarounds, like
- extracting the dictionary literal into a separate variable, type-annotated with
TypedDict[…], or
- adding a type annotation for the entire composite expression, like declaring
foo: tuple[Literal[“hello”], TypedDict[...]] = … in the first example.
However, I find these workarounds suboptimal and would like to enable narrow type inference not through a type annotation like your proposed TypedDict[...] but through some hint I can add to the expression itself.
Here I am getting back to TypeScript, whose as const syntax can be appended to any expression:
const bar = {
baz: "hello, world!",
foo: {
a: 1,
b: 2
},
}; // inferred as { baz: string; foo: { a: number; b: number; } }
const bar = {
baz: "hello, world!",
foo: {
a: 1,
b: 2
} as const, // <--- "as const" applies to the preceding expression
}; // inferred as { baz: string; foo: { readonly a: 1; readonly b: 1; } } . Notice how the type of baz has stayed the same and did not turn into a literal type "hello, world!".
In Python, I would want something similar (and similarly convenient). Anyway, we should probably take the discussion elsewhere as it’s a bit off-topic. EDIT: I have started a separate thread here.