Repository: artsiomkaltovich/zython (language: python)

Issue:
issues in float ranges
There are several issues for float ranges:

1. they do not set float used flag
2. ``zn.range(2.3, 3.3)`` will compile to ``2.3..2.3``


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 635be92..e27782e 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ programs.
 
 - You should have minizinc >=2.5 install and have it executable in ``$PATH``.
 You can download it from [official site](https://www.minizinc.org/).
-- Python 3.6+
+- Python 3.7+
 
 ### Installation
 
diff --git a/changelog.md b/changelog.md
index 3ebc7db..563db36 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,12 @@
+### 0.2.3
+#### Added
+- validation for float ranges in some constraints, e.g. forall
+#### Fixed
+- ranges with float values correctly set bigger limit
+#### Deleted
+- check for minizinc executable is available, 
+as it seems, python-minizinc implement it by itself. 
+
 ### 0.2.2
 #### Added
 - Sets support enums
diff --git a/zython/__init__.py b/zython/__init__.py
index dff543b..b3e85b2 100644
--- a/zython/__init__.py
+++ b/zython/__init__.py
@@ -1,11 +1,3 @@
-import warnings
-
-from minizinc import find_driver
-
-if not find_driver():
-    warnings.warn("Solver wasn't found, are you sure you specify minizinc folder to $PATH environment variable")
-
-
 import zython.var_par.types
 from zython.var_par.var import var
 from zython.var_par.par import par
diff --git a/zython/_compile/zinc/to_str.py b/zython/_compile/zinc/to_str.py
index bcbf2d3..5b7aef0 100644
--- a/zython/_compile/zinc/to_str.py
+++ b/zython/_compile/zinc/to_str.py
@@ -11,7 +11,7 @@
 from zython.operations.constraint import Constraint
 from zython.operations.operation import Operation
 from zython.var_par.collections.array import ArrayView, ArrayMixin
-from zython.var_par.types import is_range
+from zython.var_par.types import is_range, is_int_range
 
 
 @singledispatch
@@ -25,7 +25,7 @@ def to_str(stmt, *, flatten_arg=False, flags_=None):
     elif isinstance(stmt, Constraint):
         return Op2Str[stmt.op](*stmt.params, flags_=flags_)
     elif is_range(stmt):
-        return _range_or_slice_to_str(stmt)
+        return _range_or_slice_to_str(stmt, flags_=flags_)
     return str(stmt)
 
 
@@ -36,9 +36,13 @@ def _(stmt, *, flatten_arg=False, flags_=None):
     return f"[{', '.join(to_str(s) for s in stmt)}]"
 
 
-def _range_or_slice_to_str(stmt):
+def _range_or_slice_to_str(stmt, flags_=set()):
     _start_stop_step_validate(stmt)
-    return f"{to_str(stmt.start)}..{to_str(stmt.stop - 1)}"
+    if is_int_range(stmt):
+        return f"{to_str(stmt.start)}..{to_str(stmt.stop - 1)}"
+    else:
+        flags_.add(Flags.float_used)
+        return f"{to_str(stmt.start)}..{to_str(stmt.stop)}"
 
 
 def _compile_array_view(view):
diff --git a/zython/_compile/zinc/zinc.py b/zython/_compile/zinc/zinc.py
index 7f9933a..04b9873 100644
--- a/zython/_compile/zinc/zinc.py
+++ b/zython/_compile/zinc/zinc.py
@@ -9,7 +9,7 @@
 from zython.operations.constraint import Constraint
 from zython.var_par.collections.array import ArrayMixin
 from zython.var_par.collections.set import SetVar, SetPar
-from zython.var_par.types import is_range, is_enum
+from zython.var_par.types import is_range, is_enum, is_int_range
 
 
 def to_zinc(ir: IR):
@@ -82,7 +82,9 @@ def _elementary_var_decl(v, decl_prefix, flags):
         flags.add(Flags.float_used)
         declaration += f"{decl_prefix} float: {v.name};"
     elif is_range(v.type):
-        declaration += f"{decl_prefix} {to_str(v.type)}: {v.name};"
+        if not is_int_range(v.type):
+            flags.add(Flags.float_used)
+        declaration += f"{decl_prefix} {to_str(v.type, flags_=flags)}: {v.name};"
     elif is_enum(v.type):
         declaration += f"{decl_prefix} {v.type.__name__}: {v.name};"
     else:
@@ -90,9 +92,9 @@ def _elementary_var_decl(v, decl_prefix, flags):
     return declaration
 
 
-def _set_value_as_constraint(ir, variable, flags):
+def _set_value_as_constraint(ir, variable, flags_):
     # values like `var int: s = sum(a);` should be set as constraint or it won't be returned in result
-    ir.constraints.append(_binary_op("==", variable.name, to_str(variable.value), flags_=flags))
+    ir.constraints.append(_binary_op("==", variable.name, to_str(variable.value, flags_=flags_), flags_=flags_))
 
 
 def _process_constraints(ir, src, flags_):
diff --git a/zython/_helpers/validate.py b/zython/_helpers/validate.py
index 40b2c20..646e33a 100644
--- a/zython/_helpers/validate.py
+++ b/zython/_helpers/validate.py
@@ -1,5 +1,5 @@
 def _start_stop_step_validate(stmt):
-    if not isinstance(stmt.step, int) or stmt.step != 1:
+    if stmt.step != 1:
         raise ValueError(f"step other then 1 isn't supported, but it is {stmt.step}")
     if isinstance(stmt.start, int) and isinstance(stmt.stop, int) and stmt.start >= stmt.stop:
         raise ValueError(f"start({stmt.start}) should be smaller then stop({stmt.stop})")
diff --git a/zython/operations/_iternal.py b/zython/operations/_iternal.py
index 9de4004..d0b6648 100644
--- a/zython/operations/_iternal.py
+++ b/zython/operations/_iternal.py
@@ -5,14 +5,16 @@
 from zython import var
 from zython.operations.operation import Operation
 from zython.var_par.collections.array import ArrayMixin
-from zython.var_par.types import is_range, ZnSequence, get_type
+from zython.var_par.types import is_range, ZnSequence, get_type, is_int_range
 from ..var_par.collections.array import _AbstractCollection
 
 
 @singledispatch
 def _get_variable(seq) -> var:
     if is_range(seq):
-        return var(int)
+        if is_int_range(seq):
+            return var(int)
+        raise ValueError("float ranges are not supported as argument")
     assert False, f"{type(seq)} isn't supported, please use Sequence or Array here"
 
 
diff --git a/zython/operations/operation.py b/zython/operations/operation.py
index 20e1973..4429e24 100644
--- a/zython/operations/operation.py
+++ b/zython/operations/operation.py
@@ -4,10 +4,17 @@
 import zython
 from zython.operations._op_codes import _Op_code
 from zython.operations.constraint import Constraint
+from zython.var_par.types import get_type
 
 
 def _get_wider_type(left, right):
-    return int
+    t_types = get_type(left), get_type(right)
+    types = set(t_types)
+    if types == {int}:
+        return int
+    elif types == {int, float}:
+        return float
+    return int  # TODO: fix types, do not forget about int/int => float
 
 
 class Operation(Constraint):
diff --git a/zython/var_par/types.py b/zython/var_par/types.py
index 86705c7..b8b8a07 100644
--- a/zython/var_par/types.py
+++ b/zython/var_par/types.py
@@ -3,7 +3,13 @@
 import zython  # for type hints
 from typing import Union, Sequence
 
-from zython.operations.operation import Operation
+
+def _create_range(cls, start, stop, step):
+    obj = object.__new__(cls)
+    obj.start = start
+    obj.stop = stop
+    obj.step = step
+    return obj
 
 
 class _range:
@@ -11,16 +17,11 @@ def __new__(cls, start, stop=None, step=1):
         if stop is None:
             stop = start
             start = 0
-        if (isinstance(start, (Operation, float))
-                or isinstance(stop, (Operation, float))
-                or isinstance(step, (Operation, float))):
-            self = object.__new__(cls)
-            self.start = start
-            self.stop = stop
-            self.step = step
-            return self
-        else:
+        if isinstance(start, int) and isinstance(stop, int) and isinstance(step, int):
             return range(start, stop, step)
+        if isinstance(start, (int, float)) and isinstance(stop, (int, float)) and isinstance(step, (int, float)):
+            return _create_range(cls, float(start), float(stop), float(step))
+        return _create_range(cls, start, stop, step)
 
 
 Ranges = range, _range
@@ -32,6 +33,10 @@ def is_range(obj):
     return isinstance(obj, Ranges)
 
 
+def is_int_range(r: RangesType):
+    return all(get_type(s) is int for s in (r.start, r.stop, r.step))
+
+
 def is_enum(arg):
     return isinstance(arg, enum.EnumMeta)
 


Test patch:
diff --git a/test/compile/operations/test_forall.py b/test/compile/operations/test_forall.py
new file mode 100644
index 0000000..eafe5f0
--- /dev/null
+++ b/test/compile/operations/test_forall.py
@@ -0,0 +1,9 @@
+import pytest
+
+import zython as zn
+
+
+def test_float_range():
+    a = zn.Array([1, 2, 3])
+    with pytest.raises(ValueError, match="float ranges are not supported as argument"):
+        zn.forall(zn.range(1.0), lambda i: a[i] != a[i + 1])
diff --git a/test/compile/test_var.py b/test/compile/test_var.py
index 7a4f9e6..eeed946 100644
--- a/test/compile/test_var.py
+++ b/test/compile/test_var.py
@@ -3,6 +3,7 @@
 import pytest
 
 import zython as zn
+from zython._compile.zinc.flags import Flags
 from zython._compile.zinc.to_str import to_str
 
 
@@ -28,13 +29,13 @@ def __init__(self):
     assert 9 == result["b"]
 
 
-def create_var(name):
-    v = zn.var(int)
+def create_var(name, type=int):
+    v = zn.var(type)
     v._name = name
     return v
 
 
-def create_par(name):
+def create_par(name, value=5):
     p = zn.par(5)
     p._name = name
     return p
@@ -60,25 +61,6 @@ def test_var_to_str(self, v, expected):
     def test_flatten(self, v, expected):
         assert to_str(v, flatten_arg=True) == expected
 
-    @pytest.mark.parametrize("r, expected", [(range(100), "0..99"), (range(1, 10), "1..9"), (range(0, 100), "0..99"),
-                                             (range(-10, 10), "-10..9"), (range(-10, -9), "-10..-10")])
-    def test_range(self, r, expected):
-        assert to_str(r) == expected
-
-    def test_range_step(self):
-        with pytest.raises(ValueError, match="step other then 1 isn't supported, but it is 2"):
-            to_str(range(1, 10, 2))
-
-    @pytest.mark.parametrize("start", (10, 15))
-    def test_range_wrong_start(self, start):
-        with pytest.raises(ValueError, match=re.escape(f"start({start}) should be smaller then stop(10)")):
-            print(to_str(range(start, 10)))
-
-    def test_range_with_expr(self):
-        v = create_var("a")
-        p = create_par("b")
-        assert "(a - 1)..((b + 1) - 1)" == to_str(zn.range(v - 1, p + 1))
-
     @pytest.mark.parametrize("array, pos, expected", [(create_array("z", 3), (1, 2, 3), "z[1, 2, 3]"),
                                                       (create_array("g", 1), 2, "g[2]")])
     def test_indexes(self, array, pos, expected):
@@ -105,3 +87,52 @@ def test_tuple_and_list_to_array(self, collection, expected):
                                                ((x for x in range(3)), "[0, 1, 2]")])
     def test_generator_to_str(self, gen, expected):
         assert expected == to_str(gen)
