diff --git a/gitnexus/src/core/ingestion/cfg/visitors/c-cpp.ts b/gitnexus/src/core/ingestion/cfg/visitors/c-cpp.ts index 13918661e..14bed620a 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/c-cpp.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/c-cpp.ts @@ -93,6 +93,7 @@ const CPP_CONTROL_FLOW_TYPES = new Set([ 'for_range_loop', 'try_statement', 'throw_statement', + 'co_return_statement', ]); const LOOP_OR_SWITCH_TYPES = new Set([ @@ -589,6 +590,11 @@ class CppCfgWalk extends CCfgWalk { return this.visitTry(stmt); case 'throw_statement': return this.visitThrow(stmt); + case 'co_return_statement': + // A coroutine `co_return` terminates the coroutine like an ordinary + // return: edge to EXIT, no fallthrough (`co_await`/`co_yield` are plain + // expressions that continue, so they need no control-flow handling). + return this.visitReturn(stmt); default: return undefined; } diff --git a/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts b/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts index d3aa6c5c1..b03d66a47 100644 --- a/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts +++ b/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts @@ -253,6 +253,19 @@ describe('C++ CfgVisitor — structured bindings (#2195 P1)', () => { }); }); +describe('C++ CfgVisitor — coroutines (#2195 P1)', () => { + it('co_return edges to EXIT (return), not a seq fallthrough to the next statement', () => { + const cfg = cpp.cfgOf(`Task f(int x) { if (x) co_return early(); main(); }`); + const co = block(cfg, 'co_return early();'); + // co_return is a return terminator: it edges to EXIT… + expect( + cfg.edges.some((e) => e.from === co && e.to === cfg.exitIndex && e.kind === 'return'), + ).toBe(true); + // …and never falls through to the following statement. + expect(cfg.edges.some((e) => e.from === co && e.kind === 'seq')).toBe(false); + }); +}); + describe('C CfgVisitor — functionStartColumn', () => { it('two same-line functions get distinct functionStartColumn', () => { const cfgs = c.cfgsOf(`int a(){return 1;} int b(){return 2;}`);