示例#1
0
    def _rewrite(self, typ: type):
        if isinstance(typ, _Any):
            return typ

        arg_classes = getattr(typ, '__args__', [])
        if not arg_classes:
            return typ

        if typing_inspect.is_generic_type(typ) or typing_inspect.is_tuple_type(
                typ):
            gorg = typing_inspect.get_origin(typ)

            processed_args = []
            for arg_class in arg_classes:
                processed = self._rewrite(arg_class)
                if processed is NOT_SIMPLIFIED:
                    processed = arg_class

                processed_args.append(processed)

            return gorg[tuple(processed_args)]

        processed_args = []
        for arg_class in arg_classes:
            rewritten = self._rewrite(arg_class)
            if rewritten is NOT_SIMPLIFIED:
                processed_args.append(arg_class)
                continue

            processed_args.append(rewritten)

        new_union = make_union(processed_args)
        new_union = self.union_rewriter.rewrite_Union(new_union)

        return new_union
示例#2
0
    def rewrite(self, typ: type):
        if isinstance(typ, _Any):
            return typ

        arg_classes = getattr(typ, '__args__', None)
        if not arg_classes:
            return typ

        if typing_inspect.is_tuple_type(typ):
            processed_args = []
            for arg_class in arg_classes:
                processed_args.append(self.rewrite(arg_class))
            tup = Tuple[tuple(processed_args)]
            return self._simplify_big_tuple(tup)

        if hasattr(typ, '_gorg'):
            processed_args = []
            for arg_class in arg_classes:
                processed_args.append(self.rewrite(arg_class))

            simplified = typ._gorg[tuple(processed_args)]
            return simplified

        processed_args = []
        for arg_class in arg_classes:
            rewritten = self.rewrite(arg_class)
            if not isinstance(rewritten, _Any):
                processed_args.append(rewritten)

        if not processed_args:
            return Any

        new_union = make_union(processed_args)
        return new_union
示例#3
0
 def test_tuple(self):
     samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]]
     nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])]
     self.sample_test(is_tuple_type, samples, nonsamples)
     class MyClass(Tuple[str, int]):
         pass
     self.assertTrue(is_tuple_type(MyClass))
示例#4
0
def simplify_tuples(tup1, tup2, two_element_transformers):
    for t in [tup1, tup2]:
        if not typing_inspect.is_tuple_type(t):
            return NOT_SIMPLIFIED

        args = t.__args__
        if not args:
            return NOT_SIMPLIFIED

    transformed = []
    merge_used = False
    for i, (obj1_arg, obj2_arg) in enumerate(zip(tup1.__args__,
                                                 tup2.__args__)):
        simplified = False
        for transformer in [collapse_unions, *two_element_transformers]:
            transformed_obj = transformer(obj1_arg, obj2_arg)
            if transformed_obj is not NOT_SIMPLIFIED:
                transformed.append(transformed_obj)
                simplified = True
                break

        if not simplified:
            if not merge_used:
                transformed.append(make_union([obj1_arg, obj2_arg]))
                merge_used = True
            else:
                return NOT_SIMPLIFIED

    return Tuple[tuple(transformed)]
示例#5
0
文件: base.py 项目: signupsi/edgedb
def _check_annotation(f_type, f_fullname, f_default):
    if typing_inspect.is_tuple_type(f_type):
        if f_default is not None:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'default is defined for tuple type')

        f_default = tuple

    elif typing_inspect.is_union_type(f_type):
        for t in typing_inspect.get_args(f_type, evaluate=True):
            _check_annotation(t, f_fullname, f_default)

    elif typing_inspect.is_generic_type(f_type):
        if f_default is not None:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'default is defined for container type '
                               f'{f_type!r}')

        ot = typing_inspect.get_origin(f_type)
        if ot is None:
            raise RuntimeError(
                f'cannot find origin of a generic type {f_type}')

        if ot in (list, List, collections.abc.Sequence):
            f_default = list
        elif ot in (set, Set):
            f_default = set
        elif ot in (frozenset, FrozenSet):
            f_default = frozenset
        elif ot in (dict, Dict):
            f_default = dict
        else:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'{f_type!r} is not supported')

    elif f_type is not None:
        if f_type is Any:
            f_type = object

        if not isinstance(f_type, type):
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'{f_type!r} is not a type')

        if typeutils.is_container_type(f_type):
            if f_default is not None:
                raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                                   f'default is defined for container type '
                                   f'{f_type!r}')
            # Make sure that we can actually construct an empty
            # version of this type before we decide it is the default.
            try:
                f_type()
            except TypeError:
                pass
            else:
                f_default = f_type

    return f_default
