Repository: numba/numba

Issue:
LinkedList jitclass example is broken
```
Internal error:
TypeError: Invalid store of %"deferred.4329823704.value" to {i8*, {i32, {%"deferred.4329823704.data", i8}}*} in <numba.datamodel.models.OptionalModel object at 0x106713278>
File "linkedlist.py", line 53
```



Discussion:
The JIT class PR was merged at cc21828, where the example still works.  The example is first broken at commit ec2dc85 .

Problem confirmed to be mishandling of Optional to Optional casting.


Resolving patch:
diff --git a/numba/targets/optional.py b/numba/targets/optional.py
--- a/numba/targets/optional.py
+++ b/numba/targets/optional.py
@@ -20,6 +20,39 @@ def always_return_false_impl(context, builder, sig, args):
     return cgutils.false_bit
 
 
+@lower_cast(types.Optional, types.Optional)
+def optional_to_optional(context, builder, fromty, toty, val):
+    """
+    The handling of optional->optional cast must be special cased for
+    correct propagation of None value.  Given type T and U. casting of
+    T? to U? (? denotes optional) should always succeed.   If the from-value
+    is None, the None value the casted value (U?) should be None; otherwise,
+    the from-value is casted to U. This is different from casting T? to U,
+    which requires the from-value must not be None.
+    """
+    optty = context.make_optional(fromty)
+    optval = optty(context, builder, value=val)
+    validbit = cgutils.as_bool_bit(builder, optval.valid)
+    # Create uninitialized optional value
+    outoptty = context.make_optional(toty)
+    outoptval = outoptty(context, builder)
+
+    with builder.if_else(validbit) as (is_valid, is_not_valid):
+        with is_valid:
+            # Cast internal value
+            outoptval.valid = cgutils.true_bit
+            outoptval.data = context.cast(builder, optval.data,
+                                          fromty.type, toty.type)
+
+        with is_not_valid:
+            # Store None to result
+            outoptval.valid = cgutils.false_bit
+            outoptval.data = cgutils.get_null_value(
+                outoptval.data.type)
+
+    return outoptval._getvalue()
+
+
 @lower_cast(types.Any, types.Optional)
 def any_to_optional(context, builder, fromty, toty, val):
     if fromty == types.none:
@@ -28,6 +61,7 @@ def any_to_optional(context, builder, fromty, toty, val):
         val = context.cast(builder, val, fromty, toty.type)
         return context.make_optional_value(builder, toty.type, val)
 
+
 @lower_cast(types.Optional, types.Any)
 def optional_to_any(context, builder, fromty, toty, val):
     optty = context.make_optional(fromty)


Test patch:
diff --git a/numba/tests/test_casting.py b/numba/tests/test_casting.py
--- a/numba/tests/test_casting.py
+++ b/numba/tests/test_casting.py
@@ -65,8 +65,6 @@ def test_array_to_array(self):
 
         Cast C to A array
         """
-        from numba import njit
-
         @njit("f8(f8[:])")
         def inner(x):
             return x[0]
@@ -81,6 +79,24 @@ def driver(x):
         self.assertEqual(driver(x), x[0])
         self.assertEqual(len(inner.overloads), 1)
 
+    def test_optional_to_optional(self):
+        """
+        Test error due mishandling of Optional to Optional casting
+
+        Related issue: https://github.com/numba/numba/issues/1718
+        """
+        # Attempt to cast optional(intp) to optional(float64)
+        opt_int = types.Optional(types.intp)
+        opt_flt = types.Optional(types.float64)
+        sig = opt_flt(opt_int)
+
+        @njit(sig)
+        def foo(a):
+            return a
+
+        self.assertEqual(foo(2), 2)
+        self.assertIsNone(foo(None))
+
 
 if __name__ == '__main__':
     unittest.main()