diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake
index 98e8a84608a56..b8f2b36f324fd 100644
--- a/extmod/extmod.cmake
+++ b/extmod/extmod.cmake
@@ -43,6 +43,7 @@ set(MICROPY_SOURCE_EXTMOD
${MICROPY_EXTMOD_DIR}/modtls_axtls.c
${MICROPY_EXTMOD_DIR}/modtls_mbedtls.c
${MICROPY_EXTMOD_DIR}/modtime.c
+ ${MICROPY_EXTMOD_DIR}/modtyping.c
${MICROPY_EXTMOD_DIR}/modvfs.c
${MICROPY_EXTMOD_DIR}/modwebsocket.c
${MICROPY_EXTMOD_DIR}/modwebrepl.c
diff --git a/extmod/extmod.mk b/extmod/extmod.mk
index c132fd89ce887..f7acc0fe0251a 100644
--- a/extmod/extmod.mk
+++ b/extmod/extmod.mk
@@ -43,6 +43,7 @@ SRC_EXTMOD_C += \
extmod/modtls_axtls.c \
extmod/modtls_mbedtls.c \
extmod/modtime.c \
+ extmod/modtyping.c \
extmod/moductypes.c \
extmod/modvfs.c \
extmod/modwebrepl.c \
diff --git a/extmod/modtyping.c b/extmod/modtyping.c
new file mode 100644
index 0000000000000..c7ec3465d1356
--- /dev/null
+++ b/extmod/modtyping.c
@@ -0,0 +1,272 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/obj.h"
+#include "py/runtime.h"
+
+#if MICROPY_PY_TYPING
+
+// Implement roughly the equivalent of the following minimal Python typing module, meant to support the
+// typing syntax at runtime but otherwise ignoring any functionality:
+//
+// TYPE_CHECKING = False
+// class _AnyCall:
+// def __init__(*args, **kwargs):
+// pass
+//
+// def __call__(self, *args, **kwargs):
+// return self
+//
+// def __getitem__(self, attr):
+// return self
+//
+// _typing_obj = _AnyCall()
+//
+// def __getattr__(attr):
+// return _typing_obj
+//
+// Note this works together with the micropython compiler itself ignoring type hints, i.e. when encountering
+//
+// def hello(name: str) -> None:
+// pass
+//
+// both str and None hints are simply ignored.
+
+typedef struct _mp_obj_any_call_t
+{
+ mp_obj_base_t base;
+} mp_obj_any_call_t;
+
+static const mp_obj_type_t mp_type_any_call_t;
+static const mp_obj_type_t mp_type_typing_alias;
+
+// Lightweight runtime representation for objects such as typing.List[int].
+// The alias keeps track of the original builtin type and the tuple of
+// parameters so that __origin__ and __args__ can be queried at runtime.
+typedef struct _mp_obj_typing_alias_t {
+ mp_obj_base_t base;
+ mp_obj_t origin;
+ mp_obj_t args; // tuple or MP_OBJ_NULL when not parametrised
+} mp_obj_typing_alias_t;
+
+// Maps a qstr name to the builtin type that should back the alias.
+typedef struct {
+ qstr name;
+ const mp_obj_type_t *type;
+} typing_alias_spec_t;
+
+static mp_obj_t typing_alias_from_spec(const typing_alias_spec_t *spec_table, size_t spec_len, qstr attr);
+
+static mp_obj_t typing_alias_new(mp_obj_t origin, mp_obj_t args) {
+ mp_obj_typing_alias_t *self = mp_obj_malloc(mp_obj_typing_alias_t, &mp_type_typing_alias);
+ self->origin = origin;
+ self->args = args;
+ return MP_OBJ_FROM_PTR(self);
+}
+
+static void typing_alias_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ // Only handle reads that we recognise: __origin__ and __args__. Anything
+ // else is delegated back to the VM where it will fall through to the
+ // generic AnyCall behaviour.
+ if (dest[0] != MP_OBJ_NULL) {
+ return;
+ }
+
+ mp_obj_typing_alias_t *self = MP_OBJ_TO_PTR(self_in);
+ if (attr == MP_QSTR___origin__) {
+ dest[0] = self->origin;
+ } else if (attr == MP_QSTR___args__) {
+ dest[0] = self->args == MP_OBJ_NULL ? mp_const_empty_tuple : self->args;
+ }
+}
+
+static mp_obj_t typing_alias_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) {
+ if (value != MP_OBJ_SENTINEL) {
+ mp_raise_TypeError(MP_ERROR_TEXT("typing alias does not support assignment"));
+ }
+
+ mp_obj_typing_alias_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_obj_t args_obj;
+ if (mp_obj_is_type(index_in, &mp_type_tuple)) {
+ args_obj = index_in;
+ } else {
+ mp_obj_t items[1] = { index_in };
+ args_obj = mp_obj_new_tuple(1, items);
+ }
+
+ return typing_alias_new(self->origin, args_obj);
+}
+
+static mp_obj_t typing_alias_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_obj_typing_alias_t *self = MP_OBJ_TO_PTR(self_in);
+ return mp_call_function_n_kw(self->origin, n_args, n_kw, args);
+}
+
+static MP_DEFINE_CONST_OBJ_TYPE(
+ mp_type_typing_alias,
+ MP_QSTR_typing_alias,
+ MP_TYPE_FLAG_NONE,
+ attr, typing_alias_attr,
+ subscr, typing_alias_subscr,
+ call, typing_alias_call
+ );
+
+
+// Can be used both for __new__ and __call__: the latter's prototype is
+// (mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args)
+// so this function works as long as the argument size matches.
+static mp_obj_t any_call_new(const mp_obj_type_t *arg0, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ #if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D
+ MP_STATIC_ASSERT(sizeof(mp_obj_type_t *) == sizeof(mp_obj_t));
+ #endif
+ // If it's an actual instance we're used for __call__ so return self_in.
+ if (mp_obj_get_type(MP_OBJ_FROM_PTR(arg0)) == &mp_type_any_call_t) {
+ return MP_OBJ_FROM_PTR(arg0);
+ }
+ // Could also be we're being called as a function/decorator, return the decorated thing then.
+ // TODO obviously a bit of a hack, plus doesn't work for decorators with arguments.
+ // Note could test mp_obj_is_fun on the first arg here, then being called as decorator etc that
+ // is true, but turns out just returning the last argument works in more cases, like
+ // UserId = typing.NewType("UserId", int)
+ // assert UserId(1) == 1
+ if (n_args != 0) {
+ return args[n_args - 1];
+ }
+ return mp_obj_malloc(mp_obj_any_call_t, arg0);
+}
+
+#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D
+static mp_obj_t any_call_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ if (mp_obj_get_type(self_in) == &mp_type_any_call_t || n_args == 0) {
+ return self_in;
+ }
+ return args[n_args - 1];
+}
+#else
+#define any_call_call any_call_new
+#endif
+
+#if defined(MICROPY_UNIX_COVERAGE)
+void any_call_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
+#endif
+
+static mp_obj_t any_call_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) {
+ return self_in;
+}
+
+// TODO could probably apply same trick as in any_call_new here and merge any_call_module_attr,
+// but still have to test if that's worth it code-size wise.
+static void any_call_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ // Only loading is supported.
+ if (dest[0] == MP_OBJ_NULL) {
+ if (attr != MP_QSTR___str__ && attr != MP_QSTR___repr__) {
+ dest[0] = self_in;
+ }
+ }
+}
+
+// Only a small subset of typing.* names need concrete runtime behaviour. The
+// table below lists those names together with the builtin type that should be
+// wrapped in a typing alias. Everything else continues to use the extremely
+// small AnyCall shim.
+static const typing_alias_spec_t typing_container_specs[] = {
+ { MP_QSTR_type, &mp_type_type },
+ { MP_QSTR_Type, &mp_type_type },
+ { MP_QSTR_List, &mp_type_list },
+ { MP_QSTR_Dict, &mp_type_dict },
+ { MP_QSTR_Tuple, &mp_type_tuple },
+ { MP_QSTR_Literal, &mp_type_any_call_t },
+ #if MICROPY_PY_BUILTINS_SET
+ { MP_QSTR_Set, &mp_type_set },
+ #endif
+ #if MICROPY_PY_BUILTINS_FROZENSET
+ { MP_QSTR_FrozenSet, &mp_type_frozenset },
+ #endif
+};
+
+void any_call_module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ // Only loading is supported.
+ if (dest[0] == MP_OBJ_NULL) {
+ // First see if this attribute corresponds to a container alias that
+ // needs a proper __getitem__ implementation.
+ mp_obj_t alias = typing_alias_from_spec(typing_container_specs, MP_ARRAY_SIZE(typing_container_specs), attr);
+ if (alias != MP_OBJ_NULL) {
+ dest[0] = alias;
+ } else {
+ // Otherwise fall back to returning the singleton AnyCall object,
+ // preserving the "typing ignores everything" behaviour used for
+ // the majority of names.
+ dest[0] = MP_OBJ_FROM_PTR(&mp_type_any_call_t);
+ }
+ }
+}
+
+static MP_DEFINE_CONST_OBJ_TYPE(
+ mp_type_any_call_t,
+ MP_QSTR_any_call,
+ MP_TYPE_FLAG_NONE,
+ make_new, any_call_new,
+ attr, any_call_attr,
+ subscr, any_call_subscr,
+ call, any_call_call
+ );
+
+// Helper to look up a qstr in the alias specification table and lazily create
+// the corresponding typing alias object when a match is found.
+static mp_obj_t typing_alias_from_spec(const typing_alias_spec_t *spec_table, size_t spec_len, qstr attr) {
+ for (size_t i = 0; i < spec_len; ++i) {
+ if (spec_table[i].name == attr) {
+ mp_obj_t origin = MP_OBJ_FROM_PTR(spec_table[i].type);
+ return typing_alias_new(origin, MP_OBJ_NULL);
+ }
+ }
+ return MP_OBJ_NULL;
+}
+
+static const mp_rom_map_elem_t mp_module_typing_globals_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_typing) },
+ { MP_ROM_QSTR(MP_QSTR_TYPE_CHECKING), MP_ROM_FALSE },
+};
+
+static MP_DEFINE_CONST_DICT(mp_module_typing_globals, mp_module_typing_globals_table);
+
+const mp_obj_module_t mp_module_typing = {
+ .base = { &mp_type_module },
+ .globals = (mp_obj_dict_t *)&mp_module_typing_globals,
+};
+
+// Extensible such that a typing module implemented in Python still has priority.
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_typing, mp_module_typing);
+MP_REGISTER_MODULE_DELEGATION(mp_module_typing, any_call_module_attr);
+
+
+#if MICROPY_PY_TYPING_EXTRA_MODULES
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_abc, mp_module_typing);
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR___future__, mp_module_typing);
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_typing_extensions, mp_module_typing);
+#endif
+
+#endif // MICROPY_PY_TYPING
diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h
index cea0397414325..02cedbb266041 100644
--- a/ports/unix/variants/mpconfigvariant_common.h
+++ b/ports/unix/variants/mpconfigvariant_common.h
@@ -121,3 +121,9 @@
#define MICROPY_PY_MACHINE (1)
#define MICROPY_PY_MACHINE_PULSE (1)
#define MICROPY_PY_MACHINE_PIN_BASE (1)
+
+// Enable "typing" and related modules.
+#ifndef MICROPY_PY_TYPING
+#define MICROPY_PY_TYPING (1)
+#define MICROPY_PY_TYPING_EXTRA_MODULES (1)
+#endif
diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h
index fabc9072d6c70..cfe08024f6fc7 100644
--- a/ports/windows/mpconfigport.h
+++ b/ports/windows/mpconfigport.h
@@ -143,6 +143,8 @@
#define MICROPY_PY_TIME_TIME_TIME_NS (1)
#define MICROPY_PY_TIME_CUSTOM_SLEEP (1)
#define MICROPY_PY_TIME_INCLUDEFILE "ports/unix/modtime.c"
+#define MICROPY_PY_TYPING (1)
+#define MICROPY_PY_TYPING_EXTRA_MODULES (1)
#define MICROPY_PY_ERRNO (1)
#define MICROPY_PY_UCTYPES (1)
#define MICROPY_PY_DEFLATE (1)
diff --git a/ports/windows/msvc/sources.props b/ports/windows/msvc/sources.props
index f7c4c6bcac01b..859f8e8670911 100644
--- a/ports/windows/msvc/sources.props
+++ b/ports/windows/msvc/sources.props
@@ -20,6 +20,7 @@
+
diff --git a/py/modcollections.c b/py/modcollections.c
index 46326d13eef5a..fa8e96121bb3e 100644
--- a/py/modcollections.c
+++ b/py/modcollections.c
@@ -28,6 +28,11 @@
#if MICROPY_PY_COLLECTIONS
+#if MICROPY_PY_TYPING && MICROPY_PY_TYPING_EXTRA_MODULES && MICROPY_MODULE_BUILTIN_SUBPACKAGES
+// Enable importing collections.abc as an alias of the typing module.
+extern const mp_obj_module_t mp_module_typing;
+#endif
+
static const mp_rom_map_elem_t mp_module_collections_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_collections) },
#if MICROPY_PY_COLLECTIONS_DEQUE
@@ -37,6 +42,9 @@ static const mp_rom_map_elem_t mp_module_collections_globals_table[] = {
#if MICROPY_PY_COLLECTIONS_ORDEREDDICT
{ MP_ROM_QSTR(MP_QSTR_OrderedDict), MP_ROM_PTR(&mp_type_ordereddict) },
#endif
+ #if MICROPY_PY_TYPING && MICROPY_PY_TYPING_EXTRA_MODULES && MICROPY_MODULE_BUILTIN_SUBPACKAGES
+ { MP_ROM_QSTR(MP_QSTR_abc), MP_ROM_PTR(&mp_module_typing) },
+ #endif
};
static MP_DEFINE_CONST_DICT(mp_module_collections_globals, mp_module_collections_globals_table);
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 34eafa9e5debb..343c0105101d7 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -1614,6 +1614,17 @@ typedef double mp_float_t;
#define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32)
#endif
+// Whether to provide the minimal typing module.
+#ifndef MICROPY_PY_TYPING
+#define MICROPY_PY_TYPING (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
+// Whether to provide the minimal abc and typing_extensions modules.
+// They will simply be aliases for the typing module.
+#ifndef MICROPY_PY_TYPING_EXTRA_MODULES
+#define MICROPY_PY_TYPING_EXTRA_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
// Extended modules
#ifndef MICROPY_PY_ASYNCIO
diff --git a/tests/cpydiff/core_class_metaclass.py b/tests/cpydiff/core_class_metaclass.py
new file mode 100644
index 0000000000000..b06849156819d
--- /dev/null
+++ b/tests/cpydiff/core_class_metaclass.py
@@ -0,0 +1,12 @@
+"""
+categories: Core,Classes
+description: Defining a class with a metaclass is not possible.
+cause: Currently not implemented to limit size and complexity of the runtime.
+workaround: Use composition or class decorators instead of metaclasses. See https://realpython.com/python-metaclasses/#is-this-really-necessary
+"""
+
+from abc import ABCMeta
+
+
+class MyABC(metaclass=ABCMeta):
+ pass
diff --git a/tests/cpydiff/modules_typing_generics.py b/tests/cpydiff/modules_typing_generics.py
new file mode 100644
index 0000000000000..3f0685419b1ee
--- /dev/null
+++ b/tests/cpydiff/modules_typing_generics.py
@@ -0,0 +1,32 @@
+"""
+categories: Modules,typing
+description: User Defined Generics
+cause: Micropython does not implement User Defined Generics
+workaround: None
+"""
+
+from typing import Dict, Generic, TypeVar
+
+T = TypeVar("T")
+
+
+class Registry(Generic[T]):
+ def __init__(self) -> None:
+ self._store: Dict[str, T] = {}
+
+ def set_item(self, k: str, v: T) -> None:
+ self._store[k] = v
+
+ def get_item(self, k: str) -> T:
+ return self._store[k]
+
+
+family_name_reg = Registry[str]()
+family_age_reg = Registry[int]()
+
+family_name_reg.set_item("husband", "steve")
+family_name_reg.set_item("dad", "john")
+
+family_age_reg.set_item("steve", 30)
+
+print(repr(family_name_reg.__dict__))
diff --git a/tests/cpydiff/modules_typing_getargs.py b/tests/cpydiff/modules_typing_getargs.py
new file mode 100644
index 0000000000000..8998cca67c116
--- /dev/null
+++ b/tests/cpydiff/modules_typing_getargs.py
@@ -0,0 +1,12 @@
+"""
+categories: Modules,typing
+description: ``get_args()`` function not fully implemented.
+cause: Micropython does not implement all typing features
+workaround: None
+"""
+
+from typing import get_args
+
+# partial implementation of get_args
+x = get_args(int)
+assert x == (), f"expected () but got {x}"
diff --git a/tests/cpydiff/modules_typing_getorigin.py b/tests/cpydiff/modules_typing_getorigin.py
new file mode 100644
index 0000000000000..82c5914ecc42c
--- /dev/null
+++ b/tests/cpydiff/modules_typing_getorigin.py
@@ -0,0 +1,13 @@
+"""
+categories: Modules,typing
+description: ``get_origin()`` function not fully implemented.
+cause: Micropython does not implement all typing features from Python 3.8+
+workaround: None
+"""
+# https://docs.python.org/3/library/typing.html#typing.get_origin
+
+from typing import Dict, get_origin
+
+assert get_origin(Dict[str, int]) is dict, "origin Dict cannot be detected"
+
+assert get_origin(str) is None, "origin str should be None"
diff --git a/tests/cpydiff/modules_typing_reveal_type.py b/tests/cpydiff/modules_typing_reveal_type.py
new file mode 100644
index 0000000000000..f64957dc897ff
--- /dev/null
+++ b/tests/cpydiff/modules_typing_reveal_type.py
@@ -0,0 +1,24 @@
+"""
+categories: Modules,typing
+description: ``reveal_type()`` is not implemented.
+cause: Micropython does not implement all typing features
+workaround: None
+"""
+
+from typing import Self, reveal_type
+
+
+class Foo:
+ def return_self(self) -> Self:
+ ...
+
+
+class SubclassOfFoo(Foo):
+ pass
+
+
+foo = Foo()
+sub = SubclassOfFoo()
+
+reveal_type(foo)
+reveal_type(sub)
diff --git a/tests/cpydiff/modules_typing_runtime_checkable.py b/tests/cpydiff/modules_typing_runtime_checkable.py
new file mode 100644
index 0000000000000..4acf3e12bde75
--- /dev/null
+++ b/tests/cpydiff/modules_typing_runtime_checkable.py
@@ -0,0 +1,17 @@
+"""
+categories: Modules,typing
+description: ``runtime_checkable()`` is not implemented.
+cause: Micropython does not implement all typing features from Python 3.8+
+workaround: None
+"""
+
+from typing import runtime_checkable, Protocol
+
+
+@runtime_checkable
+class SwallowLaden(Protocol):
+ def __iter__(self):
+ ...
+
+
+assert isinstance([1, 2, 3], SwallowLaden)
diff --git a/tests/cpydiff/modules_typing_typeddict.py b/tests/cpydiff/modules_typing_typeddict.py
new file mode 100644
index 0000000000000..6311bc887668b
--- /dev/null
+++ b/tests/cpydiff/modules_typing_typeddict.py
@@ -0,0 +1,24 @@
+"""
+categories: Modules,typing
+description: ``TypedDict`` class not allowed for instance or class checks.
+cause: Micropython does not implement all typing features
+workaround: None
+"""
+
+from typing import TypeVar, TypedDict
+
+
+class Movie(TypedDict):
+ name: str
+ year: int
+
+
+movie: Movie = {"name": "Blade Runner", "year": 1982}
+
+try:
+ if isinstance(movie, Movie): # type: ignore
+ pass
+ print("TypedDict class not allowed for instance or class checks")
+
+except TypeError as e:
+ print("Handled according to spec")
diff --git a/tests/extmod/collections_abc.py b/tests/extmod/collections_abc.py
new file mode 100644
index 0000000000000..dd65d90381b39
--- /dev/null
+++ b/tests/extmod/collections_abc.py
@@ -0,0 +1,88 @@
+print("Testing runtime aspects of collections.abc module")
+
+try:
+ import typing
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+
+print("Testing : collections.abc.Mapping, Sequence")
+
+# FIXME: from collections.abc import Mapping, Sequence
+from typing import Mapping, Sequence
+
+
+class Employee:
+ ...
+
+
+def notify_by_email(employees: Sequence[Employee], overrides: Mapping[str, str]) -> None:
+ pass
+
+
+notify_by_email([], {})
+
+
+print("Testing : collections.abc.Callable, Awaitable")
+
+# from collections.abc import Callable, Awaitable
+from typing import Callable, Awaitable
+
+
+def feeder(get_next_item: Callable[[], str]) -> None:
+ ... # Body
+
+
+def async_query(
+ on_success: Callable[[int], None], on_error: Callable[[int, Exception], None]
+) -> None:
+ ... # Body
+
+
+async def on_update(value: str) -> None:
+ ... # Body
+
+
+callback: Callable[[str], Awaitable[None]] = on_update
+
+# ...
+
+
+def concat(x: str, y: str) -> str:
+ return x + y
+
+
+x: Callable[..., str]
+x = str # OK
+x = concat # Also OK
+
+
+print("Testing : collections.abc.Iterable")
+
+# FIXME: from collections.abc import Iterable
+from typing import Iterable
+from typing import Protocol
+
+
+class Combiner(Protocol):
+ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]:
+ ...
+
+
+def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
+ for item in data:
+ pass
+ return b"".join(cb_results(*data))
+
+
+def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]:
+ return [val[:maxlen] for val in vals if maxlen is not None]
+
+
+batch_proc([], good_cb) # OK
+
+
+print("Testing : collections.abc.")
+print("Testing : collections.abc.")
+print("Testing : collections.abc.")
diff --git a/tests/extmod/typing_mod_collections_abc.py b/tests/extmod/typing_mod_collections_abc.py
new file mode 100644
index 0000000000000..a34537998f0f6
--- /dev/null
+++ b/tests/extmod/typing_mod_collections_abc.py
@@ -0,0 +1,48 @@
+from math import e
+
+
+try:
+ import collections.abc
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.3+")
+print("### module collections.abc")
+# https://peps.python.org/pep-3119/
+# https://docs.python.org/3/library/collections.abc.html#module-collections.abc
+
+# No test for runtime behaviour
+
+print("collections.abc")
+
+from collections.abc import Container
+from collections.abc import Hashable
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Reversible
+from collections.abc import Generator
+from collections.abc import Sized
+from collections.abc import Callable
+from collections.abc import Collection
+from collections.abc import Sequence
+from collections.abc import MutableSequence
+
+# from collections.abc import ByteString # Deprecated since version 3.12,
+from collections.abc import Set
+from collections.abc import MutableSet
+from collections.abc import Mapping
+from collections.abc import MutableMapping
+from collections.abc import MappingView
+from collections.abc import KeysView
+from collections.abc import ItemsView
+from collections.abc import ValuesView
+from collections.abc import Awaitable
+from collections.abc import Coroutine
+from collections.abc import AsyncIterable
+from collections.abc import AsyncIterator
+from collections.abc import AsyncGenerator
+from collections.abc import Buffer
+
+
+print("-----")
diff --git a/tests/extmod/typing_mod_typing_extensions.py b/tests/extmod/typing_mod_typing_extensions.py
new file mode 100644
index 0000000000000..0a7ced3f0c8f6
--- /dev/null
+++ b/tests/extmod/typing_mod_typing_extensions.py
@@ -0,0 +1,27 @@
+try:
+ # typing_extensions MUST exist when typing is available
+ import typing_extensions
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.5")
+print("### module typing_extensions")
+# https://typing-extensions.readthedocs.io/
+
+# No test for runtime behaviour
+
+print("Special typing primitives")
+
+import typing_extensions
+
+try:
+ from typing_extensions import *
+
+except ImportError:
+ print("- [ ] FIXME: add typing_extensions module")
+
+# Do not test for all the individual names, just that the module exists
+# as the different CPython versions have different subsets of names
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0484.py b/tests/extmod/typing_pep_0484.py
new file mode 100644
index 0000000000000..18dd8b17688bc
--- /dev/null
+++ b/tests/extmod/typing_pep_0484.py
@@ -0,0 +1,190 @@
+try:
+ from typing import TYPE_CHECKING
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.5")
+print("### PEP 484")
+
+# https://peps.python.org/topic/typing/
+# https://peps.python.org/pep-0484/
+
+# Currently excludes tests using `Generic[T]` due to MicroPython runtime limitations
+
+
+print("Running PEP 484 example-based test")
+
+
+print("Type Definition Syntax")
+
+
+def greeting(name: str) -> str:
+ return "Hello " + name
+
+
+print("greeting:", greeting("world"))
+
+from typing import List
+
+l: List[int]
+
+
+print("Type aliases")
+
+Url = str
+
+
+def retry(url: Url, retry_count: int) -> None:
+ print("retry", url, retry_count)
+
+
+retry("http://example", 3)
+
+
+print("Callable example")
+from typing import Callable, TypeVar, Union
+
+
+def feeder(get_next_item: Callable[[], str]) -> None:
+ try:
+ v = get_next_item()
+ print("feeder got", v)
+ except Exception as e:
+ print("feeder runtime exception:", e)
+
+
+def get_const():
+ return "x"
+
+
+feeder(get_const)
+
+
+print("TypeVar constrained example")
+AnyStr = TypeVar("AnyStr", str, bytes)
+
+
+def concat(x: AnyStr, y: AnyStr) -> AnyStr:
+ return x + y
+
+
+print("concat:", concat("a", "b"))
+
+# Generic user-defined class
+from typing import Generic, TypeVar
+
+T = TypeVar("T")
+# FIXME: Crash - inheriting from typing.Generic[T] unsupported at runtime
+# try:
+#
+# class LoggedVar(Generic[T]):
+# pass
+#
+# def __init__(self, value: T, name: str) -> None:
+# self.name = name
+# self.value = value
+#
+# def set(self, new: T) -> None:
+# self.value = new
+#
+# def get(self) -> T:
+# return self.value
+#
+# except Exception as e:
+# print("- [ ] FIXME: Difference - Generic[T] base class unsupported:", e)
+
+
+# Union/Optional examples
+def handle_employee(e: Union[str, None]) -> None:
+ print("handle_employee called with", e)
+
+
+handle_employee("John")
+handle_employee(None)
+
+
+# Any example
+def use_map(m: dict) -> None:
+ print("use_map keys:", list(m.keys()))
+
+
+use_map({"a": 1})
+
+# NewType example: at runtime NewType returns identity function
+try:
+ from typing import NewType
+
+ UserId = NewType("UserId", int)
+ v = UserId(5)
+ print("NewType UserId runtime:", v, type(v))
+except Exception as e:
+ print("- [ ] FIXME: Difference or Crash - NewType runtime issue:", e)
+
+print("TYPE_CHECKING guard")
+
+from typing import TYPE_CHECKING
+
+# TYPE_CHECKING guard
+if TYPE_CHECKING:
+ # This block is for type checkers only
+ pass
+ print("typing.TYPE_CHECKING is True at runtime. ERROR")
+else:
+ print("typing.TYPE_CHECKING is False at runtime as expected")
+
+
+print("Forward reference example")
+
+
+class Tree:
+ def __init__(self, left: "Tree" = None, right: "Tree" = None): # type: ignore
+ self.left = left
+ self.right = right
+
+
+tr = Tree()
+print("Tree forward refs OK")
+
+# NoReturn example
+from typing import NoReturn
+
+
+def stop() -> NoReturn:
+ raise RuntimeError("stop")
+
+
+try:
+ stop()
+except RuntimeError:
+ print("stop() raised RuntimeError as expected (NoReturn at runtime)")
+
+# Overload example (runtime @overload should not be called directly)
+from typing import overload
+
+
+@overload
+def func(x: int) -> int:
+ ...
+
+
+@overload
+def func(x: str) -> str:
+ ...
+
+
+def func(x):
+ return x
+
+
+print("overload func for int:", func(1))
+
+# Cast example: at runtime cast returns the value
+from typing import cast
+
+if cast(str, 123) == 123:
+ print("cast runtime works as identity function")
+else:
+ print("- [ ] FIXME: Difference - cast runtime does not work as identity function")
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0526.py b/tests/extmod/typing_pep_0526.py
new file mode 100644
index 0000000000000..b8c9a95a7c4e8
--- /dev/null
+++ b/tests/extmod/typing_pep_0526.py
@@ -0,0 +1,167 @@
+try:
+ from typing import TYPE_CHECKING
+except Exception:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.6")
+print("### PEP 526 - Syntax for variable annotations")
+
+# https://peps.python.org/pep-0526/
+# Currently excludes tests using `Generic[T]` due to MicroPython runtime limitations
+
+
+print("Specification")
+
+my_var: int
+my_var = 5 # Passes type check.
+other_var: int = "a" # Flagged as error by type checker, # type: ignore
+# but OK at runtime.
+
+
+print("Global and local variable annotations")
+from typing import List, Tuple, Optional
+
+some_number: int # variable without initial value
+some_list: List[int] = [] # variable with initial value
+
+
+sane_world: bool
+if 2 + 2 == 4:
+ sane_world = True
+else:
+ sane_world = False
+
+# Tuple packing with variable annotation syntax
+t: Tuple[int, ...] = (1, 2, 3)
+# or
+t: Tuple[int, ...] = 1, 2, 3 # This only works in Python 3.8+
+
+# Tuple unpacking with variable annotation syntax
+header: str
+kind: int
+body: Optional[List[str]]
+
+a: int # type: ignore
+try:
+ print(a) # raises NameError # type: ignore
+
+except NameError:
+ print("Expected NameError")
+
+
+def f_1():
+ a: int
+ try:
+ print(a) # raises UnboundLocalError # type: ignore
+ except UnboundLocalError:
+ print("Expected UnboundLocalError")
+
+
+a: int # type: ignore
+a: str # Static type checker may or may not warn about this.
+
+
+print("Class and instance variable annotations")
+from typing import ClassVar, Dict
+
+
+class BasicStarship:
+ captain: str = "Picard" # instance variable with default
+ damage: int # instance variable without default
+ stats: ClassVar[Dict[str, int]] = {} # class variable
+
+
+class Starship_1:
+ captain = "Picard"
+ stats = {}
+
+ def __init__(self, damage, captain=None):
+ self.damage = damage
+ if captain:
+ self.captain = captain # Else keep the default
+
+ def hit(self):
+ Starship.stats["hits"] = Starship.stats.get("hits", 0) + 1
+
+
+class Starship:
+ captain: str = "Picard"
+ damage: int
+ stats: ClassVar[Dict[str, int]] = {}
+
+ def __init__(self, damage: int, captain: str = None): # type: ignore
+ self.damage = damage
+ if captain:
+ self.captain = captain # Else keep the default
+
+ def hit(self):
+ Starship.stats["hits"] = Starship.stats.get("hits", 0) + 1
+
+
+enterprise_d = Starship(3000)
+enterprise_d.stats = {} # Flagged as error by a type checker # type: ignore
+Starship.stats = {} # This is OK
+
+
+# FIXME: - cpy_diff - User Defined Generic Classes unsupported
+# from typing import Generic, TypeVar
+# T = TypeVar("T")
+# class Box(Generic[T]):
+# def __init__(self, content):
+# self.content: T = content
+
+
+print("Annotating expressions")
+
+
+class Cls:
+ pass
+
+
+c = Cls()
+c.x: int = 0 # Annotates c.x with int. # type: ignore
+c.y: int # Annotates c.y with int.# type: ignore
+
+d = {}
+d["a"]: int = 0 # Annotates d['a'] with int.# type: ignore
+d["b"]: int # Annotates d['b'] with int.# type: ignore
+
+(x): int # Annotates x with int, (x) treated as expression by compiler.# type: ignore
+(y): int = 0 # Same situation here.
+
+
+# print("Where annotations aren’t allowed")
+# The Examples crash both CPython and MicroPython at runtime.
+
+print("Runtime Effects of Type Annotations")
+
+
+def f():
+ x: NonexistentName # No RUNTIME error. # type: ignore
+
+
+# FIXME: cpy_diff - MicroPython does not raise NameError at runtime
+# try:
+# x: NonexistentName # Error!
+# print("- [ ] FIXME: Expected NameError")
+# except NameError:
+# print("Expected NameError:")
+
+# try:
+
+# class X:
+# var: NonexistentName # Error!
+# except NameError:
+# print("Expected NameError:")
+
+
+# FIXME: cpy_diff - MicroPython does not provide the ``__annotations__`` dict at runtime
+# print(__annotations__)
+# __annotations__["s"] = str
+
+
+alice: "well done" = "A+" # type: ignore
+bob: "what a shame" = "F-" # type: ignore
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0544.py b/tests/extmod/typing_pep_0544.py
new file mode 100644
index 0000000000000..e2d666d1370be
--- /dev/null
+++ b/tests/extmod/typing_pep_0544.py
@@ -0,0 +1,419 @@
+try:
+ from typing import TYPE_CHECKING
+except Exception:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.8")
+print("### PEP 544 - Protocols: Structural subtyping (static duck typing)")
+
+# https://peps.python.org/topic/typing/
+# Currently excludes tests using `Generic[T]` due to MicroPython runtime limitations
+
+print("Defining a protocol")
+# https://peps.python.org/pep-0544/#defining-a-protocol
+
+from typing import Protocol, Iterable
+
+
+class SupportsClose(Protocol):
+ def close(self) -> None:
+ ...
+
+
+class Resource:
+ ...
+
+ def close(self) -> None:
+ self.file.close() # type: ignore
+ self.lock.release() # type: ignore
+
+
+def close_all(things: Iterable[SupportsClose]) -> None:
+ for t in things:
+ t.close()
+
+
+# FIXME: Difference or Crash - Resource requires file and lock attributes
+# r = Resource()
+# close_all([f, r]) # OK!
+# try:
+# close_all([1]) # Error: 'int' has no 'close' method
+# except Exception:
+# print("Expected: 'int' has no 'close' method")
+
+print("Protocol members")
+# https://peps.python.org/pep-0544/#protocol-members
+
+
+from typing import Protocol
+from abc import abstractmethod
+
+
+class Example(Protocol):
+ def first(self) -> int: # This is a protocol member
+ return 42
+
+ @abstractmethod
+ def second(self) -> int: # Method without a default implementation
+ raise NotImplementedError
+
+
+# ---------
+
+from typing import Protocol, List
+
+
+class Template(Protocol):
+ name: str # This is a protocol member
+ value: int = 0 # This one too (with default)
+
+ def method(self) -> None:
+ self.temp: List[int] = [] # Error in type checker # type: ignore
+
+
+class Concrete_1:
+ def __init__(self, name: str, value: int) -> None:
+ self.name = name
+ self.value = value
+
+ def method(self) -> None:
+ return
+
+
+var: Template = Concrete_1("value", 42) # OK
+
+
+print("Explicitly declaring implementation")
+
+
+class PColor(Protocol):
+ @abstractmethod
+ def draw(self) -> str:
+ ...
+
+ def complex_method(self) -> int:
+ # some complex code here
+ ...
+
+
+class NiceColor(PColor):
+ def draw(self) -> str:
+ return "deep blue"
+
+
+class BadColor(PColor):
+ def draw(self) -> str:
+ return super().draw() # Error, no default implementation # type: ignore
+
+
+class ImplicitColor: # Note no 'PColor' base here
+ def draw(self) -> str:
+ return "probably gray"
+
+ def complex_method(self) -> int:
+ # class needs to implement this
+ ...
+
+
+nice: NiceColor
+another: ImplicitColor
+
+
+def represent(c: PColor) -> None:
+ print(c.draw(), c.complex_method())
+
+
+# -----------------------------------
+
+from typing import Protocol, Tuple
+
+
+class RGB(Protocol):
+ rgb: Tuple[int, int, int]
+
+ @abstractmethod
+ def intensity(self) -> int:
+ return 0
+
+
+class Point(RGB):
+ def __init__(self, red: int, green: int, blue: str) -> None:
+ self.rgb = red, green, blue # Error, 'blue' must be 'int' # type: ignore
+
+
+print("Merging and extending protocols")
+# https://peps.python.org/pep-0544/#merging-and-extending-protocols
+
+from typing import Sized, Protocol
+
+# FIXME: TypeError: multiple bases have instance lay-out conflict - CRASH
+# Is this a MicroPython multiple inheritance limitation?
+try:
+
+ class SizedAndClosable_1(Sized, Protocol):
+ def close(self) -> None:
+ ...
+except Exception as e:
+ print("- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict:", e)
+
+
+class SupportsClose_2(Protocol):
+ def close(self) -> None:
+ ...
+
+
+# FIXME: TypeError: multiple bases have instance lay-out conflict - CRASH
+try:
+
+ class SizedAndClosable_2(Sized, SupportsClose_2, Protocol):
+ pass
+except Exception as e:
+ print("- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict:", e)
+
+print("Generic protocols")
+# https://peps.python.org/pep-0544/#generic-protocols
+
+# FIXME: Micropython does not support User Defined Generic Classes
+# TypeError: 'type' object isn't subscriptable
+# from typing import TypeVar, Protocol, Iterator
+# T = TypeVar("T")
+# class Iterable(Protocol[T]):
+# @abstractmethod
+# def __iter__(self) -> Iterator[T]: ...
+
+
+print("Recursive protocols")
+
+from typing import Generic, TypeVar
+
+T = TypeVar("T")
+
+
+class Traversable(Protocol):
+ def leaves(self) -> Iterable["Traversable"]:
+ ...
+
+
+class SimpleTree:
+ def leaves(self) -> List["SimpleTree"]:
+ ...
+
+
+root: Traversable = SimpleTree() # OK
+
+
+# FIXME: CPY_DIFF : Micropython does not support User Defined Generic Classes
+# TypeError: 'type' object isn't subscriptable
+# class Tree(Generic[T]):
+# def leaves(self) -> List["Tree[T]"]: ...
+#
+# def walk(graph: Traversable) -> None:
+# ...
+# tree: Tree[float] = Tree()
+# tree: Tree = Tree()
+# walk(tree) # OK, 'Tree[float]' is a subtype of 'Traversable'
+
+
+print("Self-types in protocols")
+
+C = TypeVar("C", bound="Copyable") # type: ignore
+
+
+class Copyable(Protocol):
+ def copy(self: C) -> C:
+ ...
+
+
+class One:
+ def copy(self) -> "One":
+ ...
+
+
+T = TypeVar("T", bound="Other")
+
+
+class Other:
+ def copy(self: T) -> T:
+ ...
+
+
+c: Copyable
+c = One() # OK # type: ignore
+c = Other() # Also OK # type: ignore
+
+
+print("Callback protocols")
+
+from typing import Optional, List, Protocol
+
+
+class Combiner(Protocol):
+ def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> List[bytes]:
+ ...
+
+
+def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> List[bytes]:
+ ...
+
+
+def bad_cb(*vals: bytes, maxitems: Optional[int]) -> List[bytes]:
+ ...
+
+
+comb: Combiner = good_cb # OK
+comb = bad_cb # Static Typecheck Error! # type: ignore
+# Argument 2 has incompatible type because of different name and kind in the callback
+
+print("Unions and intersections of protocols")
+
+from typing import Union, Optional, Protocol
+
+
+class Exitable(Protocol):
+ def exit(self) -> int:
+ ...
+
+
+class Quittable(Protocol):
+ def quit(self) -> Optional[int]:
+ ...
+
+
+def finish(task: Union[Exitable, Quittable]) -> int:
+ ...
+
+
+class DefaultJob:
+ ...
+
+ def quit(self) -> int:
+ return 0
+
+
+finish(DefaultJob()) # OK
+
+# ---------------
+
+from typing import Iterable, Hashable
+
+
+# # class HashableFloats(Iterable[float], Hashable, Protocol):
+# FIXME: TypeError: multiple bases have instance lay-out conflict
+# class HashableFloats(Iterable, Hashable, Protocol):
+# pass
+# def cached_func(args: HashableFloats) -> float: ...
+# cached_func((1, 2, 3)) # OK, tuple is both hashable and iterable
+
+
+print("Type[] and class objects vs protocols")
+from typing import Type
+
+
+class Proto(Protocol):
+ @abstractmethod
+ def meth(self) -> int:
+ ...
+
+
+class Concrete:
+ def meth(self) -> int:
+ return 42
+
+
+def fun(cls: Type[Proto]) -> int:
+ return cls().meth() # OK
+
+
+fun(Concrete) # OK
+
+# FIXME: Should Throw: Can't instantiate protocol with abstract methods -
+# try:
+# fun(Proto) # Error # type: ignore
+# print("- [ ] FIXME: Should Throw: Can't instantiate protocol with abstract methods")
+# except Exception:
+# print("Expected: Can't instantiate protocol with abstract methods")
+
+# ---------------
+
+from typing import Any, Protocol
+
+
+class ProtoA(Protocol):
+ def meth(self, x: int) -> int:
+ ...
+
+
+class ProtoB(Protocol):
+ def meth(self, obj: Any, x: int) -> int:
+ ...
+
+
+class C:
+ def meth(self, x: int) -> int:
+ ...
+
+
+a: ProtoA = C # Type check error, signatures don't match! # type: ignore
+b: ProtoB = C # OK # type: ignore
+
+
+print("NewType() and type aliases")
+
+from typing import NewType, Protocol, Iterator
+
+
+class Id(Protocol):
+ code: int
+ secrets: Iterator[bytes]
+
+
+UserId = NewType("UserId", Id) # Error, can't provide distinct type # type: ignore
+
+# -------------------------
+
+from typing import TypeVar, Reversible, Iterable, Sized
+
+# FIXME: cpy_diff : User Defined Generic Classes unsupported
+# TypeError: 'type' object isn't subscriptable
+
+# T = TypeVar("T")
+# class SizedIterable_3(Iterable[T], Sized, Protocol):
+# pass
+# CompatReversible = Union[Reversible[T], SizedIterable_3[T]]
+
+print("@runtime_checkable decorator and narrowing types by isinstance()")
+
+
+from typing import runtime_checkable, Protocol
+
+# FIXME: cpy_diff : NotImplementedError: @runtime_checkable decorator unsupported
+# @runtime_checkable
+# class SupportsClose(Protocol):
+# def close(self): ...
+
+
+# assert isinstance(open(__file__), SupportsClose)
+
+
+class Foo(Protocol):
+ @property
+ def c(self) -> int:
+ return 42 # Default value can be provided for property...
+
+
+print("typing.Protocols")
+# https://docs.python.org/3/library/typing.html#protocols
+
+from typing import (
+ SupportsInt,
+ SupportsBytes,
+ SupportsFloat,
+ SupportsComplex,
+ SupportsRound,
+ SupportsAbs,
+ SupportsIndex,
+)
+# TODO: what are sensible tests for these protocols?
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0560.py b/tests/extmod/typing_pep_0560.py
new file mode 100644
index 0000000000000..0eb1e07482206
--- /dev/null
+++ b/tests/extmod/typing_pep_0560.py
@@ -0,0 +1,64 @@
+try:
+ from typing import TYPE_CHECKING
+except Exception:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.7")
+print("### PEP 560 - Type Hinting Generics In Standard Collections")
+
+print("Specification")
+
+
+print("__class_getitem__")
+
+
+class MyList:
+ def __getitem__(self, index):
+ return index + 1
+
+ def __class_getitem__(cls, item):
+ return f"{cls.__name__}[{item.__name__}]"
+
+
+class MyOtherList(MyList):
+ pass
+
+
+assert MyList()[0] == 1
+
+# FIXME: Difference or Crash - __class_getitem__ not supported
+# tests/extmod/typing_pep_0560.py", line 29, in
+# TypeError: 'type' object isn't subscriptable
+# assert MyList[int] == "MyList[int]"
+# assert MyOtherList()[0] == 1
+# assert MyOtherList[int] == "MyOtherList[int]"
+
+
+# -------------------------
+
+
+class GenericAlias:
+ def __init__(self, origin, item):
+ self.origin = origin
+ self.item = item
+
+ def __mro_entries__(self, bases):
+ return (self.origin,)
+
+
+class NewList:
+ def __class_getitem__(cls, item):
+ return GenericAlias(cls, item)
+
+
+# FIXME: Difference or Crash - __class_getitem__ not supported
+# TypeError: 'type' object isn't subscriptable
+# class Tokens(NewList[int]): ...
+
+
+# Not sure these make sense to test
+# assert Tokens.__bases__ == (NewList,)
+# assert Tokens.__orig_bases__ == (NewList[int],)
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0586.py b/tests/extmod/typing_pep_0586.py
new file mode 100644
index 0000000000000..9874ede1f3212
--- /dev/null
+++ b/tests/extmod/typing_pep_0586.py
@@ -0,0 +1,210 @@
+# FIXME: This may break the test if __future__ is not available
+from __future__ import annotations
+
+try:
+ from typing import TYPE_CHECKING
+except Exception:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.8")
+print("### PEP 586")
+
+# https://peps.python.org/pep-0586
+
+print("Legal parameters for Literal at type check time")
+from typing import Literal, Optional
+
+
+class Color:
+ RED = 1
+ GREEN = 2
+ BLUE = 3
+
+
+Literal[26]
+Literal[0x1A] # Exactly equivalent to Literal[26]
+Literal[-4]
+Literal["hello world"]
+Literal[b"hello world"]
+Literal["hello world"]
+Literal[True]
+Literal[Color.RED] # Assuming Color is some enum
+Literal[None]
+
+# ----------
+
+# FIXME: TypeError: 'type' object isn't subscriptable
+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]
+
+# ----------
+# FIXME: TypeError: 'type' object isn't subscriptable
+Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]
+# Optional[Literal[1, 2, 3, "foo", 5]]
+
+print("Parameters at runtime")
+
+
+def my_function(x: Literal[1, 2]) -> int:
+ return x * 3
+
+
+x: Literal[1, 2, 3] = 3
+y: Literal[my_function] = my_function # type: ignore
+
+
+print("Using non-Literals in Literal contexts")
+
+
+def expects_str(x: str) -> None:
+ ...
+
+
+var: Literal["foo"] = "foo"
+
+# Legal: Literal["foo"] is a subtype of str
+expects_str(var)
+
+# ---------------------
+
+
+def expects_literal(x: Literal["foo"]) -> None:
+ ...
+
+
+def runner(my_str: str) -> None:
+ expects_literal(my_str) # type: ignore
+
+
+runner("foo") # type: ignore
+
+print("Intelligent indexing of structured data")
+from typing import Tuple, List, Literal
+
+a: Literal[0] = 0
+b: Literal[5] = 5
+
+some_tuple: Tuple[int, str, List[bool]] = (3, "abc", [True, False])
+
+# FIXME: NameError: name 'reveal_type' isn't defined
+# reveal_type(some_tuple[a]) # Revealed type is 'int'
+
+try:
+ some_tuple[b] # Error: 5 is not a valid index into the tuple # type: ignore
+except Exception:
+ print("Expected: tuple index out of range")
+
+# -----------------
+
+
+class Test:
+ def __init__(self, param: int) -> None:
+ self.myfield = param
+
+ def mymethod(self, val: int) -> str:
+ ...
+
+
+a: Literal["myfield"] = "myfield"
+b: Literal["mymethod"] = "mymethod"
+c: Literal["blah"] = "blah"
+
+t = Test(24)
+# reveal_type(getattr(t, a)) # Revealed type is 'int'
+# reveal_type(getattr(t, b)) # Revealed type is 'Callable[[int], str]'
+
+try:
+ getattr(t, c)
+except AttributeError:
+ print("Expected: No attribute named 'blah' in Test")
+
+print("Interactions with overloads")
+from typing import overload, IO, Any, Union, Text
+
+# FIXME: TypeError: 'type' object isn't subscriptable
+# _PathType = Union[str, bytes, int]
+_PathType = str
+
+
+@overload
+def open(
+ path: _PathType,
+ mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"],
+) -> IO[Text]:
+ ...
+
+
+@overload
+def open(
+ path: _PathType,
+ mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"],
+) -> IO[bytes]:
+ ...
+
+
+# Fallback overload for when the user isn't using literal types
+@overload
+def open(path: _PathType, mode: str) -> IO[Any]:
+ pass
+
+
+print("Interactions with generics")
+# Fixme: User Defined Generic Classes unsupported
+
+# from typing import Generic, TypeVar
+
+# A = TypeVar("A", bound=int)
+# B = TypeVar("B", bound=int)
+# C = TypeVar("C", bound=int)
+
+
+# requires from __futures__ import annotations
+# A simplified definition for Matrix[row, column]
+# TypeError: 'type' object isn't subscriptable
+# class Matrix(Generic[A, B]):
+# class Matrix(Generic[A, B]):
+# def __add__(self, other: Matrix[A, B]) -> Matrix[A, B]: ...
+# def __matmul__(self, other: Matrix[B, C]) -> Matrix[A, C]: ...
+# def transpose(self) -> Matrix[B, A]: ...
+
+
+# foo: Matrix[Literal[2], Literal[3]] = Matrix()
+# bar: Matrix[Literal[3], Literal[7]] = Matrix()
+
+# baz = foo @ bar
+# reveal_type(baz) # Revealed type is 'Matrix[Literal[2], Literal[7]]'
+
+
+print("Interactions with enums and exhaustiveness checks")
+
+# FIXME: enum module not standard in MicroPython
+try:
+ from enum import Enum
+
+ class Status(Enum):
+ SUCCESS = 0
+ INVALID_DATA = 1
+ FATAL_ERROR = 2
+
+ def parse_status(s: Union[str, Status]) -> None:
+ if s is Status.SUCCESS:
+ print("Success!")
+ elif s is Status.INVALID_DATA:
+ print("The given data is invalid because...")
+ elif s is Status.FATAL_ERROR:
+ print("Unexpected fatal error...")
+ else:
+ # 's' must be of type 'str' since all other options are exhausted
+ print("Got custom status: " + s)
+
+except ImportError:
+ # print("Skipped enum test, enum module not available")
+ pass
+
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0589.py b/tests/extmod/typing_pep_0589.py
new file mode 100644
index 0000000000000..79b58570aef6b
--- /dev/null
+++ b/tests/extmod/typing_pep_0589.py
@@ -0,0 +1,242 @@
+try:
+ import typing
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.8")
+print("### PEP 589")
+
+# https://peps.python.org/topic/typing/
+# https://peps.python.org/pep-0589/
+# https://typing.python.org/en/latest/spec/typeddict.html#typeddict
+
+
+print("Class-based Syntax")
+
+from typing import TypedDict
+from typing import NotRequired, ReadOnly, Annotated
+
+
+class Movie(TypedDict):
+ name: str
+ year: int
+
+
+class EmptyDict(TypedDict):
+ pass
+
+
+# ------------------------------------------------------------------------
+print("Using TypedDict Types")
+
+movie: Movie = {"name": "Blade Runner", "year": 1982}
+
+
+def record_movie(movie: Movie) -> None:
+ ...
+
+
+record_movie({"name": "Blade Runner", "year": 1982})
+
+movie: Movie
+movie = {"name": "Blade Runner", "year": 1982}
+
+# ------------------------------------------------------------------------
+print("Totality and optional keys")
+
+try:
+ # FIXME cpy_diff: runtime typing does not accept 'total' argument
+ class MovieTotal(TypedDict, total=True):
+ name: str
+ year: int
+except TypeError as e:
+ print(
+ "-[ ] FIXME: cpy_diff : `total` parameter not supported by runtime TypedDict implementation:",
+ e,
+ )
+
+try:
+
+ class MoviePartial(TypedDict, total=False):
+ name: str
+ year: int
+except TypeError as e:
+ print(
+ "-[ ] FIXME: cpy_diff : `total` parameter not supported by runtime TypedDict implementation:",
+ e,
+ )
+
+
+mt: MovieTotal = {"name": "Alien", "year": 1979} # type : ignore
+mp: MoviePartial = {"name": "Alien"} # year is optional # type : ignore
+
+assert mt["year"] == 1979
+assert "year" not in mp or isinstance(mp.get("year"), (int, type(None)))
+
+# ------------------------------------------------------------------------
+print("Inheritance and required/optional mix")
+
+
+class Point2D(TypedDict):
+ x: int
+ y: int
+
+
+try:
+
+ class Point3D(Point2D, total=False):
+ z: int
+except TypeError as e:
+ print(
+ "FIXME: cpy_diff : `total` parameter not supported by runtime TypedDict implementation for Point3D:",
+ e,
+ )
+
+
+p2: Point2D = {"x": 1, "y": 2}
+p3: Point3D = {"x": 1, "y": 2}
+assert p2["x"] == 1
+assert "z" not in p3
+
+print("Runtime checks: TypedDict cannot be used with isinstance/class checks")
+try:
+ if isinstance(movie, Movie): # type: ignore
+ pass
+ print("-[ ] FIXME: TypedDict class allowed in isinstance (unexpected)")
+except TypeError:
+ print("TypedDict class not allowed for isinstance/class checks")
+
+print("Alternative functional syntax and constructors")
+
+MovieAlt = TypedDict("MovieAlt", {"name": str, "year": int})
+MovieAlt2 = TypedDict("MovieAlt2", {"name": str, "year": int}, total=False)
+
+m_alt: MovieAlt = {"name": "Blade Runner", "year": 1982}
+
+# FIXME: Difference or Crash - calling the functional TypedDict constructor with kwargs
+try:
+ ma = MovieAlt(name="Blade Runner", year=1982)
+ print(type(ma)) # should be dict at runtime
+except TypeError as e:
+ print(
+ "-[ ] FIXME: cpy_diff Functional TypedDict constructor call failed at runtime (expected):",
+ e,
+ )
+
+
+print("Inheritance examples")
+
+try:
+
+ class BookBasedMovie(Movie):
+ based_on: str
+except TypeError as e:
+ print(
+ "Inheritance from TypedDicts not supported by runtime implementation for BookBasedMovie:",
+ e,
+ )
+
+# KNOWN limitation - no multiple inheritance in MicroPython
+# class X(TypedDict):
+# x: int
+# class Y(TypedDict):
+# y: str
+# try:
+# class XYZ(X, Y):
+# z: bool
+# xyz: XYZ = {"x": 1, "y": "a", "z": True}
+# except TypeError as e:
+# print("Multiple inheritance for TypedDicts not supported at runtime (XYZ):", e)
+
+print("Totality and mixing with Required/NotRequired")
+
+
+class _MovieBase(TypedDict):
+ title: str
+
+
+try:
+
+ class MovieMix(_MovieBase, total=False):
+ year: int
+except TypeError as e:
+ print(
+ "FIXME: cpy_diff - total parameter not supported by runtime TypedDict implementation for MovieMix:",
+ e,
+ )
+ MovieMix = dict # fallback for runtime operations # type: ignore
+
+
+# equivalent to marking year as NotRequired
+class MovieMix2(_MovieBase):
+ year: NotRequired[int]
+
+
+# Do not try to execute known runtime errors:
+try:
+ m1: MovieMix = {} # type: ignore
+ m2: MovieMix = {"year": 2015} # type: ignore
+except TypeError as e:
+ print("Assigning to MovieMix failed at runtime (expected for missing required fields):", e)
+
+print("Required/NotRequired with Annotated/ReadOnly examples")
+
+from typing import NotRequired, ReadOnly, Annotated
+
+
+class Band(TypedDict):
+ name: str
+ members: ReadOnly[list[str]]
+
+
+blur: Band = {"name": "blur", "members": []}
+blur["name"] = "Blur"
+# the following would be a type-checker error (but allowed at runtime):
+blur["members"] = ["Daemon Albarn"] # type: ignore
+blur["members"].append("Daemon Albarn")
+
+print("extra_items and closed examples")
+
+try:
+
+ class MovieExtra(TypedDict, extra_items=int):
+ name: str
+
+ # FIXME: Difference - constructor with kwargs
+ extra_ok: MovieExtra = {"name": "BR", "year": 1982}
+except TypeError as e:
+ print("-[ ] FIXME: extra_items not supported by runtime typing implementation:", e)
+
+try:
+
+ class MovieClosed(TypedDict, closed=True):
+ name: str
+
+ try:
+ # FIXME: Difference or Crash - constructor with kwargs
+ MovieClosed(
+ name="No Country for Old Men", year=2007
+ ) # Should be runtime error per ctor semantics
+ print("Constructed ClosedMovie with extra item (may be allowed at runtime)")
+ except TypeError:
+ print("-[ ] FIXME: Closed Movie rejected extra kwargs at construction")
+except TypeError as e:
+ print("-[ ] FIXME: closed parameter not supported by runtime typing implementation:", e)
+
+print("Interaction with Mapping and dict conversions")
+
+try:
+ # FIXME:
+ class IntDict(TypedDict, extra_items=int):
+ pass
+
+ not_required_num_dict: IntDict = {"num": 1, "bar": 2}
+except TypeError as e:
+ print("-[ ] FIXME: extra_items not supported by runtime typing implementation", e)
+ # Fall back to plain dict to exercise runtime operations
+ not_required_num_dict = {"num": 1, "bar": 2}
+# at runtime this is a dict; operations like clear/popitem are available
+not_required_num_dict.clear()
+
+print("-----")
diff --git a/tests/extmod/typing_pep_0591.py b/tests/extmod/typing_pep_0591.py
new file mode 100644
index 0000000000000..14ff0e0d8a622
--- /dev/null
+++ b/tests/extmod/typing_pep_0591.py
@@ -0,0 +1,110 @@
+try:
+ from typing import TYPE_CHECKING
+except Exception:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.8")
+print("### PEP 0591 - Final qualifier for types")
+# https://peps.python.org/pep-0591/
+
+print("The final decorator")
+
+from typing import List, Sequence, final
+
+
+@final
+class Base_1:
+ ...
+
+
+try:
+
+ class Derived_1(Base_1): # Error: Cannot inherit from final class "Base"
+ ...
+except Exception:
+ print("- [ ] FIXME: Cannot inherit from final class 'Base'")
+
+
+# -----------------
+
+
+class Base_2:
+ @final
+ def foo(self) -> None:
+ ...
+
+
+try:
+
+ class Derived_2(Base_2):
+ def foo(self) -> None: # Error: Cannot override final attribute "foo"
+ # (previously declared in base class "Base")
+ ...
+except Exception:
+ print("Expected: Cannot override final attribute 'foo'")
+
+
+print("The Final annotation")
+
+from typing import Final
+
+ID_1: Final[float] = 1
+ID_2: Final = 2
+
+print("Semantics and examples")
+
+from typing import Final
+
+RATE: Final = 3000
+
+
+class Base:
+ DEFAULT_ID: Final = 0
+
+
+try:
+ RATE = 300 # Error: can't assign to final attribute
+except Exception:
+ print("Expected: can't assign to final attribute 'RATE'")
+
+try:
+ Base.DEFAULT_ID = 1 # Error: can't override a final attribute
+except Exception:
+ print("Expected: can't override a final attribute 'DEFAULT_ID'")
+
+# -------------------
+
+try:
+ x: List[Final[int]] = [] # Error!
+ print("- [ ] FIXME: document cpydiff : Final cannot be used with container types")
+except Exception:
+ print("Expected: Final cannot be used with container types")
+
+
+try:
+
+ def fun(x: Final[List[int]]) -> None: # Error!
+ ...
+
+except Exception:
+ print("Expected: Final cannot be used for parameters or return types")
+
+# -------------------
+
+x_2: Final = ["a", "b"]
+x_2.append("c") # OK
+
+y: Final[Sequence[str]] = ["a", "b"]
+z: Final = ("a", "b") # Also works
+
+
+# -------------------
+from typing import NamedTuple, Final
+
+X: Final = "x"
+Y: Final = "y"
+N = NamedTuple("N", [(X, int), (Y, int)])
+
+
+print("-----")
diff --git a/tests/extmod/typing_pep_3119.py b/tests/extmod/typing_pep_3119.py
new file mode 100644
index 0000000000000..0a4f5214e862a
--- /dev/null
+++ b/tests/extmod/typing_pep_3119.py
@@ -0,0 +1,85 @@
+from math import e
+
+
+try:
+ import abc
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.0+")
+print("### module abc")
+# https://peps.python.org/pep-3119/
+# https://docs.python.org/3/library/abc.html#module-abc
+
+
+print("PEP 3119 - abc")
+try:
+ from abc import ABC
+
+ class MyABC(ABC):
+ pass
+except Exception:
+ print("- [ ] FIXME: from abc import ABC")
+
+# do not test : class MyABC(metaclass=ABCMeta): ...
+
+
+print("@abc.abstractmethod")
+try:
+ from abc import ABC, abstractmethod
+except Exception:
+ print("- [ ] FIXME: from abc import abstractmethod")
+ raise SystemExit
+
+
+class C1(ABC):
+ @abstractmethod
+ def my_abstract_method(self, arg1):
+ ...
+
+ @classmethod
+ @abstractmethod
+ def my_abstract_classmethod(cls, arg2):
+ ...
+
+ @staticmethod
+ @abstractmethod
+ def my_abstract_staticmethod(arg3):
+ ...
+
+ @property
+ @abstractmethod
+ def my_abstract_property(self):
+ ...
+
+ @my_abstract_property.setter
+ @abstractmethod
+ def my_abstract_property(self, val):
+ ...
+
+ @abstractmethod
+ def _get_x(self):
+ ...
+
+ @abstractmethod
+ def _set_x(self, val):
+ ...
+
+ x = property(_get_x, _set_x)
+
+
+# do not test deprecated abstractclassmethod, abstractstaticmethod, abstractproperty
+
+
+print("abc.get_cache_token()")
+
+try:
+ from abc import get_cache_token
+
+ print(get_cache_token())
+except Exception:
+ print("- [ ] FIXME: from abc import get_cache_token")
+
+
+print("-----")
diff --git a/tests/extmod/typing_runtime.py b/tests/extmod/typing_runtime.py
new file mode 100644
index 0000000000000..d47d6dbc3ab4a
--- /dev/null
+++ b/tests/extmod/typing_runtime.py
@@ -0,0 +1,456 @@
+print("Testing runtime aspects of typing module")
+
+
+try:
+ from typing import TYPE_CHECKING
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+print("# Python 3.5+")
+print("### Miscellaneous")
+
+
+print("typing.TYPE_CHECKING")
+if TYPE_CHECKING:
+ from typing_extensions import TypeGuard
+
+print("typing parameter annotations")
+from typing import Any, Dict, List, Union
+
+
+def add_numbers(a: int, b: int) -> int:
+ return a + b
+
+
+def greet(name: str) -> None:
+ print(f"Hello, {name}!")
+
+
+def get_average(numbers: List[float]) -> float:
+ return sum(numbers) / len(numbers)
+
+
+def process_data(data: Dict[str, Any]) -> None:
+ # process the data
+ pass
+
+
+a = add_numbers(3, 5)
+greet("Alice")
+avg = get_average([1.0, 2.5, 3.5])
+process_data({"key": "value", "number": 42})
+
+
+print("typing.Self - Python 3.11")
+from typing import Callable, Self
+
+
+class BaseClass:
+ def register(self, callback: Callable[[Self], None]) -> None:
+ ...
+
+
+def cb(x):
+ pass
+
+
+base = BaseClass()
+base.register(cb)
+
+
+print("typing@no_type_check decorator")
+from typing import no_type_check
+
+
+@no_type_check
+def quad(r0):
+ return r0 * 4
+
+
+print(quad(1))
+
+print("typing.Protocol")
+
+from typing import Protocol
+
+
+class Adder(Protocol):
+ def add(self, x, y):
+ ...
+
+
+class IntAdder:
+ def add(self, x, y):
+ return x + y
+
+
+class FloatAdder:
+ def add(self, x, y):
+ return x + y
+
+
+def add(adder: Adder) -> None:
+ print(adder.add(2, 3))
+
+
+add(IntAdder())
+add(FloatAdder())
+
+print("typing.NewType")
+
+from typing import NewType
+
+UserId = NewType("UserId", int)
+some_id = UserId(524313)
+
+print(some_id)
+
+assert isinstance(some_id, int), "NewType should be instance of the original type"
+
+
+print("typing.Any")
+from typing import Any
+
+a: Any = None
+a = [] # OK
+a = 2 # OK
+
+s: str = ""
+s = a # OK
+
+
+def foo(item: Any) -> int:
+ # Passes type checking; 'item' could be any type,
+ # and that type might have a 'bar' method
+ item.bar()
+ return 42
+
+
+def hash_b(item: Any) -> int:
+ try:
+ # Passes type checking
+ item.magic()
+ return foo(item)
+ except AttributeError:
+ # just ignore any error for this test
+ pass
+ return 21
+ ...
+
+
+print(hash_b(42))
+print(hash_b("foo"))
+
+print("typing.AnyStr")
+
+from typing import AnyStr
+
+
+def concat(a: AnyStr, b: AnyStr) -> AnyStr:
+ return a + b
+
+
+concat("foo", "bar") # OK, output has type 'str'
+concat(b"foo", b"bar") # OK, output has type 'bytes'
+try:
+ concat("foo", b"bar") # Error, cannot mix str and bytes # type: ignore
+except TypeError:
+ print("TypeError is expected")
+
+
+print("typing.LiteralString")
+from typing import LiteralString
+
+
+def run_query(sql: LiteralString) -> None:
+ ...
+
+
+def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
+ run_query("SELECT * FROM students") # OK
+ run_query(literal_string) # OK
+ run_query("SELECT * FROM " + literal_string) # OK
+ run_query(arbitrary_string) # type: ignore # type checker error
+ run_query(f"SELECT * FROM students WHERE name = {arbitrary_string}") # type: ignore # type checker error
+
+ assert isinstance(literal_string, str), "literal_string should be a string"
+ assert isinstance(arbitrary_string, str), "arbitrary_string should be a string"
+
+
+some_str = "a" * 1000
+literal_str = "drop * from tables"
+
+caller(some_str, literal_str)
+
+print("typing.overload")
+
+from typing import overload
+
+
+@overload
+def bar(x: int) -> str:
+ ...
+
+
+@overload
+def bar(x: str) -> int:
+ ...
+
+
+def bar(x):
+ return x
+
+
+print(bar(42))
+
+
+print("typing.Required, NotRequired in TypedDict")
+# https://typing.readthedocs.io/en/latest/spec/typeddict.html#required-and-notrequired
+
+from typing import NotRequired, Required, TypedDict
+
+
+class Movie(TypedDict):
+ title: Required[str]
+ year: int
+ director: NotRequired[str]
+
+
+m = Movie(title="Life of Brian", year=1979)
+
+
+print("typing.TypeVar")
+
+from typing import List, TypeVar
+
+T = TypeVar("T")
+
+
+def first(container: List[T]) -> T:
+ return container[0]
+
+
+list_one: List[str] = ["a", "b", "c"]
+print(first(list_one))
+
+list_two: List[int] = [1, 2, 3]
+print(first(list_two))
+
+
+print("typing.Generator")
+
+from typing import Generator
+
+
+def echo(a: float) -> Generator[int, float, str]:
+ yield int(a)
+ return "Done"
+
+
+e = echo(2.4)
+v = next(e)
+print(v)
+
+
+print("typing.NoReturn")
+
+from typing import NoReturn
+
+
+def stop() -> NoReturn:
+ raise RuntimeError("no way")
+
+
+#
+
+print("typing.Final")
+
+from typing import Final
+
+CONST: Final = 42
+
+
+print(CONST)
+
+
+print("typing.final")
+
+from typing import final
+
+
+class Base:
+ @final
+ def done(self) -> None:
+ ...
+
+
+class Sub(Base):
+ def done(self) -> None: # type: ignore # Error reported by type checker
+ ...
+
+
+@final
+class Leaf:
+ ...
+
+
+class Other(Leaf): # type: ignore # Error reported by type checker
+ ...
+
+
+other = Other()
+
+
+print("typing.TypeVarTuple and typing.Unpack")
+
+from typing import TypeVarTuple, Unpack
+
+Ts = TypeVarTuple("Ts")
+tup: tuple[Unpack[Ts]] # Semantically equivalent, and backwards-compatible # type: ignore
+
+
+print("typing.Callable, ParamSpec")
+
+# ParamSpec, 3.11 notation
+# https://docs.python.org/3/library/typing.html#typing.ParamSpec
+
+try:
+ from collections.abc import Callable
+except ImportError:
+ print("- [ ] FIXME: from collections.abc import Callable")
+ from typing import Callable # Workaround for test
+
+from typing import TypeVar, ParamSpec
+
+T = TypeVar("T")
+P = ParamSpec("P")
+
+
+def add_logging(f: Callable[P, T]) -> Callable[P, T]:
+ """A type-safe decorator to add logging to a function."""
+
+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
+ print(f"{f.__name__} was called")
+ return f(*args, **kwargs)
+
+ return inner
+
+
+@add_logging
+def add_two(x: float, y: float) -> float:
+ """Add two numbers together."""
+ return x + y
+
+
+x = add_two(1, 2)
+print(x)
+assert x == 3, "add_two(1, 2) == 3"
+
+
+print("typing.get_origin()")
+# https://docs.python.org/3/library/typing.html#typing.get_origin
+
+from typing import get_origin
+
+# FIXME: - cpy_diff - get_origin() unsupported, or always returns None
+if not get_origin(str) is None:
+ print("- [ ] FIXME: document cpy_diff - get_origin(str) should be None")
+# assert get_origin(Dict[str, int]) is dict
+# assert get_origin(Union[int, str]) is Union
+
+print("typing.get_args()")
+# https://docs.python.org/3/library/typing.html#typing.get_args
+from typing import get_args, Dict, Union
+
+# FIXME: - cpy_diff - get_args() unsupported, or always returns ()
+if not get_args(int) == ():
+ print("- [ ] FIXME: document cpy_diff - get_args(int) should be ()")
+
+# assert get_args(Dict[int, str]) == (int, str), "get_args(Dict[int, str]) should be (int, str)"
+# assert get_args(Union[int, str]) == (int, str), "get_args(Union[int, str]) should be (int, str)"
+
+
+print("Subscriptables")
+
+from typing import (
+ AbstractSet,
+ AsyncContextManager,
+ AsyncGenerator,
+ AsyncIterable,
+ AsyncIterator,
+ Awaitable,
+)
+from typing import (
+ Callable,
+ ChainMap,
+ Collection,
+ Container,
+ ContextManager,
+ Coroutine,
+ Counter,
+ DefaultDict,
+)
+from typing import (
+ Deque,
+ Dict,
+ FrozenSet,
+ Generator,
+ Generic,
+ Iterable,
+ Iterator,
+ List,
+ Literal,
+ Mapping,
+)
+from typing import (
+ MutableMapping,
+ MutableSequence,
+ MutableSet,
+ NamedTuple,
+ Optional,
+ OrderedDict,
+ Self,
+)
+from typing import Sequence, Set, Tuple, Type, Union
+
+
+t_01: AbstractSet[Any]
+t_02: AsyncContextManager[Any]
+t_03: AsyncGenerator[Any]
+t_04: AsyncIterable[Any]
+t_05: AsyncIterator[Any]
+t_06: Awaitable[Any]
+t_07: Callable[[], Any]
+t_08: ChainMap[Any, Any]
+t_09: Collection[Any]
+t_10: Container[Any]
+t_11: ContextManager[Any]
+t_12: Coroutine[Any, Any, Any]
+t_13: Counter[Any]
+t_14: DefaultDict[Any, Any]
+t_15: Deque[Any]
+t_16: Dict[Any, Any]
+t_17: FrozenSet[Any]
+t_18: Generator[Any]
+# t_19: Generic[Any]
+t_20: Iterable[Any]
+t_21: Iterator[Any]
+t_22: List[Any]
+t_23: Literal[1, 2, 3, "a", b"b", True, None]
+t_24: Mapping[Any, Any]
+t_25: MutableMapping[Any, Any]
+t_26: MutableSequence[Any]
+t_27: MutableSet[Any]
+t_28: NamedTuple
+t_29: Optional[Any]
+t_30: OrderedDict[Any, Any]
+# t_31: Self[Any]
+t_32: Sequence[Any]
+t_33: Set[Any]
+t_34: Tuple[Any]
+t_35: Type[Any]
+t_36: Union[Any, Any]
+
+
+print("-----")
diff --git a/tests/extmod/typing_syntax.py b/tests/extmod/typing_syntax.py
new file mode 100644
index 0000000000000..475b0a0a20a64
--- /dev/null
+++ b/tests/extmod/typing_syntax.py
@@ -0,0 +1,65 @@
+# This doesn't quite test everything but just serves to verify that basic syntax works,
+# which for MicroPython means everything typing-related should be ignored.
+
+try:
+ import typing
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+from typing import List, Tuple, Iterable, NewType, TypeVar, Union, Generic
+from typing import Any
+
+# Available with MICROPY_PY_TYPING_EXTRA_MODULES.
+try:
+ import typing_extensions
+except ImportError:
+ typing_extensions = None
+
+# Available with MICROPY_PY_TYPING_EXTRA_MODULES and MICROPY_MODULE_BUILTIN_SUBPACKAGES.
+try:
+ import collections.abc
+
+ collections.abc.Sequence
+except ImportError:
+ pass
+
+import sys
+
+# If this is available verify it works, and try the other modules as well.
+if typing_extensions is not None:
+ import __future__
+ from abc import abstractmethod
+
+ getattr(__future__, "annotations")
+
+
+# if "micropython" in sys.implementation.name:
+# # Verify assignment is not possible.
+# try:
+# typing.a = None
+# raise Exception()
+# except AttributeError:
+# pass
+# try:
+# typing[0] = None
+# raise Exception()
+# except TypeError:
+# pass
+# try:
+# List.a = None
+# raise Exception()
+# except AttributeError:
+# pass
+
+
+MyAlias = str
+Vector: typing.List[float]
+UserId = NewType("UserId", int)
+T = TypeVar("T", int, float, complex)
+
+hintedGlobal: Any = None
+
+
+def func_with_hints(c: int, b: MyAlias, a: Union[int, None], lst: List[float] = [0.0]) -> Any:
+ pass
diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp
index 176db8e9f8479..106e702ff68a7 100644
--- a/tests/ports/unix/extra_coverage.py.exp
+++ b/tests/ports/unix/extra_coverage.py.exp
@@ -50,16 +50,18 @@ RuntimeError:
ame__
port
-builtins micropython _asyncio _thread
-array binascii btree cexample
-cmath collections cppexample cryptolib
-deflate errno example_package
-ffi framebuf gc hashlib
-heapq io json machine
-math os platform random
-re select socket struct
-sys termios time tls
-uctypes vfs websocket
+builtins micropython __future__ _asyncio
+_thread abc array binascii
+btree cexample cmath collections
+cppexample cryptolib deflate errno
+example_package ffi framebuf
+gc hashlib heapq io
+json machine math os
+platform random re select
+socket struct sys termios
+time tls typing
+typing_extensions uctypes vfs
+websocket
me
micropython machine math