Repository: pyccel/pyccel (python)

Issue:
Type conversions in expressions cause pyccel to fail
The following code:
```
from pyccel.decorators import types

@types('float')
def f_eq(a):
    return 3.0/int(a)
```
causes pyccel to crash.

Interestingly the following:
```
from pyccel.decorators import types

@types('float')
def f_eq(a):
    return int(a)
```
does not produce the same error. Presumably the type is being evaluated too early.

The following code also does not produce an error:
```
from pyccel.decorators import types

@types('float')
def f_eq(a):
    return 3.0/a
```
this seems to be due to the fact that sympy understands this expression

Pull request:
Devel issue 263
fixes #263 

Patch:
diff --git a/pyccel/ast/builtins.py b/pyccel/ast/builtins.py
index c9c93ee4..c3cc41a1 100644
--- a/pyccel/ast/builtins.py
+++ b/pyccel/ast/builtins.py
@@ -8,19 +8,20 @@ In this module we implement some of them in alphabetical order.
 """
 
 from sympy import Symbol, Function, Tuple
-from sympy import Integer as sp_Integer
-from sympy import Float as sp_Float
+from sympy import Integer
+from sympy import Float
 from sympy import sympify
 from sympy.core.assumptions import StdFactKB
 from sympy.tensor import Indexed, IndexedBase
 
 from .basic import Basic
+from .datatypes import default_precision
 
 __all__ = (
     'Bool',
     'Complex',
     'Enumerate',
-    'Float',
+    'PythonFloat',
     'Int',
     'Len',
     'List',
@@ -86,7 +87,7 @@ class Bool(Function):
 class Complex(Function):
     """ Represents a call to Python's native complex() function.
     """
-    def __new__(cls, arg0, arg1=sp_Float(0)):
+    def __new__(cls, arg0, arg1=Float(0)):
         obj = Basic.__new__(cls, arg0, arg1)
         assumptions = {'complex': True}
         ass_copy = assumptions.copy()
@@ -150,7 +151,7 @@ class Enumerate(Basic):
         return self._args[0]
 
 #==============================================================================
-class Float(Function):
+class PythonFloat(Function):
     """ Represents a call to Python's native float() function.
     """
     def __new__(cls, arg):
@@ -317,7 +318,7 @@ class Range(Basic):
         stop = None
         step = 1
 
-        _valid_args = (sp_Integer, Symbol, Indexed)
+        _valid_args = (Integer, Symbol, Indexed)
 
         if isinstance(args, (tuple, list, Tuple)):
             if len(args) == 1:
@@ -377,7 +378,7 @@ class Zip(Basic):
 #==============================================================================
 python_builtin_datatypes_dict = {
     'bool'   : Bool,
-    'float'  : Float,
+    'float'  : PythonFloat,
     'int'    : Int,
     'complex': Complex
 }
diff --git a/pyccel/ast/datatypes.py b/pyccel/ast/datatypes.py
index 236712ee..5fb596df 100644
--- a/pyccel/ast/datatypes.py
+++ b/pyccel/ast/datatypes.py
@@ -79,7 +79,8 @@ __all__ = (
 default_precision = {'real': 8, 'int': 8, 'complex': 8, 'bool':4, 'float':8}
 dtype_and_precision_registry = { 'real':('real',8),
                                  'double':('real',8),
-                                 'float':('real',8),
+                                 'float':('real',8),       # sympy.Float
+                                 'pythonfloat':('real',8), # built-in float
                                  'float32':('real',4),
                                  'float64':('real',8),
                                  'complex':('complex',8),
diff --git a/pyccel/ast/numpyext.py b/pyccel/ast/numpyext.py
index 2ce16e29..d55f0ece 100644
--- a/pyccel/ast/numpyext.py
+++ b/pyccel/ast/numpyext.py
@@ -3,7 +3,7 @@
 
 import numpy
 from sympy import Basic, Function, Tuple
-from sympy import Integer as sp_Integer, Add, Mul, Pow as sp_Pow, Float as sp_Float
+from sympy import Integer, Add, Mul, Pow as sp_Pow, Float
 from sympy import asin, acsc, acos, asec, atan, acot, sinh, cosh, tanh, log
 from sympy import Rational as sp_Rational
 from sympy import IndexedBase
@@ -14,6 +14,8 @@ from sympy.logic.boolalg import BooleanTrue, BooleanFalse
 from .core import (Variable, IndexedElement, Slice, Len,
                    For, Range, Assign, List, Nil,
                    ValuedArgument, Constant, Pow)
+from .builtins  import Int as PythonInt
+from .builtins  import PythonFloat
 from .datatypes import dtype_and_precision_registry as dtype_registry
 from .datatypes import sp_dtype, str_dtype
 from .datatypes import default_precision
@@ -38,12 +40,13 @@ __all__ = (
     'Diag',
     'Empty',
     'EmptyLike',
+    'NumpyFloat',
     'Float32',
     'Float64',
     'Full',
     'FullLike',
     'Imag',
-    'Int',
+    'NumpyInt',
     'Int32',
     'Int64',
     'Linspace',
@@ -408,60 +411,6 @@ class Shape(Array):
 
         return code_init
 
-#==============================================================================
-# TODO [YG, 09.03.2020]: Reconsider this class, given new ast.builtins.Int
-class Int(Application):
-
-    """Represents a call to  numpy.int for code generation.
-
-    arg : Variable, Real, Integer, Complex
-    """
-
-    def __new__(cls, arg):
-        if not isinstance(arg, (Variable,
-                                IndexedElement,
-                                sp_Float, sp_Integer,
-                                Mul, Add, sp_Pow,
-                                sp_Rational)):
-
-            raise TypeError('Uknown type of  %s.' % type(arg))
-
-        obj = Basic.__new__(cls, arg)
-        assumptions = {'integer':True}
-        ass_copy = assumptions.copy()
-        obj._assumptions = StdFactKB(assumptions)
-        obj._assumptions._generator = ass_copy
-        return obj
-
-    @property
-    def arg(self):
-        return self._args[0]
-
-    @property
-    def dtype(self):
-        return 'int'
-
-    @property
-    def shape(self):
-        return None
-
-    @property
-    def rank(self):
-        return 0
-
-    @property
-    def precision(self):
-        return default_precision['int']
-
-
-    def fprint(self, printer):
-        """Fortran print."""
-
-        value = printer(self.arg)
-        prec  = printer(self.precision)
-        code  = 'Int({0}, {1})'.format(value, prec)
-        return code
-
 #==============================================================================
 # TODO [YG, 09.03.2020]: Reconsider this class, given new ast.builtins.Float
 class Real(Application):
@@ -473,8 +422,8 @@ class Real(Application):
 
     def __new__(cls, arg):
 
-        _valid_args = (Variable, IndexedElement, sp_Integer, Nil,
-                       sp_Float, Mul, Add, sp_Pow, sp_Rational, Application)
+        _valid_args = (Variable, IndexedElement, Integer, Nil,
+                       Float, Mul, Add, sp_Pow, sp_Rational, Application)
 
         if not isinstance(arg, _valid_args):
             raise TypeError('Uknown type of  %s.' % type(arg))
@@ -552,10 +501,10 @@ class Complex(Application):
     arg : Variable, Float, Integer
     """
 
