# Copyright 2017 Stefan Richthofer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Created on 13.12.2016

import random
import sys
import types
import threading
import typing
import collections
import weakref
from inspect import isfunction, ismethod, isclass, ismodule, stack
try:
    from backports.typing import Tuple, Dict, List, Set, FrozenSet, Union, Any, \
            Sequence, Mapping, TypeVar, Container, Generic, Sized, Iterable, Iterator, \
            Generator, T_co, V_co, VT_co, T_contra, KT, T, VT
except ImportError:
    from typing import Tuple, Dict, List, Set, FrozenSet, Union, Any, \
            Sequence, Mapping, TypeVar, Container, Generic, Sized, Iterable, Iterator, \
            Generator, T_co, V_co, VT_co, T_contra, KT, T, VT

import pytypes
_typing_3_7 = pytypes.typing_3_7
if _typing_3_7:
    # Python 3.7
    from typing import ForwardRef
    _Generic_Singleton = Generic[T_co]
    _bases_cache = {
        List: (list, typing.MutableSequence),
        Set: (set, typing.MutableSet),
        Dict: (dict, typing.MutableMapping),
        FrozenSet: (frozenset, typing.AbstractSet),
        Tuple: (tuple,),
        # Python 3.6 thinks this should extend MutableMapping,
        # but Python 3.7 thinks it should extend Dict:
        typing.DefaultDict: (collections.defaultdict, Dict),
        typing.ItemsView: (
                # For ItemsView, Python 3.7 seems to omit the information
                # that KT, VT_co must be Tupelized.
                typing.MappingView[Tuple[KT, VT_co]],
                typing.AbstractSet[Tuple[KT, VT_co]],
                Generic[KT, VT_co])

        # There is still collections.abc.Sized vs typing.Sized.
        # Leaving it unresolved until it crashes something notable.
    }
    _orig_Iterable = collections.abc.Iterable
    _orig_Iterator = collections.abc.Iterator
else:
    from typing import _ForwardRef as ForwardRef
    _typing_3_7 = False
    _orig_Iterable = Iterable
    _orig_Iterator = Iterator
from warnings import warn, warn_explicit

from .stubfile_manager import _match_stub_type, as_stub_func_if_any
from .typecomment_parser import _get_typestrings, _funcsigtypesfromstring
from . import util

_annotated_modules = {}
_extra_dict = {}
_saved_profilers = {}
_fw_resolve_cache = {}
_checked_generator_types = weakref.WeakKeyDictionary()

for tp in typing.__all__:
    tpa = getattr(typing, tp)
    try:
        _extra_dict[tpa.__extra__] = tpa
    except AttributeError:
        try:
            _extra_dict[tpa.__origin__] = tpa
        except: pass
if not tuple in _extra_dict:
    _extra_dict[tuple] = Tuple

if sys.version_info.major >= 3:
    _basestring = str
else:
    _basestring = basestring

EMPTY = TypeVar('EMPTY', bound=Container, covariant=True)


class Empty(Generic[EMPTY]):
    """pytypes-specific type to represent empty lists, sets, dictionaries
    and other empty containers.
    See https://github.com/python/typing/issues/157 for details on why this
    is necessary.
    """
    pass


def _bases(tp):
    # Python 3.7 fundamentally changed the way to retrieve base types,
    # especially w.r.t. collections.
    try:
        # Python 3.5
        return tp.__orig_bases__
    except AttributeError: pass
    try:
        # Before typing 3.5.3.0 __bases__ used to contain all info that later
        # became reserved for __orig_bases__. So we can use it as a fallback:
        # (Also Python 3.6)
        return tp.__bases__
    except AttributeError: pass
    if not _typing_3_7:
        return ()
    # Python 3.7
    try:
        return tp.__orig_bases__
    except AttributeError: pass
    try:
        return tp.__origin__.__orig_bases__
    except AttributeError: pass
    if tp is _Generic_Singleton:
        return ()
    try:
        return _bases_cache[_extra_dict[tp.__origin__]]
    except KeyError: pass
    res = [_extra_dict[bs] if _is_extra(bs) else bs
            for bs in tp.__origin__.__bases__]
    try:
        obidx = res.index(object)
        res[obidx] = _Generic_Singleton
    except ValueError: pass
    res = tuple(res)
    if _is_extra(tp.__origin__):
        _bases_cache[_extra_dict[tp.__origin__]] = res
    return res


def _parameters(origin):
    if _typing_3_7 and _is_extra(origin):
        # Python 3.7
        origin = _extra_dict[origin]
    try: return origin.__parameters__
    except: return ()


def _origin(tp):
    try:
        return tp.__origin__
    except AttributeError:
        return None


def _extra(tp):
    try:
        return tp.__extra__
    except AttributeError:
        pass
    try:
        return tp.__origin__
    except AttributeError:
        pass
    return None


def _extra_inv(tp):
    if _typing_3_7 and not isclass(tp):
        return tp
    if tp in _extra_dict:
        return _extra_dict[tp]
    else:
        return tp


def _is_extra(tp):
    if _typing_3_7 and not isclass(tp):
        return False
    return tp in _extra_dict


def get_orig_class(obj, default_to__class__=False):
    """Robust way to access `obj.__orig_class__`. Compared to a direct access this has the
    following advantages:
    1) It works around https://github.com/python/typing/issues/658.
    2) It prevents infinite recursion when wrapping a method (`obj` is `self` or `cls`) and either
       - the object's class defines `__getattribute__`
       or
       - the object has no `__orig_class__` attribute and the object's class defines `__getattr__`.
       See discussion at https://github.com/Stewori/pytypes/pull/53.
    If `default_to__class__` is `True` it returns `obj.__class__` as final fallback.
    Otherwise, `AttributeError` is raised  in failure case (default behavior).
    """
    try:
        # See https://github.com/Stewori/pytypes/pull/53:
        # Returns  `obj.__orig_class__` protecting from infinite recursion in `__getattr[ibute]__`
        # wrapped in a `checker_tp`.
        # (See `checker_tp` in `typechecker._typeinspect_func for context)
        # Necessary if:
        # - we're wrapping a method (`obj` is `self`/`cls`) and either
        # 	- the object's class defines __getattribute__
        # or
        # 	- the object doesn't have an `__orig_class__` attribute
        # 	  and the object's class defines __getattr__.
        # In such a situation, `parent_class = obj.__orig_class__`
        # would call `__getattr[ibute]__`. But that method is wrapped in a `checker_tp` too,
        # so then we'd go into the wrapped `__getattr[ibute]__` and do
        # `parent_class = obj.__orig_class__`, which would call `__getattr[ibute]__`
        # again, and so on. So to bypass `__getattr[ibute]__` we do this:
        return object.__getattribute__(obj, '__orig_class__')
    except AttributeError:
        if sys.version_info.major >= 3:
            cls = object.__getattribute__(obj, '__class__')
        else:
            # Python 2 may return instance objects from object.__getattribute__.
            cls = obj.__class__
        if _typing_3_7 and is_Generic(cls):
            # Workaround for https://github.com/python/typing/issues/658
            stck = stack()
            # Searching from index 2 is sufficient: At 0 is get_orig_class, at 1 is the caller.
            # We assume the caller is not typing._GenericAlias.__call__ which we are after.
            for line in stck[2:]:
                try:
                    res = line[0].f_locals['self']
                    if res.__origin__ is cls:
                        return res
                except (KeyError, AttributeError):
                    pass
        if default_to__class__:
            return cls # Fallback
        raise


def get_Generic_type(ob):
    try:
        return get_orig_class(ob, True)
    except AttributeError:
        return ob.__class__


def get_generator_yield_type(genr):
    """Obtains the yield type of a generator object.
    """
    return get_generator_type(genr).__args__[0]


def get_generator_type(genr):
    """Obtains PEP 484 style type of a generator object, i.e. returns a
    typing.Generator object.
    """
    if genr in _checked_generator_types:
        return _checked_generator_types[genr]
    if not genr.gi_frame is None and 'gen_type' in genr.gi_frame.f_locals:
        return genr.gi_frame.f_locals['gen_type']
    else:
        cllble, nesting, slf = util.get_callable_fq_for_code(genr.gi_code)
        if cllble is None:
            return Generator
        return _funcsigtypes(cllble, slf, nesting[-1] if slf else None,
                genr.gi_frame.f_globals if not genr.gi_frame is None else None)[1]


def get_iterable_itemtype(obj):
    """Attempts to get an iterable's itemtype without iterating over it,
    not even partly. Note that iterating over an iterable might modify
    its inner state, e.g. if it is an iterator.
    Note that obj is expected to be an iterable, not a typing.Iterable.
    This function leverages various alternative ways to obtain that
    info, e.g. by looking for type annotations of '__iter__' or '__getitem__'.
    It is intended for (unknown) iterables, where the type cannot be obtained
    via sampling without the risk of modifying inner state.
    """
    # support further specific iterables on demand
    if isinstance(obj, _typechecked_Iterable):
        return obj.itemtype
    try:
        if isinstance(obj, range):
            tpl = tuple(deep_type(obj.start), deep_type(obj.stop), deep_type(obj.step))
            return Union[tpl]
    except TypeError:
        # We're running Python 2
        pass
    if type(obj) is tuple:
        tpl = tuple(deep_type(t) for t in obj)
        return Union[tpl]
    elif type(obj) is types.GeneratorType:
        return get_generator_yield_type(obj)
    else:
        tp = deep_type(obj)
        if is_Generic(tp):
            if issubclass(tp.__origin__, Iterable):
                if len(tp.__args__) == 1:
                    return tp.__args__[0]
                return _select_Generic_superclass_parameters(tp, Iterable)[0]
    if is_iterable(obj):
        if type(obj) is str:
            return str
        if hasattr(obj, '__iter__'):
            if has_type_hints(obj.__iter__):
                itrator = _funcsigtypes(obj.__iter__, True, obj.__class__)[1]
                if is_Generic(itrator) and itrator.__origin__ is _orig_Iterator:
                    return itrator.__args__[0]
        if hasattr(obj, '__getitem__'):
            if has_type_hints(obj.__getitem__):
                itrator =  _funcsigtypes(obj.__getitem__, True, obj.__class__)[1]
                if is_Generic(itrator) and itrator.__origin__ is _orig_Iterator:
                    return itrator.__args__[0]
        return None # means that type is unknown
    else:
        raise TypeError('Not an iterable: '+str(type(obj)))


def get_Generic_itemtype(sq, simplify=True):
    """Retrieves the item type from a PEP 484 generic or subclass of such.
    sq must be a typing.Tuple or (subclass of) typing.Iterable or typing.Container.
    Consequently this also works with typing.List, typing.Set and typing.Dict.
    Note that for typing.Dict and mapping types in general, the key type is regarded as item type.
    For typing.Tuple all contained types are returned as a typing.Union.
    If simplify == True some effort is taken to eliminate redundancies in such a union.
    """
    if is_Tuple(sq):
        if simplify:
            itm_tps = [x for x in get_Tuple_params(sq)]
            simplify_for_Union(itm_tps)
            return Union[tuple(itm_tps)]
        else:
            return Union[get_Tuple_params(sq)]
    else:
        try:
            res = _select_Generic_superclass_parameters(sq, typing.Container)
        except TypeError:
            res = None
        if res is None:
            try:
                res = _select_Generic_superclass_parameters(sq, typing.Iterable)
            except TypeError:
                pass
        if res is None or isinstance(res[0], TypeVar):
            raise TypeError("Has no itemtype: "+type_str(sq))
        else:
            return res[0]


def get_Mapping_key_value(mp):
    """Retrieves the key and value types from a PEP 484 mapping or subclass of such.
    mp must be a (subclass of) typing.Mapping.
    """
    try:
        res = _select_Generic_superclass_parameters(mp, typing.Mapping)
    except TypeError:
        res = None
    if res is None:
        raise TypeError("Has no key/value types: "+type_str(mp))
    elif isinstance(res[0], TypeVar):
        raise TypeError("Has no key type: "+type_str(mp))
    elif isinstance(res[1], TypeVar):
        raise TypeError("Has no value type: "+type_str(mp))
    else:
        return tuple(res)


