From cb2864224dd72179abc11222420c58f5c713573b Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 25 Sep 2024 11:49:31 +0200 Subject: [PATCH] extmod: Implement a minimal typing module. Signed-off-by: stijn --- extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/modtyping.c | 135 +++++++++++++++++++ ports/unix/variants/mpconfigvariant_common.h | 4 + ports/windows/mpconfigport.h | 2 + ports/windows/msvc/sources.props | 1 + py/mpconfig.h | 11 ++ tests/extmod/typing_syntax.py | 40 ++++++ 8 files changed, 195 insertions(+) create mode 100644 extmod/modtyping.c create mode 100644 tests/extmod/typing_syntax.py 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..4eb7248338d54 --- /dev/null +++ b/extmod/modtyping.c @@ -0,0 +1,135 @@ +/* + * 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" + +#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: +// +// 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. + + +// The singleton object returned everywhere, both for module attr/subscr as well as for its own +// attr/subscr calls. +extern mp_obj_base_t typing_obj; + +static mp_obj_t any_call_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return self_in; +} + +// 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 *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + MP_STATIC_ASSERT(sizeof(mp_obj_type_t *) == sizeof(mp_obj_t)); + return MP_OBJ_FROM_PTR(&typing_obj); +} + +#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) { + return MP_OBJ_FROM_PTR(&typing_obj); +} +#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 + +// Not static because it's also used for the module's attr delegation. +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) { + dest[ 0 ] = MP_OBJ_FROM_PTR(&typing_obj); + } +} + + +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 + ); + +mp_obj_base_t typing_obj = {&mp_type_any_call_t}; + + +static const mp_rom_map_elem_t mp_module_typing_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_typing) }, +}; + +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_attr); + + +#if MICROPY_PY_TYPING_EXTRA_MODULES +MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_abc, 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..f4afebff437a0 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -121,3 +121,7 @@ #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_BASE (1) + +// Enable "typing" and related modules. +#define MICROPY_PY_TYPING (1) +#define MICROPY_PY_TYPING_EXTRA_MODULES (1) 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/mpconfig.h b/py/mpconfig.h index b5414312c7caf..54589de41e92b 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1626,6 +1626,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/extmod/typing_syntax.py b/tests/extmod/typing_syntax.py new file mode 100644 index 0000000000000..73c179f2a544b --- /dev/null +++ b/tests/extmod/typing_syntax.py @@ -0,0 +1,40 @@ +# 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 + +# Available with MICROPY_PY_TYPING_EXTRA_MODULES. +try: + from typing_extensions import Any +except ImportError: + from typing import Any + + +MyAlias = str +Vector: typing.List[float] +Nested = Iterable[Tuple[Vector, ...]] +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 + + +class ClassWithHints(Generic[T]): + a: int = 0 + + def foo(self, other: int) -> None: + self.typed_thing: List[T] = [] + + +class Bar(ClassWithHints[Any]): + pass