-    def __new__(cls, arg0, arg1=sp_Float(0)):
+    def __new__(cls, arg0, arg1=Float(0)):
 
-        _valid_args = (Variable, IndexedElement, sp_Integer,
-                       sp_Float, Mul, Add, sp_Pow, sp_Rational)
+        _valid_args = (Variable, IndexedElement, Integer,
+                       Float, Mul, Add, sp_Pow, sp_Rational)
 
         for arg in [arg0, arg1]:
             if not isinstance(arg, _valid_args):
@@ -620,8 +569,8 @@ class Linspace(Application):
     def __new__(cls, *args):
 
 
-        _valid_args = (Variable, IndexedElement, sp_Float,
-                       sp_Integer, sp_Rational)
+        _valid_args = (Variable, IndexedElement, Float,
+                       Integer, sp_Rational)
 
         for arg in args:
             if not isinstance(arg, _valid_args):
@@ -732,7 +681,7 @@ class Diag(Application):
         if not isinstance(array, _valid_args):
            raise TypeError('Expecting valid args')
 
-        if not isinstance(k, (int, sp_Integer)):
+        if not isinstance(k, (int, Integer)):
            raise ValueError('k must be an integer')
 
         index = Variable('int', 'diag_index')
@@ -1467,26 +1416,44 @@ class Complex128(Complex):
         return dtype_registry['complex128'][1]
 
 #=======================================================================================
