Skip to content

bridge-cpp: async call form - {name}Async siblings, baml::Future, cancellation, co_await - #4078

Merged
codeshaunted merged 5 commits into
canaryfrom
avery/cpp-async
Jul 17, 2026
Merged

codeshaunted merged 5 commits into
canaryfrom
avery/cpp-async

Conversation

@codeshaunted

@codeshaunted codeshaunted commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Bridge-week step 10 for C++ (async half; error/panic behavior landed in #4071).

What

Every generated function gains an Async sibling returning baml::Future<T, ThrownU>:

auto fut = b::ExtractResumeAsync(text);   // returns immediately
fut.Cancel();                             // optional engine-side cancellation
Resume r = fut.get();                     // blocks + decodes; typed throws as BamlThrown<Union<...>>
  • get()/wait()/wait_for()/wait_until()/valid() mirror std::future (STYLE.md carve-out 2); Cancel() and the sibling's Async suffix are our vocabulary (Pascal). The suffix follows the opts-struct convention: verbatim BAML spelling + suffix (probe -> probeAsync), allocated through the naming pool.
  • Cancellation: Cancel() calls the v1 ABI's cancel_function_call; the envelope still arrives as a baml.panics.Cancelled panic and get() throws BamlCancelled. Destruction detaches (never blocks, never cancels) - Python task-model parity. No Rust changes.
  • co_await (C++20 only): the registry's per-call cell is now a custom CallState (mutex + condvar + continuation slot) instead of std::promise, so the dispatcher thread can resume a suspended coroutine when the envelope lands. The awaiter is feature-gated (__cpp_impl_coroutine + __cpp_lib_coroutine); the header stays C++17-clean.
  • One call path: CallSync is now literally StartCall().get().

Tests

  • test_cancellation.cc: port of python's test_cancellation.py (portable core: null-return baseline + engine-side cancel; asyncio/BamlCallContext idioms documented as deviations) + C++-specific future semantics (consume-once, wait_for timeout, detach-on-destroy).
  • Async cases added to test_optional_args.cc (python parity), test_raises.cc (async sibling repeats the doc block), test_errors.cc (typed throw through get()).
  • futures_static.cc: move-only + ThrownU order-canonicality static asserts.
  • tests/cxx20/test_coawait.cc: 6 live co_await cases (pending resume, value, fast path, typed throw into the coroutine, cancellation, escape-to-join) driven by a minimal completion-latch Task. The harness builds tests/cxx20/*.cc as a second executable only when the toolchain has C++20, so the main binary keeps proving the SDK compiles as plain C++17.

All 12 sdk_test_cpp fixture tests pass locally (26 C++17 + 6 C++20 cases in function_calls).

Note: the size gate is expected to fail until the stale baselines are refreshed - canary has drifted ~600 KB since #4057 and every open PR is at the 3% ceiling (see #4071's report).

Summary by CodeRabbit

  • New Features
    • Added dual async C++ SDK bindings (*Async) alongside sync calls.
    • Introduced baml::Future for in-flight calls, including cancellation and optional C++20 co_await support.
    • Added typed singleton literal support via BAML_LIT(...) for more precise literal and union typing.
  • Bug Fixes
    • Improved literal encoding/decoding and union arm selection to preserve exact typed literal values.
    • Enhanced async sibling error propagation and cancellation behavior.
  • Tests
    • Expanded C++ coverage for futures (including C++20), cancellation, typed throws, optional args, and literal/type-shape checks.

…cellation, co_await

Every generated function gains an Async sibling returning
baml::Future<T, ThrownU>: get()/wait()/wait_for()/wait_until() mirror
std::future, Cancel() requests engine-side cancellation through the v1
ABI's cancel_function_call (the envelope then arrives as a Cancelled
panic), and destruction detaches. The registry's per-call cell is now a
custom CallState (mutex + condvar + continuation slot) instead of
std::promise, so under C++20 a Future is co_await-able: the dispatcher
thread resumes the suspended coroutine when the envelope lands. The
awaiter is feature-gated; the header stays C++17-clean, and CallSync is
now literally StartCall().get(), one code path for both call forms.

The fixture harness builds tests/cxx20/*.cc as a second executable only
when the toolchain has C++20, keeping the main test binary as proof the
generated SDK compiles as plain C++17.
@vercel

vercel Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 17, 2026 11:15pm
promptfiddle Ready Ready Preview, Comment Jul 17, 2026 11:15pm
promptfiddle2 Ready Ready Preview, Comment Jul 17, 2026 11:15pm

Request Review

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 21:22 Inactive
@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c3cc80e6-e9bc-4497-92ce-cf245384baa1

📥 Commits

Reviewing files that changed from the base of the PR and between 266b544 and d393ddf.

📒 Files selected for processing (1)
  • baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h

📝 Walkthrough

Walkthrough

The C++ bridge adds move-only asynchronous futures with waiting, cancellation, and coroutine support. SDK generation emits Async siblings, while generated literals remain typed singleton values validated through codecs and cross-language round-trip tests.

Changes

C++ async future flow

Layer / File(s) Summary
Completion state and Future API
baml_language/sdks/cpp/bridge_cpp/include/baml/detail/registry.h, baml_language/sdks/cpp/bridge_cpp/include/baml/future.h, baml_language/sdks/cpp/bridge_cpp/include/baml/baml.h
Shared completion state supports waiting and continuations; baml::Future provides consumption, cancellation, timing, and coroutine operations.
Async call generation and shared invocation
baml_language/sdks/cpp/bridge_cpp/include/baml/detail/call.h, baml_language/sdks/cpp/sdkgen_cpp/src/lib.rs
Generated bindings emit synchronous and Async variants, with async calls using StartCall and sync calls sharing that path.
Async behavior and C++20 validation
baml_language/sdk_tests/crates/cpp/function_calls/customizable/tests/*, baml_language/sdk_tests/harness_setup/src/templates/cpp_test.sh
Tests cover Future traits, cancellation, lifecycle, typed errors, optional arguments, raises metadata, and C++20 coroutine behavior.

C++ typed literal flow

Layer / File(s) Summary
Literal types and code generation
baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h, baml_language/sdks/cpp/sdkgen_cpp/src/lib.rs
baml::Lit and BAML_LIT preserve normalized string, numeric, boolean, and enum singleton types in generated C++ types.
Literal codec and union decoding
baml_language/sdks/cpp/bridge_cpp/include/baml/codec.h
Literal values encode to wire arms, decode only on exact matches, and take priority during union selection.
Literal round-trip coverage
baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_literals/types.baml, baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/*, baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_literals.py
Fixtures and tests validate typed literals, mixed-base unions, conversions, enum variants, and canonical union ordering.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedBinding
  participant StartCall
  participant CallRegistry
  participant BamlEngine
  participant Future
  GeneratedBinding->>StartCall: encode arguments and start async call
  StartCall->>CallRegistry: create CallState
  StartCall->>BamlEngine: call_function with correlation id
  BamlEngine->>CallRegistry: complete result envelope
  CallRegistry->>Future: fulfill CallState
  Future->>GeneratedBinding: get or resume coroutine with decoded result
Loading

Possibly related PRs

Poem

A bunny found a Future bright,
It hopped through calls both day and night.
It waited, woke, and learned to flee,
Caught typed errors carefully.
Literals bloomed in singleton air.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: async {name}Async bindings with baml::Future, cancellation, and co_await support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch avery/cpp-async

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 21:28 Inactive
@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Binary size checks failed

❌ 4 violations · ✅ 3 passed

⚠️ Please fix the size gate issues or acknowledge them by updating baselines.

Artifact Platform File Gzip Gated on Baseline Delta Status
❌ baml-cli Linux 🔒 25.3 MB 10.7 MB file 24.5 MB +760.3 KB (+3.1%) FAIL
✅ packed-program Linux 🔒 17.0 MB 7.0 MB file 17.0 MB -4.1 KB (-0.0%) OK
❌ baml-cli macOS 🔒 19.5 MB 9.3 MB file 18.9 MB +644.9 KB (+3.4%) FAIL
✅ packed-program macOS 🔒 13.2 MB 6.2 MB file 13.2 MB +0 B (+0.0%) OK
❌ baml-cli Windows 🔒 21.1 MB 9.5 MB file 20.4 MB +644.1 KB (+3.2%) FAIL
✅ packed-program Windows 🔒 14.1 MB 6.2 MB file 14.1 MB -512 B (-0.0%) OK
❌ bridge_wasm WASM 16.2 MB 🔒 4.4 MB gzip 4.3 MB +130.5 KB (+3.1%) FAIL

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.

Details & how to fix

Violations:

  • baml-cli (Linux) file_bytes: 25.3 MB exceeds limit of 25.2 MB (exceeded by +25.0 KB, policy: max_file_bytes)
  • baml-cli (Linux) file_delta_pct: +3.1% exceeds limit of 3.0% (exceeded by +0.1pp, policy: max_delta_pct)
  • baml-cli (macOS) file_bytes: 19.5 MB exceeds limit of 19.5 MB (exceeded by +77.9 KB, policy: max_file_bytes)
  • baml-cli (macOS) file_delta_pct: +3.4% exceeds limit of 3.0% (exceeded by +0.4pp, policy: max_delta_pct)
  • baml-cli (Windows) file_bytes: 21.1 MB exceeds limit of 21.0 MB (exceeded by +31.3 KB, policy: max_file_bytes)
  • baml-cli (Windows) file_delta_pct: +3.2% exceeds limit of 3.0% (exceeded by +0.2pp, policy: max_delta_pct)
  • bridge_wasm (WASM) gzip_bytes: 4.4 MB exceeds limit of 4.4 MB (exceeded by +2.3 KB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +3.1% exceeds limit of 3.0% (exceeded by +0.1pp, policy: max_delta_pct)

Add/update baselines:

.ci/size-gate/aarch64-apple-darwin.toml:

[artifacts.baml-cli]
file_bytes = 19545072
stripped_bytes = 19545120
gzip_bytes = 9330140

.ci/size-gate/wasm32-unknown-unknown.toml:

[artifacts.bridge_wasm]
file_bytes = 16160508
gzip_bytes = 4402885

.ci/size-gate/x86_64-pc-windows-msvc.toml:

[artifacts.baml-cli]
file_bytes = 21070848
stripped_bytes = 21070848
gzip_bytes = 9539636

.ci/size-gate/x86_64-unknown-linux-gnu.toml:

[artifacts.baml-cli]
file_bytes = 25267920
stripped_bytes = 25267912
gzip_bytes = 10729323

Generated by cargo size-gate · workflow run

…T macro (#4079)

Stacked on #4078 (retargets to canary when it merges).

BAML literal types stop widening to their base scalars: **each literal
value is a distinct C++ type**, so literal unions dispatch and
exhaustively match at compile time, and misspellings do not compile.
Pure C++17.

## Surface

One macro classifies its argument via overloaded constexpr helpers
(overload resolution is the dispatch):

```cpp
BAML_LIT("draft")              // string -> Lit<'d','r','a','f','t'>  (Boost.Metaparse char-pack trick, 64-char cap)
BAML_LIT(42)                   // int    -> Lit<int64_t{42}>  (normalized: a bare int cannot mint a twin type)
BAML_LIT(true)                 // bool   -> Lit<true>
BAML_LIT(Sentiment::Positive)  // enum-variant type (Ty::EnumVariant), no longer widened to the enum
```

```cpp
// status "draft" | "sent" | "paid"
baml::match(invoice.status,
  [](BAML_LIT("draft")) { ... },
  [](BAML_LIT("sent"))  { ... },
  [](BAML_LIT("paid"))  { ... });   // exhaustive; add a value in .baml -> build breaks
```

Every `Lit` carries its value statically (`::value`, plus implicit
conversion to `string_view`/`int64_t`/`bool`/enum). Any non-blessed
shape (`Lit<1>` int-typed, floats, mixed packs, pointers...) lands on a
teaching `static_assert`. Generated code never uses the macro - the
emitter spells char packs directly.

## Mechanics

- `lit.h`: `template <auto... Vs> struct Lit` with LitShape tag-dispatch
(no partial-spec ambiguity), `TrimNulls` canonicalization so every
spelling of a string lands on one instantiation, `IntLit`/`BoolLit`
macro-free alternates.
- Codec: encode = the plain scalar/enum arm; decode = base-codec arm
handling + exact-value check (mismatch rejects the union arm). Union
decode gains a **literal pass** ahead of strict/lenient, so
`Lit<"auto">` beats a `std::string` sibling.
- Emitter: `Ty::Literal` + `Ty::EnumVariant` emit `::baml::Lit<...>`
(proper char escaping incl. `\xNN` and the `i64::MIN` spelling trick);
float literals stay widened (float NTTPs are C++20; BAML has none in
practice).
- Lit unions ride the existing order-canonical + dedup `baml::Union`
machinery unchanged.

## Tests

- `test_literals.cc` rewritten to typed semantics
(returns/round-trips/class-of-literals + implicit-conversion ergonomics
+ type-level static_asserts).
- New shared-fixture coverage, python-first per ground rules: mixed-base
literal union `type Flag = "active" | 1 | true` round trip
(`test_round_trip_flag_mixed_literal_union` in python, exact-alternative
dispatch + match in C++).
- `test_complex_models.cc`/`test_enums.cc` updated (Invoice.status
literal union, EnumVariant-as-type now a singleton Lit).
- `unions_static.cc`: BAML_LIT canonical-identity + int-normalization +
Lit-union order-canonicality pins.

cpp fixtures 12/12 locally; python + the rest on CI.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for strongly typed literal values in generated C++ SDKs.
* Added literal handling for strings, integers, booleans, and enum
variants.
* Added support for mixed literal unions such as `"active"`, `1`, and
`true`.
  * Added compile-time validation and exact literal round-trip behavior.

* **Bug Fixes**
* Improved union value decoding to prioritize exact literal matches
before applying broader type conversions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 22:26 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
baml_language/sdks/cpp/bridge_cpp/include/baml/codec.h (1)

314-388: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add runtime coverage for literal/base sibling precedence.

Exercise unions such as "auto" | string and 1 | int, verifying exact values select Lit while other values fall back to the base alternative. The current mixed-base literal-only test does not protect this new three-pass behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/sdks/cpp/bridge_cpp/include/baml/codec.h` around lines 314 -
388, Add runtime tests for union decoding that cover literal/base sibling
precedence, including `"auto" | string` and `1 | int`. Verify exact literal
values decode to the corresponding Lit alternative, while non-matching values
decode to the base string or integer alternative, and keep coverage focused on
the three-pass behavior around `Codec<std::variant<Ts...>>::Decode`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h`:
- Around line 137-153: The public BAML_LIT path currently caps literals at 64
bytes while generated Lit char packs accept arbitrary lengths. Update the
literal construction and matching API around TrimNulls, BAML_LIT, and related
aliases so literals longer than 64 bytes remain nameable and pattern-matchable,
either by removing the public-only cap or by exposing generated
aliases/non-fixed-arity construction; preserve existing behavior for shorter
literals.

---

Nitpick comments:
In `@baml_language/sdks/cpp/bridge_cpp/include/baml/codec.h`:
- Around line 314-388: Add runtime tests for union decoding that cover
literal/base sibling precedence, including `"auto" | string` and `1 | int`.
Verify exact literal values decode to the corresponding Lit alternative, while
non-matching values decode to the base string or integer alternative, and keep
coverage focused on the three-pass behavior around
`Codec<std::variant<Ts...>>::Decode`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c5126bca-2f57-432b-a146-0379d93f7ff1

📥 Commits

Reviewing files that changed from the base of the PR and between 839e6ab and 2172572.

📒 Files selected for processing (11)
  • baml_language/sdk_tests/crates/cpp/function_calls/customizable/tests/test_main.cc
  • baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_complex_models.cc
  • baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_enums.cc
  • baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_literals.cc
  • baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/unions_static.cc
  • baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_literals.py
  • baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_literals/types.baml
  • baml_language/sdks/cpp/bridge_cpp/include/baml/baml.h
  • baml_language/sdks/cpp/bridge_cpp/include/baml/codec.h
  • baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h
  • baml_language/sdks/cpp/sdkgen_cpp/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/sdks/cpp/bridge_cpp/include/baml/baml.h

Comment thread baml_language/sdks/cpp/bridge_cpp/include/baml/lit.h
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 17, 2026 22:33 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 17, 2026 22:47 Inactive
Composed CH8/CH64 expansion blocks instead of 64 flat entries; the cap
applies to the user-facing macro only (generated char packs have no
limit) and realistically should never be an issue - literal types are
short tag strings. The over-cap static_assert now names the decltype
escape hatch.
@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 22:49 Inactive
@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 22:55 Inactive
@codeshaunted
codeshaunted enabled auto-merge July 17, 2026 23:02
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 17, 2026 23:02 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 17, 2026 23:15 Inactive
@codeshaunted
codeshaunted added this pull request to the merge queue Jul 17, 2026
Merged via the queue into canary with commit f3ee19d Jul 17, 2026
104 of 110 checks passed
@codeshaunted
codeshaunted deleted the avery/cpp-async branch July 17, 2026 23:47

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — d393ddfe Deployed Jul 17, 2026 by vercel[bot]
Preview – promptfiddle2 — d393ddfe Deployed Jul 17, 2026 by vercel[bot]
Preview – beps — d393ddfe Deployed Jul 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant