|
5 | 5 | from contextlib import contextmanager
|
6 | 6 |
|
7 | 7 | from typing import (
|
8 |
| - Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Sequence |
| 8 | + Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Sequence, |
| 9 | + Mapping, |
9 | 10 | )
|
10 | 11 | from typing_extensions import Final
|
11 | 12 |
|
|
42 | 43 | )
|
43 | 44 | import mypy.checkexpr
|
44 | 45 | from mypy.checkmember import (
|
45 |
| - analyze_descriptor_access, type_object_type, |
| 46 | + analyze_member_access, analyze_descriptor_access, type_object_type, |
46 | 47 | )
|
47 | 48 | from mypy.typeops import (
|
48 | 49 | map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
|
49 |
| - erase_def_to_union_or_bound, erase_to_union_or_bound, |
50 |
| - true_only, false_only, function_type, TypeVarExtractor |
| 50 | + erase_def_to_union_or_bound, erase_to_union_or_bound, coerce_to_literal, |
| 51 | + try_getting_str_literals_from_type, try_getting_int_literals_from_type, |
| 52 | + tuple_fallback, is_singleton_type, try_expanding_enum_to_union, |
| 53 | + true_only, false_only, function_type, TypeVarExtractor, |
51 | 54 | )
|
52 | 55 | from mypy import message_registry
|
53 | 56 | from mypy.subtypes import (
|
|
71 | 74 | from mypy.plugin import Plugin, CheckerPluginInterface
|
72 | 75 | from mypy.sharedparse import BINARY_MAGIC_METHODS
|
73 | 76 | from mypy.scope import Scope
|
74 |
| -from mypy.typeops import ( |
75 |
| - tuple_fallback, coerce_to_literal, is_singleton_type, try_expanding_enum_to_union |
76 |
| -) |
77 | 77 | from mypy import state, errorcodes as codes
|
78 | 78 | from mypy.traverser import has_return_statement, all_return_statements
|
79 | 79 | from mypy.errorcodes import ErrorCode
|
@@ -3708,6 +3708,12 @@ def find_isinstance_check(self, node: Expression
|
3708 | 3708 |
|
3709 | 3709 | Guaranteed to not return None, None. (But may return {}, {})
|
3710 | 3710 | """
|
| 3711 | + if_map, else_map = self.find_isinstance_check_helper(node) |
| 3712 | + new_if_map = self.propagate_up_typemap_info(self.type_map, if_map) |
| 3713 | + new_else_map = self.propagate_up_typemap_info(self.type_map, else_map) |
| 3714 | + return new_if_map, new_else_map |
| 3715 | + |
| 3716 | + def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeMap]: |
3711 | 3717 | type_map = self.type_map
|
3712 | 3718 | if is_true_literal(node):
|
3713 | 3719 | return {}, None
|
@@ -3834,28 +3840,196 @@ def find_isinstance_check(self, node: Expression
|
3834 | 3840 | else None)
|
3835 | 3841 | return if_map, else_map
|
3836 | 3842 | elif isinstance(node, OpExpr) and node.op == 'and':
|
3837 |
| - left_if_vars, left_else_vars = self.find_isinstance_check(node.left) |
3838 |
| - right_if_vars, right_else_vars = self.find_isinstance_check(node.right) |
| 3843 | + left_if_vars, left_else_vars = self.find_isinstance_check_helper(node.left) |
| 3844 | + right_if_vars, right_else_vars = self.find_isinstance_check_helper(node.right) |
3839 | 3845 |
|
3840 | 3846 | # (e1 and e2) is true if both e1 and e2 are true,
|
3841 | 3847 | # and false if at least one of e1 and e2 is false.
|
3842 | 3848 | return (and_conditional_maps(left_if_vars, right_if_vars),
|
3843 | 3849 | or_conditional_maps(left_else_vars, right_else_vars))
|
3844 | 3850 | elif isinstance(node, OpExpr) and node.op == 'or':
|
3845 |
| - left_if_vars, left_else_vars = self.find_isinstance_check(node.left) |
3846 |
| - right_if_vars, right_else_vars = self.find_isinstance_check(node.right) |
| 3851 | + left_if_vars, left_else_vars = self.find_isinstance_check_helper(node.left) |
| 3852 | + right_if_vars, right_else_vars = self.find_isinstance_check_helper(node.right) |
3847 | 3853 |
|
3848 | 3854 | # (e1 or e2) is true if at least one of e1 or e2 is true,
|
3849 | 3855 | # and false if both e1 and e2 are false.
|
3850 | 3856 | return (or_conditional_maps(left_if_vars, right_if_vars),
|
3851 | 3857 | and_conditional_maps(left_else_vars, right_else_vars))
|
3852 | 3858 | elif isinstance(node, UnaryExpr) and node.op == 'not':
|
3853 |
| - left, right = self.find_isinstance_check(node.expr) |
| 3859 | + left, right = self.find_isinstance_check_helper(node.expr) |
3854 | 3860 | return right, left
|
3855 | 3861 |
|
3856 | 3862 | # Not a supported isinstance check
|
3857 | 3863 | return {}, {}
|
3858 | 3864 |
|
| 3865 | + def propagate_up_typemap_info(self, |
| 3866 | + existing_types: Mapping[Expression, Type], |
| 3867 | + new_types: TypeMap) -> TypeMap: |
| 3868 | + """Attempts refining parent expressions of any MemberExpr or IndexExprs in new_types. |
| 3869 | +
|
| 3870 | + Specifically, this function accepts two mappings of expression to original types: |
| 3871 | + the original mapping (existing_types), and a new mapping (new_types) intended to |
| 3872 | + update the original. |
| 3873 | +
|
| 3874 | + This function iterates through new_types and attempts to use the information to try |
| 3875 | + refining any parent types that happen to be unions. |
| 3876 | +
|
| 3877 | + For example, suppose there are two types "A = Tuple[int, int]" and "B = Tuple[str, str]". |
| 3878 | + Next, suppose that 'new_types' specifies the expression 'foo[0]' has a refined type |
| 3879 | + of 'int' and that 'foo' was previously deduced to be of type Union[A, B]. |
| 3880 | +
|
| 3881 | + Then, this function will observe that since A[0] is an int and B[0] is not, the type of |
| 3882 | + 'foo' can be further refined from Union[A, B] into just B. |
| 3883 | +
|
| 3884 | + We perform this kind of "parent narrowing" for member lookup expressions and indexing |
| 3885 | + expressions into tuples, namedtuples, and typeddicts. We repeat this narrowing |
| 3886 | + recursively if the parent is also a "lookup expression". So for example, if we have |
| 3887 | + the expression "foo['bar'].baz[0]", we'd potentially end up refining types for the |
| 3888 | + expressions "foo", "foo['bar']", and "foo['bar'].baz". |
| 3889 | +
|
| 3890 | + We return the newly refined map. This map is guaranteed to be a superset of 'new_types'. |
| 3891 | + """ |
| 3892 | + if new_types is None: |
| 3893 | + return None |
| 3894 | + output_map = {} |
| 3895 | + for expr, expr_type in new_types.items(): |
| 3896 | + # The original inferred type should always be present in the output map, of course |
| 3897 | + output_map[expr] = expr_type |
| 3898 | + |
| 3899 | + # Next, try using this information to refine the parent types, if applicable. |
| 3900 | + new_mapping = self.refine_parent_types(existing_types, expr, expr_type) |
| 3901 | + for parent_expr, proposed_parent_type in new_mapping.items(): |
| 3902 | + # We don't try inferring anything if we've already inferred something for |
| 3903 | + # the parent expression. |
| 3904 | + # TODO: Consider picking the narrower type instead of always discarding this? |
| 3905 | + if parent_expr in new_types: |
| 3906 | + continue |
| 3907 | + output_map[parent_expr] = proposed_parent_type |
| 3908 | + return output_map |
| 3909 | + |
| 3910 | + def refine_parent_types(self, |
| 3911 | + existing_types: Mapping[Expression, Type], |
| 3912 | + expr: Expression, |
| 3913 | + expr_type: Type) -> Mapping[Expression, Type]: |
| 3914 | + """Checks if the given expr is a 'lookup operation' into a union and iteratively refines |
| 3915 | + the parent types based on the 'expr_type'. |
| 3916 | +
|
| 3917 | + For example, if 'expr' is an expression like 'a.b.c.d', we'll potentially return refined |
| 3918 | + types for expressions 'a', 'a.b', and 'a.b.c'. |
| 3919 | +
|
| 3920 | + For more details about what a 'lookup operation' is and how we use the expr_type to refine |
| 3921 | + the parent types of lookup_expr, see the docstring in 'propagate_up_typemap_info'. |
| 3922 | + """ |
| 3923 | + output = {} # type: Dict[Expression, Type] |
| 3924 | + |
| 3925 | + # Note: parent_expr and parent_type are progressively refined as we crawl up the |
| 3926 | + # parent lookup chain. |
| 3927 | + while True: |
| 3928 | + # First, check if this expression is one that's attempting to |
| 3929 | + # "lookup" some key in the parent type. If so, save the parent type |
| 3930 | + # and create function that will try replaying the same lookup |
| 3931 | + # operation against arbitrary types. |
| 3932 | + if isinstance(expr, MemberExpr): |
| 3933 | + parent_expr = expr.expr |
| 3934 | + parent_type = existing_types.get(parent_expr) |
| 3935 | + member_name = expr.name |
| 3936 | + |
| 3937 | + def replay_lookup(new_parent_type: ProperType) -> Optional[Type]: |
| 3938 | + msg_copy = self.msg.clean_copy() |
| 3939 | + msg_copy.disable_count = 0 |
| 3940 | + member_type = analyze_member_access( |
| 3941 | + name=member_name, |
| 3942 | + typ=new_parent_type, |
| 3943 | + context=parent_expr, |
| 3944 | + is_lvalue=False, |
| 3945 | + is_super=False, |
| 3946 | + is_operator=False, |
| 3947 | + msg=msg_copy, |
| 3948 | + original_type=new_parent_type, |
| 3949 | + chk=self, |
| 3950 | + in_literal_context=False, |
| 3951 | + ) |
| 3952 | + if msg_copy.is_errors(): |
| 3953 | + return None |
| 3954 | + else: |
| 3955 | + return member_type |
| 3956 | + elif isinstance(expr, IndexExpr): |
| 3957 | + parent_expr = expr.base |
| 3958 | + parent_type = existing_types.get(parent_expr) |
| 3959 | + |
| 3960 | + index_type = existing_types.get(expr.index) |
| 3961 | + if index_type is None: |
| 3962 | + return output |
| 3963 | + |
| 3964 | + str_literals = try_getting_str_literals_from_type(index_type) |
| 3965 | + if str_literals is not None: |
| 3966 | + # Refactoring these two indexing replay functions is surprisingly |
| 3967 | + # tricky -- see https://github.com/python/mypy/pull/7917, which |
| 3968 | + # was blocked by https://github.com/mypyc/mypyc/issues/586 |
| 3969 | + def replay_lookup(new_parent_type: ProperType) -> Optional[Type]: |
| 3970 | + if not isinstance(new_parent_type, TypedDictType): |
| 3971 | + return None |
| 3972 | + try: |
| 3973 | + assert str_literals is not None |
| 3974 | + member_types = [new_parent_type.items[key] for key in str_literals] |
| 3975 | + except KeyError: |
| 3976 | + return None |
| 3977 | + return make_simplified_union(member_types) |
| 3978 | + else: |
| 3979 | + int_literals = try_getting_int_literals_from_type(index_type) |
| 3980 | + if int_literals is not None: |
| 3981 | + def replay_lookup(new_parent_type: ProperType) -> Optional[Type]: |
| 3982 | + if not isinstance(new_parent_type, TupleType): |
| 3983 | + return None |
| 3984 | + try: |
| 3985 | + assert int_literals is not None |
| 3986 | + member_types = [new_parent_type.items[key] for key in int_literals] |
| 3987 | + except IndexError: |
| 3988 | + return None |
| 3989 | + return make_simplified_union(member_types) |
| 3990 | + else: |
| 3991 | + return output |
| 3992 | + else: |
| 3993 | + return output |
| 3994 | + |
| 3995 | + # If we somehow didn't previously derive the parent type, abort completely |
| 3996 | + # with what we have so far: something went wrong at an earlier stage. |
| 3997 | + if parent_type is None: |
| 3998 | + return output |
| 3999 | + |
| 4000 | + # We currently only try refining the parent type if it's a Union. |
| 4001 | + # If not, there's no point in trying to refine any further parents |
| 4002 | + # since we have no further information we can use to refine the lookup |
| 4003 | + # chain, so we end early as an optimization. |
| 4004 | + parent_type = get_proper_type(parent_type) |
| 4005 | + if not isinstance(parent_type, UnionType): |
| 4006 | + return output |
| 4007 | + |
| 4008 | + # Take each element in the parent union and replay the original lookup procedure |
| 4009 | + # to figure out which parents are compatible. |
| 4010 | + new_parent_types = [] |
| 4011 | + for item in parent_type.items: |
| 4012 | + item = get_proper_type(item) |
| 4013 | + member_type = replay_lookup(item) |
| 4014 | + if member_type is None: |
| 4015 | + # We were unable to obtain the member type. So, we give up on refining this |
| 4016 | + # parent type entirely and abort. |
| 4017 | + return output |
| 4018 | + |
| 4019 | + if is_overlapping_types(member_type, expr_type): |
| 4020 | + new_parent_types.append(item) |
| 4021 | + |
| 4022 | + # If none of the parent types overlap (if we derived an empty union), something |
| 4023 | + # went wrong. We should never hit this case, but deriving the uninhabited type or |
| 4024 | + # reporting an error both seem unhelpful. So we abort. |
| 4025 | + if not new_parent_types: |
| 4026 | + return output |
| 4027 | + |
| 4028 | + expr = parent_expr |
| 4029 | + expr_type = output[parent_expr] = make_simplified_union(new_parent_types) |
| 4030 | + |
| 4031 | + return output |
| 4032 | + |
3859 | 4033 | #
|
3860 | 4034 | # Helpers
|
3861 | 4035 | #
|
|
0 commit comments