+class NumpyFloat(PythonFloat):
+    """ Represents a call to numpy.float() function.
+    """
+    def __new__(cls, arg):
+        return PythonFloat.__new__(cls, arg)
 
-class Float32(Real):
+class Float32(NumpyFloat):
+    """ Represents a call to numpy.float32() function.
+    """
     @property
     def precision(self):
         return dtype_registry['float32'][1]
 
-class Float64(Real):
+class Float64(NumpyFloat):
+    """ Represents a call to numpy.float64() function.
+    """
     @property
     def precision(self):
         return dtype_registry['float64'][1]
 
-
 #=======================================================================================
+# TODO [YG, 13.03.2020]: handle case where base != 10
+class NumpyInt(PythonInt):
+    """ Represents a call to numpy.int() function.
+    """
+    def __new__(cls, arg=None, base=10):
+        return PythonInt.__new__(cls, arg)
 
-class Int32(Int):
+class Int32(NumpyInt):
+    """ Represents a call to numpy.int32() function.
+    """
     @property
     def precision(self):
         return dtype_registry['int32'][1]
 
-class Int64(Int):
+class Int64(NumpyInt):
+    """ Represents a call to numpy.int64() function.
+    """
     @property
     def precision(self):
         return dtype_registry['int64'][1]
diff --git a/pyccel/ast/utilities.py b/pyccel/ast/utilities.py
index f465eccc..cf37da48 100644
--- a/pyccel/ast/utilities.py
+++ b/pyccel/ast/utilities.py
@@ -17,14 +17,14 @@ from .core import FunctionDef, Return, Assign
 from .core import ValuedArgument
 from .core import Constant, Variable, IndexedVariable
 
-from .builtins import Bool, Enumerate, Len, Map, Range, Zip
+from .builtins import Bool, Enumerate, Int, PythonFloat, Len, Map, Range, Zip
 
 from .numpyext import Full, Empty, Zeros, Ones
 from .numpyext import FullLike, EmptyLike, ZerosLike, OnesLike
 from .numpyext import Diag, Cross
 from .numpyext import Min, Max, Abs, Norm, Where
-from .numpyext import Array, Shape, Int, Rand, NumpySum, Matmul, Real, Complex, Imag, Mod
-from .numpyext import Int64, Int32, Float32, Float64, Complex64, Complex128
+from .numpyext import Array, Shape, Rand, NumpySum, Matmul, Real, Complex, Imag, Mod
+from .numpyext import NumpyInt, Int32, Int64, NumpyFloat, Float32, Float64, Complex64, Complex128
 from .numpyext import Sqrt, Asin, Acsc, Acos, Asec, Atan, Acot, Sinh, Cosh, Tanh, Log
 from .numpyext import numpy_constants, Linspace
 from .numpyext import Product as Prod