+
+
+class TestRange:
+    @pytest.mark.parametrize("r, expected", [(range(100), "0..99"), (range(1, 10), "1..9"), (range(0, 100), "0..99"),
+                                             (range(-10, 10), "-10..9"), (range(-10, -9), "-10..-10")])
+    def test_range(self, r, expected):
+        assert to_str(r) == expected
+
+    def test_zn_int_range(self):
+        assert to_str(zn.range(1, 10)) == "1..9"
+
+    def test_range_step(self):
+        with pytest.raises(ValueError, match="step other then 1 isn't supported, but it is 2"):
+            to_str(range(1, 10, 2))
+
+    @pytest.mark.parametrize("start", (10, 15))
+    def test_range_wrong_start(self, start):
+        with pytest.raises(ValueError, match=re.escape(f"start({start}) should be smaller then stop(10)")):
+            print(to_str(range(start, 10)))
+
+    def test_range_with_expr(self):
+        v = create_var("a")
+        p = create_par("b")
+        assert "(a - 1)..((b + 1) - 1)" == to_str(zn.range(v - 1, p + 1))
+
+    def test_float(self):
+        flags = set()
+        a = to_str(zn.range(1, 10.0), flags_=flags)
+        assert a == "1.0..10.0"
+        assert Flags.float_used in flags
+
+    def test_int_op(self):
+        v = create_var("a", int)
+        a = to_str(zn.range(v, v + 1))
+        assert a == "a..((a + 1) - 1)"
+
+    def test_float_op(self):
+        flags = set()
+        v = create_var("a", float)
+        a = to_str(zn.range(v, v + 2), flags_=flags)
+        assert a == "a..(a + 2)"
+        assert Flags.float_used in flags
+
+    def test_float_int_par_float_op(self):
+        flags = set()
+        v = create_var("a", int)
+        a = to_str(zn.range(v, v + 2.4), flags_=flags)
+        assert a == "a..(a + 2.4)"
+        assert Flags.float_used in flags