2525 */
2626
2727#include "py/obj.h"
28+ #include "py/runtime.h"
2829
2930#if MICROPY_PY_TYPING
3031
3132// Implement roughly the equivalent of the following minimal Python typing module, meant to support the
3233// typing syntax at runtime but otherwise ignoring any functionality:
3334//
35+ // TYPE_CHECKING = False
3436// class _AnyCall:
3537// def __init__(*args, **kwargs):
3638// pass
@@ -59,6 +61,78 @@ typedef struct _mp_obj_any_call_t
5961} mp_obj_any_call_t ;
6062
6163static const mp_obj_type_t mp_type_any_call_t ;
64+ static const mp_obj_type_t mp_type_typing_alias ;
65+
66+ // Lightweight runtime representation for objects such as typing.List[int].
67+ // The alias keeps track of the original builtin type and the tuple of
68+ // parameters so that __origin__ and __args__ can be queried at runtime.
69+ typedef struct _mp_obj_typing_alias_t {
70+ mp_obj_base_t base ;
71+ mp_obj_t origin ;
72+ mp_obj_t args ; // tuple or MP_OBJ_NULL when not parametrised
73+ } mp_obj_typing_alias_t ;
74+
75+ // Maps a qstr name to the builtin type that should back the alias.
76+ typedef struct {
77+ qstr name ;
78+ const mp_obj_type_t * type ;
79+ } typing_alias_spec_t ;
80+
81+ static mp_obj_t typing_alias_from_spec (const typing_alias_spec_t * spec_table , size_t spec_len , qstr attr );
82+
83+ static mp_obj_t typing_alias_new (mp_obj_t origin , mp_obj_t args ) {
84+ mp_obj_typing_alias_t * self = mp_obj_malloc (mp_obj_typing_alias_t , & mp_type_typing_alias );
85+ self -> origin = origin ;
86+ self -> args = args ;
87+ return MP_OBJ_FROM_PTR (self );
88+ }
89+
90+ static void typing_alias_attr (mp_obj_t self_in , qstr attr , mp_obj_t * dest ) {
91+ // Only handle reads that we recognise: __origin__ and __args__. Anything
92+ // else is delegated back to the VM where it will fall through to the
93+ // generic AnyCall behaviour.
94+ if (dest [0 ] != MP_OBJ_NULL ) {
95+ return ;
96+ }
97+
98+ mp_obj_typing_alias_t * self = MP_OBJ_TO_PTR (self_in );
99+ if (attr == MP_QSTR___origin__ ) {
100+ dest [0 ] = self -> origin ;
101+ } else if (attr == MP_QSTR___args__ ) {
102+ dest [0 ] = self -> args == MP_OBJ_NULL ? mp_const_empty_tuple : self -> args ;
103+ }
104+ }
105+
106+ static mp_obj_t typing_alias_subscr (mp_obj_t self_in , mp_obj_t index_in , mp_obj_t value ) {
107+ if (value != MP_OBJ_SENTINEL ) {
108+ mp_raise_TypeError (MP_ERROR_TEXT ("typing alias does not support assignment" ));
109+ }
110+
111+ mp_obj_typing_alias_t * self = MP_OBJ_TO_PTR (self_in );
112+ mp_obj_t args_obj ;
113+ if (mp_obj_is_type (index_in , & mp_type_tuple )) {
114+ args_obj = index_in ;
115+ } else {
116+ mp_obj_t items [1 ] = { index_in };
117+ args_obj = mp_obj_new_tuple (1 , items );
118+ }
119+
120+ return typing_alias_new (self -> origin , args_obj );
121+ }
122+
123+ 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 ) {
124+ mp_obj_typing_alias_t * self = MP_OBJ_TO_PTR (self_in );
125+ return mp_call_function_n_kw (self -> origin , n_args , n_kw , args );
126+ }
127+
128+ static MP_DEFINE_CONST_OBJ_TYPE (
129+ mp_type_typing_alias ,
130+ MP_QSTR_typing_alias ,
131+ MP_TYPE_FLAG_NONE ,
132+ attr , typing_alias_attr ,
133+ subscr , typing_alias_subscr ,
134+ call , typing_alias_call
135+ ) ;
62136
63137
64138// Can be used both for __new__ and __call__: the latter's prototype is
@@ -114,10 +188,39 @@ static void any_call_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
114188 }
115189}
116190
191+ // Only a small subset of typing.* names need concrete runtime behaviour. The
192+ // table below lists those names together with the builtin type that should be
193+ // wrapped in a typing alias. Everything else continues to use the extremely
194+ // small AnyCall shim.
195+ static const typing_alias_spec_t typing_container_specs [] = {
196+ { MP_QSTR_type , & mp_type_type },
197+ { MP_QSTR_Type , & mp_type_type },
198+ { MP_QSTR_List , & mp_type_list },
199+ { MP_QSTR_Dict , & mp_type_dict },
200+ { MP_QSTR_Tuple , & mp_type_tuple },
201+ { MP_QSTR_Literal , & mp_type_any_call_t },
202+ #if MICROPY_PY_BUILTINS_SET
203+ { MP_QSTR_Set , & mp_type_set },
204+ #endif
205+ #if MICROPY_PY_BUILTINS_FROZENSET
206+ { MP_QSTR_FrozenSet , & mp_type_frozenset },
207+ #endif
208+ };
209+
117210void any_call_module_attr (mp_obj_t self_in , qstr attr , mp_obj_t * dest ) {
118211 // Only loading is supported.
119212 if (dest [0 ] == MP_OBJ_NULL ) {
120- dest [0 ] = MP_OBJ_FROM_PTR (& mp_type_any_call_t );
213+ // First see if this attribute corresponds to a container alias that
214+ // needs a proper __getitem__ implementation.
215+ mp_obj_t alias = typing_alias_from_spec (typing_container_specs , MP_ARRAY_SIZE (typing_container_specs ), attr );
216+ if (alias != MP_OBJ_NULL ) {
217+ dest [0 ] = alias ;
218+ } else {
219+ // Otherwise fall back to returning the singleton AnyCall object,
220+ // preserving the "typing ignores everything" behaviour used for
221+ // the majority of names.
222+ dest [0 ] = MP_OBJ_FROM_PTR (& mp_type_any_call_t );
223+ }
121224 }
122225}
123226
@@ -131,9 +234,21 @@ static MP_DEFINE_CONST_OBJ_TYPE(
131234 call , any_call_call
132235 ) ;
133236
237+ // Helper to look up a qstr in the alias specification table and lazily create
238+ // the corresponding typing alias object when a match is found.
239+ static mp_obj_t typing_alias_from_spec (const typing_alias_spec_t * spec_table , size_t spec_len , qstr attr ) {
240+ for (size_t i = 0 ; i < spec_len ; ++ i ) {
241+ if (spec_table [i ].name == attr ) {
242+ mp_obj_t origin = MP_OBJ_FROM_PTR (spec_table [i ].type );
243+ return typing_alias_new (origin , MP_OBJ_NULL );
244+ }
245+ }
246+ return MP_OBJ_NULL ;
247+ }
134248
135249static const mp_rom_map_elem_t mp_module_typing_globals_table [] = {
136250 { MP_ROM_QSTR (MP_QSTR___name__ ), MP_ROM_QSTR (MP_QSTR_typing ) },
251+ { MP_ROM_QSTR (MP_QSTR_TYPE_CHECKING ), MP_ROM_FALSE },
137252};
138253
139254static MP_DEFINE_CONST_DICT (mp_module_typing_globals , mp_module_typing_globals_table ) ;
0 commit comments