in chz/tiepin.py [0:0]
def is_subtype(left: TypeForm, right: TypeForm) -> bool:
left_origin = getattr(left, "__origin__", left)
left_args = getattr(left, "__args__", ())
right_origin = getattr(right, "__origin__", right)
right_args = getattr(right, "__args__", ())
if left_origin is typing.Any or left_origin is typing_extensions.Any:
return True
if right_origin is typing.Any or right_origin is typing_extensions.Any:
return True
if left_origin is None:
if right_origin is None or right_origin is type(None):
return True
if is_union_type(right_origin):
if is_union_type(left_origin):
possible_left_types = left_args
else:
possible_left_types = [left]
return all(
any(is_subtype(possible_left, right_arg) for right_arg in right_args)
for possible_left in possible_left_types
)
if right_origin is typing.Literal or right_origin is typing_extensions.Literal:
if left_origin is typing.Literal or left_origin is typing_extensions.Literal:
return all(left_arg in right_args for left_arg in left_args)
return False
if left_origin is typing.Literal or left_origin is typing_extensions.Literal:
return all(is_subtype_instance(left_arg, right) for left_arg in left_args)
if isinstance(left_origin, typing.TypeVar):
if left_origin == right_origin:
return True
bound = left_origin.__bound__
if bound is None:
bound = object
if is_subtype(bound, right_origin):
return True
if left_origin.__constraints__:
return any(
is_subtype(left_arg, right_origin) for left_arg in left_origin.__constraints__
)
return False
if typing_extensions.is_protocol(left) and typing_extensions.is_protocol(right):
left_attrs = typing_extensions.get_protocol_members(left)
right_attrs = typing_extensions.get_protocol_members(right)
if not right_attrs.issubset(left_attrs):
return False
# TODO: this is incorrect
return True
if typing_extensions.is_protocol(right):
if not isinstance(left_origin, type):
return False
right_attrs = typing_extensions.get_protocol_members(right)
if not all(hasattr(left_origin, attr) for attr in right_attrs):
return False
# TODO: this is incorrect
return True
if isinstance(left, _SignatureOf) and isinstance(right, _SignatureOf):
empty = inspect.Parameter.empty
for left_param, right_param in zip(left.pos, right.pos):
if right_param.kind is right_param.POSITIONAL_OR_KEYWORD:
if right_param.name != left_param.name:
return False
if left_param.kind is left_param.POSITIONAL_ONLY:
return False
if right_param.default is not empty and left_param.default is empty:
return False
left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)
right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)
if not is_subtype(right_param_annot, left_param_annot):
return False
if len(left.pos) < len(right.pos):
# Okay if left has a *args that accepts all the extra args
if left.varpos is None:
return False
left_varpos_annot = maybe_eval_in_context(left.varpos.annotation, left.fn)
for i in range(len(left.pos), len(right.pos)):
right_param = right.pos[i]
right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)
if not is_subtype(right_param_annot, left_varpos_annot):
return False
if len(left.pos) > len(right.pos):
# Must either have a default or correspond to a required keyword-only arg
for i in range(len(right.pos), len(left.pos)):
left_param = left.pos[i]
if left_param.default is not empty:
continue
if (
left_param.name in right.kwonly
and left_param.kind is left_param.POSITIONAL_OR_KEYWORD
):
continue
return False
for name in left.kwonly.keys() & right.kwonly.keys():
right_param = right.kwonly[name]
left_param = left.kwonly[name]
if right_param.default is not empty and left_param.default is empty:
return False
left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)
right_param_annot = maybe_eval_in_context(right_param.annotation, right.fn)
if not is_subtype(right_param_annot, left_param_annot):
return False
for name in left.kwonly.keys() - right.kwonly.keys():
# Must either have a default or match a varkwarg
left_param = left.kwonly[name]
if left_param.default is not empty:
continue
if right.varkw is not None:
left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)
right_varkw_annot = maybe_eval_in_context(right.varkw.annotation, right.fn)
if is_subtype(right_varkw_annot, left_param_annot):
continue
return False
right_only_kwonly = right.kwonly.keys() - left.kwonly.keys()
if right_only_kwonly:
# Must correspond to a positional-or-keyword arg
left_pos_or_kw = {p.name: p for p in left.pos if p.kind is p.POSITIONAL_OR_KEYWORD}
for name in right_only_kwonly:
if name not in left_pos_or_kw:
return False
left_param = left_pos_or_kw[name]
if right.kwonly[name].default is not empty and left_param.default is empty:
return False
left_param_annot = maybe_eval_in_context(left_param.annotation, left.fn)
right_param_annot = maybe_eval_in_context(right.kwonly[name].annotation, right.fn)
if not is_subtype(right_param_annot, left_param_annot):
return False
if right.varkw is not None:
if left.varkw is None:
return False
right_varkw_annot = maybe_eval_in_context(right.varkw.annotation, right.fn)
left_varkw_annot = maybe_eval_in_context(left.varkw.annotation, left.fn)
if not is_subtype(right_varkw_annot, left_varkw_annot):
return False
if right.ret is not empty and left.ret is not empty:
# TODO: handle Cls.__init__ like below
if not is_subtype(left.ret, right.ret):
return False
return True
if left_origin is collections.abc.Callable and right_origin is collections.abc.Callable:
*left_params, left_ret = left_args
*right_params, right_ret = right_args
if len(left_params) != len(right_params):
return False
if not is_subtype(left_ret, right_ret):
return False
return all(
is_subtype(right_param, left_param)
for left_param, right_param in zip(left_params, right_params)
)
# TODO: handle other special forms
try:
if not issubclass(left_origin, right_origin):
return False
except TypeError:
return False
# see comments in is_subtype_instance
# TODO: add invariance
# TODO: think about some of this logic more carefully
if hasattr(left_origin, "__class_getitem__") and hasattr(right_origin, "__class_getitem__"):
if (
issubclass(right_origin, collections.abc.Mapping)
and typing.Generic not in left_origin.__mro__
and typing.Generic not in right_origin.__mro__
):
if left_args:
left_key, left_value = left_args
else:
left_key, left_value = typing.Any, typing.Any
if right_args:
right_key, right_value = right_args
else:
right_key, right_value = typing.Any, typing.Any
return is_subtype(left_key, right_key) and is_subtype(left_value, right_value)
if left_origin is tuple and right_origin is tuple:
if not left_args:
left_args = (typing.Any, ...)
if not right_args:
right_args = (typing.Any, ...)
if len(right_args) == 2 and right_args[1] is ...:
return all(is_subtype(left_arg, right_args[0]) for left_arg in left_args)
if len(left_args) == 2 and left_args[1] is ...:
return False
return len(left_args) == len(right_args) and all(
is_subtype(left_arg, right_arg)
for left_arg, right_arg in zip(left_args, right_args)
)
if (
issubclass(right_origin, collections.abc.Iterable)
and typing.Generic not in left_origin.__mro__
and typing.Generic not in right_origin.__mro__
):
if left_args:
(left_item,) = left_args
else:
left_item = typing.Any
if right_args:
(right_item,) = right_args
else:
right_item = typing.Any
return is_subtype(left_item, right_item)
return True