Type check descriptor access. Arguments: descriptor_type: The type of the descriptor attribute being accessed (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). mx: The current member access context. Return: The return type of the appropriate ``__
(descriptor_type: Type, mx: MemberContext)
| 665 | |
| 666 | |
| 667 | def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: |
| 668 | """Type check descriptor access. |
| 669 | |
| 670 | Arguments: |
| 671 | descriptor_type: The type of the descriptor attribute being accessed |
| 672 | (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). |
| 673 | mx: The current member access context. |
| 674 | Return: |
| 675 | The return type of the appropriate ``__get__/__set__`` overload for the descriptor. |
| 676 | """ |
| 677 | instance_type = get_proper_type(mx.self_type) |
| 678 | orig_descriptor_type = descriptor_type |
| 679 | descriptor_type = get_proper_type(descriptor_type) |
| 680 | |
| 681 | if isinstance(descriptor_type, UnionType): |
| 682 | # Map the access over union types |
| 683 | return make_simplified_union( |
| 684 | [analyze_descriptor_access(typ, mx) for typ in descriptor_type.items] |
| 685 | ) |
| 686 | elif not isinstance(descriptor_type, Instance): |
| 687 | return orig_descriptor_type |
| 688 | |
| 689 | if not mx.is_lvalue and not descriptor_type.type.has_readable_member("__get__"): |
| 690 | return orig_descriptor_type |
| 691 | |
| 692 | # We do this check first to accommodate for descriptors with only __set__ method. |
| 693 | # If there is no __set__, we type-check that the assigned value matches |
| 694 | # the return type of __get__. This doesn't match the python semantics, |
| 695 | # (which allow you to override the descriptor with any value), but preserves |
| 696 | # the type of accessing the attribute (even after the override). |
| 697 | if mx.is_lvalue and descriptor_type.type.has_readable_member("__set__"): |
| 698 | return analyze_descriptor_assign(descriptor_type, mx) |
| 699 | |
| 700 | if mx.is_lvalue and not descriptor_type.type.has_readable_member("__get__"): |
| 701 | # This turned out to be not a descriptor after all. |
| 702 | return orig_descriptor_type |
| 703 | |
| 704 | dunder_get = descriptor_type.type.get_method("__get__") |
| 705 | if dunder_get is None: |
| 706 | mx.fail( |
| 707 | message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( |
| 708 | descriptor_type.str_with_options(mx.msg.options) |
| 709 | ) |
| 710 | ) |
| 711 | return AnyType(TypeOfAny.from_error) |
| 712 | |
| 713 | bound_method = analyze_decorator_or_funcbase_access( |
| 714 | defn=dunder_get, |
| 715 | itype=descriptor_type, |
| 716 | name="__get__", |
| 717 | mx=mx.copy_modified(self_type=descriptor_type), |
| 718 | ) |
| 719 | |
| 720 | typ = map_instance_to_supertype(descriptor_type, dunder_get.info) |
| 721 | dunder_get_type = expand_type_by_instance(bound_method, typ) |
| 722 | |
| 723 | if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): |
| 724 | owner_type = instance_type.items[0].ret_type |
no test coverage detected
searching dependent graphs…