def resolve_signature(
    fn: Callable
) -> Tuple[Optional[inspect.Signature], Optional[ConditionSyntaxMessage]]:
    '''
    Get signature and resolve type annotations with get_type_hints.
    Returns a pair of Nones if no signature is available for the function.
    (e.g. it's implemented in C)
    Returns an unresolved signature and an error message if the type resultion errors.
    (e.g. the annotation references a type name that isn't dfined)
    '''
    # TODO: Test resolution with members at multiple places in the hierarchy.
    # e.g. https://bugs.python.org/issue29966
    try:
        sig = inspect.signature(fn)
    except ValueError:
        return (None, None)
    try:
        type_hints = get_type_hints(fn, fn_globals(fn))
    except NameError as name_error:
        filename, lineno = source_position(fn)
        return (sig, ConditionSyntaxMessage(filename, lineno, str(name_error)))
    params = sig.parameters.values()
    newparams = []
    for name, param in sig.parameters.items():
        if name in type_hints:
            param = param.replace(annotation=type_hints[name])
        newparams.append(param)
    newreturn = type_hints.get('return', sig.return_annotation)
    return (inspect.Signature(newparams, return_annotation=newreturn), None)
    def get_fn_conditions(
            self,
            fn: Callable,
            first_arg_type: Optional[type] = None) -> Optional[Conditions]:
        filename, first_line = source_position(fn)
        sig, resolution_err = resolve_signature(fn)
        if sig is None:
            return None
        if resolution_err:
            return Conditions(fn, [], [], frozenset(), sig, None,
                              [resolution_err])
        if first_arg_type:
            sig = set_first_arg_type(sig, first_arg_type)
        if isinstance(fn, types.BuiltinFunctionType):
            return Conditions(fn, [], [], frozenset(), sig, frozenset(), [])
        lines = list(get_doc_lines(fn))
        parse = parse_sections(lines, ('pre', 'post', 'raises'), filename)
        pre: List[ConditionExpr] = []
        raises: Set[Type[BaseException]] = set()
        post_conditions: List[ConditionExpr] = []
        mutable_args: Optional[FrozenSet[str]] = None
        if parse.mutable_expr is not None:
            mutable_args = frozenset(expr.strip().split('.')[0]
                                     for expr in parse.mutable_expr.split(',')
                                     if expr != '')
        for line_num, expr in parse.sections['pre']:
            pre.append(
                condition_from_source_text(filename, line_num, expr,
                                           fn_globals(fn)))
        for line_num, expr in parse.sections['raises']:
            if '#' in expr:
                expr = expr.split('#')[0]
            for exc_source in expr.split(','):
                try:
                    exc_type = eval(exc_source)
                except:
                    e = sys.exc_info()[1]
                    parse.syntax_messages.append(
                        ConditionSyntaxMessage(filename, line_num, str(e)))
                    continue
                if not issubclass(exc_type, BaseException):
                    parse.syntax_messages.append(
                        ConditionSyntaxMessage(
                            filename, line_num,
                            f'"{exc_type}" is not an exception class'))
                    continue
                raises.add(exc_type)
        for line_num, expr in parse.sections['post']:
            post_conditions.append(
                condition_from_source_text(filename, line_num, expr,
                                           fn_globals(fn)))

        return Conditions(fn, pre, post_conditions, frozenset(raises), sig,
                          mutable_args, parse.syntax_messages)
    def get_class_invariants(
            self, cls: type,
            super_conditions: ClassConditions) -> List[ConditionExpr]:
        try:
            filename = inspect.getsourcefile(cls)
        except TypeError:  # raises TypeError for builtins
            filename = None
        invariants = getattr(cls, '__invariants__', ())  # type: ignore
        ret = []

        def inv_eval(contract, kwargs):
            return contract.condition(self=kwargs['self'])

        for contract in invariants:
            filename, line_num = source_position(contract.condition)
            ret.append(
                ConditionExpr(functools.partial(inv_eval, contract), filename,
                              line_num, self.contract_text(contract)))
        return ret
    def get_fn_conditions(
            self,
            fn: Callable,
            first_arg_type: Optional[type] = None) -> Optional[Conditions]:
        icontract = self.icontract
        checker = icontract._checkers.find_checker(func=fn)  # type: ignore
        if checker is None:
            return None
        contractless_fn = fn.__wrapped__  # type: ignore
        sig, resolution_err = resolve_signature(fn)
        if sig is None:
            return None
        if resolution_err:
            return Conditions(contractless_fn, [], [], frozenset(), sig, None,
                              [resolution_err])
        if first_arg_type:
            sig = set_first_arg_type(sig, first_arg_type)

        pre: List[ConditionExpr] = []
        post: List[ConditionExpr] = []

        def eval_contract(contract, kwargs):
            condition_kwargs = icontract._checkers.select_condition_kwargs(
                contract=contract, resolved_kwargs=kwargs)
            return contract.condition(**condition_kwargs)

        disjunction = checker.__preconditions__
        if len(disjunction) == 0:
            pass
        elif len(disjunction) == 1:
            for contract in disjunction[0]:
                evalfn = functools.partial(eval_contract, contract)
                filename, line_num = source_position(contract.condition)
                pre.append(
                    ConditionExpr(evalfn, filename, line_num,
                                  self.contract_text(contract)))
        else:

            def eval_disjunction(disjunction, kwargs) -> bool:
                for conjunction in disjunction:
                    ok = True
                    for contract in conjunction:
                        if not eval_contract(contract, kwargs):
                            ok = False
                            break
                    if ok:
                        return True
                return False

            evalfn = functools.partial(eval_disjunction, disjunction)
            filename, line_num = source_position(contractless_fn)
            source = '(' + ') or ('.join([
                ' and '.join([self.contract_text(c) for c in conj])
                for conj in disjunction
            ]) + ')'
            pre.append(ConditionExpr(evalfn, filename, line_num, source))

        # TODO handle snapshots
        #snapshots = checker.__postcondition_snapshots__  # type: ignore

        def post_eval(contract, kwargs):
            _old = kwargs.pop('__old__')
            kwargs['result'] = kwargs.pop('__return__')
            del kwargs['_']
            condition_kwargs = icontract._checkers.select_condition_kwargs(
                contract=contract, resolved_kwargs=kwargs)
            return contract.condition(**condition_kwargs)

        for postcondition in checker.__postconditions__:
            evalfn = functools.partial(post_eval, postcondition)
            filename, line_num = source_position(postcondition.condition)
            post.append(
                ConditionExpr(evalfn, filename, line_num,
                              self.contract_text(postcondition)))
        return Conditions(
            contractless_fn,
            pre,
            post,
            raises=frozenset((
                AttributeError,
                IndexError,
            )),  # TODO all exceptions are OK?
            sig=sig,
            mutable_args=None,
            fn_syntax_messages=[])