def get_Generic_parameters(tp, generic_supertype=None):
    """tp must be a subclass of generic_supertype.
    Retrieves the type values from tp that correspond to parameters
    defined by generic_supertype.

    E.g. get_Generic_parameters(tp, typing.Mapping) is equivalent
    to get_Mapping_key_value(tp) except for the error message.

    generic_supertype defaults to tp.__origin__ or closest __origin__ in
    terms of util.orig_mro if the former is not available.

    Note that get_Generic_itemtype(tp) is not exactly equal to
    get_Generic_parameters(tp, typing.Container), as that method
    additionally contains treatment for typing.Tuple and typing.Iterable.
    """
    if generic_supertype is None:
        generic_supertype = _origin(tp)
    if generic_supertype is None:
        for bs in pytypes.util.orig_mro(tp):
            generic_supertype = _origin(bs)
            if not generic_supertype is None:
                break
    try:
        res = _select_Generic_superclass_parameters(tp, generic_supertype)
    except TypeError:
        res = None
    if res is None:
        raise TypeError("%s has no proper parameters defined by %s."%
                (type_str(tp), type_str(generic_supertype)))
    else:
        return tuple(res)


def get_Tuple_params(tpl):
    """Python version independent function to obtain the parameters
    of a typing.Tuple object.
    Omits the ellipsis argument if present. Use is_Tuple_ellipsis for that.
    Tested with CPython 2.7, 3.5, 3.6 and Jython 2.7.1.
    """
    try:
        return tpl.__tuple_params__
    except AttributeError:
        pass
    try:
        if tpl.__args__ is None:
            return None
        # Python 3.6
        if tpl.__args__[0] == ():
            return ()
        else:
            if tpl.__args__[-1] is Ellipsis:
                return tpl.__args__[:-1] if len(tpl.__args__) > 1 else None
            else:
                return tpl.__args__
    except (AttributeError, IndexError):
        return None


def is_Tuple_ellipsis(tpl):
    """Python version independent function to check if a typing.Tuple object
    contains an ellipsis."""
    try:
        return tpl.__tuple_use_ellipsis__
    except AttributeError:
        try:
            if tpl.__args__ is None:
                return False
            # Python 3.6
            if tpl.__args__[-1] is Ellipsis:
                return True
        except AttributeError:
            pass
        return False


def get_Union_params(un):
    """Python version independent function to obtain the parameters
    of a typing.Union object.
    Tested with CPython 2.7, 3.5, 3.6 and Jython 2.7.1.
    """
    try:
        return un.__union_params__
    except AttributeError:
        # Python 3.6
        return un.__args__


def get_Callable_args_res(clb):
    """Python version independent function to obtain the parameters
    of a typing.Callable object. Returns as tuple: args, result.
    Tested with CPython 2.7, 3.5, 3.6 and Jython 2.7.1.
    """
    try:
        return clb.__args__, clb.__result__
    except AttributeError:
        # Python 3.6
        return clb.__args__[:-1], clb.__args__[-1]


def is_iterable(obj):
    """Tests if an object implements the iterable protocol.
    This function is intentionally not capitalized, because
    it does not check w.r.t. (capital) Iterable class from
    typing or collections.
    """
    try:
        itr = iter(obj)
        del itr
        return True
    except:
        return False

def is_iterator(obj):
    """Tests if an object is an iterator.
    This function is intentionally not capitalized, because
    it does not check w.r.t. (capital) Iterator class from
    typing.
    """
    return isinstance(obj, collections.Iterator)

def is_Type(tp):
    """Python version independent check if an object is a type.
    For Python 3.7 onwards(?) this is not equivalent to
    ``isinstance(tp, type)`` any more, as that call would return
    ``False`` for PEP 484 types.
    Tested with CPython 2.7, 3.5, 3.6, 3.7 and Jython 2.7.1.
    """
    if isinstance(tp, type):
        return True
    try:
        typing._type_check(tp, '')
        return True
    except TypeError:
        return False


def is_Union(tp):
    """Python version independent check if a type is typing.Union.
    Tested with CPython 2.7, 3.5, 3.6 and Jython 2.7.1.
    """
    if tp is Union:
        return True
    try:
        # Python 3.6
        return tp.__origin__ is Union
    except AttributeError:
        try:
            return isinstance(tp, typing.UnionMeta)
        except AttributeError:
            return False


def is_Tuple(tp):
    try:
        return isinstance(tp, typing.TupleMeta)
    except AttributeError:
        try:
            return isinstance(tp, typing._GenericAlias) and \
                    tp.__origin__ is tuple
        except AttributeError:
            return False


def is_Generic(tp):
    try:
        return isinstance(tp, typing.GenericMeta)
    except AttributeError:
        try:
            return issubclass(tp, typing.Generic)
# 			return isinstance(tp, typing._VariadicGenericAlias) and \
# 					tp.__origin__ is tuple
        except AttributeError:
            return False
        except TypeError:
            # Shall we accept _GenericAlias, i.e. Tuple, Union, etc?
            return isinstance(tp, typing._GenericAlias)
            #return False


def is_Callable(tp):
    try:
        return isinstance(tp, typing.CallableMeta)
    except AttributeError:
        try:
            return isinstance(tp, typing._GenericAlias) and \
                    _origin(tp) is collections.abc.Callable
        except AttributeError:
            raise #return False


def is_Generator(tp):
    if not _typing_3_7:
        return _origin(tp) is typing.Generator
    else:
        try:
            return isinstance(tp, typing._GenericAlias) and \
                    _origin(tp) is collections.abc.Generator
        except AttributeError:
            raise #return False


def is_Iterable(tp):
    if not _typing_3_7:
        return _origin(tp) is typing.Iterable
    else:
        try:
            return isinstance(tp, typing._GenericAlias) and \
                    _origin(tp) is collections.abc.Iterable
        except AttributeError:
            raise


def is_Iterator(tp):
    if not _typing_3_7:
        return _origin(tp) is typing.Iterator
    else:
        try:
            return isinstance(tp, typing._GenericAlias) and \
                    _origin(tp) is collections.abc.Iterator
        except AttributeError:
            raise


def deep_type(obj, depth = None, max_sample = None, get_type = None):
    """Tries to construct a type for a given value. In contrast to type(...),
    deep_type does its best to fit structured types from typing as close as
    possible to the given value.
    E.g. deep_type((1, 2, 'a')) will return Tuple[int, int, str] rather than
    just tuple.
    Supports various types from typing, but not yet all.
    Also detects nesting up to given depth (uses pytypes.default_typecheck_depth
    if no value is given).
    If a value for max_sample is given, this number of elements is probed
    from lists, sets and dictionaries to determine the element type. By default,
    all elements are probed. If there are fewer elements than max_sample, all
    existing elements are probed.
    Optionally, a custom get_type function can be provided to further
    customize how types are resolved. By default it uses type function.
    """
    return _deep_type(obj, [], 0, depth, max_sample, get_type)


