# cython: binding=True
# mode: run
# tag: cyfunction

import platform
cimport cython

from functools import wraps, update_wrapper

def inspect_isroutine():
    """
    >>> inspect_isroutine()
    True
    """
    import inspect
    return inspect.isroutine(inspect_isroutine)


def inspect_isfunction():
    """
    >>> inspect_isfunction()
    False
    False
    """
    import inspect, types
    print isinstance(inspect_isfunction, types.FunctionType)
    return inspect.isfunction(inspect_isfunction)


def inspect_isbuiltin():
    """
    >>> inspect_isbuiltin()
    False
    False
    """
    import inspect, types
    print isinstance(inspect_isfunction, types.BuiltinFunctionType)
    return inspect.isbuiltin(inspect_isbuiltin)


def inspect_signature(a, b, c=123, *, d=234):
    """
    >>> sig = inspect_signature(1, 2)
    >>> list(sig.parameters)
    ['a', 'b', 'c', 'd']
    >>> sig.parameters['c'].default == 123
    True
    >>> sig.parameters['d'].default == 234
    True
    """
    import inspect
    return inspect.signature(inspect_signature)


# def test___signature__(a, b, c=123, *, d=234):
#     """
#     >>> sig = test___signature__(1, 2)
#     >>> list(sig.parameters)
#     ['a', 'b', 'c', 'd']
#     >>> sig.parameters['c'].default == 123
#     True
#     >>> sig.parameters['d'].default == 234
#     True
#     """
#     return inspect_signature.__signature__


def test_dict():
    """
    >>> test_dict.foo = 123
    >>> test_dict.__dict__
    {'foo': 123}
    >>> test_dict.__dict__ = {'bar': 321}
    >>> test_dict.__dict__
    {'bar': 321}
    >>> test_dict.func_dict
    {'bar': 321}
    """

def test_name():
    """
    >>> test_name.__name__
    'test_name'
    >>> test_name.func_name
    'test_name'
    >>> test_name.__name__ = 123 #doctest:+ELLIPSIS
    Traceback (most recent call last):
    TypeError: __name__ must be set to a ... object
    >>> test_name.__name__ = 'foo'
    >>> test_name.__name__
    'foo'
    """

def test_doc():
    """
    >>> del test_doc.__doc__
    >>> test_doc.__doc__
    >>> test_doc.__doc__ = 'docstring'
    >>> test_doc.__doc__
    'docstring'
    >>> test_doc.func_doc
    'docstring'
    """


def test_hash():
    """
    >>> d = {test_hash: 123}
    >>> test_hash in d
    True
    >>> d[test_hash]
    123
    >>> hash(test_hash) == hash(test_hash)
    True
    """


def test_closure():
    """
    >>> test_closure.func_closure is None
    True
    """

def test_globals():
    """
    >>> test_globals.func_globals is not None
    True
    >>> 'test_globals' in test_globals.func_globals or test_globals.func_globals
    True
    >>> 'test_name' in test_globals.func_globals or test_globals.func_globals
    True
    >>> 'not there' not in test_globals.func_globals or test_globals.func_globals
    True
    >>> try: test_globals.func_globals = {}
    ... except (AttributeError, TypeError): pass
    ... else: assert 0, 'FAILED'
    """

def test_reduce():
    """
    >>> import pickle
    >>> pickle.loads(pickle.dumps(test_reduce))()
    'Hello, world!'
    """
    return 'Hello, world!'

def test_method(self):
    return self

class BindingTest:
    """
    >>> BindingTest.test_method = test_method
    >>> BindingTest.test_method() #doctest:+ELLIPSIS
    Traceback (most recent call last):
    TypeError: ...
    >>> BindingTest().test_method()
    <BindingTest instance>
    """
    def __repr__(self):
        return '<BindingTest instance>'


def codeof(func):
    return func.__code__

def varnamesof(func):
    code = codeof(func)
    varnames = code.co_varnames
    return varnames

def namesof(func):
    code = codeof(func)
    names = code.co_names
    return names

def cy_no_arg():
    l = m = 1
def cy_one_arg(a):
    l = m = 1
def cy_two_args(x, b):
    l = m = 1
def cy_default_args(x=1, b=2):
    l = m = 1