示例#6
0
 def test_tuple(self):
     samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]]
     if PY39:
         samples.append(tuple[int, str])
     nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])]
     self.sample_test(is_tuple_type, samples, nonsamples)
     if SUBCLASSABLE_TUPLES:
         class MyClass(Tuple[str, int]):
             pass
         self.assertTrue(is_tuple_type(MyClass))
示例#7
0
 def __call__(self, tp):
     if inspect.isclass(tp):
         return self.iter_mro(tp)
     if ti.is_optional_type(tp):
         return self.iter_optional(tp)
     if ti.is_tuple_type(tp):
         return self.iter_generic(tp)
     if ti.is_union_type(tp):
         return self.iter_generic(tp)
     if ti.is_generic_type(tp):
         return self.iter_generic(tp)
示例#8
0
def decode_generic_tuple(decoder, typ, json_value):
    if not inspect.is_tuple_type(typ):
        return Unsupported
    check_type(list, json_value)
    items_types = inspect.get_args(typ)
    if len(items_types) != len(json_value):
        raise JsonError(
            f'Expected list of size: {len(items_types)}, found tuple of size: {len(json_value)}, value: {json_value}'
        )
    return tuple(
        map(lambda item, item_type: decoder.decode(item_type, item),
            json_value, items_types))
示例#9
0
def encode_generic_tuple(encoder, typ, value):
    if not inspect.is_tuple_type(typ):
        return Unsupported
    check_type(tuple, value)
    items_types = inspect.get_args(typ)
    if len(items_types) != len(value):
        raise JsonError(
            f'Expected tuple of size: {len(items_types)}, found tuple of size: {len(value)}, value: {value}'
        )
    return tuple(
        map(lambda item, item_type: encoder.encode(item, item_type), value,
            items_types))
示例#10
0
    def _typing2st(self,
                   container,
                   param_name,
                   p_annotation,
                   args,
                   default,
                   meta=None):
        if tp.is_tuple_type(p_annotation):
            if not has_default(default):
                default = [None] * len(args)
            tuple_values = []
            with container:
                cols = st.beta_columns([1, 6] * len(args))
                for i, (type_, param_default) in enumerate(zip(args, default)):
                    if i != 0:
                        meta = "AND"
                    p_name = f'{param_name} [{i}]'
                    param_type = type_.__name__
                    ret = self.basic2st(cols, p_name, param_type, i,
                                        param_default, meta)
                    tuple_values.append(ret)
                return tuple(tuple_values)
        elif tp.is_union_type(p_annotation):
            with container:
                return_value = None
                #                 st.write(param_name)
                #                 st.write(default)
                if not has_default(default):
                    default = None

                for i, type_ in enumerate(args):
                    cols = st.beta_columns([1, 6] * len(args))
                    if i != 0:
                        meta = "OR"
                    p_name = f'{param_name} [{i}]'
                    if is_typing(type_):
                        nargs = tp.get_args(type_)
                        if not default:
                            default = [None] * len(nargs)
                        ret = self._typing2st(
                            p_name, type_, nargs, default,
                            meta)  # Not supporting nested defaults?
                    else:
                        param_type = type_.__name__
                        ret = self.basic2st(cols, p_name, param_type, i,
                                            default, meta)
                    if ret is not None and return_value is None:
                        return_value = ret
                return return_value
示例#11
0
    def _simplify_big_tuple(self, typ: Tuple):
        if not typing_inspect.is_tuple_type(typ):
            return typ

        arg_classes = typ.__args__
        if not arg_classes:
            return typ

        if len(arg_classes) > self.max_members and len(set(arg_classes)) == 1:
            return Tuple[arg_classes[0], ...]

        if len(arg_classes) > self.max_members:
            return Tuple[Any, ...]

        return typ
