@@ -239,10 +239,55 @@ def is_dataclass(cdef: ClassDef) -> bool:
239
239
return any (is_dataclass_decorator (d ) for d in cdef .decorators )
240
240
241
241
242
- def is_extension_class (cdef : ClassDef ) -> bool :
242
+ def get_mypyc_attr_literal (e : Expression ) -> Any :
243
+ """Convert an expression from a mypyc_attr decorator to a value.
244
+
245
+ Supports a pretty limited range."""
246
+ if isinstance (e , (StrExpr , IntExpr , FloatExpr )):
247
+ return e .value
248
+ elif isinstance (e , RefExpr ) and e .fullname == 'builtins.True' :
249
+ return True
250
+ elif isinstance (e , RefExpr ) and e .fullname == 'builtins.False' :
251
+ return False
252
+ elif isinstance (e , RefExpr ) and e .fullname == 'builtins.None' :
253
+ return None
254
+ return NotImplemented
255
+
256
+
257
+ def get_mypyc_attr_call (d : Expression ) -> Optional [CallExpr ]:
258
+ """Check if an expression is a call to mypyc_attr and return it if so."""
259
+ if (
260
+ isinstance (d , CallExpr )
261
+ and isinstance (d .callee , RefExpr )
262
+ and d .callee .fullname == 'mypy_extensions.mypyc_attr'
263
+ ):
264
+ return d
265
+ return None
266
+
267
+
268
+ def get_mypyc_attrs (stmt : Union [ClassDef , Decorator ]) -> Dict [str , Any ]:
269
+ """Collect all the mypyc_attr attributes on a class definition or a function."""
270
+ attrs = {} # type: Dict[str, Any]
271
+ for dec in stmt .decorators :
272
+ d = get_mypyc_attr_call (dec )
273
+ if d :
274
+ for name , arg in zip (d .arg_names , d .args ):
275
+ if name is None :
276
+ if isinstance (arg , StrExpr ):
277
+ attrs [arg .value ] = True
278
+ else :
279
+ attrs [name ] = get_mypyc_attr_literal (arg )
280
+
281
+ return attrs
282
+
243
283
244
- if any (not is_trait_decorator (d ) and not is_dataclass_decorator (d )
245
- for d in cdef .decorators ):
284
+ def is_extension_class (cdef : ClassDef ) -> bool :
285
+ if any (
286
+ not is_trait_decorator (d )
287
+ and not is_dataclass_decorator (d )
288
+ and not get_mypyc_attr_call (d )
289
+ for d in cdef .decorators
290
+ ):
246
291
return False
247
292
elif (cdef .info .metaclass_type and cdef .info .metaclass_type .type .fullname not in (
248
293
'abc.ABCMeta' , 'typing.TypingMeta' , 'typing.GenericMeta' )):
@@ -285,10 +330,11 @@ def specialize_parent_vtable(cls: ClassIR, parent: ClassIR) -> VTableEntries:
285
330
# TODO: emit a wrapper for __init__ that raises or something
286
331
if (is_same_method_signature (orig_parent_method .sig , child_method .sig )
287
332
or orig_parent_method .name == '__init__' ):
288
- entry = VTableMethod (entry .cls , entry .name , child_method )
333
+ entry = VTableMethod (entry .cls , entry .name , child_method , entry . shadow_method )
289
334
else :
290
335
entry = VTableMethod (entry .cls , entry .name ,
291
- defining_cls .glue_methods [(entry .cls , entry .name )])
336
+ defining_cls .glue_methods [(entry .cls , entry .name )],
337
+ entry .shadow_method )
292
338
else :
293
339
# If it is an attribute from a trait, we need to find out
294
340
# the real class it got mixed in at and point to that.
@@ -346,7 +392,8 @@ def compute_vtable(cls: ClassIR) -> None:
346
392
# TODO: don't generate a new entry when we overload without changing the type
347
393
if fn == cls .get_method (fn .name ):
348
394
cls .vtable [fn .name ] = len (entries )
349
- entries .append (VTableMethod (t , fn .name , fn ))
395
+ shadow = cls .glue_methods .get ((cls , fn .name ))
396
+ entries .append (VTableMethod (t , fn .name , fn , shadow ))
350
397
351
398
# Compute vtables for all of the traits that the class implements
352
399
if not cls .is_trait :
@@ -546,6 +593,10 @@ def prepare_class_def(path: str, module_name: str, cdef: ClassDef,
546
593
ir = mapper .type_to_ir [cdef .info ]
547
594
info = cdef .info
548
595
596
+ attrs = get_mypyc_attrs (cdef )
597
+ if attrs .get ("allow_interpreted_children" ) is True :
598
+ ir .allow_interpreted_children = True
599
+
549
600
# We sort the table for determinism here on Python 3.5
550
601
for name , node in sorted (info .names .items ()):
551
602
# Currenly all plugin generated methods are dummies and not included.
@@ -1169,20 +1220,26 @@ def handle_ext_method(self, cdef: ClassDef, fdef: FuncDef) -> None:
1169
1220
and not is_same_method_signature (class_ir .method_decls [name ].sig ,
1170
1221
cls .method_decls [name ].sig )):
1171
1222
1223
+ if cls is class_ir and not cls .allow_interpreted_children :
1224
+ continue
1225
+
1172
1226
# TODO: Support contravariant subtyping in the input argument for
1173
1227
# property setters. Need to make a special glue method for handling this,
1174
1228
# similar to gen_glue_property.
1175
1229
1176
- if fdef .is_property :
1177
- f = self .gen_glue_property (cls .method_decls [name ].sig , func_ir , class_ir ,
1178
- cls , fdef .line )
1179
- else :
1180
- f = self .gen_glue_method (cls .method_decls [name ].sig , func_ir , class_ir ,
1181
- cls , fdef .line )
1182
-
1230
+ f = self .gen_glue (cls .method_decls [name ].sig , func_ir , class_ir , cls , fdef )
1183
1231
class_ir .glue_methods [(cls , name )] = f
1184
1232
self .functions .append (f )
1185
1233
1234
+ # If the class allows interpreted children, create glue
1235
+ # methods that dispatch via the Python API. These will go in a
1236
+ # "shadow vtable" that will be assigned to interpreted
1237
+ # children.
1238
+ if class_ir .allow_interpreted_children :
1239
+ f = self .gen_glue (func_ir .sig , func_ir , class_ir , class_ir , fdef , do_py_ops = True )
1240
+ class_ir .glue_methods [(class_ir , name )] = f
1241
+ self .functions .append (f )
1242
+
1186
1243
def handle_non_ext_method (
1187
1244
self , non_ext : NonExtClassInfo , cdef : ClassDef , fdef : FuncDef ) -> None :
1188
1245
# Perform the function of visit_method for methods inside non-extension classes.
@@ -1481,6 +1538,11 @@ def visit_class_def(self, cdef: ClassDef) -> None:
1481
1538
if any (ir .base_mro [i ].base != ir . base_mro [i + 1 ] for i in range (len (ir .base_mro ) - 1 )):
1482
1539
self .error ("Non-trait MRO must be linear" , cdef .line )
1483
1540
1541
+ if ir .allow_interpreted_children and any (
1542
+ not parent .allow_interpreted_children for parent in ir .mro
1543
+ ):
1544
+ self .error ("Parents must allow interpreted children also" , cdef .line )
1545
+
1484
1546
# Currently, we only create non-extension classes for classes that are
1485
1547
# decorated or inherit from Enum. Classes decorated with @trait do not
1486
1548
# apply here, and are handled in a different way.
@@ -1707,8 +1769,24 @@ def visit_import_all(self, node: ImportAll) -> None:
1707
1769
return
1708
1770
self .gen_import (node .id , node .line )
1709
1771
1772
+ def gen_glue (self , sig : FuncSignature , target : FuncIR ,
1773
+ cls : ClassIR , base : ClassIR , fdef : FuncItem ,
1774
+ * ,
1775
+ do_py_ops : bool = False
1776
+ ) -> FuncIR :
1777
+ if fdef .is_property :
1778
+ return self .gen_glue_property (
1779
+ cls .method_decls [fdef .name ].sig , target , cls , base , fdef .line , do_py_ops
1780
+ )
1781
+ else :
1782
+ return self .gen_glue_method (
1783
+ cls .method_decls [fdef .name ].sig , target , cls , base , fdef .line , do_py_ops
1784
+ )
1785
+
1710
1786
def gen_glue_method (self , sig : FuncSignature , target : FuncIR ,
1711
- cls : ClassIR , base : ClassIR , line : int ) -> FuncIR :
1787
+ cls : ClassIR , base : ClassIR , line : int ,
1788
+ do_pycall : bool ,
1789
+ ) -> FuncIR :
1712
1790
"""Generate glue methods that mediate between different method types in subclasses.
1713
1791
1714
1792
For example, if we have:
@@ -1745,7 +1823,11 @@ def f(self, x: object) -> int: ...
1745
1823
arg_names = [arg .name for arg in rt_args ]
1746
1824
arg_kinds = [concrete_arg_kind (arg .kind ) for arg in rt_args ]
1747
1825
1748
- retval = self .call (target .decl , args , arg_kinds , arg_names , line )
1826
+ if do_pycall :
1827
+ retval = self .py_method_call (
1828
+ args [0 ], target .name , args [1 :], line , arg_kinds [1 :], arg_names [1 :])
1829
+ else :
1830
+ retval = self .call (target .decl , args , arg_kinds , arg_names , line )
1749
1831
retval = self .coerce (retval , sig .ret_type , line )
1750
1832
self .add (Return (retval ))
1751
1833
@@ -1758,7 +1840,8 @@ def f(self, x: object) -> int: ...
1758
1840
blocks , env )
1759
1841
1760
1842
def gen_glue_property (self , sig : FuncSignature , target : FuncIR , cls : ClassIR , base : ClassIR ,
1761
- line : int ) -> FuncIR :
1843
+ line : int ,
1844
+ do_pygetattr : bool ) -> FuncIR :
1762
1845
"""Similarly to methods, properties of derived types can be covariantly subtyped. Thus,
1763
1846
properties also require glue. However, this only requires the return type to change.
1764
1847
Further, instead of a method call, an attribute get is performed."""
@@ -1767,7 +1850,10 @@ def gen_glue_property(self, sig: FuncSignature, target: FuncIR, cls: ClassIR, ba
1767
1850
rt_arg = RuntimeArg (SELF_NAME , RInstance (cls ))
1768
1851
arg = self .read (self .add_self_to_env (cls ), line )
1769
1852
self .ret_types [- 1 ] = sig .ret_type
1770
- retval = self .add (GetAttr (arg , target .name , line ))
1853
+ if do_pygetattr :
1854
+ retval = self .py_get_attr (arg , target .name , line )
1855
+ else :
1856
+ retval = self .add (GetAttr (arg , target .name , line ))
1771
1857
retbox = self .coerce (retval , sig .ret_type , line )
1772
1858
self .add (Return (retbox ))
1773
1859
@@ -3103,7 +3189,7 @@ def py_call(self,
3103
3189
arg_values : List [Value ],
3104
3190
line : int ,
3105
3191
arg_kinds : Optional [List [int ]] = None ,
3106
- arg_names : Optional [List [Optional [str ]]] = None ) -> Value :
3192
+ arg_names : Optional [Sequence [Optional [str ]]] = None ) -> Value :
3107
3193
"""Use py_call_op or py_call_with_kwargs_op for function call."""
3108
3194
# If all arguments are positional, we can use py_call_op.
3109
3195
if (arg_kinds is None ) or all (kind == ARG_POS for kind in arg_kinds ):
@@ -3152,8 +3238,8 @@ def py_method_call(self,
3152
3238
method_name : str ,
3153
3239
arg_values : List [Value ],
3154
3240
line : int ,
3155
- arg_kinds : Optional [List [int ]] = None ,
3156
- arg_names : Optional [List [Optional [str ]]] = None ) -> Value :
3241
+ arg_kinds : Optional [List [int ]],
3242
+ arg_names : Optional [Sequence [Optional [str ]]]) -> Value :
3157
3243
if (arg_kinds is None ) or all (kind == ARG_POS for kind in arg_kinds ):
3158
3244
method_name_reg = self .load_static_unicode (method_name )
3159
3245
return self .primitive_op (py_method_call_op , [obj , method_name_reg ] + arg_values , line )
0 commit comments