def test_code():
    """
    >>> def no_arg(): l = m = 1
    >>> def one_arg(a): l = m = 1
    >>> def two_args(x, b): l = m = 1
    >>> def default_args(x=1, b=2): l = m = 1

    >>> codeof(no_arg).co_argcount
    0
    >>> codeof(cy_no_arg).co_argcount
    0
    >>> print(codeof(no_arg).co_name)
    no_arg
    >>> print(codeof(cy_no_arg).co_name)
    cy_no_arg
    >>> namesof(no_arg)
    ()
    >>> codeof(cy_no_arg).co_names
    ()
    >>> varnamesof(no_arg)
    ('l', 'm')
    >>> codeof(cy_no_arg).co_varnames
    ('l', 'm')

    >>> codeof(one_arg).co_argcount
    1
    >>> codeof(cy_one_arg).co_argcount
    1
    >>> print(codeof(one_arg).co_name)
    one_arg
    >>> print(codeof(cy_one_arg).co_name)
    cy_one_arg
    >>> namesof(one_arg)
    ()
    >>> codeof(cy_one_arg).co_names
    ()
    >>> varnamesof(one_arg)
    ('a', 'l', 'm')
    >>> codeof(cy_one_arg).co_varnames
    ('a', 'l', 'm')

    >>> codeof(two_args).co_argcount
    2
    >>> codeof(cy_two_args).co_argcount
    2
    >>> namesof(two_args)
    ()
    >>> codeof(cy_two_args).co_names
    ()
    >>> varnamesof(two_args)
    ('x', 'b', 'l', 'm')
    >>> codeof(cy_two_args).co_varnames
    ('x', 'b', 'l', 'm')

    >>> codeof(default_args).co_argcount
    2
    >>> codeof(cy_default_args).co_argcount
    2
    >>> namesof(default_args)
    ()
    >>> codeof(cy_default_args).co_names
    ()
    >>> varnamesof(default_args)
    ('x', 'b', 'l', 'm')
    >>> codeof(cy_default_args).co_varnames
    ('x', 'b', 'l', 'm')
    """


def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
    """
    >>> isinstance(test_annotations.__annotations__, dict)
    True
    >>> sorted(test_annotations.__annotations__.items())
    [('a', "'test'"), ('b', "'other'"), ('c', '123'), ('return', "'ret'")]

    >>> def func_b(): return 42
    >>> def func_c(): return 99
    >>> inner = test_annotations(1, func_b, func_c)
    >>> sorted(inner.__annotations__.items())
    [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]

    >>> inner.__annotations__ = {234: 567}
    >>> inner.__annotations__
    {234: 567}
    >>> inner.__annotations__ = None
    >>> inner.__annotations__
    {}
    >>> inner.__annotations__ = 321
    Traceback (most recent call last):
    TypeError: __annotations__ must be set to a dict object
    >>> inner.__annotations__
    {}

    >>> inner = test_annotations(1, func_b, func_c)
    >>> sorted(inner.__annotations__.items())
    [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
    >>> inner.__annotations__['abc'] = 66
    >>> sorted(inner.__annotations__.items())
    [('abc', 66), ('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]

    >>> inner = test_annotations(1, func_b, func_c)
    >>> sorted(inner.__annotations__.items())
    [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
    """
    def inner(x: "banana", y: b()) -> c():
        return x,y
    return inner


def add_one(func):
    "Decorator to add 1 to the last argument of the function call"
    def inner(*args):
        args = args[:-1] + (args[-1] + 1,)
        return func(*args)
    return inner

@add_one
def test_decorated(x):
    """
    >>> test_decorated(0)
    1
    """
    return x

@add_one
@add_one
def test_decorated2(x):
    """
    >>> test_decorated2(0)
    2
    """
    return x


cdef class TestDecoratedMethods:
    @add_one
    def test(self, x):
        """
        >>> TestDecoratedMethods().test(0)
        1
        """
        return x

    @add_one
    @add_one
    def test2(self, x):
        """
        >>> TestDecoratedMethods().test2(0)
        2
        """
        return x

    def test_calls(self, x):
        """
        >>> TestDecoratedMethods().test_calls(2)
        25
        """
        return self.test(x) + self.test2(x*10)


cdef class TestUnboundMethodCdef:
    """
    >>> C = TestUnboundMethodCdef
    >>> C.meth is C.__dict__["meth"]
    True
    >>> TestUnboundMethodCdef.meth()  # doctest:+ELLIPSIS
    Traceback (most recent call last):
    TypeError: ...
    """
    def meth(self): pass


