From 202a57899eab0fef0b33c334391541de73d2b4cb Mon Sep 17 00:00:00 2001 From: PP1 <74917296+pengpengyi92@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:02:59 +0800 Subject: [PATCH 1/2] fix: preserve caller baskets during legacy int-to-float casts Signed-off-by: PP1 <74917296+pengpengyi92@users.noreply.github.com> --- csp/impl/wiring/signature.py | 9 ++++++-- csp/tests/test_type_checking.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) 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..8209a06ae 100644 --- a/csp/tests/test_type_checking.py +++ b/csp/tests/test_type_checking.py @@ -601,6 +601,44 @@ 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): + 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 From 993c949e42ff9c0ad7b53302256252c91a33e744 Mon Sep 17 00:00:00 2001 From: PP1 <74917296+pengpengyi92@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:04:52 +0800 Subject: [PATCH 2/2] test: assert converted basket entries are floats at runtime Signed-off-by: PP1 <74917296+pengpengyi92@users.noreply.github.com> --- csp/tests/test_type_checking.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/csp/tests/test_type_checking.py b/csp/tests/test_type_checking.py index 8209a06ae..f3d0077fd 100644 --- a/csp/tests/test_type_checking.py +++ b/csp/tests/test_type_checking.py @@ -621,6 +621,10 @@ 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():