示例#12
0
def _check_annotation(f_type, f_fullname, f_default):
    if typing_inspect.is_tuple_type(f_type):
        if f_default is not None:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'default is defined for tuple type')

        f_default = tuple

    elif typing_inspect.is_union_type(f_type):
        for t in typing_inspect.get_args(f_type, evaluate=True):
            _check_annotation(t, f_fullname, f_default)

    elif typing_inspect.is_generic_type(f_type):
        if f_default is not None:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'default is defined for container type '
                               f'{f_type!r}')

        ot = typing_inspect.get_origin(f_type)
        if ot is None:
            raise RuntimeError(
                f'cannot find origin of a generic type {f_type}')

        if ot in (list, typing.List):
            f_default = list
        elif ot in (set, typing.Set):
            f_default = set
        elif ot in (frozenset, typing.FrozenSet):
            f_default = frozenset
        elif ot in (dict, typing.Dict):
            f_default = dict
        else:
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'{f_type!r} is not supported')

    elif f_type is not None:
        if not isinstance(f_type, type):
            raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                               f'{f_type!r} is not a type')

        if typeutils.is_container_type(f_type):
            if f_default is not None:
                raise RuntimeError(f'invalid type annotation on {f_fullname}: '
                                   f'default is defined for container type '
                                   f'{f_type!r}')
            f_default = f_type

    return f_default
示例#13
0
def _check_type_real(type_, value, raise_error):
    if type_ is None:
        return

    if typing_inspect.is_union_type(type_):
        for t in typing_inspect.get_args(type_, evaluate=True):
            try:
                _check_type(t, value, raise_error)
            except TypeError:
                pass
            else:
                break
        else:
            raise_error(str(type_), value)

    elif typing_inspect.is_tuple_type(type_):
        _check_tuple_type(type_, value, raise_error, tuple)

    elif typing_inspect.is_generic_type(type_):
        ot = typing_inspect.get_origin(type_)

        if ot in (list, List, collections.abc.Sequence):
            _check_container_type(type_, value, raise_error, list)

        elif ot in (set, Set):
            _check_container_type(type_, value, raise_error, set)

        elif ot in (frozenset, FrozenSet):
            _check_container_type(type_, value, raise_error, frozenset)

        elif ot in (dict, Dict):
            _check_mapping_type(type_, value, raise_error, dict)

        elif ot is not None:
            raise TypeError(f'unsupported typing type: {type_!r}')

    elif type_ is not Any:
        if value is not None and not isinstance(value, type_):
            raise_error(type_.__name__, value)
    def get(cls, type_or_hint, *, is_argument: bool = False) -> "TypeChecker":
        # This ensures the validity of the type passed (see typing documentation for info)
        type_or_hint = is_valid_type(type_or_hint, "Invalid type.",
                                     is_argument)

        if type_or_hint is Any:
            return AnyTypeChecker()

        if is_type(type_or_hint):
            return TypeTypeChecker.make(type_or_hint, is_argument)

        if is_literal_type(type_or_hint):
            return LiteralTypeChecker.make(type_or_hint, is_argument)

        if is_generic_type(type_or_hint):
            origin = get_origin(type_or_hint)
            if issubclass(origin, MappingCol):
                return MappingTypeChecker.make(type_or_hint, is_argument)

            if issubclass(origin, Collection):
                return CollectionTypeChecker.make(type_or_hint, is_argument)

            # CONSIDER: how to cater for exhaustible generators?
            if issubclass(origin, Iterable):
                raise NotImplementedError(
                    "No type-checker is setup for iterables that exhaust.")

            return GenericTypeChecker.make(type_or_hint, is_argument)

        if is_tuple_type(type_or_hint):
            return TupleTypeChecker.make(type_or_hint, is_argument)

        if is_callable_type(type_or_hint):
            return CallableTypeChecker.make(type_or_hint, is_argument)

        if isclass(type_or_hint):
            if is_typed_dict(type_or_hint):
                return TypedDictChecker.make(type_or_hint, is_argument)
            return ConcreteTypeChecker.make(type_or_hint, is_argument)

        if is_union_type(type_or_hint):
            return UnionTypeChecker.make(type_or_hint, is_argument)

        if is_typevar(type_or_hint):
            bound_type = get_bound(type_or_hint)
            if bound_type:
                return cls.get(bound_type)
            constraints = get_constraints(type_or_hint)
            if constraints:
                union_type_checkers = tuple(
                    cls.get(type_) for type_ in constraints)
                return UnionTypeChecker(Union.__getitem__(constraints),
                                        union_type_checkers)
            else:
                return AnyTypeChecker()

        if is_new_type(type_or_hint):
            super_type = getattr(type_or_hint, "__supertype__", None)
            if super_type is None:
                raise TypeError(
                    f"No supertype for NewType: {type_or_hint}. This is not allowed."
                )
            return cls.get(super_type)

        if is_forward_ref(type_or_hint):
            return ForwardTypeChecker.make(type_or_hint,
                                           is_argument=is_argument)

        if is_classvar(type_or_hint):
            var_type = get_args(type_or_hint, evaluate=True)[0]
            return cls.get(var_type)

        raise NotImplementedError(
            f"No {TypeChecker.__qualname__} is available for type or hint: '{type_or_hint}'"
        )
