Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot. The wrapper takes two arguments besides self - attribute name and the new value. Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__ wrapper above. The wrapper
(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef)
| 425 | |
| 426 | |
| 427 | def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef) -> None: |
| 428 | """ |
| 429 | Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot. |
| 430 | The wrapper takes two arguments besides self - attribute name and the new value. |
| 431 | Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__ |
| 432 | wrapper above. |
| 433 | |
| 434 | The wrapper calls the user-defined __setattr__ when the value to set is not NULL. |
| 435 | When it's NULL, this means that the call to tp_setattro comes from a del statement, |
| 436 | so it calls __delattr__ instead. If __delattr__ is not overridden in the native class, |
| 437 | this will call the base implementation in object which doesn't work without __dict__. |
| 438 | """ |
| 439 | name = setattr.name + "__wrapper" |
| 440 | ir = builder.mapper.type_to_ir[cdef.info] |
| 441 | line = setattr.line |
| 442 | |
| 443 | error_base = f'"__setattr__" not supported in class "{cdef.name}" because ' |
| 444 | if ir.allow_interpreted_subclasses: |
| 445 | builder.error(error_base + "it allows interpreted subclasses", line) |
| 446 | if ir.inherits_python: |
| 447 | builder.error(error_base + "it inherits from a non-native class", line) |
| 448 | |
| 449 | with builder.enter_method(ir, name, c_int_rprimitive, internal=True): |
| 450 | attr_arg = builder.add_argument("attr", object_rprimitive) |
| 451 | value_arg = builder.add_argument("value", object_rprimitive) |
| 452 | |
| 453 | call_delattr, call_setattr = BasicBlock(), BasicBlock() |
| 454 | null = Integer(0, object_rprimitive, line) |
| 455 | is_delattr = builder.add(ComparisonOp(value_arg, null, ComparisonOp.EQ, line)) |
| 456 | builder.add_bool_branch(is_delattr, call_delattr, call_setattr) |
| 457 | |
| 458 | builder.activate_block(call_delattr) |
| 459 | delattr_symbol = cdef.info.get("__delattr__") |
| 460 | delattr = delattr_symbol.node if delattr_symbol else None |
| 461 | delattr_override = delattr is not None and not delattr.fullname.startswith("builtins.") |
| 462 | if delattr_override: |
| 463 | builder.gen_method_call(builder.self(), "__delattr__", [attr_arg], None, line) |
| 464 | else: |
| 465 | # Call internal function that cpython normally calls when deleting an attribute. |
| 466 | # Cannot call object.__delattr__ here because it calls PyObject_SetAttr internally |
| 467 | # which in turn calls our wrapper and recurses infinitely. |
| 468 | # Note that since native classes don't have __dict__, this will raise AttributeError |
| 469 | # for dynamic attributes. |
| 470 | builder.call_c(generic_setattr, [builder.self(), attr_arg, null], line) |
| 471 | builder.add(Return(Integer(0, c_int_rprimitive), line)) |
| 472 | |
| 473 | builder.activate_block(call_setattr) |
| 474 | builder.gen_method_call(builder.self(), setattr.name, [attr_arg, value_arg], None, line) |
| 475 | builder.add(Return(Integer(0, c_int_rprimitive), line)) |
| 476 | |
| 477 | |
| 478 | def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None: |
no test coverage detected
searching dependent graphs…