diff --git a/dace/frontend/python/common.py b/dace/frontend/python/common.py index ae6fe06fe5..f23193aed0 100644 --- a/dace/frontend/python/common.py +++ b/dace/frontend/python/common.py @@ -24,8 +24,8 @@ def __str__(self): col = 0 if self.visitor is not None: - return (self.message + "\n in File " + str(self.visitor.filename) + - ", line " + str(line) + ":" + str(col)) + return (self.message + "\n File \"" + str(self.visitor.filename) + + "\", line " + str(line) + ", column " + str(col)) else: return (self.message + "\n in line " + str(line) + ":" + str(col)) @@ -162,5 +162,7 @@ def combine_nested_closures(self): new_name = data.find_new_name(arrname, self.closure_arrays.keys()) - self.closure_arrays[new_name] = (arrname, desc, evaluator, True) - self.array_mapping[id(arr)] = new_name + if not desc.transient: + self.closure_arrays[new_name] = (arrname, desc, evaluator, + True) + self.array_mapping[id(arr)] = new_name diff --git a/dace/frontend/python/newast.py b/dace/frontend/python/newast.py index 066981d20a..ce21cfd4aa 100644 --- a/dace/frontend/python/newast.py +++ b/dace/frontend/python/newast.py @@ -3651,10 +3651,11 @@ def _parse_sdfg_call(self, funcname: str, outer_name = self.sdfg.add_datadesc(aname, desc, find_new_name=True) - self.nested_closure_arrays[outer_name] = (arr, desc) - # Add closure arrays as function arguments - args.append((aname, outer_name)) - required_args.append(aname) + if not desc.transient: + self.nested_closure_arrays[outer_name] = (arr, desc) + # Add closure arrays as function arguments + args.append((aname, outer_name)) + required_args.append(aname) else: raise DaceSyntaxError( self, node, 'Unrecognized SDFG type "%s" in call to "%s"' % @@ -4006,9 +4007,13 @@ def visit_Call(self, node: ast.Call): if func or funcname in self.other_sdfgs: try: return self._parse_sdfg_call(funcname, func, node) - except SkipCall: + except SkipCall as ex: # Re-parse call with non-parsed information - return self.visit_Call(node.func.oldnode) + try: + return self.visit_Call(node.func.oldnode) + except Exception: # Anything could happen here + # Raise original exception instead + raise ex.__context__ # Set arguments args = [] diff --git a/tests/python_frontend/callee_autodetect_test.py b/tests/python_frontend/callee_autodetect_test.py index b1ee36df83..7475f36fef 100644 --- a/tests/python_frontend/callee_autodetect_test.py +++ b/tests/python_frontend/callee_autodetect_test.py @@ -4,7 +4,7 @@ not annotated with @dace decorators. """ import dace -from dace.frontend.python.common import DaceSyntaxError +from dace.frontend.python.common import DaceSyntaxError, SDFGConvertible from dataclasses import dataclass import numpy as np import pytest @@ -104,7 +104,7 @@ def notworking2(a: dace.float64[20]): return notworking_nested(a) A = np.random.rand(20) - with pytest.raises(DaceSyntaxError, match='notworking_nested'): + with pytest.raises(DaceSyntaxError, match='numpy.allclose'): notworking2(A) @@ -133,7 +133,7 @@ def recursive_autoparse(a: dace.float64[20]): return nested_a(a) A = np.random.rand(20) - with pytest.raises(DaceSyntaxError, match='nested_a'): + with pytest.raises(DaceSyntaxError, match='nested_b'): recursive_autoparse(A) @@ -173,6 +173,59 @@ def adff(A): assert np.allclose(A, ref + 2 * 5) +def test_error_handling(): + class NotConvertible(SDFGConvertible): + def __call__(self, a): + import numpy as np + print('A very pythonic method', a) + + def __sdfg__(self, *args, **kwargs): + # Raise a special type of error that does not naturally occur in dace + raise NotADirectoryError('I am not really convertible') + + def __sdfg_signature__(self): + return ([], []) + + A = np.random.rand(20) + + with pytest.raises(NotADirectoryError): + + @dace.program + def testprogram(A, nc: dace.constant): + nc(A) + + testprogram(A, NotConvertible()) + + +def test_nested_class_error_handling(): + def not_convertible(f): + class NotConvertibleMethod(SDFGConvertible): + def __sdfg__(self, *args, **kwargs): + # Raise a special type of error that does not naturally occur in dace + raise NotADirectoryError('I am not really convertible') + + def __sdfg_signature__(self): + return ([], []) + + return NotConvertibleMethod() + + class MaybeConvertible: + @not_convertible + def __call__(self, a): + import numpy as np + print('A very pythonic method', a) + + A = np.random.rand(20) + + with pytest.raises(NotADirectoryError): + + @dace.program + def testprogram(A, nc: dace.constant): + nc(A) + + testprogram(A, MaybeConvertible()) + + if __name__ == '__main__': test_autodetect_function() test_autodetect_method() @@ -184,3 +237,5 @@ def adff(A): test_nested_recursion2_fail() test_nested_autoparse_dec_fail() test_autodetect_function_in_for() + test_error_handling() + test_nested_class_error_handling() diff --git a/tests/python_frontend/fields_and_global_arrays_test.py b/tests/python_frontend/fields_and_global_arrays_test.py index b370620200..005e62db35 100644 --- a/tests/python_frontend/fields_and_global_arrays_test.py +++ b/tests/python_frontend/fields_and_global_arrays_test.py @@ -671,6 +671,38 @@ def __call__(self, A): assert np.allclose(1.0, A) +def test_nested_transient_field(): + class Something: + def __init__(self): + self._nonglobal = TransientField(shape=[10, 11], dtype=np.float64) + + @dace.method + def __call__(self, A): + self._nonglobal[...] = A + return self._nonglobal + 1 + + class MainSomething: + def __init__(self) -> None: + self.something_else = Something() + + @dace.method + def __call__(self, A): + return self.something_else(A) + + A = np.ones((10, 11)) + + outer = MainSomething() + + # Ensure no other global arrays were created apart from A and __return + sdfg = outer.__call__.to_sdfg(A) + assert len([k for k, v in sdfg.arrays.items() if not v.transient]) == 2 + + # Run code + outer(A) + + assert np.allclose(1.0, A) + + if __name__ == '__main__': test_bad_closure() test_dynamic_closure() @@ -702,3 +734,4 @@ def __call__(self, A): test_same_global_array() test_two_inner_methods() test_transient_field() + test_nested_transient_field()