class TestUnboundMethod:
    """
    >>> C = TestUnboundMethod
    >>> C.meth is C.__dict__["meth"]
    True
    >>> TestUnboundMethod.meth()  # doctest:+ELLIPSIS
    Traceback (most recent call last):
    TypeError: ...
    """
    def meth(self): pass


class TestStaticmethod(object):
    """
    >>> x = TestStaticmethod()
    >>> x.staticmeth(42)
    42
    >>> x.staticmeth.__get__(42)()
    42
    """
    @staticmethod
    def staticmeth(arg): return arg


cdef class TestOptimisedBuiltinMethod:
    """
    >>> obj = TestOptimisedBuiltinMethod()
    >>> obj.append(2)
    3
    >>> obj.call(2)
    4
    >>> obj.call(3, obj)
    5
    """
    def append(self, arg):
        print(arg+1)

    def call(self, arg, obj=None):
        (obj or self).append(arg+1)  # optimistically optimised => uses fast fallback method call


def do_nothing(f):
    """Dummy decorator for `test_firstlineno_decorated_function`"""
    return f


@do_nothing
@do_nothing
def test_firstlineno_decorated_function():
    """
    check that `test_firstlineno_decorated_function` starts 5 lines below `do_nothing`

    >>> test_firstlineno_decorated_function()
    5
    """
    l1 = do_nothing.__code__.co_firstlineno
    l2 = test_firstlineno_decorated_function.__code__.co_firstlineno
    return l2 - l1

def test_module():
    """
    See module-level docstring. doctest uses __module__ to decide what to run. So if it's wrong
    then we don't run the tests, and never find out about the failure!
    """
    pass

def test_fused_module(cython.numeric arg):
    """
    See module-level docstring. doctest uses __module__ to decide what to run. So if it's wrong
    then we don't run the tests, and never find out about the failure!
    """
    pass


def to_be_wrapped(a: int, b: float):
    """
    Hello from to_be_wrapped's docstring
    """
    pass

to_be_wrapped.__module__ = "other_module"


def test_wraps(f):
    """
    >>> wrapped_func = test_wraps(to_be_wrapped)
    >>> wrapped_func.__module__
    'other_module'
    >>> wrapped_func.__name__
    'to_be_wrapped'
    >>> wrapped_func.__qualname__
    'to_be_wrapped'
    >>> wrapped_func.__annotations__ == to_be_wrapped.__annotations__  or  (
    ...     wrapped_func.__annotations__, to_be_wrapped.__annotations__)
    True
    >>> wrapped_func.__doc__
    "\\n    Hello from to_be_wrapped's docstring\\n    "

    >>> def py_to_be_wrapped(a: int, b: float):
    ...     return None
    >>> py_to_be_wrapped.__module__ = "other_python_module"
    >>> wrapped_py_func = test_wraps(py_to_be_wrapped)
    >>> wrapped_py_func.__module__
    'other_python_module'
    >>> wrapped_py_func.__name__
    'py_to_be_wrapped'
    >>> wrapped_py_func.__annotations__ == py_to_be_wrapped.__annotations__  or  (
    ...     wrapped_py_func.__annotations__, py_to_be_wrapped.__annotations__)
    True

    >>> def py_wraps(f):
    ...     @wraps(f)
    ...     def wrapper(*args, **kwds):
    ...         return f(*args, **kwds)
    ...     return wrapper
    >>> py_wrapped_func = py_wraps(to_be_wrapped)
    >>> py_wrapped_func.__module__
    'other_module'
    >>> py_wrapped_func.__name__
    'to_be_wrapped'
    >>> py_wrapped_func.__annotations__ == to_be_wrapped.__annotations__  or  (
    ...     py_wrapped_func.__annotations__, to_be_wrapped.__annotations__)
    True

    # __type_params__ should also be copied, but Cython doesn't support this at all
    """
    @wraps(f)
    def wrapper(*args, **kwds):
        return f(*args, **kwds)
    return wrapper


