Generate a wrapper function for __getattr__ that can be put into the tp_getattro slot. The wrapper takes one argument besides self which is the attribute name. It first checks if the name matches any of the attributes of this class. If it does, it returns that attribute. If none mat
(builder: IRBuilder, cdef: ClassDef, getattr: FuncDef)
| 375 | |
| 376 | |
| 377 | def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDef) -> None: |
| 378 | """ |
| 379 | Generate a wrapper function for __getattr__ that can be put into the tp_getattro slot. |
| 380 | The wrapper takes one argument besides self which is the attribute name. |
| 381 | It first checks if the name matches any of the attributes of this class. |
| 382 | If it does, it returns that attribute. If none match, it calls __getattr__. |
| 383 | |
| 384 | __getattr__ is not supported in classes that allow interpreted subclasses because the |
| 385 | tp_getattro slot is inherited by subclasses and if the subclass overrides __getattr__, |
| 386 | the override would be ignored in our wrapper. TODO: To support this, the wrapper would |
| 387 | have to check type of self and if it's not the compiled class, resolve "__getattr__" against |
| 388 | the type at runtime and call the returned method, like _Py_slot_tp_getattr_hook in cpython. |
| 389 | |
| 390 | __getattr__ is not supported in classes which inherit from non-native classes because those |
| 391 | have __dict__ which currently has some strange interactions when class attributes and |
| 392 | variables are assigned through __dict__ vs. through regular attribute access. Allowing |
| 393 | __getattr__ on top of that could be problematic. |
| 394 | """ |
| 395 | name = getattr.name + "__wrapper" |
| 396 | ir = builder.mapper.type_to_ir[cdef.info] |
| 397 | line = getattr.line |
| 398 | |
| 399 | error_base = f'"__getattr__" not supported in class "{cdef.name}" because ' |
| 400 | if ir.allow_interpreted_subclasses: |
| 401 | builder.error(error_base + "it allows interpreted subclasses", line) |
| 402 | if ir.inherits_python: |
| 403 | builder.error(error_base + "it inherits from a non-native class", line) |
| 404 | |
| 405 | with builder.enter_method(ir, name, object_rprimitive, internal=True): |
| 406 | attr_arg = builder.add_argument("attr", object_rprimitive) |
| 407 | generic_getattr_result = builder.call_c(generic_getattr, [builder.self(), attr_arg], line) |
| 408 | |
| 409 | return_generic, call_getattr = BasicBlock(), BasicBlock() |
| 410 | null = Integer(0, object_rprimitive, line) |
| 411 | got_generic = builder.add( |
| 412 | ComparisonOp(generic_getattr_result, null, ComparisonOp.NEQ, line) |
| 413 | ) |
| 414 | builder.add_bool_branch(got_generic, return_generic, call_getattr) |
| 415 | |
| 416 | builder.activate_block(return_generic) |
| 417 | builder.add(Return(generic_getattr_result, line)) |
| 418 | |
| 419 | builder.activate_block(call_getattr) |
| 420 | # No attribute matched so call user-provided __getattr__. |
| 421 | getattr_result = builder.gen_method_call( |
| 422 | builder.self(), getattr.name, [attr_arg], object_rprimitive, line |
| 423 | ) |
| 424 | builder.add(Return(getattr_result, line)) |
| 425 | |
| 426 | |
| 427 | def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef) -> None: |
no test coverage detected
searching dependent graphs…