test(ingestion): pin the LEFT operand of ?? / or / elvis per provider (#3354)

The Python, Kotlin, Swift and Dart "override ?? fn" cases put an
unresolvable parameter on the left, so a fan-out that kept only the
last operand still passed them. Each fixture now adds a callableLeft
case with a real function on the left and a parameter on the right
(`run_left or fallback`, `::runLeft ?: fallback`, `runLeft ?? fallback`),
and each language asserts `callableLeft → runLeft`.

Mutation check: dropping the left branch from the shared `??`/`||`/`or`
rule and making the four provider hooks return only their last operand
fails all four new tests, while the existing right-operand tests still
pass.

Capture goldens were regenerated with UPDATE_GOLDEN=1:
- python app.py: 35 -> 85 groups. 35 -> 75 is stale drift from
  8ad0d8db7, which added the comparison cases without regenerating.
  75 -> 85 is the new case: 2 declarations, 2 scopes, the variable,
  the `run()` reference, and 4 callable-flow captures (seed -> run_left,
  copy <- fallback, formal, invoke).
- swift App.swift: 25 -> 36 groups for the same new case, plus
  Swift's type-binding for the fallback parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-09-25 17:50:25 +00:00
parent 8ad0d8db7f
commit 5487d8dae2
7 changed files with 52 additions and 4 deletions

View file

@ -7,6 +7,13 @@ void ifNull(void Function()? override) {
run();
}
void runLeft() {}
void callableLeft(void Function() fallback) {
final run = runLeft ?? fallback;
run();
}
void conditional(bool fast) {
final run = fast ? runThen : runElse;
run();

View file

@ -7,6 +7,13 @@ fun elvis(override: (() -> Unit)?) {
run()
}
fun runLeft() {}
fun callableLeft(fallback: () -> Unit) {
val run = ::runLeft ?: fallback
run()
}
fun ifExpression(fast: Boolean) {
val run = if (fast) ::runThen else ::runElse
run()

View file

@ -18,6 +18,15 @@ def run_or_else():
pass
def run_left():
pass
def callable_left(fallback):
run = run_left or fallback
run()
def logical_or(override):
run = override or run_sweep
run()

View file

@ -7,6 +7,13 @@ func nilCoalescing(override: (() -> Void)?) {
run()
}
func runLeft() {}
func callableLeft(fallback: @escaping () -> Void) {
let run = runLeft ?? fallback
run()
}
func ternary(fast: Bool) {
let run = fast ? runThen : runElse
run()

View file

@ -96,8 +96,8 @@
"digest": "9c7f1c54a9b0ba3bfa4410e1843bf5edb570777789ee659a00d7dd6771f3670f"
},
"python-callable-alternatives/app.py": {
"captureGroups": 35,
"digest": "e35b269b282c3955c4a196c6ecc2292c82bbba1c9579590cc970fe2ddee6f3c5"
"captureGroups": 85,
"digest": "d5fc223832a33ff701f09b00174bdf1e127063aae9bdead0e5d242e8bd8e12ef"
},
"python-calls/one.py": {
"captureGroups": 4,

View file

@ -24,8 +24,8 @@
"digest": "f192ae885ea3de848f3b93acf7672d91b0118800cb0c6513628a6c09c26fadbf"
},
"swift-callable-alternatives/App.swift": {
"captureGroups": 25,
"digest": "0d76e7d11637a41550666de93a390861d15a0a26edf9c49af19fbd625a3ba1f3"
"captureGroups": 36,
"digest": "3720e42c769ba4f6fa2becfe55f10109e170e8ac274ce24e69b04b7b23a25a8a"
},
"swift-child-extends-parent/Sources/App.swift": {
"captureGroups": 11,

View file

@ -40,6 +40,12 @@ describe('Python callable chosen by `or` / `and` / `x if c else y`', () => {
expect(callsOf(result)).toContain('logical_or → run_sweep');
});
// The `override or fn` case above resolves only its RIGHT operand, so it
// would still pass if the fan-out kept just the last branch.
it('`fn or fallback` reaches fn through the LEFT operand', () => {
expect(callsOf(result)).toContain('callable_left → run_left');
});
it('`f if c else g` reaches both branches', () => {
expect(callsOf(result)).toEqual(
expect.arrayContaining(['ternary → run_then', 'ternary → run_else']),
@ -75,6 +81,10 @@ describe('Kotlin callable chosen by `?:` / `if` expression', () => {
expect(callsOf(result)).toContain('elvis → runSweep');
});
it('`::fn ?: fallback` reaches fn through the LEFT operand', () => {
expect(callsOf(result)).toContain('callableLeft → runLeft');
});
it('`if (c) ::f else ::g` reaches both branches', () => {
expect(callsOf(result)).toEqual(
expect.arrayContaining(['ifExpression → runThen', 'ifExpression → runElse']),
@ -104,6 +114,10 @@ describe('Swift callable chosen by `??` / `?:`', () => {
expect(callsOf(result)).toContain('nilCoalescing → runSweep');
});
it('`fn ?? fallback` reaches fn through the LEFT operand', () => {
expect(callsOf(result)).toContain('callableLeft → runLeft');
});
it('`c ? f : g` reaches both branches', () => {
expect(callsOf(result)).toEqual(
expect.arrayContaining(['ternary → runThen', 'ternary → runElse']),
@ -121,6 +135,10 @@ describe('Dart callable chosen by `??` / `?:`', () => {
expect(callsOf(result)).toContain('ifNull → runSweep');
});
it('`fn ?? fallback` reaches fn through the LEFT operand', () => {
expect(callsOf(result)).toContain('callableLeft → runLeft');
});
it('`c ? f : g` reaches both branches', () => {
expect(callsOf(result)).toEqual(
expect.arrayContaining(['conditional → runThen', 'conditional → runElse']),