示例#15
0
def is_typing(type_):
    return (tp.is_union_type(type_) or tp.is_tuple_type(type_))
示例#16
0
def object_actions(type_def: Type[Generic], tree: AST) -> Dict[str, ObjectAction]:

    ret: Dict[str, ObjectAction] = {}

    # ...inspect.ismethod does not work on un-initialized classes
    for method_name, method_def in iterate_over_actions(type_def):

        meta: ActionMetadata = method_def.__action__  # type: ignore

        data = ObjectAction(name=method_name, meta=meta)

        if method_name in type_def.CANCEL_MAPPING:
            meta.cancellable = True

        try:

            if not method_def.__doc__:
                doc = {}
            else:
                doc = parse_docstring(method_def.__doc__)
                doc_short = doc["short_description"]
                if doc_short:
                    data.description = doc_short

            signature = inspect.signature(method_def)  # sig.parameters is OrderedDict

            try:
                method_tree = find_function(method_name, tree)
            except SourceException:
                # function is probably defined in predecessor, will be added later
                continue

            hints = get_type_hints(method_def)  # standard (unordered) dict

            if "an" not in signature.parameters.keys():
                raise IgnoreActionException("Action is missing 'an' parameter.")

            try:
                if hints["an"] != Optional[str]:
                    raise IgnoreActionException("Parameter 'an' has invalid type annotation.")
            except KeyError:
                raise IgnoreActionException("Parameter 'an' is missing type annotation.")

            parameter_names_without_self = list(signature.parameters.keys())[1:]

            if not parameter_names_without_self or parameter_names_without_self[-1] != "an":
                raise IgnoreActionException("The 'an' parameter have to be the last one.")

            # handle return
            try:
                return_ttype = hints["return"]
            except KeyError:
                raise IgnoreActionException("Action is missing return type annotation.")

            # ...just ignore NoneType for returns
            if return_ttype != type(None):  # noqa: E721

                if typing_inspect.is_tuple_type(return_ttype):
                    for arg in typing_inspect.get_args(return_ttype):
                        resolved_param = plugin_from_type(arg)
                        if resolved_param is None:
                            raise IgnoreActionException("None in return tuple is not supported.")
                        data.returns.append(resolved_param.type_name())
                else:
                    # TODO resolving needed for e.g. enums - add possible values to action metadata somewhere?
                    data.returns = [plugin_from_type(return_ttype).type_name()]

            for name in parameter_names_without_self[:-1]:  # omit also an

                try:
                    ttype = hints[name]
                except KeyError:
                    raise IgnoreActionException(f"Parameter {name} is missing type annotation.")

                param_type = plugin_from_type(ttype)

                assert param_type is not None

                args = ParameterMeta(name=name, type=param_type.type_name())
                try:
                    param_type.meta(args, method_def, method_tree)
                except ParameterPluginException as e:
                    raise IgnoreActionException(e) from e

                if name in type_def.DYNAMIC_PARAMS:
                    args.dynamic_value = True
                    dvp = type_def.DYNAMIC_PARAMS[name][1]
                    if dvp:
                        args.dynamic_value_parents = dvp

                def_val = signature.parameters[name].default
                if def_val is not inspect.Parameter.empty:
                    args.default_value = param_type.value_to_json(def_val)

                try:
                    args.description = doc["params"][name].strip()
                except KeyError:
                    pass

                data.parameters.append(args)

        except Arcor2Exception as e:
            data.disabled = True
            data.problem = str(e)
            glob.logger.warn(f"Disabling action {method_name} of  {type_def.__name__}. {str(e)}")

        ret[data.name] = data

    return ret
