|
42 | 42 | )
|
43 | 43 | from mypyc.ir.ops import (
|
44 | 44 | BasicBlock,
|
| 45 | + ComparisonOp, |
45 | 46 | GetAttr,
|
46 | 47 | Integer,
|
47 | 48 | LoadAddress,
|
|
81 | 82 | dict_new_op,
|
82 | 83 | exact_dict_set_item_op,
|
83 | 84 | )
|
84 |
| -from mypyc.primitives.generic_ops import py_setattr_op |
| 85 | +from mypyc.primitives.generic_ops import generic_getattr, py_setattr_op |
85 | 86 | from mypyc.primitives.misc_ops import register_function
|
86 | 87 | from mypyc.primitives.registry import builtin_names
|
87 | 88 | from mypyc.sametype import is_same_method_signature, is_same_type
|
@@ -364,6 +365,56 @@ def gen_func_ir(
|
364 | 365 | return (func_ir, func_reg)
|
365 | 366 |
|
366 | 367 |
|
| 368 | +def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDef) -> None: |
| 369 | + """ |
| 370 | + Generate a wrapper function for __getattr__ that can be put into the tp_getattro slot. |
| 371 | + The wrapper takes one argument besides self which is the attribute name. |
| 372 | + It first checks if the name matches any of the attributes of this class. |
| 373 | + If it does, it returns that attribute. If none match, it calls __getattr__. |
| 374 | +
|
| 375 | + __getattr__ is not supported in classes that allow interpreted subclasses because the |
| 376 | + tp_getattro slot is inherited by subclasses and if the subclass overrides __getattr__, |
| 377 | + the override would be ignored in our wrapper. TODO: To support this, the wrapper would |
| 378 | + have to check type of self and if it's not the compiled class, resolve "__getattr__" against |
| 379 | + the type at runtime and call the returned method, like _Py_slot_tp_getattr_hook in cpython. |
| 380 | +
|
| 381 | + __getattr__ is not supported in classes which inherit from non-native classes because those |
| 382 | + have __dict__ which currently has some strange interactions when class attributes and |
| 383 | + variables are assigned through __dict__ vs. through regular attribute access. Allowing |
| 384 | + __getattr__ on top of that could be problematic. |
| 385 | + """ |
| 386 | + name = getattr.name + "__wrapper" |
| 387 | + ir = builder.mapper.type_to_ir[cdef.info] |
| 388 | + line = getattr.line |
| 389 | + |
| 390 | + error_base = f'"__getattr__" not supported in class "{cdef.name}" because ' |
| 391 | + if ir.allow_interpreted_subclasses: |
| 392 | + builder.error(error_base + "it allows interpreted subclasses", line) |
| 393 | + if ir.inherits_python: |
| 394 | + builder.error(error_base + "it inherits from a non-native class", line) |
| 395 | + |
| 396 | + with builder.enter_method(ir, name, object_rprimitive, internal=True): |
| 397 | + attr_arg = builder.add_argument("attr", object_rprimitive) |
| 398 | + generic_getattr_result = builder.call_c(generic_getattr, [builder.self(), attr_arg], line) |
| 399 | + |
| 400 | + return_generic, call_getattr = BasicBlock(), BasicBlock() |
| 401 | + null = Integer(0, object_rprimitive, line) |
| 402 | + got_generic = builder.add( |
| 403 | + ComparisonOp(generic_getattr_result, null, ComparisonOp.NEQ, line) |
| 404 | + ) |
| 405 | + builder.add_bool_branch(got_generic, return_generic, call_getattr) |
| 406 | + |
| 407 | + builder.activate_block(return_generic) |
| 408 | + builder.add(Return(generic_getattr_result, line)) |
| 409 | + |
| 410 | + builder.activate_block(call_getattr) |
| 411 | + # No attribute matched so call user-provided __getattr__. |
| 412 | + getattr_result = builder.gen_method_call( |
| 413 | + builder.self(), getattr.name, [attr_arg], object_rprimitive, line |
| 414 | + ) |
| 415 | + builder.add(Return(getattr_result, line)) |
| 416 | + |
| 417 | + |
367 | 418 | def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None:
|
368 | 419 | # Perform the function of visit_method for methods inside extension classes.
|
369 | 420 | name = fdef.name
|
@@ -430,6 +481,9 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
|
430 | 481 | class_ir.glue_methods[(class_ir, name)] = f
|
431 | 482 | builder.functions.append(f)
|
432 | 483 |
|
| 484 | + if fdef.name == "__getattr__": |
| 485 | + generate_getattr_wrapper(builder, cdef, fdef) |
| 486 | + |
433 | 487 |
|
434 | 488 | def handle_non_ext_method(
|
435 | 489 | builder: IRBuilder, non_ext: NonExtClassInfo, cdef: ClassDef, fdef: FuncDef
|
|
0 commit comments