Skip to content

Commit

Permalink
Put feature behind flag; add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ilevkivskyi committed Jul 4, 2024
1 parent a22d0ae commit 8e1ce8c
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 6 deletions.
21 changes: 21 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,27 @@ List of currently incomplete/experimental features:
# Without PreciseTupleTypes: tuple[int, ...]
# With PreciseTupleTypes: tuple[()] | tuple[int] | tuple[int, int]
* ``NewGenericSyntax``: this feature enables support for syntax defined
by :pep:`695`. For example:

.. code-block:: python
class Container[T]: # defines a generic class
content: T
def first[T](items: list[T]) -> T: # defines a generic function
return items[0]
type Items[T] = list[tuple[T, T]] # defines a generic type alias
* ``InlineTypedDict``: this feature enables non-standard syntax for inline
:ref:`TypedDicts <typeddict>`, for example:

.. code-block:: python
def test_values() -> {"int": int, "str": str}:
return {"int": 42, "str": "test"}
Miscellaneous
*************
Expand Down
38 changes: 38 additions & 0 deletions docs/source/typed_dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,41 @@ section of the docs has a full description with an example, but in short, you wi
need to give each TypedDict the same key where each value has a unique
:ref:`Literal type <literal_types>`. Then, check that key to distinguish
between your TypedDicts.

Inline TypedDict types
----------------------

.. note::

This is an experimental (non-standard) feature. Use
``--enable-incomplete-feature=InlineTypedDict`` to enable.

Sometimes you may want to define a complex nested JSON schema, or annotate
a one-off function that returns a TypedDict. In such cases it may be convenient
to use inline TypedDict syntax. For example:

.. code-block:: python
def test_values() -> {"int": int, "str": str}:
return {"int": 42, "str": "test"}
class Response(TypedDict):
status: int
msg: str
# Using inline syntax here avoids defining two additional TypedDicts.
content: {"items": list[{"key": str, "value": str}]}
Inline TypedDicts can also by used as targets of type aliases, but due to
ambiguity with a regular variables it is only allowed for (newer) explicit
type alias forms:

.. code-block:: python
from typing import TypeAlias
X = {"a": int, "b": int} # creates a variable with type dict[str, type[int]]
Y: TypeAlias = {"a": int, "b": int} # creates a type alias
type Z = {"a": int, "b": int} # same as above (Python 3.12+ only)
Also, due to incompatibility with runtime type-checking it is strongly recommended
to *not* use inline syntax in union types.
4 changes: 2 additions & 2 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _extract_argument_name(expr: Expression) -> str | None:

def expr_to_unanalyzed_type(
expr: Expression,
options: Options | None = None,
options: Options,
allow_new_syntax: bool = False,
_parent: Expression | None = None,
allow_unpack: bool = False,
Expand Down Expand Up @@ -122,7 +122,7 @@ def expr_to_unanalyzed_type(
elif (
isinstance(expr, OpExpr)
and expr.op == "|"
and ((options and options.python_version >= (3, 10)) or allow_new_syntax)
and ((options.python_version >= (3, 10)) or allow_new_syntax)
):
return UnionType(
[
Expand Down
3 changes: 2 additions & 1 deletion mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class BuildType:
UNPACK: Final = "Unpack"
PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes"
NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax"
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX))
INLINE_TYPEDDICT: Final = "InlineTypedDict"
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX, INLINE_TYPEDDICT))
COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK))


Expand Down
8 changes: 7 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
check_arg_names,
get_nongen_builtins,
)
from mypy.options import Options
from mypy.options import INLINE_TYPEDDICT, Options
from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface
from mypy.semanal_shared import (
SemanticAnalyzerCoreInterface,
Expand Down Expand Up @@ -1238,6 +1238,12 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
req_keys.add(item_name)
items[item_name] = analyzed
if t.fallback.type is MISSING_FALLBACK: # anonymous/inline TypedDict
if INLINE_TYPEDDICT not in self.options.enable_incomplete_feature:
self.fail(
"Inline TypedDict is experimental,"
" must be enabled with --enable-incomplete-feature=InlineTypedDict",
t,
)
required_keys = req_keys
fallback = self.named_type("typing._TypedDict")
for typ in t.extra_items_from:
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,8 @@ e: Literal[dummy()] # E: Invalid type: Literal[...] cannot contain a
from typing_extensions import Literal
a: Literal[{"a": 1, "b": 2}] # E: Parameter 1 of Literal[...] is invalid
b: Literal[{1, 2, 3}] # E: Invalid type: Literal[...] cannot contain arbitrary expressions
c: {"a": 1, "b": 2} # E: Invalid type: try using Literal[1] instead? \
c: {"a": 1, "b": 2} # E: Inline TypedDict is experimental, must be enabled with --enable-incomplete-feature=InlineTypedDict \
# E: Invalid type: try using Literal[1] instead? \
# E: Invalid type: try using Literal[2] instead?
d: {1, 2, 3} # E: Invalid type comment or annotation
[builtins fixtures/tuple.pyi]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ type I3 = None | C[TD]
[typing fixtures/typing-full.pyi]

[case testTypedDictInlineYesNewStyleAlias]
# flags: --enable-incomplete-feature=NewGenericSyntax
# flags: --enable-incomplete-feature=NewGenericSyntax --enable-incomplete-feature=InlineTypedDict
type X[T] = {"item": T, "other": X[T] | None}
x: X[str]
reveal_type(x) # N: Revealed type is "TypedDict({'item': builtins.str, 'other': Union[..., None]})"
Expand Down
6 changes: 6 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -3552,12 +3552,14 @@ class Test:
[builtins fixtures/tuple.pyi]

[case testTypedDictInlineNoOldStyleAlias]
# flags: --enable-incomplete-feature=InlineTypedDict
X = {"int": int, "str": str}
reveal_type(X) # N: Revealed type is "builtins.dict[builtins.str, def () -> builtins.object]"
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictInlineYesMidStyleAlias]
# flags: --enable-incomplete-feature=InlineTypedDict
from typing_extensions import TypeAlias
X: TypeAlias = {"int": int, "str": str}
x: X
Expand All @@ -3566,12 +3568,14 @@ reveal_type(x) # N: # N: Revealed type is "TypedDict({'int': builtins.int, 'st
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictInlineNoEmpty]
# flags: --enable-incomplete-feature=InlineTypedDict
x: {} # E: Invalid type comment or annotation
reveal_type(x) # N: Revealed type is "Any"
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictInlineNotRequired]
# flags: --enable-incomplete-feature=InlineTypedDict
from typing import NotRequired

x: {"one": int, "other": NotRequired[int]}
Expand All @@ -3582,6 +3586,7 @@ y = {"one": 1} # E: Expected TypedDict keys ("one", "other") but found only key
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictInlineNestedSchema]
# flags: --enable-incomplete-feature=InlineTypedDict
def nested() -> {"one": str, "other": {"a": int, "b": int}}:
if bool():
return {"one": "yes", "other": {"a": 1, "b": 2}} # OK
Expand All @@ -3591,6 +3596,7 @@ def nested() -> {"one": str, "other": {"a": int, "b": int}}:
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictInlineMergeAnother]
# flags: --enable-incomplete-feature=InlineTypedDict
from typing import TypeVar
from typing_extensions import TypeAlias

Expand Down

0 comments on commit 8e1ce8c

Please sign in to comment.