示例#17
0
def _populate_parser(func, parser, parsers, short, strict_kwonly):
    sig = _public_signature(func)
    doc = _parse_function_docstring(func)
    hints = typing.get_type_hints(func) or {}  # Py2 backport returns None.
    parser.description = doc.text

    types = dict((name, _get_type(func, name, doc, hints))
                 for name, param in sig.parameters.items())
    positionals = set(name for name, param in sig.parameters.items()
                      if ((param.default is param.empty or strict_kwonly)
                          and not types[name].container
                          and param.kind != param.KEYWORD_ONLY))
    if short is None:
        count_initials = Counter(name[0] for name in sig.parameters
                                 if name not in positionals)
        short = dict(
            (name.replace('_', '-'), name[0]) for name in sig.parameters
            if name not in positionals and count_initials[name[0]] == 1)

    for name, param in sig.parameters.items():
        kwargs = {}
        if name in doc.params:
            help_ = doc.params[name].text
            if help_ is not None:
                kwargs['help'] = help_.replace('%', '%%')
        type_ = types[name]
        if param.kind == param.VAR_KEYWORD:
            raise ValueError('**kwargs not supported')
        hasdefault = param.default is not param.empty
        default = param.default if hasdefault else SUPPRESS
        required = not hasdefault and param.kind != param.VAR_POSITIONAL
        positional = name in positionals
        if type_.type == bool and not positional and not type_.container:
            # Special case: just add parameterless --name and --no-name flags.
            if default == SUPPRESS:
                # Work around lack of "required non-exclusive group" in
                # argparse by checking for that case ourselves.
                default = _SUPPRESS_BOOL_DEFAULT
            _add_argument(parser, name, short,
                          action='store_true',
                          default=default,
                          # Add help if available.
                          **kwargs)
            _add_argument(parser, 'no-' + name, short,
                          action='store_false',
                          dest=name)
            continue
        if positional:
            kwargs['_positional'] = True
            if param.default is not param.empty:
                kwargs['nargs'] = '?'
                kwargs['default'] = default
            if param.kind == param.VAR_POSITIONAL:
                kwargs['nargs'] = '*'
                # This is purely to override the displayed default of None.
                # Ideally we wouldn't want to show a default at all.
                kwargs['default'] = []
        else:
            kwargs['required'] = required
            kwargs['default'] = default
        if type_.container:
            assert type_.container == list
            kwargs['nargs'] = '*'
            if param.kind == param.VAR_POSITIONAL:
                kwargs['action'] = 'append'
                kwargs['default'] = []
        make_tuple = member_types = None
        if ti.is_tuple_type(type_.type):
            make_tuple = tuple
            member_types = ti.get_args(type_.type)
            kwargs['nargs'] = len(member_types)
            kwargs['action'] = _make_store_tuple_action_class(
                tuple, member_types, parsers)
        elif (inspect.isclass(type_.type) and issubclass(type_.type, tuple)
              and hasattr(type_.type, '_fields')
              and hasattr(type_.type, '_field_types')):
            # Before Py3.6, `_field_types` does not preserve order, so retrieve
            # the order from `_fields`.
            member_types = tuple(type_.type._field_types[field]
                                 for field in type_.type._fields)
            kwargs['nargs'] = len(member_types)
            kwargs['action'] = _make_store_tuple_action_class(
                lambda args, type_=type_: type_.type(*args),
                member_types, parsers)
            if not positional:  # http://bugs.python.org/issue14074
                kwargs['metavar'] = type_.type._fields
        elif inspect.isclass(type_.type) and issubclass(type_.type, Enum):
            # Want these to behave like argparse choices.
            kwargs['choices'] = _ValueOrderedDict(
                (x.name, x) for x in type_.type)
            kwargs['type'] = _enum_getter(type_.type)
        else:
            kwargs['type'] = _get_parser(type_.type, parsers)
        _add_argument(parser, name, short, **kwargs)
