Skip to content

Commit

Permalink
extmod: Implement a minimal typing module.
Browse files Browse the repository at this point in the history
Signed-off-by: stijn <[email protected]>
  • Loading branch information
stinos committed Sep 25, 2024
1 parent 51974f2 commit 44eb78e
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 0 deletions.
1 change: 1 addition & 0 deletions extmod/extmod.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions extmod/extmod.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
135 changes: 135 additions & 0 deletions extmod/modtyping.c
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions ports/unix/variants/mpconfigvariant_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 2 additions & 0 deletions ports/windows/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions ports/windows/msvc/sources.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<PyExtModSource Include="$(PyBaseDir)extmod\modre.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\modselect.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\modtime.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\modtyping.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\virtpin.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\vfs.c" />
<PyExtModSource Include="$(PyBaseDir)extmod\vfs_posix.c" />
Expand Down
11 changes: 11 additions & 0 deletions py/mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions tests/extmod/typing_syntax.py
Original file line number Diff line number Diff line change
@@ -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[MyAlias, ...]]
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

0 comments on commit 44eb78e

Please sign in to comment.