def test_annotate():
    """
    Currently we don't implement PEP 649 fully, so __annotate__ is a limited
    placeholder version mainly to make `functools.wraps` work on Python 3.14+.
    Therefore, we shouldn't worry too much if this test needs to change in
    future.

    >>> f = test_annotate()
    >>> list(f.__annotate__(1).keys())
    ['a', 'b']

    Assigning should work
    >>> f.__annotate__ = lambda arg=0: {'different_argument': 5}
    >>> f.__annotations__  # f.__annotate__ = lambda ...
    {'different_argument': 5}
    >>> f.__annotate__(1)
    {'different_argument': 5}

    Assigning a Python 3.14 annotation function should pass the required
    annotation format argument when annotations are evaluated.
    >>> import sys
    >>> if sys.version_info >= (3, 14):
    ...     def py_function() -> int:
    ...         pass
    ...     f.__annotate__ = py_function.__annotate__
    ...     f.__annotations__ == {'return': int}  or  f.__annotations__
    ... else:
    ...     True
    True

    Assignment itself should not evaluate the annotation function, since
    evaluating annotations may fail normally when they contain forward
    references.
    >>> if sys.version_info >= (3, 14):
    ...     def py_function_with_forward_ref(arg: MissingType) -> int:
    ...         pass
    ...     f.__annotate__ = py_function_with_forward_ref.__annotate__
    ...     assigned = True
    ...     try:
    ...         f.__annotations__
    ...     except NameError:
    ...         failed_later = True
    ...     else:
    ...         failed_later = False
    ... else:
    ...     assigned = failed_later = True
    >>> assigned, failed_later
    (True, True)

    Setting __annotate__ to None should clear the lazy annotation function.
    >>> f = test_annotate()
    >>> f.__annotate__ = None
    >>> f.__annotate__ is None  or  f.__annotate__
    True
    >>> f.__annotations__
    {'a': 'int', 'b': 'str'}

    Setting __annotate__ to None should not discard already materialized annotations.
    >>> f = test_annotate()
    >>> f.__annotations__
    {'a': 'int', 'b': 'str'}
    >>> f.__annotate__ = None
    >>> f.__annotate__ is None  or  f.__annotate__
    True
    >>> f.__annotations__
    {'a': 'int', 'b': 'str'}

    Setting __annotate__ to itself should not cause a recursion disaster.
    >>> f = test_annotate()
    >>> f.__annotate__ = f.__annotate__
    >>> list(f.__annotate__(1).keys())
    ['a', 'b']
    >>> f.__annotations__
    {'a': 'int', 'b': 'str'}

    Setting __annotations__ should clear any assigned lazy annotation function.
    >>> f = test_annotate()
    >>> f.__annotate__ = lambda arg=0: {'different_argument': 5}
    >>> f.__annotations__ = {'manual': str}
    >>> f.__annotate__ is None  or  f.__annotate__
    True
    >>> f.__annotations__
    {'manual': <class 'str'>}

    Deleting is banned
    >>> f = test_annotate()
    >>> del f.__annotate__
    Traceback (most recent call last):
    TypeError: __annotate__ cannot be deleted
    """
    def inner(a: int, b: str):
        pass
    return inner


class FuncWrapper:
    def __init__(self, function):
        self.function = function
        update_wrapper(self, self.function)

    def __call__(self, *args, **kwds):
        return self.function(*args, **kwds)


def test_pickle_unpickle() -> int:
    """
    >>> import pickle
    >>> unpickled = pickle.loads(pickle.dumps(test_pickle_unpickle))
    >>> unpickled()
    1

    >>> wrapped = FuncWrapper(test_pickle_unpickle)
    >>> unpickled = pickle.loads(pickle.dumps(wrapped))
    >>> unpickled()
    1
    """
    return 1



__doc__ = """
>>> test_module.__module__
'cyfunction'

# Our metaclass doesn't stick on PyPy unfortunately
>>> type(test_module).__module__.startswith("_cython") if platform.python_implementation() != 'PyPy' else True
True
>>> test_module.__module__ = "something_else"
>>> test_module.__module__
'something_else'
>>> del test_module.__module__
>>> test_module.__module__
>>> test_fused_module.__module__
'cyfunction'
>>> type(test_fused_module).__module__.startswith("_cython") if platform.python_implementation() != 'PyPy' else True
True
>>> test_fused_module.__module__ = "something_else"
>>> test_fused_module.__module__
'something_else'
>>> del test_fused_module.__module__
>>> test_fused_module.__module__
"""
