Commit 48dc1f01 authored by Dmitry Shesterkin's avatar Dmitry Shesterkin Committed by GitHub

Avoid incorrect type calls from cython.declare and cython.cast in Shadow.py (GH-3244)

The following code:
```
# cython: infer_types=True
import cython

@cython.cclass
class Foo:
    a: cython.double
    b: cython.double
    c: cython.double

    def __init__(self, a: cython.double, b: cython.double ,c: cython.double):
        self.a = a
        self.b = b
        self.c = c

def bar():
    l = []
    l.append(Foo(10, 20, 30))

    v = cython.declare(Foo, l[0])
    r = v.a + v.b
    print( r )

    v2 = cython.cast(Foo, l[0]) #Faster - No __Pyx_TypeTest() call
    r = v2.b + v2.c
    print( r )

bar()
```
works fine when compiled and throws an exception when interpreted: `TypeError: __init__() missing 2 required positional arguments: 'b' and 'c'`

It could be fixed if we change implementations as shown in the patch.
Also, added more tests for the cases I'm trying to fix
NB: Removed execution of `test_declare(None)` to make sure that the new `none_declare()` test works instead. `test_declare(None)` doesn't throw exception in pure mode but does it in the native mode

Replacing `hasattr(t, '__call__')` to `callable(t)` in the master branch broke the implementation and the tests because the construction was used to detect typedefs. To fix that I got rid of this check completely and replaced it to exact checks which also simplified the code

Changed `declare` implementation when initializing arguments are not provided. Now it correctly works with typedefs of the user classes and also directly support arrays:
    ```
    >>> EmptyClassSyn = cython.typedef(EmptyClass)
    >>> cython.declare(EmptyClassSyn) is None
    True
    >>> cython.declare(cython.int[2]) is not None
    True
    ```
Added missed return statement to `index_type` which made the following assigment possible:
    ```
        a = cython.declare(cython.int[2])
        a[0] = 1
    ```
parent fbf400b5
......@@ -71,7 +71,7 @@ def index_type(base_type, item):
else:
# int[8] etc.
assert int(item) == item # array size must be a plain integer
array(base_type, item)
return array(base_type, item)
# END shameless copy
......@@ -162,12 +162,16 @@ def cmod(a, b):
# Emulated language constructs
def cast(type_, *args, **kwargs):
def cast(t, *args, **kwargs):
kwargs.pop('typecheck', None)
assert not kwargs
if callable(type_):
if not isinstance(type_, type) or not (args and isinstance(args[0], type_)):
return type_(*args)
if isinstance(t, typedef):
return t(*args)
elif isinstance(t, type): #Doesn't work with old-style classes of Python 2.x
if len(args) != 1 or not (args[0] is None or isinstance(args[0], t)):
return t(*args)
return args[0]
def sizeof(arg):
......@@ -180,14 +184,19 @@ def typeof(arg):
def address(arg):
return pointer(type(arg))([arg])
def declare(type=None, value=_Unspecified, **kwds):
if type not in (None, object) and hasattr(type, '__call__'):
if value is not _Unspecified:
return type(value)
else:
return type()
def _is_value_type(t):
if isinstance(t, typedef):
return _is_value_type(t._basetype)
return isinstance(t, type) and issubclass(t, (StructType, UnionType, ArrayType))
def declare(t=None, value=_Unspecified, **kwds):
if value is not _Unspecified:
return cast(t, value)
elif _is_value_type(t):
return t()
else:
return value
return None
class _nogil(object):
"""Support for 'with nogil' statement and @nogil decorator.
......
......@@ -36,10 +36,6 @@ def test_declare(n):
(100, 100)
>>> test_declare(100.5)
(100, 100)
>>> test_declare(None) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...
"""
x = cython.declare(cython.int)
y = cython.declare(cython.int, n)
......@@ -422,3 +418,122 @@ class TestUnboundMethod:
True
"""
def meth(self): pass
@cython.cclass
class Foo:
a = cython.declare(cython.double)
b = cython.declare(cython.double)
c = cython.declare(cython.double)
@cython.locals(a=cython.double, b=cython.double, c=cython.double)
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
@cython.cclass
class EmptyClass(object):
def __init__(self, *args):
pass
def same_type_cast():
"""
>>> same_type_cast()
True
"""
f = EmptyClass()
return f is cython.cast(EmptyClass, f)
def multi_args_init_cast():
"""
>>> multi_args_init_cast()
True
"""
f = Foo(10, 20, 30)
return cython.cast(Foo, f) is f
def multi_args_init_declare():
"""
>>> multi_args_init_declare() is None
True
"""
f = cython.declare(Foo)
if cython.compiled:
f = None
return f
EmptyClassSyn = cython.typedef(EmptyClass)
def empty_declare():
"""
>>> empty_declare()
[]
"""
r0 = cython.declare(EmptyClass)
r1 = cython.declare(EmptyClassSyn)
r2 = cython.declare(MyStruct)
r3 = cython.declare(MyUnion)
r4 = cython.declare(MyStruct2)
r5 = cython.declare(cython.int[2])
if cython.compiled:
r0 = None
r1 = None
res = [
r0 is None,
r1 is None,
r2 is not None,
r3 is not None,
r4 is not None,
r5 is not None
]
r2.is_integral = True
assert( r2.is_integral == True )
r3.x = 12.3
assert( r3.x == 12.3 )
#It generates a correct C code, but raises an exception when interpreted
if cython.compiled:
r4[0].is_integral = True
assert( r4[0].is_integral == True )
r5[0] = 42
assert ( r5[0] == 42 )
return [i for i, x in enumerate(res) if not x]
def same_declare():
"""
>>> same_declare()
True
"""
f = EmptyClass()
f2 = cython.declare(EmptyClass, f)
return f2 is f
def none_cast():
"""
>>> none_cast() is None
True
"""
f = None
return cython.cast(EmptyClass, f)
def none_declare():
"""
>>> none_declare() is None
True
"""
f = None
f2 = cython.declare(Foo, f)
return f2
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment