diff --git a/csp/impl/wiring/signature.py b/csp/impl/wiring/signature.py index 79285b011..828a39081 100644 --- a/csp/impl/wiring/signature.py +++ b/csp/impl/wiring/signature.py @@ -195,7 +195,10 @@ def parse_inputs(self, forced_tvars, *args, allow_none_ts=False, **kwargs): if declared_type.__args__[0].typ is float: from csp.baselib import cast_int_to_float - resolved_input[j] = cast_int_to_float(resolved_input[j]) + # Copy once before casting a basket that may alias the caller's input. + if resolved_ts_inputs[i] is resolved_input: + resolved_ts_inputs[i] = resolved_input.copy() + resolved_ts_inputs[i][j] = cast_int_to_float(edge) elif isinstance(resolved_input, dict): declared_type = GenericValuesResolver.resolve_generic_values(input.typ, tvars) for k, edge in resolved_input.items(): @@ -203,7 +206,9 @@ def parse_inputs(self, forced_tvars, *args, allow_none_ts=False, **kwargs): if declared_type.__args__[1].typ is float: from csp.baselib import cast_int_to_float - resolved_input[k] = cast_int_to_float(resolved_input[k]) + if resolved_ts_inputs[i] is resolved_input: + resolved_ts_inputs[i] = resolved_input.copy() + resolved_ts_inputs[i][k] = cast_int_to_float(edge) return tuple(type_resolver.ts_inputs), tuple(type_resolver.scalar_inputs), type_resolver.tvars diff --git a/csp/tests/test_type_checking.py b/csp/tests/test_type_checking.py index 456ad7899..f3d0077fd 100644 --- a/csp/tests/test_type_checking.py +++ b/csp/tests/test_type_checking.py @@ -601,6 +601,48 @@ def graph(): build_graph(graph) + def test_graph_basket_cast_preserves_inputs(self): + @csp.graph + def accepts_float(x: Dict[str, ts[float]], y: List[ts[float]]) -> ts[bool]: + return csp.const(True) + + def graph(): + edge = csp.const(1) + x, y = {"a": edge}, [edge] + accepts_float(x, y) + self.assertIs(x["a"], edge) + self.assertIs(y[0], edge) + csp.cast_int_to_float(x["a"]) + csp.cast_int_to_float(y[0]) + + build_graph(graph) + + def test_node_basket_cast_preserves_inputs(self): + @csp.node + def accepts_float(x: Dict[str, ts[float]], y: List[ts[float]]) -> ts[float]: + if csp.ticked(x, y): + self.assertIs(type(x["a"]), float) + self.assertIs(type(x["b"]), float) + self.assertIs(type(y[0]), float) + self.assertIs(type(y[1]), float) + return x["a"] + x["b"] + x["c"] + y[0] + y[1] + y[2] + + def graph(): + integer, other_integer, floating = csp.const(1), csp.const(2), csp.const(2.5) + x, y = {"a": integer, "b": other_integer, "c": floating}, [integer, other_integer, floating] + result = accepts_float(x, y) + self.assertIs(x["a"], integer) + self.assertIs(x["b"], other_integer) + self.assertIs(x["c"], floating) + self.assertIs(y[0], integer) + self.assertIs(y[1], other_integer) + self.assertIs(y[2], floating) + return result + + result = csp.run(graph, starttime=datetime(2020, 1, 1), endtime=timedelta()) + self.assertEqual(result[0][0][1], 11.0) + self.assertIs(type(result[0][0][1]), float) + def test_basket_type_check_bug(self): # Tests a bug that wasn't covered in the initial implementation. The code below was crashing on _ForwardRef before the fix @csp.node