示例#18
0
def _type_from_runtime(val, ctx):
    if isinstance(val, str):
        return _eval_forward_ref(val, ctx)
    elif isinstance(val, tuple):
        # This happens under some Python versions for types
        # nested in tuples, e.g. on 3.6:
        # > typing_inspect.get_args(Union[Set[int], List[str]])
        # ((typing.Set, int), (typing.List, str))
        origin = val[0]
        if len(val) == 2:
            args = (val[1], )
        else:
            args = val[1:]
        return _value_of_origin_args(origin, args, val, ctx)
    elif typing_inspect.is_literal_type(val):
        args = typing_inspect.get_args(val)
        if len(args) == 0:
            return KnownValue(args[0])
        else:
            return unite_values(*[KnownValue(arg) for arg in args])
    elif typing_inspect.is_union_type(val):
        args = typing_inspect.get_args(val)
        return unite_values(*[_type_from_runtime(arg, ctx) for arg in args])
    elif typing_inspect.is_tuple_type(val):
        args = typing_inspect.get_args(val)
        if not args:
            return TypedValue(tuple)
        elif len(args) == 2 and args[1] is Ellipsis:
            return GenericValue(tuple, [_type_from_runtime(args[0], ctx)])
        else:
            args_vals = [_type_from_runtime(arg, ctx) for arg in args]
            return SequenceIncompleteValue(tuple, args_vals)
    elif is_instance_of_typing_name(val, "_TypedDictMeta"):
        return TypedDictValue({
            key: _type_from_runtime(value, ctx)
            for key, value in val.__annotations__.items()
        })
    elif typing_inspect.is_callable_type(val):
        return TypedValue(Callable)
    elif typing_inspect.is_generic_type(val):
        origin = typing_inspect.get_origin(val)
        args = typing_inspect.get_args(val)
        return _value_of_origin_args(origin, args, val, ctx)
    elif GenericAlias is not None and isinstance(val, GenericAlias):
        origin = get_origin(val)
        args = get_args(val)
        return GenericValue(origin,
                            [_type_from_runtime(arg, ctx) for arg in args])
    elif isinstance(val, type):
        if val is type(None):
            return KnownValue(None)
        return TypedValue(val)
    elif val is None:
        return KnownValue(None)
    elif is_typing_name(val, "NoReturn"):
        return NO_RETURN_VALUE
    elif val is typing.Any:
        return UNRESOLVED_VALUE
    elif hasattr(val, "__supertype__"):
        if isinstance(val.__supertype__, type):
            # NewType
            return NewTypeValue(val)
        elif typing_inspect.is_tuple_type(val.__supertype__):
            # TODO figure out how to make NewTypes over tuples work
            return UNRESOLVED_VALUE
        else:
            ctx.show_error("Invalid NewType %s" % (val, ))
            return UNRESOLVED_VALUE
    elif typing_inspect.is_typevar(val):
        # TypeVar; not supported yet
        return UNRESOLVED_VALUE
    elif typing_inspect.is_classvar(val):
        return UNRESOLVED_VALUE
    elif is_instance_of_typing_name(
            val, "_ForwardRef") or is_instance_of_typing_name(
                val, "ForwardRef"):
        # This has issues because the forward ref may be defined in a different file, in
        # which case we don't know which names are valid in it.
        with qcore.override(ctx, "suppress_undefined_name", True):
            return UNRESOLVED_VALUE
    elif val is Ellipsis:
        # valid in Callable[..., ]
        return UNRESOLVED_VALUE
    elif is_instance_of_typing_name(val, "_TypeAlias"):
        # typing.Pattern and Match, which are not normal generic types for some reason
        return GenericValue(val.impl_type,
                            [_type_from_runtime(val.type_var, ctx)])
    else:
        origin = get_origin(val)
        if origin is not None:
            return TypedValue(origin)
        ctx.show_error("Invalid type annotation %s" % (val, ))
        return UNRESOLVED_VALUE
