OR-1 dataflow CPU sketch

fix(asm): add regression test for @ret cascade prevention, fix error collection

- Add test_inner_ret_failure_does_not_cascade_to_outer to verify inner
macro @ret errors don't produce spurious outer macro errors
- Fix inconsistent error collection in _expand_body_recursive (edge
errors now go to body_errors like node errors, not directly to
enclosing errors list)

Orual ba08ffde 734df023

+27 -1
+1 -1
asm/expand.py
··· 694 694 # Qualify the body's own edges 695 695 for edge in body.edges: 696 696 qualified_edge, edge_errors = _clone_and_substitute_edge(edge, macro_scope, subst_map, func_scope, parent_scope) 697 - errors.extend(edge_errors) 697 + body_errors.extend(edge_errors) 698 698 body_edges.append(qualified_edge) 699 699 700 700 # Expand macro calls at this body level
+26
tests/test_macro_ret_wiring.py
··· 353 353 if e.dest == "&final_sink" 354 354 ] 355 355 assert len(outer_ret_edges) >= 1 356 + 357 + def test_inner_ret_failure_does_not_cascade_to_outer(self): 358 + """Inner macro @ret failure should not cause spurious outer errors. 359 + 360 + Uses named output (@ret_out / out=&sink) to disambiguate from plain_edge. 361 + """ 362 + source = """ 363 + @system pe=1, sm=1 364 + #inner val |> { 365 + &i <| const, ${val} 366 + &i |> @ret 367 + } 368 + #outer |> { 369 + &o <| pass 370 + #inner 1 371 + &o |> @ret_out 372 + } 373 + &sink <| add 374 + #outer |> out=&sink 375 + """ 376 + graph = parse_lower_expand(source) 377 + macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] 378 + assert len(macro_errors) == 1, f"Expected 1 error (inner #inner), got {len(macro_errors)}: {[e.message for e in macro_errors]}" 379 + assert "#inner" in macro_errors[0].message 380 + sink_edges = [e for e in graph.edges if e.dest == "&sink"] 381 + assert len(sink_edges) >= 1