def _deep_type(obj, checked, checked_len, depth = None, max_sample = None, get_type = None):
    """checked_len allows to operate with a fake length for checked.
    This is necessary to ensure that each depth level operates based
    on the same checked list subset. Otherwise our recursion detection
    mechanism can fall into false-positives.
    """
    if depth is None:
        depth = pytypes.default_typecheck_depth
    if max_sample is None:
        max_sample = pytypes.deep_type_samplesize
    if -1 != max_sample < 2:
        max_sample = 2
    if get_type is None:
        get_type = type
    try:
        res = get_orig_class(obj, True)
    except AttributeError:
        res = get_type(obj)
    if depth == 0 or util._is_in(obj, checked[:checked_len]):
        return res
    elif not util._is_in(obj, checked[checked_len:]):
        checked.append(obj)
    # We must operate with a consistent checked list for one certain depth level
    # to avoid issues with a list, tuple, dict, etc containing the same element
    # multiple times. This could otherwise be misconcepted as a recursion.
    # Using a fake len checked_len2 ensures this. Each depth level operates with
    # a common fake length of checked list:
    checked_len2 = len(checked)
    if res == tuple:
        res = Tuple[tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in obj)]
    elif res == list:
        if len(obj) == 0:
            return Empty[List]
        if max_sample == -1 or max_sample >= len(obj)-1 or len(obj) <= 2:
            tpl = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in obj)
        else:
            # In case of lists I somehow feel it's better to ensure that
            # first and last element are part of the sample
            sample = [0, len(obj)-1]
            try:
                rsmp = random.sample(xrange(1, len(obj)-1), max_sample-2)
            except NameError:
                rsmp = random.sample(range(1, len(obj)-1), max_sample-2)
            sample.extend(rsmp)
            tpl = tuple(_deep_type(obj[t], checked, checked_len2, depth-1, None, get_type) for t in sample)
        res = List[Union[tpl]]
    elif res == dict:
        if len(obj) == 0:
            return Empty[Dict]
        if max_sample == -1 or max_sample >= len(obj)-1 or len(obj) <= 2:
            try:
                # We prefer a view (avoid copy)
                tpl1 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) \
                        for t in obj.viewkeys())
                tpl2 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) \
                        for t in obj.viewvalues())
            except AttributeError:
                # Python 3 gives views like this:
                tpl1 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in obj.keys())
                tpl2 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in obj.values())
        else:
            try:
                kitr = iter(obj.viewkeys())
                vitr = iter(obj.viewvalues())
            except AttributeError:
                kitr = iter(obj.keys())
                vitr = iter(obj.values())
            ksmpl = []
            vsmpl = []
            block = (len(obj) // max_sample)-1
            # I know this method has some bias towards beginning of iteration
            # sequence, but it's still more random than just taking the
            # initial sample and better than O(n) random.sample.
            while len(ksmpl) < max_sample:
                if block > 0:
                    j = random.randint(0, block)
                    k = random.randint(0, block)
                    while j > 0:
                        next(vitr) # discard
                        j -= 1
                    while k > 0:
                        next(kitr) # discard
                        k -= 1
                ksmpl.append(next(kitr))
                vsmpl.append(next(vitr))
            tpl1 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in ksmpl)
            tpl2 = tuple(_deep_type(t, checked, checked_len2, depth-1, None, get_type) for t in vsmpl)
        res = Dict[Union[tpl1], Union[tpl2]]
    elif res == set or res == frozenset:
        if res == set:
            typ = Set
        else:
            typ = FrozenSet
        if len(obj) == 0:
            return Empty[typ]
        if max_sample == -1 or max_sample >= len(obj)-1 or len(obj) <= 2:
            tpl = tuple(_deep_type(t, checked, depth-1, None, None, get_type) for t in obj)
        else:
            itr = iter(obj)
            smpl = []
            block = (len(obj) // max_sample)-1
            # I know this method has some bias towards beginning of iteration
            # sequence, but it's still more random than just taking the
            # initial sample and better than O(n) random.sample.
            while len(smpl) < max_sample:
                if block > 0:
                    j = random.randint(0, block)
                    while j > 0:
                        next(itr) # discard
                        j -= 1
                smpl.append(next(itr))
            tpl = tuple(_deep_type(t, checked, depth-1, None, None, get_type) for t in smpl)
        res = typ[Union[tpl]]
    elif res == types.GeneratorType:
        res = get_generator_type(obj)
    elif sys.version_info.major == 2 and isinstance(obj, types.InstanceType):
        # For old-style instances return the actual class:
        return obj.__class__
    elif _has_base(res, Container) and len(obj) == 0:
        return Empty[res]
    elif hasattr(res, '__origin__') and _has_base(res.__origin__, Container) and len(obj) == 0:
        return Empty[res.__origin__]
    return res


def is_builtin_type(tp):
    """Checks if the given type is a builtin one.
    """
    return hasattr(__builtins__, tp.__name__) and tp is getattr(__builtins__, tp.__name__)


def has_type_hints(func0):
    """Detects if the given function or method has type annotations.
    Also considers typecomments and stubfiles.
    """
    return _has_type_hints(func0)


def _check_as_func(memb):
    return isfunction(memb) or ismethod(memb) or \
            isinstance(memb, classmethod) or isinstance(memb, staticmethod) or \
            isinstance(memb, property)


def _has_type_hints(func0, func_class = None, nesting = None):
    actual_func = util._actualfunc(func0)
    func = as_stub_func_if_any(actual_func, func0, func_class, nesting)
    stub_func = func
    func = util._actualfunc(func)
    tpHints = _tpHints_from_annotations(func0, actual_func, stub_func, func)
    if not tpHints is None:
        return True
    try:
        tpHints = typing.get_type_hints(func)
    except NameError:
        # Some typehint caused this NameError, so typhints are present in some form
        return True
    except TypeError:
        # func seems to be not suitable to have type hints
        return False
    except AttributeError:
        # func seems to be not suitable to have type hints
        return False
    try:
        tpStr = _get_typestrings(func, False)
        return not ((tpStr is None or tpStr[0] is None) and (tpHints is None or not tpHints))
    except TypeError:
        return False


_implicit_globals = set()
try:
    _implicit_globals.add(sys.modules['__builtin__'])
except:
    _implicit_globals.add(sys.modules['builtins'])
def _tp_relfq_name(tp, tp_name=None, assumed_globals=None, update_assumed_globals=None,
            implicit_globals=None):
    # _type: (type, Optional[Union[Set[Union[type, types.ModuleType]], Mapping[Union[type, types.ModuleType], str]]], Optional[bool]) -> str
    """Provides the fully qualified name of a type relative to a set of
    modules and types that is assumed as globally available.
    If assumed_globals is None this always returns the fully qualified name.
    If update_assumed_globals is True, this will return the plain type name,
    but will add the type to assumed_globals (expected to be a set).
    This way a caller can query how to generate an appropriate import section.
    If update_assumed_globals is False, assumed_globals can alternatively be
    a mapping rather than a set. In that case the mapping is expected to be
    an alias table, mapping modules or types to their alias names desired for
    displaying.
    update_assumed_globals can be None (default). In that case this will return the
    plain type name if assumed_globals is None as well (default).
    This mode is there to have a less involved default behavior.
    """
    if tp_name is None:
        tp_name = util.get_class_qualname(tp)
    if implicit_globals is None:
        implicit_globals = _implicit_globals
    else:
        implicit_globals = implicit_globals.copy()
        implicit_globals.update(_implicit_globals)
    if assumed_globals is None:
        if update_assumed_globals is None:
            return tp_name
        md = sys.modules[tp.__module__]
        if md in implicit_globals:
            return tp_name
        name = tp.__module__+'.'+tp_name
        pck = None
        if not (md.__package__ is None or md.__package__ == ''
                or name.startswith(md.__package__)):
            pck = md.__package__
        return name if pck is None else pck+'.'+name
    if tp in assumed_globals:
        try:
            return assumed_globals[tp]
        except:
            return tp_name
    elif hasattr(tp, '__origin__') and tp.__origin__ in assumed_globals:
        try:
            return assumed_globals[tp.__origin__]
        except:
            return tp_name
    # For some reason Callable does not have __origin__, so we special-case
    # it here. Todo: Find a cleaner solution.
    elif is_Callable(tp) and typing.Callable in assumed_globals:
        try:
            return assumed_globals[typing.Callable]
        except:
            return tp_name
    elif update_assumed_globals == True:
        if not assumed_globals is None:
            if hasattr(tp, '__origin__') and not tp.__origin__ is None:
                toadd = tp.__origin__
            elif is_Callable(tp):
                toadd = typing.Callable
            else:
                toadd = tp
            if not sys.modules[toadd.__module__] in implicit_globals:
                assumed_globals.add(toadd)
        return tp_name
    else:
        md = sys.modules[tp.__module__]
        if md in implicit_globals:
            return tp_name
        md_name = tp.__module__
        if md in assumed_globals:
            try:
                md_name = assumed_globals[md]
            except:
                pass
        else:
            if not (md.__package__ is None or md.__package__ == ''
                    or md_name.startswith(md.__package__)):
                md_name = md.__package__+'.'+tp.__module__
        return md_name+'.'+tp_name


def type_str(tp, assumed_globals=None, update_assumed_globals=None,
            implicit_globals=None, bound_Generic=None, bound_typevars=None):
    """Generates a nicely readable string representation of the given type.
    The returned representation is workable as a source code string and would
    reconstruct the given type if handed to eval, provided that globals/locals
    are configured appropriately (e.g. assumes that various types from typing
    have been imported).
    Used as type-formatting backend of ptypes' code generator abilities
    in modules typelogger and stubfile_2_converter.

    If tp contains unbound TypeVars and bound_Generic is provided, this
    function attempts to retrieve corresponding values for the unbound TypeVars
    from bound_Generic.

    For semantics of assumed_globals and update_assumed_globals see
    _tp_relfq_name. Its doc applies to every argument or result contained in
    tp (recursively) and to tp itself.
    """
    if assumed_globals is None and update_assumed_globals is None:
        if implicit_globals is None:
            implicit_globals = set()
        else:
            implicit_globals = implicit_globals.copy()
        implicit_globals.add(sys.modules['typing'])
        implicit_globals.add(sys.modules['__main__'])
    if isinstance(tp, tuple):
        return '('+', '.join([type_str(tp0, assumed_globals, update_assumed_globals,
                implicit_globals, bound_Generic, bound_typevars) for tp0 in tp])+')'
    try:
        return type_str(tp.__orig_class__, assumed_globals, update_assumed_globals,
                implicit_globals, bound_Generic, bound_typevars)
    except AttributeError:
        pass
    tp = _match_stub_type(tp)
    if isinstance(tp, TypeVar):
        prm = None
        if not bound_typevars is None:
            try:
                prm = bound_typevars[tp]
            except:
                pass
        if prm is None and not bound_typevars is None and tp in bound_typevars:
            prm = bound_typevars[tp]
        if prm is None and not bound_Generic is None:
            prm = get_arg_for_TypeVar(tp, bound_Generic)
        if not prm is None:
            return type_str(prm, assumed_globals, update_assumed_globals,
                    implicit_globals, bound_Generic, bound_typevars)
        return tp.__name__
    elif isinstance(tp, ForwardRef):
        return "'%s'" % tp.__forward_arg__
    elif isclass(tp) and not is_Generic(tp) \
            and not hasattr(typing, tp.__name__):
        tp_name = _tp_relfq_name(tp, None, assumed_globals, update_assumed_globals,
                implicit_globals)
        prm = ''
        if hasattr(tp, '__args__') and not tp.__args__ is None:
            params = [type_str(param, assumed_globals, update_assumed_globals,
                    implicit_globals, bound_Generic, bound_typevars) for param in tp.__args__]
            prm = '[%s]'%', '.join(params)
        return tp_name+prm
    elif is_Union(tp):
        prms = get_Union_params(tp)
        params = [type_str(param, assumed_globals, update_assumed_globals,
                implicit_globals, bound_Generic, bound_typevars) for param in prms]
        # See: https://github.com/Stewori/pytypes/issues/44
        if pytypes.canonical_type_str:
            params = sorted(params)
        return '%s[%s]'%(_tp_relfq_name(Union, 'Union', assumed_globals,
                update_assumed_globals, implicit_globals), ', '.join(params))
    elif is_Tuple(tp):
        prms = get_Tuple_params(tp)
        tpl_params = [type_str(param, assumed_globals, update_assumed_globals,
                implicit_globals, bound_Generic, bound_typevars) for param in prms]
        return '%s[%s]'%(_tp_relfq_name(Tuple, 'Tuple', assumed_globals,
                update_assumed_globals, implicit_globals), ', '.join(tpl_params))
    elif hasattr(tp, '__args__'):
        tp_name = _tp_relfq_name(tp, None, assumed_globals, update_assumed_globals,
                implicit_globals)
        if tp.__args__ is None:
            if hasattr(tp, '__parameters__') and \
                    hasattr(tp, '__origin__') and tp.__origin__ is Generic and \
                    not tp.__parameters__ is None and len(tp.__parameters__) > 0:
                args = tp.__parameters__
            else:
                return tp_name
        elif hasattr(tp, '__parameters__') and tp.__parameters__ == tp.__args__:
            # Happens on Python 3.7. For now, we avoid printing unbound typevars.
            return tp_name
        else:
            args = tp.__args__
        params = [type_str(param, assumed_globals, update_assumed_globals,
                implicit_globals, bound_Generic, bound_typevars) for param in args]
        if hasattr(tp, '__result__'):
            return '%s[[%s], %s]'%(tp_name, ', '.join(params),
                    type_str(tp.__result__, assumed_globals, update_assumed_globals,
                    implicit_globals, bound_Generic, bound_typevars))
        elif is_Callable(tp):
            return '%s[[%s], %s]'%(tp_name, ', '.join(params[:-1]),
                    type_str(params[-1], assumed_globals, update_assumed_globals,
                    implicit_globals, bound_Generic, bound_typevars))
        else:
            return '%s[%s]'%(tp_name, ', '.join(params))
    elif hasattr(tp, '__name__'):
        result = _tp_relfq_name(tp, None, assumed_globals, update_assumed_globals,
                implicit_globals)
    elif tp is Any:
        # In Python 3.6 Any does not have __name__.
        result = _tp_relfq_name(tp, 'Any', assumed_globals, update_assumed_globals,
                implicit_globals)
    else:
        # Todo: Care for other special types from typing where necessary.
        result = str(tp)
    if not implicit_globals is None:
        for s in implicit_globals:
            result = result.replace(s.__name__+'.', '')
    return result


def get_types(func):
    """Works like get_type_hints, but returns types as a sequence rather than a
    dictionary. Types are returned in declaration order of the corresponding arguments.
    """
    return _get_types(func, util.is_classmethod(func), util.is_method(func))


def get_member_types(obj, member_name, prop_getter = False):
    """Still experimental, incomplete and hardly tested.
    Works like get_types, but is also applicable to descriptors.
    """
    cls = obj.__class__
    member = getattr(cls, member_name)
    slf = not (isinstance(member, staticmethod) or isinstance(member, classmethod))
    clsm = isinstance(member, classmethod)
    return _get_types(member, clsm, slf, cls, prop_getter)


def _get_types(func, clsm, slf, clss = None, prop_getter = False,
            unspecified_type = Any, infer_defaults = None):
    """Helper for get_types and get_member_types.
    """
    func0 = util._actualfunc(func, prop_getter)
    # check consistency regarding special case with 'self'-keyword
    if not slf:
        argNames = util.getargnames(util.getargspecs(func0))
        if len(argNames) > 0:
            if clsm:
                if argNames[0] != 'cls':
                    util._warn_argname('classmethod using non-idiomatic cls argname',
                            func0, slf, clsm, clss)
    if clss is None and (slf or clsm):
        if slf:
            assert util.is_method(func) or isinstance(func, property)
        if clsm:
            assert util.is_classmethod(func)
        clss = util.get_class_that_defined_method(func)
        assert hasattr(clss, func.__name__)
    args, res = _funcsigtypes(func, slf or clsm, clss, None, prop_getter,
            unspecified_type = unspecified_type, infer_defaults = infer_defaults)
    return _match_stub_type(args), _match_stub_type(res)


def get_type_hints(func):
    """Resembles typing.get_type_hints, but is also workable on Python 2.7 and
    searches stubfiles for type information.
    Also on Python 3, this takes type comments
    (python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code)
    into account if present.
    """
    if not has_type_hints(func):
        # What about defaults?
        return {}
    return _get_type_hints(func)


def _get_type_hints(func, args = None, res = None, infer_defaults = None):
    """Helper for get_type_hints.
    """
    if args is None or res is None:
        args2, res2 = _get_types(func, util.is_classmethod(func),
                util.is_method(func), unspecified_type = type(NotImplemented),
                infer_defaults = infer_defaults)
        if args is None:
            args = args2
        if res is None:
            res = res2
    slf = 1 if util.is_method(func) else 0
    argNames = util.getargnames(util.getargspecs(util._actualfunc(func)))
    result = {}
    if not args is Any:
        prms = get_Tuple_params(args)
        for i in range(slf, len(argNames)):
            if not prms[i-slf] is type(NotImplemented):
                result[argNames[i]] = prms[i-slf]
    result['return'] = res
    return result


def _make_invalid_type_msg(descr, func_name, tp):
    msg = 'Invalid %s in %s:\n    %s is not a type.' % (descr, func_name, str(tp))
    if isinstance(tp, tuple):
        mask = '\n  You might rather want to use typing.Tuple:\n      Tuple[%s]'
        try:
            msg += mask % (', '.join(type_str(t) for t in tp))
        except:
            msg += mask % (', '.join(str(t) for t in tp))
    return msg


def _tpHints_from_annotations(*args):
    for func in args:
        if not func is None and hasattr(func, '__annotations__'):
            res = func.__annotations__
            if not res is None and len(res) > 0:
                return res
    return None


# Only intended for use with __annotations__.
# For typestrings, _funcsigtypesfromstring can directly insert defaults
def _handle_defaults(sig_types, arg_specs, unspecified_indices = None):
    if arg_specs.defaults is None:
        return sig_types
    prms = get_Tuple_params(sig_types[0])
    if len(prms) < len(arg_specs.args):
        # infer missing types from defaults...
        df = len(arg_specs.args)-len(prms)
        if df <= len(arg_specs.defaults):
            resType = [prm for prm in prms]
            for obj in arg_specs.defaults[-df:]:
                resType.append(deep_type(obj))
            if unspecified_indices is None:
                res = Tuple[tuple(resType)], sig_types[1]
                return res
        elif not unspecified_indices is None:
            resType = [prm for prm in prms]
    elif not unspecified_indices is None:
        resType = [prm for prm in prms]
    if not unspecified_indices is None and len(unspecified_indices) > 0:
        off = len(arg_specs.args)-len(arg_specs.defaults)
        for i in unspecified_indices:
            if i >= off:
                resType[i] = deep_type(arg_specs.defaults[i-off])
        res = Tuple[tuple(resType)], sig_types[1]
        return res
    return sig_types


def resolve_fw_decl(in_type, module_name=None, globs=None, level=0,
        search_stack_depth=2):
    '''Resolves forward references in ``in_type``, see
    https://www.python.org/dev/peps/pep-0484/#forward-references.


    Note:

    ``globs`` should be a dictionary containing values for the names
    that must be resolved in ``in_type``. If ``globs`` is not provided, it
    will be created by ``__globals__`` from the module named ``module_name``,
    plus ``__locals__`` from the last ``search_stack_depth`` stack frames (Default: 2),
    beginning at the calling function. This is to resolve cases where ``in_type`` and/or
    types it fw-references are defined inside a function.

    To prevent walking the stack, set ``search_stack_depth=0``.
    Ideally provide a proper ``globs`` for best efficiency.
    See ``util.get_function_perspective_globals`` for obtaining a ``globs`` that can be
    cached. ``util.get_function_perspective_globals`` works like described above.
    '''
    # Also see discussion at https://github.com/Stewori/pytypes/pull/43
    if in_type in _fw_resolve_cache:
        return _fw_resolve_cache[in_type], True
    if globs is None:
        #if not module_name is None:
        globs = util.get_function_perspective_globals(module_name, level+1,
                level+1+search_stack_depth)
    if isinstance(in_type, _basestring):
        # For the case that a pure forward ref is given as string
        out_type = eval(in_type, globs)
        _fw_resolve_cache[in_type] = out_type
        return out_type, True
    elif isinstance(in_type, ForwardRef):
        # Todo: Mabe somehow get globs from in_type.__forward_code__
        if not in_type.__forward_evaluated__:
            in_type.__forward_value__ = eval(in_type.__forward_arg__, globs)
            in_type.__forward_evaluated__ = True
            return in_type, True
    elif is_Tuple(in_type):
        return in_type, any([resolve_fw_decl(in_tp, None, globs)[1] \
                for in_tp in get_Tuple_params(in_type)])
    elif is_Union(in_type):
        return in_type, any([resolve_fw_decl(in_tp, None, globs)[1] \
                for in_tp in get_Union_params(in_type)])
    elif is_Callable(in_type):
        args, res = get_Callable_args_res(in_type)
        ret = any([resolve_fw_decl(in_tp, None, globs)[1] \
                for in_tp in args])
        ret = resolve_fw_decl(res, None, globs)[1] or ret
        return in_type, ret
    elif hasattr(in_type, '__args__') and in_type.__args__ is not None:
        return in_type, any([resolve_fw_decl(in_tp, None, globs)[1] \
                for in_tp in in_type.__args__])
    return in_type, False


def _funcsigtypes(func0, slf, func_class=None, globs=None, prop_getter=False,
        unspecified_type=Any, infer_defaults=None):
    if infer_defaults is None:
        infer_defaults = pytypes.infer_default_value_types
    # Check for stubfile
    actual_func = util._actualfunc(func0, prop_getter)
    func = as_stub_func_if_any(actual_func, func0, func_class)
    stub_func = None
    if isinstance(func, property):
        stub_func = func
        func = util._actualfunc(func, prop_getter)
    try:
        try:
            tpHints = typing.get_type_hints(func)
        except NameError:
            if globs is None:
                globs = util.get_function_perspective_globals(func.__module__, 3)
            tpHints = typing.get_type_hints(func, globs)
    except AttributeError:
        tpHints = None
    tpStr = _get_typestrings(func, slf)
    argSpecs = util.getargspecs(actual_func)
    hints_from_annotations = False
    if tpHints is None or not tpHints:
        tpHints = _tpHints_from_annotations(func0, actual_func, stub_func, func)
        hints_from_annotations = True
    if (tpStr is None or tpStr[0] is None) and tpHints is None:
        # What about defaults?
        return Any, Any
    if not (tpStr is None or tpStr[0] is None) and tpStr[0].find('...') != -1:
        numArgs = len(argSpecs.args) - 1 if slf else 0
        while len(tpStr[1]) < numArgs:
            tpStr[1].append(None)
    argNames = util.getargnames(argSpecs)
    if slf:
        if not tpHints is None and tpHints and not func_class is None and \
                argNames[0] in tpHints:
            # cls or self was type-annotated
            if not util.is_classmethod(func) and not _issubclass(tpHints[argNames[0]], func_class):
                # todo: What about classmethods?
                str_args = (func.__module__, func_class.__name__, func.__name__,
                        'self' if argNames[0] == 'self' else "self-arg '"+argNames[0]+"'",
                        type_str(func_class), type_str(tpHints[argNames[0]]),
                        "\nCalling the self-arg '"+
                        argNames[0]+"' is not recommended." if argNames[0] != 'self' else '')
                msg = ('%s.%s.%s declares invalid type for %s:\n'+
                        'Expected: %s\nDeclared: %s'+
                        "\nAnnotating the self-arg with a type is not recommended.%s") % str_args
                if pytypes.checking_enabled and not pytypes.warning_mode:
                    raise TypeError(msg)
                else:
                    import traceback
                    tb = traceback.extract_stack()
                    off = util._calc_traceback_list_offset(tb)
                    warn_explicit(msg, pytypes.TypeWarning, tb[off][0], tb[off][1])
        argNames = argNames[1:]
    if not tpHints is None and tpHints:
        if hints_from_annotations:
            tmp = tpHints
            tpHints = {}
            for key in tmp:
                val = tmp[key]
                if val is None:
                    val = type(None)
                else:
                    # We manually create globals here for resolve_fw_decl, because globals
                    # might be needed again later. Usually resolve_fw_decl can create
                    # globals internally.
                    if globs is None:
                        globs = util.get_function_perspective_globals(func.__module__, 3)
                    val = resolve_fw_decl(val, func.__module__, globs, 3)[0]
                tpHints[key] = val
        # We're running Python 3 or have custom __annotations__ in Python 2.7
        retTp = tpHints['return'] if 'return' in tpHints else Any
        unspecIndices = []
        for i in range(len(argNames)):
            if not argNames[i] in tpHints:
                unspecIndices.append(i)
        resType = (Tuple[tuple((tpHints[t] if t in tpHints else unspecified_type) \
                for t in argNames)], retTp if not retTp is None else type(None))
        if infer_defaults:
            resType = _handle_defaults(resType, argSpecs, unspecIndices)
        if not pytypes.annotations_override_typestring and not \
                (tpStr is None or tpStr[0] is None or tpStr[0] == 'ignore'):
            if pytypes.strict_annotation_collision_check:
                raise TypeError('%s.%s has multiple type declarations.'
                        % (func.__module__, func.__name__))
            else:
                if globs is None:
                    globs = util.get_function_perspective_globals(func.__module__, 3)
                resType2 = _funcsigtypesfromstring(*tpStr, argspec=argSpecs, glbls=globs,
                        argCount=len(argNames), unspecified_type=unspecified_type,
                        defaults=argSpecs.defaults if infer_defaults else None,
                        func=actual_func, func_class=func_class, slf=slf)
                if resType != resType2:
                    raise TypeError('%s.%s declares incompatible types:\n'
                            % (func.__module__, func.__name__)
                            + 'Via hints:   %s\nVia comment: %s'
                            % (type_str(resType), type_str(resType2)))
        try:
            typing._type_check(resType[0], '') # arg types
        except TypeError:
            raise TypeError(_make_invalid_type_msg('arg types',
                    util._fully_qualified_func_name(func, slf, func_class), resType[0]))
        try:
            typing._type_check(resType[1], '') # return type
        except TypeError:
            raise TypeError(_make_invalid_type_msg('return type',
                    util._fully_qualified_func_name(func, slf, func_class), resType[1]))
        return resType
    if globs is None:
        globs = util.get_function_perspective_globals(func.__module__, 3)
    res = _funcsigtypesfromstring(*tpStr, glbls=globs, argspec=argSpecs,
            argCount=len(argNames),
            defaults = argSpecs.defaults if infer_defaults else None,
            unspecified_type=unspecified_type, func=actual_func,
            func_class=func_class, slf=slf)
    try:
        typing._type_check(res[0], '') # arg types
    except TypeError:
        raise TypeError(_make_invalid_type_msg('arg types',
                util._fully_qualified_func_name(func, slf, func_class), res[0]))
    try:
        typing._type_check(res[1], '') # return type
    except TypeError:
        raise TypeError(_make_invalid_type_msg('return type',
                util._fully_qualified_func_name(func, slf, func_class), res[1]))
    if pytypes.annotations_from_typestring:
        if not hasattr(func0, '__annotations__') or len(func0.__annotations__) == 0:
            if not infer_defaults:
                func0.__annotations__ = _get_type_hints(func0, res[0], res[1])
            else:
                res2 = _funcsigtypesfromstring(*tpStr, argspec = argSpecs, glbls = globs,
                        argCount = len(argNames), unspecified_type = unspecified_type,
                        func = actual_func, func_class = func_class, slf = slf)
                func0.__annotations__ = _get_type_hints(func0, res2[0], res2[1])
    return res


def _issubclass_Mapping_covariant(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    This subclass-check treats Mapping-values as covariant.
    """
    if is_Generic(subclass):
        suborigin = _origin(subclass)
        if suborigin is None or not issubclass(suborigin, Mapping):
            return _issubclass_Generic(subclass, superclass, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        if superclass.__args__ is None:
            if not pytypes.check_unbound_types:
                raise TypeError("Attempted to check unbound mapping type(superclass): "+
                        str(superclass))
            if pytypes.strict_unknown_check:
                # Nothing is subtype of unknown type
                return False
            super_args = (Any, Any)
        else:
            super_args = superclass.__args__
        if subclass.__args__ is None:
            if not pytypes.check_unbound_types:
                raise TypeError("Attempted to check unbound mapping type(subclass): "+
                        str(subclass))
            if pytypes.strict_unknown_check:
                # Nothing can subclass unknown type
                # For value type it would be okay if superclass had Any as value type,
                # as unknown type is subtype of Any. However, since key type is invariant
                # and also unknown, it cannot pass.
                return False
            sub_args = (Any, Any)
        else:
            sub_args = subclass.__args__
        if not _issubclass(sub_args[0], super_args[0],
                bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            return False
        if not _issubclass(sub_args[1], super_args[1],
                bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            return False
        return True
    if is_Generic(superclass):
        # For Python 3.5; in this Python e.g. issubclass(dict, Dict[str, str]) would pass.
        return False
    try:
        return issubclass(subclass, superclass)
    except TypeError:
        return False


def _get_inheritance_stack(subclass, superclass_origin):
    stack = [subclass]
    b = subclass
    bo = _origin(b)
    bo = _extra_inv(bo)
    if bo is superclass_origin:
        return stack, [_parameters(x) for x in stack], [getattr(x, '__args__', None) for x in stack]
    while True:
        for bs in _bases(b):
            bso = _origin(bs)
            bso = _extra_inv(bso)
            try:
                bs_check = is_Generic(bs) and issubclass(bs, superclass_origin)
            except TypeError:
                bs_check = False
            if bs_check or bso is not None and issubclass(bso, superclass_origin):
                if getattr(bs, '__args__', None) is not None:
                    # Required for Python 2.7 and 3.5
                    stack.append(bs)
                b = bs
                bo = bso
                if b is superclass_origin or bo is superclass_origin:
                    return stack, [_parameters(x) for x in stack], [getattr(x, '__args__', None) for x in stack]
                break
        else:
            break
        continue


def _resolve_parameters(params_list, args_list):
    pos_list = [[x[1].index(y) if x[1] is not None else -1 for y in x[0]]
            for x in zip(params_list, args_list)][::-1]
    state = list(args_list[-1])
    for i in range(len(pos_list)-1):
        pos = 0
        if args_list[-2-i] is not None:
            for j in pos_list[i][:len(state)]:
                if j >= len(state):
                    break
                state[j] = args_list[-2-i][pos]
                pos += 1
        else:
            for j in pos_list[i][:len(state)]:
                if j >= len(state):
                    break
                state[j] = params_list[-2-i][pos]
                pos += 1
    return state


def _select_Generic_superclass_parameters(subclass, superclass_origin):
    """Helper for _issubclass_Generic.
    """
    superclass_origin = _extra_inv(superclass_origin)
    x = _get_inheritance_stack(subclass, superclass_origin)
    if x is not None:
        return _resolve_parameters(x[1], x[2])


def _find_Generic_super_origin(subclass, superclass_origin):
    """Helper for _issubclass_Generic.
    """
    stack = [subclass]
    param_map = {}
    _alias = _typing_3_7 and isinstance(subclass, typing._GenericAlias)
    if _alias:
        alsprms = _parameters(subclass.__origin__)
    while len(stack) > 0:
        bs = stack.pop()
        if is_Generic(bs):
            orig_bs = _origin(bs)
            if not orig_bs is None:
                bsoprms = _parameters(orig_bs)
                if len(bsoprms) > 0:
                    if _alias: # Python 3.7
                    # The correct rule seems to be to match typevars by order if no name matches.
                        for i in range(len(bs.__args__)):
                            try:
                                ors = alsprms[i]
                                if bs.__args__[i] != ors and isinstance(bs.__args__[i], TypeVar):
                                    if not bs.__args__[i] in alsprms:
                                        param_map[bs.__args__[i]] = ors
                            except IndexError:
                                ors = bsoprms[i]
                                if bs.__args__[i] != ors and isinstance(bs.__args__[i], TypeVar):
                                    param_map[ors] = bs.__args__[i]
                    else:
                        for i in range(len(bs.__args__)):
                            ors = bsoprms[i]
                            if bs.__args__[i] != ors and isinstance(bs.__args__[i], TypeVar):
                                param_map[ors] = bs.__args__[i]
            if (orig_bs is superclass_origin or \
                    bs is superclass_origin):
                prms = []
                bsprms = _parameters(bs)
                try:
                    if len(bsoprms) > len(bsprms):
                        prms.extend(bsoprms)
                    else:
                        prms.extend(bsprms)
                except:
                    prms.extend(bsprms)
                for i in range(len(prms)):
                    while prms[i] in param_map:
                        prms[i] = param_map[prms[i]]
                return prms
            stack.extend(_bases(bs))
    return None


def get_arg_for_TypeVar(typevar, generic, arg_holder=None):
    """Retrieves the parameter value of a given TypeVar from a Generic.
    Returns None if the generic does not contain an appropriate value.
    Note that the TypeVar is compared by instance and not by name.
    E.g. using a local TypeVar T would yield different results than
    using typing.T despite the equal name.
    """
    if not is_Generic(generic):
        generic = get_Generic_type(generic)
    return _get_arg_for_TypeVar(typevar, generic,
            generic if arg_holder is None else arg_holder)


def _get_arg_for_TypeVar(typevar, generic, arg_holder):
    bases = _bases(generic)
    try:
        # typing-3.5.3.0+ special treatment:
        # It does not contain __origin__ in __bases__.
        if not _origin(generic) is None and not bases[0] == _origin(generic):
            res = _get_arg_for_TypeVar(typevar, generic.__origin__, arg_holder)
            if not res is None:
                return res
    except:
        pass
    try:
        if _typing_3_7:
            og = _origin(generic)
            _extra_inv(og)
        else:
            og = generic
        prms = _parameters(og)
        if typevar in prms:
            idx = prms.index(typevar)
            res = _select_Generic_superclass_parameters(arg_holder, og)
            return res[idx]
    except (AttributeError, TypeError):
        return None
    for base in bases:
        res = _get_arg_for_TypeVar(typevar, base, arg_holder)
        if not res is None:
            return res


def _issubclass_Generic(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    """
    # this function is partly based on code from typing module 3.5.2.2
    if subclass is None:
        return False
    subclass = _extra_inv(subclass)
    origin = _origin(superclass)
    if is_Tuple(subclass):
        tpl_prms = get_Tuple_params(subclass)
        if not tpl_prms is None and len(tpl_prms) == 0:
            # (This section is required because Empty shall not be
            # used on Tuples.)
            # an empty Tuple is any Sequence, regardless of type
            # note that we needn't consider superclass beeing a tuple,
            # because that should have been checked in _issubclass_Tuple
            sup = superclass if origin is None else origin
            sup = _extra_inv(sup)
            return issubclass(typing.Sequence, sup)
        subclass = Sequence[Union[tpl_prms]]
    if is_Generic(subclass):
        # For a class C(Generic[T]) where T is co-variant,
        # C[X] is a subclass of C[Y] iff X is a subclass of Y.
        suborigin = _origin(subclass)
        if suborigin is None:
            orig_bases = _bases(subclass)
            for scls in orig_bases:
                if is_Generic(scls):
                    if _issubclass_Generic(scls, superclass, bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs,
                            _recursion_check):
                        return True
        #Formerly: if origin is not None and origin is subclass.__origin__:
        elif origin is not None and \
                _issubclass(_origin(subclass), origin, bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            prms = _parameters(origin)
            assert len(superclass.__args__) == len(prms)
            if len(subclass.__args__) == len(prms):
                sub_args = subclass.__args__
            else:
                # We select the relevant subset of args by TypeVar-matching
                sub_args = _select_Generic_superclass_parameters(subclass, origin)
                assert len(sub_args) == len(prms)
            for p_self, p_cls, p_origin in zip(superclass.__args__,
                                            sub_args,
                                            prms):
                if isinstance(p_origin, TypeVar):
                    if p_origin.__covariant__:
                        # Covariant -- p_cls must be a subclass of p_self.
                        if not _issubclass(p_cls, p_self, bound_Generic, bound_typevars,
                                bound_typevars_readonly, follow_fwd_refs,
                                _recursion_check):
                            break
                    elif p_origin.__contravariant__:
                        # Contravariant.  I think it's the opposite. :-)
                        if not _issubclass(p_self, p_cls, bound_Generic, bound_typevars,
                                bound_typevars_readonly, follow_fwd_refs,
                                _recursion_check):
                            break
                    else:
                        # Invariant -- p_cls and p_self must equal.
                        if p_self != p_cls:
                            if not _issubclass(p_cls, p_self, bound_Generic, bound_typevars,
                                    bound_typevars_readonly, follow_fwd_refs,
                                    _recursion_check):
                                break
                            if not _issubclass(p_self, p_cls, bound_Generic, bound_typevars,
                                    bound_typevars_readonly, follow_fwd_refs,
                                    _recursion_check):
                                break
                else:
                    # If the origin's parameter is not a typevar,
                    # insist on invariance.
                    if p_self != p_cls:
                        if not _issubclass(p_cls, p_self, bound_Generic, bound_typevars,
                                    bound_typevars_readonly, follow_fwd_refs,
                                    _recursion_check):
                            break
                        if not _issubclass(p_self, p_cls, bound_Generic, bound_typevars,
                                    bound_typevars_readonly, follow_fwd_refs,
                                    _recursion_check):
                            break
            else:
                return True
            # If we break out of the loop, the superclass gets a chance.

        # I.e.: origin is None or not _issubclass(suborigin, origin)
        # In this case we must consider origin or suborigin to be None
        # We treat param-values as unknown in the following sense:
        #   for covariant params: treat unknown more-or-equal specific than Any
        #   for contravariant param: Any more-or-equal specific than Unknown
        #   for invariant param: unknown never passes
        # if both are unknown:
        #   return False (?) (or NotImplemented? Or let a flag decide behavior?)
        if origin is None:
            if not pytypes.check_unbound_types:
                raise TypeError("Attempted to check unbound type(superclass): "+str(superclass))
            if not suborigin is None:
                if not type.__subclasscheck__(superclass, suborigin):
                    return False
                prms = _find_Generic_super_origin(suborigin, superclass)
                args = _select_Generic_superclass_parameters(subclass, superclass)
                for i in range(len(prms)):
                    if prms[i].__covariant__:
                        if pytypes.strict_unknown_check:
                            return False
                    elif prms[i].__contravariant__:
                        # Subclass-value must be wider than or equal to Any, i.e. must be Any:
                        if not args[i] is Any:
                            return False
                    else:
                        return False
                return True
            #else:
                # nothing to do here... (?)
        elif suborigin is None:
            if not pytypes.check_unbound_types:
                raise TypeError("Attempted to check unbound type (subclass): "+str(subclass))
            if not type.__subclasscheck__(origin, subclass):
                return False
            prms = _parameters(origin)
            for i in range(len(prms)):
                if prms[i].__covariant__:
                    # subclass-arg here is unknown, so in superclass only Any can pass:
                    if not superclass.__args__[i] is Any:
                        return False
                elif prms[i].__contravariant__:
                    if pytypes.strict_unknown_check:
                        return False
                else:
                    return False
            return True
# 	Formerly: if super(GenericMeta, superclass).__subclasscheck__(subclass):
    try:
        if type.__subclasscheck__(superclass, subclass):
            return True
    except TypeError: pass
    if _extra(superclass) is None or is_Generic(subclass):
        return False
    return _issubclass_2(subclass, _extra(superclass), bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check)


def _issubclass_Tuple(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    """
    # this function is partly based on code from typing module 3.5.2.2
    subclass = _extra_inv(subclass)
    if not is_Type(subclass):
        # To TypeError.
        return False
    if not is_Tuple(subclass):
        if is_Generic(subclass):
            try:
                return _issubclass_Generic(subclass, superclass,
                        bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs,
                        _recursion_check)
            except:
                pass
        elif is_Union(subclass):
            return all(_issubclass_Tuple(t, superclass, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
                    for t in get_Union_params(subclass))
        else:
            return False
    super_args = get_Tuple_params(superclass)
    if super_args is None:
        return True
    sub_args = get_Tuple_params(subclass)
    if sub_args is None:
        return False  # ???
    # Covariance.
    # For now we check ellipsis in most explicit manner.
    # Todo: Compactify and Pythonify ellipsis branches (tests required before this).
    if is_Tuple_ellipsis(subclass):
        if is_Tuple_ellipsis(superclass):
            # both are ellipsis, so no length check
            common = min(len(super_args), len(sub_args))
            for i in range(common):
                if not _issubclass(sub_args[i], super_args[i], bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                    return False
            if len(super_args) < len(sub_args):
                for i in range(len(super_args), len(sub_args)):
                    # Check remaining super args against the ellipsis type
                    if not _issubclass(sub_args[i], super_args[-1], bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                        return False
            elif len(super_args) > len(sub_args):
                for i in range(len(sub_args), len(super_args)):
                    # Check remaining super args against the ellipsis type
                    if not _issubclass(sub_args[-1], super_args[i], bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                        return False
            return True
        else:
            # only subclass has ellipsis
            if len(super_args) < len(sub_args)-1:
                return False
            for i in range(len(sub_args)-1):
                if not _issubclass(sub_args[i], super_args[i], bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                    return False
            for i in range(len(sub_args), len(super_args)):
                # Check remaining super args against the ellipsis type
                if not _issubclass(sub_args[-1], super_args[i], bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                    return False
            return True
    elif is_Tuple_ellipsis(superclass):
        # only superclass has ellipsis
        if len(super_args)-1 > len(sub_args):
            return False
        for i in range(len(super_args)-1):
            if not _issubclass(sub_args[i], super_args[i], bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                return False
        for i in range(len(super_args), len(sub_args)):
            # Check remaining sub args against the ellipsis type
            if not _issubclass(sub_args[i], super_args[-1], bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                return False
        return True
    else:
        # none has ellipsis, so strict length check
        return (len(super_args) == len(sub_args) and
                all(_issubclass(x, p, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
                for x, p in zip(sub_args, super_args)))


def _issubclass_Union(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    """
    if not follow_fwd_refs:
        return _issubclass_Union_rec(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    try:
        # try to succeed fast, before we go the expensive way involving recursion checks
        return _issubclass_Union_rec(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, False, _recursion_check)
    except pytypes.ForwardRefError:
        return _issubclass_Union_rec(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check)


def _issubclass_Union_rec(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass_Union.
    """
    # this function is partly based on code from typing module 3.5.2.2
    super_args = get_Union_params(superclass)
    if super_args is None:
        return is_Union(subclass)
    elif is_Union(subclass):
        sub_args = get_Union_params(subclass)
        if sub_args is None:
            return False
        return all(_issubclass(c, superclass, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check) \
                for c in (sub_args))
    elif isinstance(subclass, TypeVar):
        if subclass in super_args:
            return True
        if subclass.__constraints__:
            return _issubclass(Union[subclass.__constraints__],
                    superclass, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        return False
    else:
        return any(_issubclass(subclass, t, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check) \
                for t in super_args)


# This is just a crutch, because issubclass sometimes tries to be too smart.
# Note that this doesn't consider __subclasshook__ etc, so use with care!
def _has_base(cls, base):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    """
    if cls is base:
        return True
    elif cls is None:
        return False
    try:
        for bs in cls.__bases__:
            if _has_base(bs, base):
                return True
    except:
        pass
    return False


def _issubclass(subclass, superclass, bound_Generic=None, bound_typevars=None,
            bound_typevars_readonly=False, follow_fwd_refs=True, _recursion_check=None):
    """Access this via ``pytypes.is_subtype``.
    Works like ``issubclass``, but supports PEP 484 style types from ``typing`` module.

    subclass : type
    The type to check for being a subtype of ``superclass``.

    superclass : type
    The type to check for being a supertype of ``subclass``.

    bound_Generic : Optional[Generic]
    A type object holding values for unbound typevars occurring in ``subclass`` or ``superclass``.
    Default: None
    If subclass or superclass contains unbound ``TypeVar``s and ``bound_Generic`` is
    provided, this function attempts to retrieve corresponding values for the
    unbound ``TypeVar``s from ``bound_Generic``.
    In collision case with ``bound_typevars`` the value from ``bound_Generic`` if preferred.

    bound_typevars : Optional[Dict[typing.TypeVar, type]]
    A dictionary holding values for unbound typevars occurring in ``subclass`` or ``superclass``.
    Default: {}
    Depending on ``bound_typevars_readonly`` pytypes can also bind values to typevars as needed.
    This is done by inserting according mappings into this dictionary. This can e.g. be useful to
    infer values for ``TypeVar``s or to consistently check a set of ``TypeVar``s across multiple
    calls, e.g. when checking all arguments of a function call.
    In collision case with ``bound_Generic`` the value from ``bound_Generic`` if preferred.

    bound_typevars_readonly : bool
    Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary.
    Default: True
    If set to False, pytypes cannot assign values to ``TypeVar``s, but only checks regarding
    values already present in ``bound_typevars`` or ``bound_Generic``.

    follow_fwd_refs : bool
    Defines if ``_ForwardRef``s should be explored.
    Default: True
    If this is set to ``False`` and a ``_ForwardRef`` is encountered, pytypes aborts the check
    raising a ForwardRefError.

    _recursion_check : Optional[Dict[type, Set[type]]]
    Internally used for recursion checks.
    Default: None
    If ``Union``s and ``_ForwardRef``s occur in the same type, recursions can occur. As soon as
    a ``_ForwardRef`` is encountered, pytypes automatically creates this dictionary and
    continues in recursion-proof manner.
    """
    if bound_typevars is None:
        bound_typevars = {}
    if superclass is Any:
        return True
    if subclass == superclass:
        return True
    if subclass is Any:
        return superclass is Any
    if isinstance(subclass, ForwardRef) or isinstance(superclass, ForwardRef):
        if not follow_fwd_refs:
            raise pytypes.ForwardRefError(
                    "ForwardRef encountered, but follow_fwd_refs is False: '%s'\n%s"%
                    ((subclass if isinstance(subclass, ForwardRef) else superclass)
                    .__forward_arg__,
                    "Retry with follow_fwd_refs=True."))
        # Now that forward refs are in the game, we must continue in recursion-proof manner:
        # Since Python 3.7 ForwardRef's hash is itself affected by this type of recursion.
        # So we only store __forward_arg__ if it's a ForwardRef. This might fail if the sub-
        # or superclass that is not directly a ForwardRef contains a ForwardRef. So it this
        # approach might require revision.
        supkey = superclass.__forward_arg__ if isinstance(superclass, ForwardRef) else superclass
        subkey = subclass.__forward_arg__ if isinstance(subclass, ForwardRef) else subclass
        if _recursion_check is None:
            _recursion_check = {supkey: {subkey}}
        elif supkey in _recursion_check:
            if subkey in _recursion_check[supkey]:
                # recursion detected
                return False
            else:
                _recursion_check[supkey].add(subkey)
        else:
            _recursion_check[supkey] = {subkey}
        if isinstance(subclass, ForwardRef):
            if not subclass.__forward_evaluated__:
                raise pytypes.ForwardRefError("ForwardRef in subclass not evaluated: '%s'\n%s"%
                        (subclass.__forward_arg__, "Use pytypes.resolve_fw_decl"))
            else:
                return _issubclass(subclass.__forward_value__, superclass,
                        bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        else: # isinstance(superclass, ForwardRef)
            if not superclass.__forward_evaluated__:
                raise pytypes.ForwardRefError("ForwardRef in superclass not evaluated: '%s'\n%s"%
                        (superclass.__forward_arg__, "Use pytypes.resolve_fw_decl"))
            else:
                return _issubclass(subclass, superclass.__forward_value__,
                        bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    if pytypes.apply_numeric_tower:
        if superclass is float and subclass is int:
            return True
        elif superclass is complex and \
                (subclass is int or subclass is float):
            return True
    if _is_extra(superclass):
        if _is_extra(subclass):
            try:
                # if both are not PEP 484 types, attempt to use ordinary issubclass
                return issubclass(subclass, superclass)
            except: pass
        superclass = _extra_dict[superclass]
    try:
        if _issubclass_2(subclass, Empty, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            for empty_target in [Container, Sized, Iterable]:
                # We cannot simply use Union[Container, Sized, Iterable] as empty_target
                # because of implementation detail behavior of _issubclass_2.
                # It would e.g. cause false negative result of
                # is_subtype(Empty[Dict], Empty[Container])
                try:
                    suporigin = _origin(superclass)
                    suporigin = _extra_inv(suporigin)
                    if _issubclass_2(suporigin, empty_target,
                            bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                        return _issubclass_2(subclass.__args__[0], suporigin,
                                bound_Generic, bound_typevars,
                                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
                except: pass
                if _issubclass_2(superclass, empty_target,
                        bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                    return _issubclass_2(subclass.__args__[0], superclass,
                            bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    except: pass
    try:
        if _issubclass_2(superclass, Empty, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            for empty_target in [Container, Sized, Iterable]:
                # We cannot simply use Union[Container, Sized, Iterable] as empty_target
                # because of implementation detail behavior of _issubclass_2.
                try:
                    if _issubclass_2(subclass.__origin__, empty_target,
                            bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                        return _issubclass_2(subclass.__origin__, superclass.__args__[0],
                                bound_Generic, bound_typevars,
                                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
                except: pass
                if _issubclass_2(subclass, empty_target, bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                    return _issubclass_2(subclass, superclass.__args__[0],
                            bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    except: pass
    if isinstance(superclass, TypeVar):
        if not superclass.__bound__ is None:
            if not _issubclass(subclass, superclass.__bound__, bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                return False
        if not bound_typevars is None:
            try:
                if superclass.__contravariant__:
                    return _issubclass(bound_typevars[superclass], subclass, bound_Generic,
                            bound_typevars, bound_typevars_readonly, follow_fwd_refs,
                            _recursion_check)
                elif superclass.__covariant__:
                    return _issubclass(subclass, bound_typevars[superclass], bound_Generic,
                            bound_typevars, bound_typevars_readonly, follow_fwd_refs,
                            _recursion_check)
                else:
                    return _issubclass(bound_typevars[superclass], subclass, bound_Generic,
                            bound_typevars, bound_typevars_readonly, follow_fwd_refs,
                            _recursion_check) and \
                            _issubclass(subclass, bound_typevars[superclass], bound_Generic,
                            bound_typevars, bound_typevars_readonly, follow_fwd_refs,
                            _recursion_check)
            except:
                pass
        if not bound_Generic is None:
            superclass = get_arg_for_TypeVar(superclass, bound_Generic)
            if not superclass is None:
                return _issubclass(subclass, superclass, bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        if not bound_typevars is None:
            if bound_typevars_readonly:
                return False
            else:
                # bind it...
                bound_typevars[superclass] = subclass
                return True
        return False
    if isinstance(subclass, TypeVar):
        if not bound_typevars is None:
            try:
                return _issubclass(bound_typevars[subclass], superclass, bound_Generic,
                        bound_typevars, bound_typevars_readonly, follow_fwd_refs,
                        _recursion_check)
            except:
                pass
        if not bound_Generic is None:
            subclass = get_arg_for_TypeVar(subclass, bound_Generic)
            if not subclass is None:
                return _issubclass(subclass, superclass, bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        if not subclass.__bound__ is None:
            return _issubclass(subclass.__bound__, superclass, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
        return False
    res = _issubclass_2(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    return res


def _issubclass_2(subclass, superclass, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Helper for _issubclass, a.k.a pytypes.issubtype.
    """
    if is_Tuple(superclass):
        return _issubclass_Tuple(subclass, superclass, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    if is_Union(superclass):
        return _issubclass_Union(subclass, superclass, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    if is_Union(subclass):
        return all(_issubclass(t, superclass, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check) \
                for t in get_Union_params(subclass))
    if is_Generic(superclass):
        cls = _origin(superclass)
        if cls is None:
            cls = superclass
        # We would rather use issubclass(superclass.__origin__, Mapping), but that's somehow erroneous
        if pytypes.covariant_Mapping and (_has_base(cls, Mapping) or
                    # Python 3.7 maps everything to collections.abc:
                    (_is_extra(cls) and issubclass(cls, collections.abc.Mapping))):
            return _issubclass_Mapping_covariant(subclass, superclass,
                    bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs,
                    _recursion_check)
        else:
            return _issubclass_Generic(subclass, superclass, bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    subclass = _extra_inv(subclass)
    try:
        return issubclass(subclass, superclass)
    except TypeError:
        if not is_Type(subclass):
            # For Python 3.7, types from typing are not types.
            # So issubclass emits TypeError: issubclass() arg 1 must be a class
            raise TypeError("Invalid type declaration: %s, %s" %
                        (type_str(subclass), type_str(superclass)))
        return False


def _isinstance_Callable(obj, cls, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check,
            check_callables = True):
    # todo: Let pytypes somehow create a Callable-scoped error message,
    # e.g. instead of
    #	Expected: Tuple[Callable[[str, int], str], str]
    #	Received: Tuple[function, str]
    # make
    #	Expected: Tuple[Callable[[str, int], str], str]
    #	Received: Tuple[Callable[[str, str], str], str]
    if not hasattr(obj, '__call__'):
        return False
    if has_type_hints(obj):
        slf_or_cls = util.is_method(obj) or util.is_classmethod(obj)
        parent_cls = util.get_class_that_defined_method(obj) if slf_or_cls else None
        argSig, resSig = _funcsigtypes(obj, slf_or_cls, parent_cls)
        argSig = _match_stub_type(argSig)
        resSig = _match_stub_type(resSig)
        clb_args, clb_res = get_Callable_args_res(cls)
        if not _issubclass(Tuple[clb_args], argSig, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            return False
        if not _issubclass(resSig, clb_res, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            return False
        return True
    return not check_callables


def _isinstance(obj, cls, bound_Generic=None, bound_typevars=None,
            bound_typevars_readonly=False, follow_fwd_refs=True, _recursion_check=None):
    """Access this via ``pytypes.is_of_type``.
    Works like ``isinstance``, but supports PEP 484 style types from ``typing`` module.

    obj : Any
    The object to check for being an instance of ``cls``.

    cls : type
    The type to check for ``obj`` being an instance of.

    bound_Generic : Optional[Generic]
    A type object holding values for unbound typevars occurring in ``cls``.
    Default: None
    If ``cls`` contains unbound ``TypeVar``s and ``bound_Generic`` is provided, this function
    attempts to retrieve corresponding values for the unbound ``TypeVar``s from ``bound_Generic``.
    In collision case with ``bound_typevars`` the value from ``bound_Generic`` if preferred.

    bound_typevars : Optional[Dict[typing.TypeVar, type]]
    A dictionary holding values for unbound typevars occurring in ``cls``.
    Default: {}
    Depending on ``bound_typevars_readonly`` pytypes can also bind values to typevars as needed.
    This is done by inserting according mappings into this dictionary. This can e.g. be useful to
    infer values for ``TypeVar``s or to consistently check a set of ``TypeVar``s across multiple
    calls, e.g. when checking all arguments of a function call.
    In collision case with ``bound_Generic`` the value from ``bound_Generic`` if preferred.

    bound_typevars_readonly : bool
    Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary.
    Default: True
    If set to False, pytypes cannot assign values to ``TypeVar``s, but only checks regarding
    values already present in ``bound_typevars`` or ``bound_Generic``.

    follow_fwd_refs : bool
    Defines if ``ForwardRef``s should be explored.
    Default: True
    If this is set to ``False`` and a ``ForwardRef`` is encountered, pytypes aborts the check
    raising a ForwardRefError.

    _recursion_check : Optional[Dict[type, Set[type]]]
    Internally used for recursion checks.
    Default: None
    If ``Union``s and ``ForwardRef``s occur in the same type, recursions can occur. As soon as
    a ``ForwardRef`` is encountered, pytypes automatically creates this dictionary and
    continues in recursion-proof manner.
    """
    if bound_typevars is None:
        bound_typevars = {}
    # Special treatment if cls is Iterable[...]
    if is_Generic(cls) and cls.__origin__ is typing.Iterable:
        if not is_iterable(obj):
            return False
        itp = get_iterable_itemtype(obj)
        if itp is None:
            return True
        else:
            return _issubclass(itp, cls.__args__[0], bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    if is_Generic(cls) and cls.__origin__ is typing.Iterator:
        if not is_iterator(obj):
            return False
        itp = get_iterable_itemtype(obj)
        if itp is None:
            return True
        else:
            return _issubclass(itp, cls.__args__[0], bound_Generic, bound_typevars,
                    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    if is_Callable(cls):
        return _isinstance_Callable(obj, cls, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check)
    return _issubclass(deep_type(obj), cls, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check)


def _make_generator_error_message(tp, gen, expected_tp, incomp_text):
    _cmp_msg_format = 'Expected: %s\nReceived: %s'
    # todo: obtain fully qualified generator name
    return gen.__name__+' '+incomp_text+':\n'+_cmp_msg_format \
                % (type_str(expected_tp), type_str(tp))


def generator_checker_py3(gen, gen_type, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Builds a typechecking wrapper around a Python 3 style generator object.
    """
    initialized = False
    sn = None
    try:
        while True:
            a = gen.send(sn)
            if initialized or not a is None:
                if not gen_type.__args__[0] is Any and \
                        not _isinstance(a, gen_type.__args__[0], bound_Generic, bound_typevars,
                                bound_typevars_readonly, follow_fwd_refs,
                                _recursion_check):
                    tpa = deep_type(a)
                    msg = _make_generator_error_message(deep_type(a), gen, gen_type.__args__[0],
                            'has incompatible yield type')
                    _raise_typecheck_error(msg, True, a, tpa, gen_type.__args__[0])
            initialized = True
            sn = yield a
            if not gen_type.__args__[1] is Any and \
                    not _isinstance(sn, gen_type.__args__[1], bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                tpsn = deep_type(sn)
                msg = _make_generator_error_message(tpsn, gen, gen_type.__args__[1],
                        'has incompatible send type')
                _raise_typecheck_error(msg, False, sn, tpsn, gen_type.__args__[1])
    except StopIteration as st:
        # Python 3:
        # todo: Check if st.value is always defined (i.e. as None if not present)
        if not gen_type.__args__[2] is Any and \
                not _isinstance(st.value, gen_type.__args__[2], bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            tpst = deep_type(st.value)
            msg = _make_generator_error_message(tpst, gen, gen_type.__args__[2],
                    'has incompatible return type')
            _raise_typecheck_error(msg, True, st.value, tpst, gen_type.__args__[2])
        return


def generator_checker_py2(gen, gen_type, bound_Generic, bound_typevars,
            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
    """Builds a typechecking wrapper around a Python 2 style generator object.
    """
    initialized = False
    sn = None
    while True:
        a = gen.send(sn)
        if initialized or not a is None:
            if not gen_type.__args__[0] is Any and \
                    not _isinstance(a, gen_type.__args__[0], bound_Generic, bound_typevars,
                            bound_typevars_readonly, follow_fwd_refs, _recursion_check):
                tpa = deep_type(a)
                msg = _make_generator_error_message(tpa, gen, gen_type.__args__[0],
                        'has incompatible yield type')
                _raise_typecheck_error(msg, True, a, tpa, gen_type.__args__[0])
        initialized  = True
        sn = yield a
        if not gen_type.__args__[1] is Any and \
                not _isinstance(sn, gen_type.__args__[1], bound_Generic, bound_typevars,
                        bound_typevars_readonly, follow_fwd_refs, _recursion_check):
            tpsn = deep_type(sn)
            msg = _make_generator_error_message(tpsn, gen, gen_type.__args__[1],
                    'has incompatible send type')
            _raise_typecheck_error(msg, False, sn, tpsn, gen_type.__args__[1])


def _make_iterator_error_message(tp, itr, expected_tp, incomp_text,
            bound_Generic, bound_typevars, bound_typevars_readonly):
    _cmp_msg_format = 'Expected: %s\nReceived: %s'
    # todo: obtain fully qualified generator name
    return type_str(itr, bound_Generic=bound_Generic, bound_typevars=bound_typevars)+' '+ \
            incomp_text+':\n'+_cmp_msg_format % (
            type_str(expected_tp, bound_Generic=bound_Generic, bound_typevars=bound_typevars),
            type_str(tp, bound_Generic=bound_Generic, bound_typevars=bound_typevars))


class _typechecked_Iterable(collections.Iterable):
    def __init__(self, iter_obj, cls, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check, force):
        if not hasattr(iter_obj, '__iter__'):
            raise TypeError(
                    'Can only create iterable-checker for objects with __iter__ method.')
        self.iter_obj = iter_obj
        self.itemtype = cls.__args__[0]
        self.cls = cls
        self.bound_Generic = bound_Generic
        self.bound_typevars = bound_typevars
        self.bound_typevars_readonly = bound_typevars_readonly
        self.follow_fwd_refs = follow_fwd_refs
        self._recursion_check = _recursion_check
        self.force = force

    def __iter__(self):
        return _typechecked_Iterator(self.iter_obj.__iter__(), self.cls, self.bound_Generic,
                self.bound_typevars, self.bound_typevars_readonly, self.follow_fwd_refs,
                self._recursion_check, self.force)

    def __getattr__(self, name):
        return getattr(self.iter_obj, name)


class _typechecked_Iterator(collections.Iterator, _typechecked_Iterable):
    def __init__(self, iter_obj, cls, bound_Generic, bound_typevars,
                bound_typevars_readonly, follow_fwd_refs, _recursion_check, force):
        if not hasattr(iter_obj, '__iter__'):
            raise TypeError(
                    'Can only create iterator-checker for objects with __iter__ method.')
        if (sys.version_info.major == 2 and not hasattr(iter_obj, 'next')) \
            or (sys.version_info.major > 2 and not hasattr(iter_obj, '__next__')):
            raise TypeError(
                    'Can only create iterator-checker for objects with next method.')
        self.iter_obj = iter_obj
        self.itemtype = cls.__args__[0]
        self.cls = cls
        self.bound_Generic = bound_Generic
        self.bound_typevars = bound_typevars
        self.bound_typevars_readonly = bound_typevars_readonly
        self.follow_fwd_refs = follow_fwd_refs
        self._recursion_check = _recursion_check
        self.force = force

    def __next__(self):
        res = self.iter_obj.__next__()
        if not _isinstance(res, self.itemtype, self.bound_Generic, self.bound_typevars,
                self.bound_typevars_readonly, self.follow_fwd_refs, self._recursion_check):
            tpa = deep_type(res)
            msg = _make_iterator_error_message(tpa, self.cls, self.itemtype,
                    'has incompatible item type',
                    self.bound_Generic, self.bound_typevars, self.bound_typevars_readonly)
            _raise_typecheck_error(msg, True, res, tpa, self.itemtype)
        return res

    # For Python 2.7
    def next(self):
        res = self.iter_obj.next()
        if not _isinstance(res, self.itemtype, self.bound_Generic, self.bound_typevars,
                self.bound_typevars_readonly, self.follow_fwd_refs, self._recursion_check):
            tpa = deep_type(res)
            msg = _make_iterator_error_message(tpa, self.cls, self.itemtype,
                    'has incompatible item type',
                    self.bound_Generic, self.bound_typevars, self.bound_typevars_readonly)
            _raise_typecheck_error(msg, True, res, tpa, self.itemtype)
        return res


def _find_typed_base_method(meth, cls):
    meth0 = util._actualfunc(meth)
    for cls1 in util.mro(cls):
        if hasattr(cls1, meth0.__name__):
            fmeth = getattr(cls1, meth0.__name__)
            if has_type_hints(util._actualfunc(fmeth)):
                return fmeth, cls1
    return None, None


def annotations_func(func):
    """Works like annotations, but is only applicable to functions,
    methods and properties.
    """
    if not has_type_hints(func):
        # What about defaults?
        func.__annotations__ =  {}
    func.__annotations__ = _get_type_hints(func,
            infer_defaults = False)
    return func


def annotations_class(cls):
    """Works like annotations, but is only applicable to classes.
    """
    assert(isclass(cls))
    # To play it safe we avoid to modify the dict while iterating over it,
    # so we previously cache keys.
    # For this we don't use keys() because of Python 3.
    # Todo: Better use inspect.getmembers here
    keys = [key for key in cls.__dict__]
    for key in keys:
        memb = cls.__dict__[key]
        if _check_as_func(memb):
            annotations_func(memb)
        elif isclass(memb):
            annotations_class(memb)
    return cls


def annotations_module(md):
    """Works like annotations, but is only applicable to modules (by explicit call).
    md must be a module or a module name contained in sys.modules.
    """
    if isinstance(md, str):
        if md in sys.modules:
            md = sys.modules[md]
            if md is None:
                return md
        elif md in pytypes.typechecker._pending_modules:
            # if import is pending, we just store this call for later
            pytypes.typechecker._pending_modules[md].append(annotations_module)
            return md
    assert(ismodule(md))
    if md.__name__ in pytypes.typechecker._pending_modules:
            # if import is pending, we just store this call for later
            pytypes.typechecker._pending_modules[md.__name__].append(annotations_module)
            # we already process the module now as far as possible for its internal use
            # todo: Issue warning here that not the whole module might be covered yet
    if md.__name__ in _annotated_modules and \
            _annotated_modules[md.__name__] == len(md.__dict__):
        return md
    # To play it safe we avoid to modify the dict while iterating over it,
    # so we previously cache keys.
    # For this we don't use keys() because of Python 3.
    # Todo: Better use inspect.getmembers here
    keys = [key for key in md.__dict__]
    for key in keys:
        memb = md.__dict__[key]
        if _check_as_func(memb) and memb.__module__ == md.__name__:
            annotations_func(memb)
        elif isclass(memb) and memb.__module__ == md.__name__:
            annotations_class(memb)
    if not md.__name__ in pytypes.typechecker._pending_modules:
        _annotated_modules[md.__name__] = len(md.__dict__)
    return md


def annotations(memb):
    """Decorator applicable to functions, methods, properties,
    classes or modules (by explicit call).
    If applied on a module, memb must be a module or a module name contained in sys.modules.
    See pytypes.set_global_annotations_decorator to apply this on all modules.
    Methods with type comment will have type hints parsed from that
    string and get them attached as __annotations__ attribute.
    Methods with either a type comment or ordinary type annotations in
    a stubfile will get that information attached as __annotations__
    attribute (also a relevant use case in Python 3).
    Behavior in case of collision with previously (manually)
    attached __annotations__ can be controlled using the flags
    pytypes.annotations_override_typestring and pytypes.annotations_from_typestring.
    """
    if _check_as_func(memb):
        return annotations_func(memb)
    if isclass(memb):
        return annotations_class(memb)
    if ismodule(memb):
        return annotations_module(memb)
    if memb in sys.modules or memb in pytypes.typechecker._pending_modules:
        return annotations_module(memb)
    return memb


def _catch_up_global_annotations_decorator():
    for mod_name in sys.modules:
        if not mod_name in _annotated_modules:
            try:
                md = sys.modules[mod_name]
            except KeyError:
                md = None
            if not md is None and ismodule(md):
                annotations_module(mod_name)


def simplify_for_Union(type_list):
    """Removes types that are subtypes of other elements in the list.
    Does not return a copy, but instead modifies the given list.
    Intended for preprocessing of types to be combined into a typing.Union.
    Subtypecheck is backed by pytypes.is_subtype, so this differs from
    typing.Union's own simplification efforts.
    E.g. this also considers numeric tower like described in
    https://www.python.org/dev/peps/pep-0484/#the-numeric-tower
    (treats int as subtype of float as subtype of complex)
    Use pytypes.apply_numeric_tower flag to switch off numeric tower support.
    """
    i = 0
    while i < len(type_list):
        j = 0
        while j < i:
            if _issubclass(type_list[j], type_list[i]):
                del type_list[j]
                i -= 1
            else:
                j += 1
        j = i+1
        while j < len(type_list):
            if _issubclass(type_list[j], type_list[i]):
                del type_list[j]
            else:
                j += 1
        i += 1


def _preprocess_typecheck(argSig, argspecs, slf_or_clsm=False):
    """From a PEP 484 style type-tuple with types for *varargs and/or **kw
    this returns a type-tuple containing Tuple[tp, ...] and Dict[str, kw-tp]
    instead.
    """
    # todo: Maybe move also slf-logic here
    vargs = argspecs.varargs
    try:
        kw = argspecs.keywords
    except AttributeError:
        kw = argspecs.varkw
    try:
        kwonly = argspecs.kwonlyargs
    except AttributeError:
        kwonly = None
    if not vargs is None or not kw is None:
        arg_type_lst = list(get_Tuple_params(argSig))
        if not vargs is None:
            vargs_pos = (len(argspecs.args)-1) \
                    if slf_or_clsm else len(argspecs.args)
            # IndexErrors in this section indicate that a child-method was
            # checked against a parent's type-info with the child featuring
            # a more wider type on signature level (e.g. adding vargs)
            try:
                vargs_type = typing.Sequence[arg_type_lst[vargs_pos]]
            except IndexError:
                vargs_type = typing.Sequence[typing.Any]
            try:
                arg_type_lst[vargs_pos] = vargs_type
            except IndexError:
                arg_type_lst.append(vargs_type)
        if not kw is None:
            kw_pos = len(argspecs.args)
            if slf_or_clsm:
                kw_pos -= 1
            if not vargs is None:
                kw_pos += 1
            if not kwonly is None:
                kw_pos += len(kwonly)
            try:
                kw_type = typing.Dict[str, arg_type_lst[kw_pos]]
            except IndexError:
                kw_type = typing.Dict[str, typing.Any]
            try:
                arg_type_lst[kw_pos] = kw_type
            except IndexError:
                arg_type_lst.append(kw_type)
        return typing.Tuple[tuple(arg_type_lst)]
    else:
        return argSig


def _raise_typecheck_error(msg, is_return=False, value=None, received_type=None,
            expected_type=None, func=None):
    if pytypes.warning_mode:
        import traceback
        tb = traceback.extract_stack()
        off = util._calc_traceback_list_offset(tb)
        cat = pytypes.ReturnTypeWarning if is_return else pytypes.InputTypeWarning
        warn_explicit(msg, cat, tb[off][0], tb[off][1])
# 		if not func is None:
# 			warn_explicit(msg, cat, func.__code__.co_filename,
# 					func.__code__.co_firstlineno, func.__module__)
# 		else:
# 			warn(msg, pytypes.ReturnTypeWarning)
    else:
        if is_return:
            raise pytypes.ReturnTypeError(msg)
        else:
            raise pytypes.InputTypeError(msg)


def _get_current_call_info(clss=None, caller_level=0):
    prop = None
    prop_getter = False
    fq, code = util._get_current_function_fq(caller_level+1)
    if isinstance(fq[0], property):
        prop = fq[0]
        if fq[0].fget.__code__ is code:
            cllable = fq[0].fget
            prop_getter = True
        elif not fq[0].fset is None and fq[0].fset.__code__ is code:
            cllable = fq[0].fset
    else:
        cllable = fq[0]
    if cllable is None:
        raise RuntimeError("Couldn't determine caller.")
    slf = fq[2]
    clsm = pytypes.is_classmethod(fq[0])
    if clss is None and len(fq[1]) > 0:
        clss = fq[1][-1]
    return cllable, clss, slf, clsm, prop, prop_getter


def _check_caller_type(return_type, cllable=None, call_args=None, clss=None, caller_level=0):
    prop = None
    prop_getter = False
    if cllable is None:
        cllable, clss, slf, clsm, prop, prop_getter = _get_current_call_info(clss, caller_level+1)
    else:
        clsm = pytypes.is_classmethod(cllable)
        slf = ismethod(cllable)
        if clss is None:
            clss = util.get_class_that_defined_method(cllable) if slf or clsm else None
    act_func = util._actualfunc(cllable)
    has_hints = has_type_hints(cllable)
    if not has_hints and not slf:
        return
    specs = util.getargspecs(act_func)
    orig_clss = clss
    if not return_type:
        if call_args is None:
            # If return_type is True we must assume that call_args being None is authorative
            # i.e. None is the actual value to check rather than an indicator to
            # auto-retrieve the args. See https://github.com/Stewori/pytypes/issues/64
            call_args = util.get_current_args(caller_level+1, cllable, util.getargnames(specs))
        if slf:
            orig_clss = get_Generic_type(call_args[0])
            call_args = call_args[1:]
        elif clsm:
            orig_clss = call_args[0]
            call_args = call_args[1:]
    if not prop is None:
        argSig, retSig = _get_types(prop, clsm, slf, clss, prop_getter)
        try:
            if return_type:
                pytypes._checkfuncresult(retSig, call_args, act_func, slf or clsm, orig_clss,
                        prop_getter, force_exception=True)
            else:
                pytypes._checkfunctype(argSig, call_args, act_func, slf or clsm, orig_clss,
                        False, False, specs, force_exception=True)
        except pytypes.TypeCheckError:
            if not pytypes.warning_mode:
                raise
            else:
                return False
    else:
        if slf:
            check_parent = pytypes.always_check_parent_types
            if not check_parent:
                try:
                    check_parent = act_func.override_checked
                except AttributeError:
                    pass
            if not check_parent:
                try:
                    check_parent = cllable.override_checked
                except AttributeError:
                    pass
            if check_parent:
                cllable, clss = _find_typed_base_method(cllable, clss)
                if cllable is None:
                    return
                act_func = util._actualfunc(cllable)
                if not return_type:
                    specs = util.getargspecs(act_func)
            elif not has_hints:
                return
        argSig, retSig = _get_types(cllable, clsm, slf, clss)
        try:
            if return_type:
                pytypes._checkfuncresult(retSig, call_args, act_func, slf or clsm, orig_clss,
                        prop_getter, force_exception=True)
            else:
                pytypes._checkfunctype(argSig, call_args, act_func, slf or clsm, orig_clss,
                        False, False, specs, force_exception=True)
        except pytypes.TypeCheckError:
            if not pytypes.warning_mode:
                raise
            else:
                return False
        return True


def restore_profiler():
    """If a typechecking profiler is active, e.g. created by
    pytypes.set_global_typechecked_profiler(), such a profiler
    must be restored whenever a TypeCheckError is caught.
    The call must stem from the thread that raised the error.
    Otherwise the typechecking profiler is implicitly disabled.
    Alternatively one can turn pytypes into warning mode. In that
    mode no calls to this function are required (unless one uses
    filterwarnings("error") or likewise).
    """
    idn = threading.current_thread().ident
    if not sys.getprofile() is None:
        warn("restore_profiler: Current profile is not None!")
    if not idn in _saved_profilers:
        warn("restore_profiler: No saved profiler for calling thread!")
    else:
        sys.setprofile(_saved_profilers[idn])
        del _saved_profilers[idn]


class TypeAgent(object):

    def __init__(self, all_threads = True):
        self.all_threads = all_threads
        self._previous_profiler = None
        self._previous_thread_profiler = None
        self._active = False
        self._pending = False
        self._cleared = False
        self._checking_enabled = False
        self._logging_enabled = False
        self._caller_level_shift = 0

    def _is_checking(self):
        if not pytypes.checking_enabled:
            return False
        if pytypes.global_typechecked_profiler:
            # a global checker is already doing the job
            # So return true only if self is this global checker.
            return self is pytypes._global_type_agent
        else:
            return self._checking_enabled

    def _is_logging(self):
        if not pytypes.typelogging_enabled:
            return False
        if pytypes.global_typelogged_profiler:
            # a global checker is already doing the job
            # So return true only if self is this global checker.
            return self is pytypes._global_type_agent
        else:
            return self._logging_enabled

    @property
    def active(self):
        return self._active

    def _set_caller_level_shift(self, shift):
        self._caller_level_shift = shift
        if not self._previous_profiler is None and \
                isinstance(self._previous_profiler, TypeAgent):
            self._previous_profiler._set_caller_level_shift(shift+1)

    def start(self):
        if self._active:
            raise RuntimeError('type checker already running')
        elif self._pending:
            raise RuntimeError('type checker already starting up')
        self._pending = True
        # Install this instance as the current profiler
        self._previous_profiler = sys.getprofile()
        self._set_caller_level_shift(0)
        sys.setprofile(self)

        # If requested, set this instance as the default profiler for all future threads
        # (does not affect existing threads)
        if self.all_threads:
            self._previous_thread_profiler = threading._profile_hook
            threading.setprofile(self)
        self._active, self._pending = True, False

    def stop(self):
        if self._active and not self._pending:
            self._pending = True
            if sys.getprofile() is self:
                sys.setprofile(self._previous_profiler)
                if not self._previous_profiler is None and \
                        isinstance(self._previous_profiler, TypeAgent):
                    self._previous_profiler._set_caller_level_shift(0)
            else:
                if sys.getprofile() is not None or not self._cleared:
                    warn('the system profiling hook has changed unexpectedly')
            if self.all_threads:
                if threading._profile_hook is self:
                    threading.setprofile(self._previous_thread_profiler)
                else:  # pragma: no cover
                    warn('the threading profiling hook has changed unexpectedly')
            self._active, self._pending = False, False

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()

    def __call__(self, frame, event, arg):
        if not self._active and not self._pending:
            # This happens if all_threads was enabled and a thread was created when the checker was
            # running but was then stopped. The thread's profiler callback can't be reset any other
            # way but this.
            sys.setprofile(self._previous_thread_profiler)
            return
        if self._pending:
            if self._previous_profiler is not None:
                self._previous_profiler(frame, event, arg)
        else:
            # If an actual profiler is running, don't include the type checking times in its results
            if event == 'call':
                if self._is_checking():
                    try:
                        #check_argument_types(caller_level=self._caller_level_shift+1)
                        _check_caller_type(False, caller_level=self._caller_level_shift+1)
                    except RuntimeError:
                        # Caller could not be determined.
                        pass
                    except pytypes.TypeCheckError:
                        _saved_profilers[threading.current_thread().ident] = self
                        self._cleared = True
                        raise
                if self._previous_profiler is not None:
                    self._previous_profiler(frame, event, arg)
            elif event == 'return':
                if self._previous_profiler is not None:
                    self._previous_profiler(frame, event, arg)
                if self._is_checking():
                    try:
                        _check_caller_type(True, None, arg, caller_level=self._caller_level_shift+1)
                    except RuntimeError:
                        # Caller could not be determined.
                        pass
                    except pytypes.TypeCheckError:
                        _saved_profilers[threading.current_thread().ident] = self
                        self._cleared = True
                        raise
                    except TypeError:
                        # Caller could not be determined.
                        pass
                if self._is_logging():
                    try:
                        cllable, clss, slf, clsm, prop, prop_getter = \
                                _get_current_call_info(caller_level=self._caller_level_shift+1)
                        act_func = util._actualfunc(cllable)
                        specs = util.getargspecs(act_func)
                        call_args = util.get_current_args(self._caller_level_shift+1, cllable,
                                util.getargnames(specs))
                        if slf or clsm:
                            call_args = call_args[1:]
                        pytypes.log_type(call_args, arg, cllable if prop is None else prop,
                                slf, prop_getter, clss, specs)
                    except:
                        pass
            else:
                if self._previous_profiler is not None:
                    self._previous_profiler(frame, event, arg)