def _extract_collection_base_type(
        collection_object_type,
        exception_if_none: bool = True,
        resolve_fwd_refs: bool = True) -> Tuple[Type, Optional[Type]]:
    """
    Utility method to extract the base item type from a collection/iterable item type.
    Throws
    * a TypeError if the collection_object_type a Dict with non-string keys.
    * an AttributeError if the collection_object_type is actually not a collection
    * a TypeInformationRequiredError if somehow the inner type can't be found from the collection type (either if dict,
    list, set, tuple were used instead of their typing module equivalents (Dict, List, Set, Tuple), or if the latter
    were specified without inner content types (as in Dict instead of Dict[str, Foo])

    :param collection_object_type:
    :return: a tuple containing the collection's content type (which may itself be a Tuple in case of a Tuple) and the
    collection's content key type for dicts (or None)
    """
    contents_item_type = None
    contents_key_type = None

    check_var(collection_object_type,
              var_types=type,
              var_name='collection_object_type')

    is_tuple = False
    if is_tuple_type(
            collection_object_type
    ):  # Tuple is a special construct, is_generic_type does not work
        is_tuple = True
        # --old: hack into typing module
        # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
        # contents_item_type = collection_object_type.__args__

        # --new : using typing_inspect
        # __args = get_last_args(collection_object_type)
        # this one works even in typevar+config cases such as t = Tuple[int, Tuple[T, T]][Optional[int]]
        __args = get_args(collection_object_type, evaluate=True)
        if len(__args) > 0:
            contents_item_type = __args

    elif issubclass(collection_object_type, Mapping):  # Dictionary-like
        if is_generic_type(collection_object_type):
            # --old: hack into typing module
            # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
            # contents_key_type, contents_item_type = collection_object_type.__args__

            # --new : using typing_inspect
            # __args = get_last_args(collection_object_type)
            # this one works even in typevar+config cases such as d = Dict[int, Tuple[T, T]][Optional[int]]
            __args = get_args(collection_object_type, evaluate=True)
            if len(__args) > 0:
                contents_key_type, contents_item_type = __args
                if not issubclass(contents_key_type, str):
                    raise TypeError(
                        'Collection object has type Dict, but its PEP484 type hints declare '
                        'keys as being of type ' + str(contents_key_type) +
                        ' which is not supported. Only '
                        'str keys are supported at the moment, since we use them as item names'
                    )

    elif issubclass(
            collection_object_type,
            Iterable):  # List or Set. Should we rather use Container here ?
        if is_generic_type(collection_object_type):
            # --old: hack into typing module
            # if hasattr(collection_object_type, '__args__') and collection_object_type.__args__ is not None:
            # contents_item_type = collection_object_type.__args__[0]

            # --new : using typing_inspect
            # __args = get_last_args(collection_object_type)
            # this one works even in typevar+config cases such as i = Iterable[Tuple[T, T]][Optional[int]]
            __args = get_args(collection_object_type, evaluate=True)
            if len(__args) > 0:
                contents_item_type, = __args

    elif issubclass(collection_object_type, dict) or issubclass(collection_object_type, list)\
            or issubclass(collection_object_type, tuple) or issubclass(collection_object_type, set):
        # the error is now handled below with the other under-specified types situations
        pass

    else:
        # Not a collection
        raise AttributeError(
            'Cannot extract collection base type, object type ' +
            str(collection_object_type) + ' is not a collection')

    # Finally return if something was found, otherwise tell it
    try:
        if contents_item_type is None or contents_item_type is Parameter.empty:
            # Empty type hints
            raise TypeInformationRequiredError.create_for_collection_items(
                collection_object_type, contents_item_type)

        elif is_tuple:
            # --- tuple: Iterate on all sub-types
            resolved = []
            for t in contents_item_type:
                # Check for empty type hints
                if contents_item_type is None or contents_item_type is Parameter.empty:
                    raise TypeInformationRequiredError.create_for_collection_items(
                        collection_object_type, t)

                # Resolve any forward references if needed
                if resolve_fwd_refs:
                    t = resolve_forward_ref(t)
                resolved.append(t)

                # Final type hint compliance
                if not is_valid_pep484_type_hint(t):
                    raise InvalidPEP484TypeHint.create_for_collection_items(
                        collection_object_type, t)

            if resolve_fwd_refs:
                contents_item_type = tuple(resolved)

        else:
            # --- Not a tuple
            # resolve any forward references first
            if resolve_fwd_refs:
                contents_item_type = resolve_forward_ref(contents_item_type)

            # check validity then
            if not is_valid_pep484_type_hint(contents_item_type):
                # Invalid type hints
                raise InvalidPEP484TypeHint.create_for_collection_items(
                    collection_object_type, contents_item_type)

    except TypeInformationRequiredError as e:
        # only raise it if the flag says it
        if exception_if_none:
            raise e.with_traceback(e.__traceback__)

    return contents_item_type, contents_key_type