/usr/lib/python3/dist-packages/mypy/meet.py is in python3-mypy 0.560-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | from collections import OrderedDict
from typing import List, Optional, cast, Tuple
from mypy.join import is_similar_callables, combine_similar_callables, join_type_list
from mypy.types import (
Type, AnyType, TypeVisitor, UnboundType, NoneTyp, TypeVarType, Instance, CallableType,
TupleType, TypedDictType, ErasedType, TypeList, UnionType, PartialType, DeletedType,
UninhabitedType, TypeType, TypeOfAny
)
from mypy.subtypes import is_equivalent, is_subtype, is_protocol_implementation
from mypy import experiments
# TODO Describe this module.
def meet_types(s: Type, t: Type) -> Type:
"""Return the greatest lower bound of two types."""
if isinstance(s, ErasedType):
return s
if isinstance(s, AnyType):
return t
if isinstance(s, UnionType) and not isinstance(t, UnionType):
s, t = t, s
return t.accept(TypeMeetVisitor(s))
def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
"""Return the declared type narrowed down to another type."""
if declared == narrowed:
return declared
if isinstance(declared, UnionType):
return UnionType.make_simplified_union([narrow_declared_type(x, narrowed)
for x in declared.relevant_items()])
elif not is_overlapping_types(declared, narrowed, use_promotions=True):
if experiments.STRICT_OPTIONAL:
return UninhabitedType()
else:
return NoneTyp()
elif isinstance(narrowed, UnionType):
return UnionType.make_simplified_union([narrow_declared_type(declared, x)
for x in narrowed.relevant_items()])
elif isinstance(narrowed, AnyType):
return narrowed
elif isinstance(declared, (Instance, TupleType)):
return meet_types(declared, narrowed)
elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType):
return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item))
return narrowed
def is_overlapping_types(t: Type, s: Type, use_promotions: bool = False) -> bool:
"""Can a value of type t be a value of type s, or vice versa?
Note that this effectively checks against erased types, since type
variables are erased at runtime and the overlapping check is based
on runtime behavior. The exception is protocol types, it is not safe,
but convenient and is an opt-in behavior.
If use_promotions is True, also consider type promotions (int and
float would only be overlapping if it's True).
This does not consider multiple inheritance. For example, A and B in
the following example are not considered overlapping, even though
via C they can be overlapping:
class A: ...
class B: ...
class C(A, B): ...
The rationale is that this case is usually very unlikely as multiple
inheritance is rare. Also, we can't reliably determine whether
multiple inheritance actually occurs somewhere in a program, due to
stub files hiding implementation details, dynamic loading etc.
TODO: Don't consider callables always overlapping.
TODO: Don't consider type variables with values always overlapping.
"""
# Any overlaps with everything
if isinstance(t, AnyType) or isinstance(s, AnyType):
return True
# object overlaps with everything
if (isinstance(t, Instance) and t.type.fullname() == 'builtins.object' or
isinstance(s, Instance) and s.type.fullname() == 'builtins.object'):
return True
# Since we are effectively working with the erased types, we only
# need to handle occurrences of TypeVarType at the top level.
if isinstance(t, TypeVarType):
t = t.erase_to_union_or_bound()
if isinstance(s, TypeVarType):
s = s.erase_to_union_or_bound()
if isinstance(t, TypedDictType):
t = t.as_anonymous().fallback
if isinstance(s, TypedDictType):
s = s.as_anonymous().fallback
if isinstance(t, UnionType):
return any(is_overlapping_types(item, s)
for item in t.relevant_items())
if isinstance(s, UnionType):
return any(is_overlapping_types(t, item)
for item in s.relevant_items())
# We must check for TupleTypes before Instances, since Tuple[A, ...]
# is an Instance
tup_overlap = is_overlapping_tuples(t, s, use_promotions)
if tup_overlap is not None:
return tup_overlap
if isinstance(t, Instance):
if isinstance(s, Instance):
# Consider two classes non-disjoint if one is included in the mro
# of another.
if use_promotions:
# Consider cases like int vs float to be overlapping where
# there is only a type promotion relationship but not proper
# subclassing.
if t.type._promote and is_overlapping_types(t.type._promote, s):
return True
if s.type._promote and is_overlapping_types(s.type._promote, t):
return True
if t.type in s.type.mro or s.type in t.type.mro:
return True
if t.type.is_protocol and is_protocol_implementation(s, t):
return True
if s.type.is_protocol and is_protocol_implementation(t, s):
return True
return False
if isinstance(t, TypeType) and isinstance(s, TypeType):
# If both types are TypeType, compare their inner types.
return is_overlapping_types(t.item, s.item, use_promotions)
elif isinstance(t, TypeType) or isinstance(s, TypeType):
# If exactly only one of t or s is a TypeType, check if one of them
# is an `object` or a `type` and otherwise assume no overlap.
one = t if isinstance(t, TypeType) else s
other = s if isinstance(t, TypeType) else t
if isinstance(other, Instance):
return other.type.fullname() in {'builtins.object', 'builtins.type'}
else:
return isinstance(other, CallableType) and is_subtype(other, one)
if experiments.STRICT_OPTIONAL:
if isinstance(t, NoneTyp) != isinstance(s, NoneTyp):
# NoneTyp does not overlap with other non-Union types under strict Optional checking
return False
# We conservatively assume that non-instance, non-union, non-TupleType and non-TypeType types
# can overlap any other types.
return True
def is_overlapping_tuples(t: Type, s: Type, use_promotions: bool) -> Optional[bool]:
"""Part of is_overlapping_types(), for tuples only"""
t = adjust_tuple(t, s) or t
s = adjust_tuple(s, t) or s
if isinstance(t, TupleType) or isinstance(s, TupleType):
if isinstance(t, TupleType) and isinstance(s, TupleType):
if t.length() == s.length():
if all(is_overlapping_types(ti, si, use_promotions)
for ti, si in zip(t.items, s.items)):
return True
# TupleType and non-tuples do not overlap
return False
# No tuples are involved here
return None
def adjust_tuple(left: Type, r: Type) -> Optional[TupleType]:
"""Find out if `left` is a Tuple[A, ...], and adjust its length to `right`"""
if isinstance(left, Instance) and left.type.fullname() == 'builtins.tuple':
n = r.length() if isinstance(r, TupleType) else 1
return TupleType([left.args[0]] * n, left)
return None
class TypeMeetVisitor(TypeVisitor[Type]):
def __init__(self, s: Type) -> None:
self.s = s
def visit_unbound_type(self, t: UnboundType) -> Type:
if isinstance(self.s, NoneTyp):
if experiments.STRICT_OPTIONAL:
return AnyType(TypeOfAny.special_form)
else:
return self.s
elif isinstance(self.s, UninhabitedType):
return self.s
else:
return AnyType(TypeOfAny.special_form)
def visit_any(self, t: AnyType) -> Type:
return self.s
def visit_union_type(self, t: UnionType) -> Type:
if isinstance(self.s, UnionType):
meets = [] # type: List[Type]
for x in t.items:
for y in self.s.items:
meets.append(meet_types(x, y))
else:
meets = [meet_types(x, self.s)
for x in t.items]
return UnionType.make_simplified_union(meets)
def visit_none_type(self, t: NoneTyp) -> Type:
if experiments.STRICT_OPTIONAL:
if isinstance(self.s, NoneTyp) or (isinstance(self.s, Instance) and
self.s.type.fullname() == 'builtins.object'):
return t
else:
return UninhabitedType()
else:
return t
def visit_uninhabited_type(self, t: UninhabitedType) -> Type:
return t
def visit_deleted_type(self, t: DeletedType) -> Type:
if isinstance(self.s, NoneTyp):
if experiments.STRICT_OPTIONAL:
return t
else:
return self.s
elif isinstance(self.s, UninhabitedType):
return self.s
else:
return t
def visit_erased_type(self, t: ErasedType) -> Type:
return self.s
def visit_type_var(self, t: TypeVarType) -> Type:
if isinstance(self.s, TypeVarType) and self.s.id == t.id:
return self.s
else:
return self.default(self.s)
def visit_instance(self, t: Instance) -> Type:
if isinstance(self.s, Instance):
si = self.s
if t.type == si.type:
if is_subtype(t, self.s) or is_subtype(self.s, t):
# Combine type arguments. We could have used join below
# equivalently.
args = [] # type: List[Type]
for i in range(len(t.args)):
args.append(self.meet(t.args[i], si.args[i]))
return Instance(t.type, args)
else:
if experiments.STRICT_OPTIONAL:
return UninhabitedType()
else:
return NoneTyp()
else:
if is_subtype(t, self.s):
return t
elif is_subtype(self.s, t):
# See also above comment.
return self.s
else:
if experiments.STRICT_OPTIONAL:
return UninhabitedType()
else:
return NoneTyp()
elif isinstance(self.s, TypeType):
return meet_types(t, self.s)
elif isinstance(self.s, TupleType):
return meet_types(t, self.s)
else:
return self.default(self.s)
def visit_callable_type(self, t: CallableType) -> Type:
if isinstance(self.s, CallableType) and is_similar_callables(t, self.s):
if is_equivalent(t, self.s):
return combine_similar_callables(t, self.s)
result = meet_similar_callables(t, self.s)
if isinstance(result.ret_type, UninhabitedType):
# Return a plain None or <uninhabited> instead of a weird function.
return self.default(self.s)
return result
else:
return self.default(self.s)
def visit_tuple_type(self, t: TupleType) -> Type:
if isinstance(self.s, TupleType) and self.s.length() == t.length():
items = [] # type: List[Type]
for i in range(t.length()):
items.append(self.meet(t.items[i], self.s.items[i]))
# TODO: What if the fallbacks are different?
return TupleType(items, t.fallback)
# meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>].
elif (isinstance(self.s, Instance) and
self.s.type.fullname() == 'builtins.tuple' and self.s.args):
return t.copy_modified(items=[meet_types(it, self.s.args[0]) for it in t.items])
elif (isinstance(self.s, Instance) and t.fallback.type == self.s.type):
# Uh oh, a broken named tuple type (https://github.com/python/mypy/issues/3016).
# Do something reasonable until that bug is fixed.
return t
else:
return self.default(self.s)
def visit_typeddict_type(self, t: TypedDictType) -> Type:
if isinstance(self.s, TypedDictType):
for (name, l, r) in self.s.zip(t):
if (not is_equivalent(l, r) or
(name in t.required_keys) != (name in self.s.required_keys)):
return self.default(self.s)
item_list = [] # type: List[Tuple[str, Type]]
for (item_name, s_item_type, t_item_type) in self.s.zipall(t):
if s_item_type is not None:
item_list.append((item_name, s_item_type))
else:
# at least one of s_item_type and t_item_type is not None
assert t_item_type is not None
item_list.append((item_name, t_item_type))
items = OrderedDict(item_list)
mapping_value_type = join_type_list(list(items.values()))
fallback = self.s.create_anonymous_fallback(value_type=mapping_value_type)
required_keys = t.required_keys | self.s.required_keys
return TypedDictType(items, required_keys, fallback)
else:
return self.default(self.s)
def visit_partial_type(self, t: PartialType) -> Type:
# We can't determine the meet of partial types. We should never get here.
assert False, 'Internal error'
def visit_type_type(self, t: TypeType) -> Type:
if isinstance(self.s, TypeType):
typ = self.meet(t.item, self.s.item)
if not isinstance(typ, NoneTyp):
typ = TypeType.make_normalized(typ, line=t.line)
return typ
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
return t
else:
return self.default(self.s)
def meet(self, s: Type, t: Type) -> Type:
return meet_types(s, t)
def default(self, typ: Type) -> Type:
if isinstance(typ, UnboundType):
return AnyType(TypeOfAny.special_form)
else:
if experiments.STRICT_OPTIONAL:
return UninhabitedType()
else:
return NoneTyp()
def meet_similar_callables(t: CallableType, s: CallableType) -> CallableType:
from mypy.join import join_types
arg_types = [] # type: List[Type]
for i in range(len(t.arg_types)):
arg_types.append(join_types(t.arg_types[i], s.arg_types[i]))
# TODO in combine_similar_callables also applies here (names and kinds)
# The fallback type can be either 'function' or 'type'. The result should have 'function' as
# fallback only if both operands have it as 'function'.
if t.fallback.type.fullname() != 'builtins.function':
fallback = t.fallback
else:
fallback = s.fallback
return t.copy_modified(arg_types=arg_types,
ret_type=meet_types(t.ret_type, s.ret_type),
fallback=fallback,
name=None)
|