@@ -79,10 +79,10 @@ numpy_functions = {
     # ...
     'shape'     : Shape,
     'norm'      : Norm,
-    'int'       : Int,
+    'int'       : NumpyInt,
     'real'      : Real,
     'imag'      : Imag,
-    'float'     : Float64,
+    'float'     : NumpyFloat,
     'double'    : Float64,
     'mod'       : Mod,
     'float32'   : Float32,
@@ -108,7 +108,7 @@ builtin_functions_dict = {
     'zip'      : Zip,
     'enumerate': Enumerate,
     'int'      : Int,
-    'float'    : Real,
+    'float'    : PythonFloat,
     'bool'     : Bool,
     'sum'      : NumpySum,
     'len'      : Len,
@@ -144,7 +144,7 @@ def builtin_function(expr, args=None):
 
     if name in ['complex']:
         if len(args)==1:
-            args = [args[0],Float(0)]
+            args = [args[0], Float(0)]
         return Complex(args[0],args[1])
 
     if name == 'Not':
diff --git a/pyccel/codegen/printing/fcode.py b/pyccel/codegen/printing/fcode.py
index 9c9f6c70..94fc33b3 100644
--- a/pyccel/codegen/printing/fcode.py
+++ b/pyccel/codegen/printing/fcode.py
@@ -11,7 +11,7 @@ import numpy as np
 
 from sympy import Lambda
 from sympy.core import Symbol
-from sympy.core import Float as sp_Float, Integer as sp_Integer
+from sympy.core import Float, Integer
 from sympy.core import S, Add, N
 from sympy.core import Tuple
 from sympy.core.function import Function
@@ -52,7 +52,7 @@ from pyccel.ast.core import (Assign, AugAssign, Variable, CodeBlock,
                              IndexedElement, Slice, List, Dlist,
                              DottedName, AsName, DottedVariable,
                              If, Nil, Is, IsNot)
-from pyccel.ast.builtins import Enumerate, Len, Map, Print, Range, Zip
+from pyccel.ast.builtins import Enumerate, Int, Len, Map, Print, Range, Zip
 from pyccel.ast.datatypes import DataType, is_pyccel_datatype
 from pyccel.ast.datatypes import is_iterable_datatype, is_with_construct_datatype
 from pyccel.ast.datatypes import NativeBool, NativeSymbol, NativeString, NativeList
@@ -67,7 +67,7 @@ from pyccel.ast.parallel.openmp  import OMP_For
 from pyccel.ast.parallel.openacc import ACC_For
 
 from pyccel.ast.numpyext import Full, Array, Linspace, Diag, Cross
-from pyccel.ast.numpyext import Int, Real, Shape, Where, Mod
+from pyccel.ast.numpyext import Real, Shape, Where, Mod
 from pyccel.ast.numpyext import Complex
 from pyccel.ast.numpyext import FullLike, EmptyLike, ZerosLike, OnesLike
 
@@ -543,7 +543,7 @@ class FCodePrinter(CodePrinter):
         return self._print(expr.name)
 
     def _print_Constant(self, expr):
-        val = sp_Float(expr.value)
+        val = Float(expr.value)
         return self._print(val)
 
     def _print_ValuedArgument(self, expr):
@@ -646,6 +646,12 @@ class FCodePrinter(CodePrinter):
     def _print_Int(self, expr):
         return expr.fprint(self._print)
 
+    def _print_PythonFloat(self, expr):
+        return expr.fprint(self._print)
+
+    def _print_NumpyFloat(self, expr):
+        return expr.fprint(self._print)
+
     def _print_Real(self, expr):
         return expr.fprint(self._print)
 
@@ -788,10 +794,10 @@ class FCodePrinter(CodePrinter):
                     if i.start is None and i.end is None:
                         shape.append(s)
                     elif i.start is None:
-                        if (isinstance(i.end, (int, sp_Integer)) and i.end>0) or not(isinstance(i.end, (int, sp_Integer))):
+                        if (isinstance(i.end, (int, Integer)) and i.end>0) or not(isinstance(i.end, (int, Integer))):
                             shape.append(i.end)
                     elif i.end is None:
-                        if (isinstance(i.start, (int, sp_Integer)) and i.start<s-1) or not(isinstance(i.start, (int, sp_Integer))):
+                        if (isinstance(i.start, (int, Integer)) and i.start<s-1) or not(isinstance(i.start, (int, Integer))):
                             shape.append(s-i.start)
                     else:
                         shape.append(i.end-i.start+1)
@@ -898,7 +904,7 @@ class FCodePrinter(CodePrinter):
 
         # TODO improve
 
-        if ((rank == 1) and (isinstance(shape, (int, sp_Integer, Variable, Add))) and
+        if ((rank == 1) and (isinstance(shape, (int, Integer, Variable, Add))) and
             (not(allocatable or is_pointer) or is_static or is_stack_array)):
             rankstr =  '({0}:{1}-1)'.format(self._print(s), self._print(shape))
             enable_alloc = False
@@ -2170,7 +2176,7 @@ class FCodePrinter(CodePrinter):
     def _print_Pow(self, expr):
         PREC = precedence(expr)
         if expr.exp == -1:
-            one = sp_Float(1.0)
+            one = Float(1.0)
             code = '{0}/{1}'.format(self._print(one), \
                                     self.parenthesize(expr.base, PREC))
             return code
diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py
index 33fcab1f..42360f5b 100644
--- a/pyccel/parser/semantic.py
+++ b/pyccel/parser/semantic.py
@@ -911,9 +911,10 @@ class SemanticParser(BasicParser):
                 d_var['precision'  ] = max(d_vars[0]['precision'],
                                            d_vars[1]['precision'])
 
-            elif name in ['Int','Int32','Int64','Real','Imag',
-                          'Float32','Float64','Complex',
-                          'Complex128','Complex64','Bool']:
+            elif name in ['Int','Int32','Int64',
+                          'PythonFloat','NumpyFloat','Float32','Float64',
+                          'Complex','Complex64','Complex128',
+                          'Real','Imag','Bool']:
 
                 d_var['datatype'   ] = sp_dtype(expr)
                 d_var['rank'       ] = 0


Test patch:
diff --git a/tests/semantic/scripts/expressions.py b/tests/semantic/scripts/expressions.py
index ed93d658..79e42e6b 100644
--- a/tests/semantic/scripts/expressions.py
+++ b/tests/semantic/scripts/expressions.py
@@ -13,5 +13,18 @@ cd = ad + 1.
 dd = cd * 2. + 3. * bd + ad
 td = ad + bd * (cd + dd)
 
+conversion_1  = int(3.0)
+conversion_2  = 2.0 + int(3.0)
+conversion_3  = 2.0 - int(3.0)
+conversion_4  = 2.0 * int(3.0)
+conversion_5  = 2.0 / int(3.0)
+conversion_6  = 2.0 % int(3.0)
+conversion_7  = float(3.0)
+conversion_8  = 2.0 + float(3.0)
+conversion_9  = 2.0 - float(3.0)
+conversion_10 = 2.0 * float(3.0)
+conversion_11 = 2.0 / float(3.0)
+conversion_12 = 2.0 % float(3.0)
+
 # this statement will be ignored at the codegen
 x = ad is None
diff --git a/tests/semantic/test_semantic.py b/tests/semantic/test_semantic.py
index ae2a4b85..734f13c6 100644
--- a/tests/semantic/test_semantic.py
+++ b/tests/semantic/test_semantic.py
@@ -1,43 +1,42 @@
 # coding: utf-8
 
-# Note that we need to change the directory for tests involving the import
-# statement
-
 from pyccel.parser import Parser
 from pyccel.parser.errors import Errors
 import os
+import pytest
 
-def test_semantic():
-    print('*********************************')
-    print('***                           ***')
-    print('***      TESTING SEMANTIC     ***')
-    print('***                           ***')
-    print('*********************************')
+base_dir = os.path.dirname(os.path.realpath(__file__))
+path_dir = os.path.join(base_dir, 'scripts')
 
-    init_dir = os.getcwd()
-    base_dir = os.path.dirname(os.path.realpath(__file__))
-    path_dir = os.path.join(base_dir, 'scripts')
+files = sorted(os.listdir(path_dir))
+files = [os.path.join(path_dir,f) for f in files if (f.endswith(".py"))]
 
-    files = sorted(os.listdir(path_dir))
-    files = [f for f in files if (f.endswith(".py"))]
+@pytest.mark.parametrize( "f", files )
+def test_semantic(f):
+    print('> testing {0}'.format(str(f)))
 
-    os.chdir(path_dir)
-    for f in files:
-        print('> testing {0}'.format(str(f)))
+    pyccel = Parser(f)
+    ast = pyccel.parse()
 
-        pyccel = Parser(f)
-        ast = pyccel.parse()
+    settings = {}
+    ast = pyccel.annotate(**settings)
 
-        settings = {}
-        ast = pyccel.annotate(**settings)
+    # reset Errors singleton
+    errors = Errors()
+    errors.reset()
 
-        # reset Errors singleton
-        errors = Errors()
-        errors.reset()
 
-    os.chdir(init_dir)
-    print('\n')
 
 ######################
 if __name__ == '__main__':
-    test_semantic()
+    print('*********************************')
+    print('***                           ***')
+    print('***      TESTING SEMANTIC     ***')
+    print('***                           ***')
+    print('*********************************')
+
+    for f in files:
+        test_semantic(f)
+
+    print('\n')
+