# mode: run
# tag: exttype, tpnew
# ticket: t808

cimport cython

cdef class MyType:
    cdef public args, kwargs
    def __cinit__(self, *args, **kwargs):
        self.args, self.kwargs = args, kwargs
        print "CINIT"
    def __init__(self, *args, **kwargs):
        print "INIT"

cdef class MySubType(MyType):
    def __cinit__(self, *args, **kwargs):
        self.args, self.kwargs = args, kwargs
        print "CINIT(SUB)"
    def __init__(self, *args, **kwargs):
        print "INIT"

class MyClass(object):
    def __cinit__(self, *args, **kwargs):
        self.args, self.kwargs = args, kwargs
        print "CINIT"
    def __init__(self, *args, **kwargs):
        print "INIT"

class MyTypeSubClass(MyType):
    def __cinit__(self, *args, **kwargs):
        # not called: Python class!
        print "CINIT(PYSUB)"
    def __init__(self, *args, **kwargs):
        print "INIT"

# See ticket T808, vtab must be set even if there is no __cinit__.

cdef class Base(object):
    pass

cdef class Derived(Base):
    cpdef int f(self):
        return 42

def test_derived_vtab():
    """
    >>> test_derived_vtab()
    42
    """
    cdef Derived d = Derived.__new__(Derived)
    return d.f()


# only these can be safely optimised:

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new():
    """
    >>> isinstance(make_new(), MyType)
    CINIT
    True
    """
    m = MyType.__new__(MyType)
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_typed_target():
    """
    >>> isinstance(make_new_typed_target(), MyType)
    CINIT
    True
    """
    cdef MyType m
    m = MyType.__new__(MyType)
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_with_args():
    """
    >>> isinstance(make_new_with_args(), MyType)
    CINIT
    (1, 2, 3)
    {}
    True
    """
    m = MyType.__new__(MyType, 1, 2 ,3)
    print m.args
    print m.kwargs
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_with_args_kwargs():
    """
    >>> isinstance(make_new_with_args_kwargs(), MyType)
    CINIT
    (1, 2, 3)
    {'a': 4}
    True
    """
    m = MyType.__new__(MyType, 1, 2 ,3, a=4)
    print m.args
    print m.kwargs
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_builtin():
    """
    >>> isinstance(make_new_builtin(), tuple)
    True
    """
    m = dict.__new__(dict)
    m = list.__new__(list)
    m = tuple.__new__(tuple)
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_none(type t=None):
    """
    >>> make_new_none()  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    TypeError: ... is not a type object (NoneType)
    """
    m = t.__new__(t)
    return m

@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.test_fail_if_path_exists(
    '//SimpleCallNode/AttributeNode',
    '//PyMethodCallNode',
)
def make_new_kwargs(type t=None):
    """
    >>> m = make_new_kwargs(MyType)
    CINIT
    >>> isinstance(m, MyType)
    True
    >>> m.args
    (1, 2, 3)
    >>> m.kwargs
    {'a': 5}
    """
    m = t.__new__(t, 1, 2, 3, a=5)
    return m

# these cannot:

@cython.test_assert_path_exists('//PyMethodCallNode/AttributeNode')
@cython.test_fail_if_path_exists('//PythonCapiCallNode')
def make_new_pyclass():
    """
    >>> isinstance(make_new_pyclass(), MyTypeSubClass)
    CINIT
    True
    """
    m = MyClass.__new__(MyClass)
    m = MyTypeSubClass.__new__(MyTypeSubClass)
    return m

@cython.test_assert_path_exists('//PyMethodCallNode/AttributeNode')
@cython.test_fail_if_path_exists('//PythonCapiCallNode')
def make_new_args(type t1=None, type t2=None):
    """
    >>> isinstance(make_new_args(), MyType)
    CINIT
    True
    >>> isinstance(make_new_args(MyType), MyType)
    CINIT
    True
    >>> isinstance(make_new_args(MyType, MyType), MyType)
    CINIT
    True

    >>> isinstance(make_new_args(MyType, MySubType), MySubType)
    Traceback (most recent call last):
    TypeError: tp_new.MyType.__new__(tp_new.MySubType) is not safe, use tp_new.MySubType.__new__()
    >>> isinstance(make_new_args(MySubType, MyType), MyType)
    Traceback (most recent call last):
    TypeError: tp_new.MySubType.__new__(tp_new.MyType): tp_new.MyType is not a subtype of tp_new.MySubType
    """
    if t1 is None:
        t1 = MyType
    if t2 is None:
        t2 = MyType
    m = t1.__new__(t2)
    return m

@cython.test_assert_path_exists('//PyMethodCallNode/AttributeNode')
@cython.test_fail_if_path_exists('//PythonCapiCallNode')
def make_new_none_typed(tuple t=None):
    """
    >>> make_new_none_typed()  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    TypeError: ... is not a type object (NoneType)
    """
    m = t.__new__(t)
    return m

@cython.test_assert_path_exists('//PyMethodCallNode/AttributeNode')
@cython.test_fail_if_path_exists('//PythonCapiCallNode')
def make_new_untyped(t):
    """
    >>> make_new_untyped(None)  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    TypeError: ... is not a type object (NoneType)
    """
    m = t.__new__(t)
    return m
