Skip to content

Commit 93bbb94

Browse files
Copilotaarne
andauthored
Fix ?. + chained || literal fallback parsing to preserve left-to-right short-circuit semantics (#94)
* Initial plan * fix parser short-circuit for chained literal OR fallbacks Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * test and parser update for chained literal OR fallback order Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * test mixed || ?? chain semantics and add release changeset Co-authored-by: aarne <82001+aarne@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aarne <82001+aarne@users.noreply.github.com>
1 parent 3599bd1 commit 93bbb94

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

.changeset/tough-cups-smoke.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@stackables/bridge": patch
3+
"@stackables/bridge-parser": patch
4+
---
5+
6+
Fix chained `||` literal fallback parsing so authored left-to-right short-circuiting is preserved after safe pulls (`?.`), and add regression coverage for mixed `||` + `??` chains.

packages/bridge-parser/src/parser/parser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5237,13 +5237,18 @@ function buildBridgeBody(
52375237

52385238
let falsyFallback: string | undefined;
52395239
let falsyControl: ControlFlowInstruction | undefined;
5240+
let hasTruthyLiteralFallback = false;
52405241
for (const alt of subs(wireNode, "nullAlt")) {
5242+
if (hasTruthyLiteralFallback) break;
52415243
const altResult = extractCoalesceAlt(alt, lineNum);
52425244
if ("literal" in altResult) {
52435245
falsyFallback = altResult.literal;
5246+
hasTruthyLiteralFallback = Boolean(JSON.parse(altResult.literal));
52445247
} else if ("control" in altResult) {
5248+
falsyFallback = undefined;
52455249
falsyControl = altResult.control;
52465250
} else {
5251+
falsyFallback = undefined;
52475252
sourceParts.push({ ref: altResult.sourceRef, isPipeFork: false });
52485253
}
52495254
}

packages/bridge/test/coalesce-cost.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,50 @@ bridge Query.lookup {
649649
assert.equal(data.label, "fallback");
650650
});
651651

652+
test("?. with chained || literals short-circuits at first truthy literal", async () => {
653+
const doc = parseBridge(`version 1.5
654+
const lorem = {
655+
"ipsum":"dolor sit amet",
656+
"consetetur":8.9
657+
}
658+
659+
bridge Query.lookup {
660+
with const
661+
with output as o
662+
663+
o.label <- const.lorem.ipsums?.kala || "A" || "B"
664+
}`);
665+
const gateway = createGateway(typeDefs, doc, { tools: {} });
666+
const executor = buildHTTPExecutor({ fetch: gateway.fetch as any });
667+
668+
const result: any = await executor({
669+
document: parse(`{ lookup(q: "x") { label } }`),
670+
});
671+
assert.equal(result.data.lookup.label, "A");
672+
});
673+
674+
test("mixed || and ?? remains left-to-right with first truthy || winner", async () => {
675+
const doc = parseBridge(`version 1.5
676+
const lorem = {
677+
"ipsum": "dolor sit amet",
678+
"consetetur": 8.9
679+
}
680+
681+
bridge Query.lookup {
682+
with const
683+
with output as o
684+
685+
o.label <- const.lorem.kala || const.lorem.ipsums?.mees || "B" ?? "C"
686+
}`);
687+
const gateway = createGateway(typeDefs, doc, { tools: {} });
688+
const executor = buildHTTPExecutor({ fetch: gateway.fetch as any });
689+
690+
const result: any = await executor({
691+
document: parse(`{ lookup(q: "x") { label } }`),
692+
});
693+
assert.equal(result.data.lookup.label, "B");
694+
});
695+
652696
test("?. passes through value when tool succeeds", async () => {
653697
const { data } = await run(
654698
`version 1.5

0 commit comments

Comments
 (0)