Repository: pyccel/pyccel (python)

Issue:
Missing error when passing the wrong type to an optional argument
### Describe the bug
When the wrong type is passed as an optional argument the wrapper checks the types without raising an error. This results in a system error from Python

### To Reproduce
Provide code to reproduce the behavior:
```python
def f(y  : 'int' =  None):
    if y is None:
        return 2
    else:
        return y
```
```python
from tmp_optional import f

f(4.0)
```

### Error details
Provide the generated code, or the error message:
```
Traceback (most recent call last):
  File "/home/emily/Code/pyccel/tests/test.py", line 3, in <module>
    f(4.0)
SystemError: <built-in function f> returned NULL without setting an exception
```

### Expected behavior
The error should be raised as for non-optionals:
```
Traceback (most recent call last):
  File "/home/emily/Code/pyccel/tests/test.py", line 3, in <module>
    f(4.0)
TypeError: Expected an argument of type int for argument y
```

### Language
Fortran/C

### Additional context
Fixing this may result in the error seen in #1963 


Pull request description:
Ensure an error is raised when passing the wrong type as an optional argument
Ensure that a type check generates an error for optionals and reorder if blocks to ensure that the error is not raised if it is not required. Fixes #1970 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a2d718a..fa94c06d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@ All notable changes to this project will be documented in this file.
 -   #1951 : Fix return type for class whose argument cannot be wrapped.
 -   #1892 : Fix implementation of list function when an iterable is passed as parameter.
 -   #1924 : Fix internal error arising in Duplicate or list comprehensions.
+-   #1970 : Fix missing `TypeError` for wrong type passed as optional argument.
 
 ### Changed
 
diff --git a/pyccel/codegen/wrapper/c_to_python_wrapper.py b/pyccel/codegen/wrapper/c_to_python_wrapper.py
index e8e866bc..a6f06f38 100644
--- a/pyccel/codegen/wrapper/c_to_python_wrapper.py
+++ b/pyccel/codegen/wrapper/c_to_python_wrapper.py
@@ -1503,10 +1503,9 @@ class CToPythonWrapper(Wrapper):
 
         # Create any necessary type checks and errors
         if expr.has_default:
-            check_func, err = self._get_check_function(collect_arg, orig_var, False)
-            body.append(If( IfSection(check_func, cast),
-                        IfSection(PyccelIsNot(collect_arg, Py_None), [*err, Return([self._error_exit_code])])
-                        ))
+            check_func, err = self._get_check_function(collect_arg, orig_var, True)
+            body.append(If( IfSection(PyccelIsNot(collect_arg, Py_None), [
+                                If(IfSection(check_func, cast), IfSection(LiteralTrue(), [*err, Return([self._error_exit_code])]))])))
         elif not (in_interface or bound_argument):
             check_func, err = self._get_check_function(collect_arg, orig_var, True)
             body.append(If( IfSection(check_func, cast),


Test patch:
diff --git a/tests/epyccel/test_epyccel_optional_args.py b/tests/epyccel/test_epyccel_optional_args.py
index 8836a965..3286c698 100644
--- a/tests/epyccel/test_epyccel_optional_args.py
+++ b/tests/epyccel/test_epyccel_optional_args.py
@@ -29,6 +29,9 @@ def test_f1(language):
     assert f() == f1()
     assert f(None) == f1(None)
     assert f(0) == f1(0)
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(3.5)
     # ...
 #------------------------------------------------------------------------------
 def test_f2(language):
@@ -44,6 +47,9 @@ def test_f2(language):
     assert np.isclose(f(), f2(), rtol=RTOL, atol=ATOL)
     assert np.isclose(f(None), f2(None), rtol=RTOL, atol=ATOL)
     assert np.isclose(f(0.0), f2(0.0), rtol=RTOL, atol=ATOL)
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(3)
     # ...
 #------------------------------------------------------------------------------
 def test_f3(language):
@@ -58,6 +64,9 @@ def test_f3(language):
     assert np.isclose(f(complex(1, 2.2)), f3(complex(1, 2.2)), rtol=RTOL, atol=ATOL)
     assert np.isclose(f(), f3(), rtol=RTOL, atol=ATOL)
     assert np.isclose(f(None), f3(None), rtol=RTOL, atol=ATOL)
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(3.5)
     # ...
 #------------------------------------------------------------------------------
 def test_f4(language):
@@ -73,6 +82,9 @@ def test_f4(language):
     assert f() == f4()
     assert f(None) == f4(None)
     assert f(False) == f4(False)
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(3.5)
     # ...
 #------------------------------------------------------------------------------
 def test_f5(language):
@@ -151,6 +163,10 @@ def test_optional_args_1d(language):
     # ...
     assert np.array_equal(x1, x2)
 
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(x1, 3)
+
 #------------------------------------------------------------------------------
 def test_optional_2d_F(language):
     def f13(x : 'int32[:,:](order=F)', y  : 'int32[:,:](order=F)' =  None):
@@ -167,6 +183,11 @@ def test_optional_2d_F(language):
 
     # ...
     assert np.array_equal(x1, x2)
+
+    if language != 'python':
+        with pytest.raises(TypeError):
+            f(x1, 3.5)
+
 #------------------------------------------------------------------------------
 
 def test_f14(language):