Skip to content

feat(bridges): add swift bridge - #4122

Merged
ATX24 merged 32 commits into
canaryfrom
dhilan/swift-bridge-1
Jul 22, 2026
Merged

feat(bridges): add swift bridge#4122
ATX24 merged 32 commits into
canaryfrom
dhilan/swift-bridge-1

Conversation

@ATX24

@ATX24 ATX24 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

A complete Swift bridge for BAML: a Swift program calls BAML functions as ordinary typed Swift functions — sync and async — with full support for the type system (classes, enums, unions, aliases, recursion, generics), both error directions, host callbacks (Swift closures invoked by BAML), streaming, media, cancellation, and opaque resource handles. Functional parity with the Python reference bridge: all 5 sdk_tests fixtures enforced, gate 7/7 with zero skipped (203 test functions across 33 files, including a streaming e2e over the replay server).

Three artifacts:

  • sdks/swift/Sources/BamlBridge/ — hand-written runtime package (~2,300 lines): encode/decode, call engine, BamlUnionN family, streams, handles, host callables. Consumes the canonical V1 C ABI (baml_get_api_v1 table, registered as BridgeLanguage::Swift = 8), statically linked.
  • sdks/swift/rust/sdkgen_swift/ — the generator (~1,600 lines): supported-types fixpoint, cycle boxing, namespace routing, real typed func bodies (the type surface IS the binding — no stub drift). Unsupported constructs produce absent API, never broken/Any-typed API, and every skip is recorded with the failing type in a generated _BamlSkipped.swift manifest (#warning when the skip is in the user's own package).
  • sdk_tests/crates/swift/ + harness wiring — fixture-driven parity tests ported from Python test-for-test.

Design highlights:

  • Unions as BamlUnion2..8 — the cross-team no-generated-names design: positional cases in wire order, type-directed convenience layer, labeled match, and metadata-first decode using the wire's value_option_name (exact arm selection where Python guesses structurally).
  • Zero-wire generics — swiftc solves TypeVars statically, the engine re-infers from values; no _types= machinery. Return-only TypeVars are skipped pending a wire type-hint hook.
  • Identity across the boundary — a Swift error thrown in a host callback rehydrates as the original object on the error path; host-value lifetimes are engine-driven.

Packaging (dev channel, already live)

BoundaryML/baml-swift publishes from this branch automatically: immutable checksum-pinned XCFramework release assets (5 Apple slices, release-bridge-swift profile, LLVM bitcode stripped — 104MB download, ~26MB linked into an app), stamped root Package.swift, and a smoke gate that resolves, links, and executes the published package. Graduates into the canonical Phase 6 pipeline by swapping the trigger to canary release tags. Full canonical release CI (builder/verify/publisher per the contract) is follow-up work.

Notes for reviewers

  • Reviewed-in-order commit stack: Phase 0 scaffolding → 1 primitives → 2 types → 3 unions → 4a/4b methods/errors/handles → 5 generics/streaming/cancellation → V1 ABI adoption → skip diagnostics → packaging.
  • This branch cedes discriminant 7 to the Java bridge (canary) — Swift registers as 8; canonical header regenerated, layout asserts updated.
  • CI: the proto-sync job now covers sdks/swift generated files (input-hash manifest for the protoc-gen-swift outputs, canonical-header copy check).
  • Known deferrals (see the completeness matrix below): BamlBigInt, return-only TypeVar hook, TypeMismatch→native mapping, Codable, physical-iOS-device smoke (simulator + macOS verified).

Test plan

  • cargo nextest run -p sdk_test_swift — 7/7, 0 skipped (macOS; xcframework setup script bound in nextest.toml)
  • cargo nextest run -p bridge_cffi — 32/32 (header determinism + ABI layout incl. new discriminants)
  • Published-package smoke: clean SwiftPM project resolves the release asset by checksum, links, runs (also enforced per-publish in the dev channel CI)

Completeness matrix

Full feature table — status + Swift representation (✅ gate-enforced · 🚧 partial · ❌ not yet)
Area Feature Status Swift representation / notes
Calls Sync call func f(...) throws -> R; caller parks on a semaphore (deadlock-safe: completions always arrive on engine threads)
Calls Async call _async sibling of every function; continuation in the pending table
Calls Static / instance methods Real methods; receiver passed as ("self", self) kwarg 0
Calls Optional args + engine defaults BamlOptional<T> = .unset.unset omits (engine default), .some(nil) explicit null
Calls Positional args n/a Swift labels everything
Calls Cancellation 🚧 Async only: withTaskCancellationHandler → reserve-based cancel_function_call; engine Cancelled → native CancellationError. No sync cancellation; BamlCallContext unported by design
Calls Companions ($build_request / $parse / $parse_stream / $stream) Ordinary generated functions beside the parent; $_ in Swift spellings
Types Primitives Swift.String/Int/Double/Bool, Foundation.Data — always fully qualified
Types Classes Equatable, Sendable structs carrying their own _bamlEncode/_bamlDecode; fields present-as-null on the wire
Types Enums Native Swift enums
Types Type aliases typealias; recursive union aliases → nominal indirect enum under the user's name; nullable ones keep ? at reference sites
Types Unions BamlUnion2..8<T0,…> generic indirect enums — no generated names. Positional cases in wire order + type-directed layer (value(as:), holds, init overloads) + labeled match + native switch. Metadata-first decode via the wire's value_option_name (exact where Python guesses structurally)
Types Literal types ("a" | "b", 42) ✅* Erase to base type; engine validates. *Compile-time literal safety traded away (Swift has no string value generics); matches Python
Types Recursive classes One field per cycle boxed with @BamlIndirect (CoW); unions break cycles free via indirect enum; cyclic graphs unconstructible (value semantics)
Types Maps / lists [K: V] / [T], element-wise codec recursion
Types Media BamlMedia.fromBase64/URL/file over canonical BamlCffiMediaKind; tagged engine handle
Types Resource handles (File, sockets, spawn, …) BamlHandle: u64 key; deinit releases, encode clones the key (no double-release)
Types Bigint 🚧 Decodes only when it fits Int; no BamlBigInt yet
Generics Generic classes struct Box<T: BamlCodableValue>; non-BAML type args are compile errors
Generics Generic functions (value-position TypeVars) Zero-wire: swiftc solves statically, engine re-infers from values — no type_args, no _types= (35 inference cases enforced)
Generics Return-only TypeVars (parse_as<T>) Unemitted (listed in _BamlSkipped.swift); needs a wire type-hint hook
Errors BAML throws → Swift BamlError { message, className, bamlTrace, payload }; typed access decode-on-demand via value(as:); thrown types as - Throws: docs
Errors Panics / exit BamlPanic; Cancelled → native CancellationError (async); exit-panics exit()
Errors Swift error thrown in a host callback Identity-preserving: crosses as opaque handle, error path rehydrates and re-throws the ORIGINAL object
Errors Typed error values from callbacks throw BamlThrownValue(model) — payload any BamlEncodable & Sendable
Errors TypeMismatch → native error Generic BamlError for now (Python maps to TypeError)
Errors ValidationError / FinishReasonError Planned; absent in every v1 bridge
Callbacks Host callables Closure → registry key → handle; detached-Task dispatch, complete_host_call exactly once on every path; async closures first-class; engine-driven lifetime
Streaming LLM streaming BamlStream<Partial, Final>; next().value / .finished enum (mid-stream partials can be null); partials in stream_types with all-optional fields; replay-server e2e enforced
Codegen Unsupported constructs Absent API via the fixpoint — never Any-typed; every skip + failing type recorded in _BamlSkipped.swift (#warning for user-package skips)
Codegen Mis-encode bug class Statically impossible (typed BamlEncodable params)
Codegen Determinism Sorted pool iteration + raw-name sort keys; byte-identical output
ABI Canonical V1 table baml_get_api_v1() verified/unwrapped once; BridgeLanguage::Swift = 8; exact-version register_bridge at init
Serde User-facing Codable Deferred pending the cross-SDK JSON-dialect decision
Observability Collector / TypeBuilder / logging / OnTick Absent in every v1 bridge
Packaging Installable SwiftPM package ✅ (dev) BoundaryML/baml-swift: immutable checksum-pinned XCFramework releases (bitcode-stripped, 104 MB download / ~26 MB linked), auto-published, smoke-gated
Platforms macOS (arm64 + x86_64) Full gate + published-package smoke
Platforms iOS device + simulator 🚧 All slices build + sim smoke passed; physical-device verification pending → experimental

Summary by CodeRabbit

  • New Features
    • Added Swift SDK generation support for Swift output, including generated runtime APIs for generics, unions, streams, media, and host callables.
    • Introduced Swift bridge/runtime capabilities for encoding/decoding, sync/async calls, optionals, errors/panics, and native framework packaging.
  • Tests
    • Added extensive Swift XCTest coverage: reachability, round-trips, streaming SSE replay, cancellation, error surfacing, and ABI/FFI smoke checks.
  • Documentation
    • Added/updated Swift bridge status and union design docs, plus Swift client regeneration instructions.
  • Chores
    • Enhanced CI proto/generated-file drift checks and macOS Swift test setup; added Swift crypto/TLS feature controls for builds.

ATX24 and others added 20 commits July 15, 2026 09:52
Stand up every layer of the Swift SDK skeleton end to end, with no real
capability yet — the working pipe Phase 1 pumps primitives through:

- bridge_swift: staticlib re-exporting bridge_cffi's C ABI; all 12
  symbols verified in the archive. New release-bridge-swift profile
  (panic=unwind) for eventual release builds.
- sdks/swift SwiftPM package (BamlBridge): cbindgen-generated C header
  + modulemap (with HostDispatchFn/HostReleaseFn typedefs injected —
  they live in dep crates cbindgen doesn't parse), checked-in
  swift-protobuf clients for the 4 wire protos, XCFramework build
  script (--host-only dev / --all release), and an FFI smoke test
  proving SwiftPM -> XCFramework -> Rust linking via version().
- sdkgen_swift: generator stub registered as output_type = "swift"
  (OutputType::Swift + baml_cli dispatch arm); emits the base64
  bytecode payload (_InlinedBaml.swift) and a stub Baml namespace.
- sdk_test_swift: full harness wiring per DEVELOPMENT.md — build.rs
  codegen soft-failing into build_diagnostics, per-fixture SwiftPM
  packages (path dep on sdks/swift; note SwiftPM identifies path deps
  by directory name), swift build/test scaffold with a one-line
  ENFORCED_FIXTURES dial (empty for now), macOS-only nextest
  setup-script binding that assembles the host XCFramework.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pump real values through the Phase 0 pipe: free functions over the
primitive subset, sync + async, with the type_shapes and function_calls
fixtures enforced under `cargo nextest run -p sdk_test_swift` (4/4
green; failure propagation verified with a deliberate assert-break).

- BamlBridge runtime: idempotent bytecode init + completion-callback
  plumbing (locked callback-id -> waiter table; bytes copied off the
  FFI thread). Async via CheckedContinuation with Task cancellation
  forwarding to cancel_function_call; sync via semaphore park — safe
  because completions always arrive on an engine Tokio thread
  (call_function fully decodes its buffer before returning). Two IDs
  per call by design: u64 proto call_id (engine handle, cancellation)
  vs u32 callback correlation key (CallbackFn only carries u32).
- Encode.swift: BamlEncodable conformances (Int/Double/Bool/String/
  Data/BamlNull/Optional/Array/Dictionary<String,_>); empty containers
  still set their oneof (SetInParent analog — Python's "Bug A");
  BamlNull is the spelling for BAML's standalone null type.
- Decode.swift: BamlOutboundResult envelope (ok/error/panic; exit
  panic -> flush_events + exit), union/literal unwrapping, wire-driven
  BamlDecodable conformances with named typeMismatch errors.
- sdkgen_swift: translate_ty for the primitive subset (null-union ->
  T? collapse, literal -> base type, string-keyed maps), namespace
  routing into caseless-enum trees, real fn/fn_async body emission;
  functions with unsupported signature types are skipped so generated
  packages always compile. Bytecode payload emitted as ONE multiline
  string literal — a `"…" + "…"` chunk chain sent swiftc's expression
  type-checker super-linear (55+ min for multi-MB payloads; now ~35 s).
- Tests ported 1:1 from python_pydantic2 (TestPrimitives/Void/Lists/
  Maps/Main; per-file notes name the Python cases waiting on later
  phases). ENFORCED_FIXTURES dial flipped to type_shapes +
  function_calls. Scaffold now emits ONE test per fixture: a sibling
  `swift build` test contended for the SwiftPM .build lock and a
  killed run wedged every later run behind an orphaned lock holder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…args)

Widen the bridge from primitives to the named-type core, with three
fixtures enforced (type_shapes, function_calls, docstrings_etc) and the
gate green first try (5/5, ~36s).

- BamlBridge: BamlOptional<T> (.unset omits the kwarg so the engine
  evaluates the BAML default; nil = explicit null — Python's UNSET
  design), BamlIndirect CoW property wrapper for recursive struct
  fields, baml_class/baml_enum inbound builders, classFields()/
  enumVariant() outbound helpers (missing field decodes as null).
- sdkgen_swift: classes -> Equatable/Sendable structs with memberwise
  inits + generated BamlEncodable/BamlDecodable conformances (FQN baked
  in; decode is wire-tolerant like Python); enums -> String-raw enums;
  non-recursive aliases -> typealias; a supported-types fixpoint drops
  classes with unsupported fields and everything referencing them (the
  generated package always compiles); a reachability pass over direct
  non-List/Map class references marks cycle-forming fields
  @BamlIndirect (self-recursion, mutual A/B, 3-class SCCs); optional
  args emit as BamlOptional<T> = .unset with conditional appends.
  Namespace trees now hold type decls too, with ancestor enums
  synthesized for deep paths (Baml.a.b.Thing).
- Tests ported 1:1: enums, optional, class_refs, recursion (both
  SCCs), aliases (string list), forward_refs, the optional-args
  UNSET/nil matrix (negative cases are compile errors in Swift, noted
  per file), previously-deferred class cases in primitives/maps,
  type_shapes namespace reachability, and docstrings_etc as
  generated-source assertions (Swift has no runtime __doc__; /// in
  source is the analog).

Deliberately deferred: Codable conformance on generated structs
(nothing in the parity suite needs it; revisit with serde delegation),
unions (Phase 3), generics + $stream companions (Phase 5), methods
(Phase 4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BAML unions are structural; Swift's sum types are nominal. Instead of
generating a named enum per union shape (an earlier design — every name
an API promise, placement dependent on first-use, decode by structural
guessing), ship ONE reusable generic family in the runtime and never
synthesize a union type name. Cross-bridge aligned (C# BamlUnion<T0,T1>,
Java BamlUnion2<A,B>); design doc at sdks/swift/docs/unions-design.md.

- BamlBridge/BamlUnions.swift: BamlUnion2..8 as `indirect` generic
  enums — positional cases (t0/t1/... in canonical BAML arm order),
  type-directed init overloads (arm by argument type — insertion-
  stable construction, the C# implicit-conversion analog), anyValue,
  per-arm accessors, value(as:)/holds (std::variant analogs), labeled
  match (sync + async; exhaustive by signature), conditional
  Equatable/Hashable/Sendable. `int | string` is BamlUnion2<Int,String>
  everywhere: structural identity, zero generated types.
- Wire: encode passes the selected arm bare (unchanged). Decode is
  METADATA-FIRST: the union_variant_value wrapper's value_option_name
  names the selected arm and is matched against a new _bamlArmIdentity
  protocol REQUIREMENT (witness-dispatched; extension-only members
  would statically resolve to the nil default) — primitives declare
  canonical names, generated classes/enums their FQN; class-FQN match
  second; structural try-order last. Fixes the old design's flaw of
  discarding the wire's answer and guessing.
- Emitter: Ty::Union → normalize (null→Optional, dedup, singleton
  collapse) → literal arms to their base types (literal-only unions
  collapse entirely — "draft"|"sent"|"paid" is String; no raw-value
  enums) → BamlUnionN<...> inline. No registry, naming, or placement
  machinery exists. Non-recursive union aliases are typealiases;
  recursive ones (RecList = int | RecList[]) emit a nominal indirect
  enum under the USER'S name with the identical family surface
  (typealias can't self-reference — every nominal-language bridge
  needs this escape hatch). Cycle-boxer skips >=2-arm unions
  (indirect enums are already heap-boxed).
- Tests: TestUnions exercises all three consumption tiers (exhaustive
  switch, match, type-directed access) plus the normalization matrix;
  TestComplexModels asserts class-arm identity survives the round trip
  (payment .t0/.t1 selected by wire identity, not guessing); union
  cases unlocked in Lists/Optional/Aliases/ForwardRefs. Gate 5/5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate 5/5 green. Three capability rows land, plus the stdlib package
opens to codegen.

- Methods: render_callable grows FnKind (Free/Static/Instance); class
  methods emit on their structs with class-scoped FQNs and the
  ("self", receiver) kwarg-0 convention. Greeter + OptBox suites.
- Error contract: BamlError/BamlPanic carry the raw thrown payload;
  `err.value(as: MyError.self)` is the typed analog of Python's
  `exc.value` + isinstance. Union-throws preserve className; engine
  traces assert; `- Throws:` doc lines are the Raises: analog.
- Stdlib emission: pkg filters open to `baml` (-> Baml.baml.*) and
  vendor pkgs (-> Baml.vendor.*), pruned by the existing fixpoint.
  Two real bugs flushed out: `baml.swift` clobbered `Baml.swift` on
  case-insensitive APFS (root file now BamlRoot.swift); stdlib classes
  named String/Int shadowed Swift's own inside generated scopes (all
  standard types now emit fully qualified: Swift.String,
  Foundation.Data, ... — including the family unions' _bamlArmIdentity
  and holds() spellings). Replay note: under BamlUnionN family unions,
  literal-only unions collapse to their base type, so the old literal
  raw-value enums (and their "r"/"r+" duplicate-case hazard) don't
  exist; nullable recursive union aliases (stdlib json) gained support
  (non-null arms in the nominal enum, ? at reference sites).
- Host callables (BAML -> Swift closures): generated closure params are
  uniformly `async throws` (sync closures coerce); an erased
  BamlHostCallable registers in the single per-process registry
  (callables + opaque thrown errors, bridge_python's design) and rides
  as HOST_VALUE_CALLABLE. Dispatch copies the BamlToHostCall bytes and
  hops to a detached Task (fire-and-return; no sync re-entrancy), then
  complete_host_call exactly once: ok InboundValue / typed throw via
  BamlThrownValue (matched against the declared contract) / opaque
  baml.errors.HostCallable envelope whose _handle rehydrates the
  ORIGINAL Swift error on the way back out. Wire lesson: the engine
  requires the envelope's `traceback string?` field present-as-null.
- Tests: TestMethodsOnClasses, TestErrors, TestHostCallables (19
  cases), OptBox matrix unlocked in TestOptionalArgs.

Remaining for Phase 4b: BamlHandle + $rust_type fields, media handle
tags, stdlib entrypoints (fs/sys/http) + test_handles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate 5/5 green. Completes Phase 4: opaque engine resources round-trip
and the stdlib is callable end to end.

- BamlHandle: final class owning an engine handle-table key. deinit
  releases (EXCEPT host-value keyspaces — the per-bridge registry is
  disjoint from HANDLE_TABLE; Python's BamlPyHandle::Drop rule);
  encoding clones a fresh key for the wire (`_clone_key_for_wire`
  semantics) so instances stay independently droppable; equality is
  resource identity.
- Ty::RustType -> `BamlHandle?`, unlocking File/Response/Image and
  every other $rust_type-bearing stdlib class through the existing
  fixpoint — their read/seek/text/mime_type methods come free via the
  4a method emission.
- Media: constructors are VM-native ops that never enter the codegen
  pool, so BamlMedia.fromBase64 wraps the baml_media_* C ABI (the same
  reason Python routes construction through its PyO3 wrapper).
  Generated single-rust-field classes decode BOTH forms: class_value
  (normal instances) and the bare ADT_MEDIA_* tagged handle the engine
  emits for media values.
- Collision rule: BAML allows a function and a child namespace with
  the same name (Python separates module vs attribute lookup); Swift
  has one lookup space per scope, so `func id()` vs `enum id`
  (vendor boundary.id) is an invalid redeclaration. The namespace wins;
  colliding functions drop. Surfaced only now because RustType support
  made boundary.id()'s return type emittable.
- Tests: TestHandles (media payload round-trip, HTTP fetch against a
  minimal in-test socket server, file open/close, cursor state
  persisting across FFI calls) and TestStdlibEntrypoints (now_ms,
  fs.exists, compiler-intrinsics source assertions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ld notes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The final capability phase: every sdk_tests fixture is now enforced
(6/6, zero skipped), including the streaming e2e over the replay
server. No wire type_args anywhere — Swift's static generics plus the
engine's inbound inference cover every case Python needs runtime
machinery for.

- Generics: `class Wrapper<T>` -> `struct Wrapper<T: BamlCodableValue>`
  (Equatable+Sendable+codec constraint bundle); parameterized refs
  (`Wrapper<int>` -> `Wrapper<Swift.Int>`); bare TypeVar translation;
  boxing analysis covers parameterized self-references
  (GenericLinkedList<T>). Generic functions/methods emit a Swift
  generic signature when every TypeVar is inferable from a required
  argument VALUE (the engine re-infers server-side; nothing on the
  wire) — return-only TypeVars (parse_as) stay unemitted until a wire
  type-hint hook exists (Python requires _types= for those too), and
  the coverage check deliberately does not recurse into Callable
  params (host callables are opaque to inference; Python's apply<T,R>
  proves it). Python's subscript/_types=/reified-metadata surfaces
  collapse into compile-time facts.
- Streaming: a `$stream` companion is an ORDINARY pool function whose
  return type is baml.llm.Stream<Partial, Final> — translate maps it
  to the runtime BamlStream wrapper (never a generated struct);
  next/final reuse the standard call path with ("self", handle), and
  next() returns BamlStreamNext<Partial> (.value/.finished — the
  StreamFinished sentinel is distinct from a legitimately-nil
  partial). `$` maps to `_` wholesale (fn_stream, fn_build_request,
  fn_parse, fn_parse_stream); `$stream` CLASSES route under
  Baml.stream_types.<ns> (suffix stripped), FUNCTIONS beside their
  parent — Python's routing split, mirrored. Two real bugs found by
  the llm fixture: the decl-map sort key used bare_name(), so a base
  function silently overwrote its $stream companion; and
  namespace_for routed companion FUNCTIONS into stream_types.
- Replay harness: ports to ~60 lines because replay_serve_detached is
  itself a generated BAML function — call it, setenv the
  BAML_REPLAY_* vars, POST shutdown. No thread/addr-file dance.
- Media: Ty::Media -> the 4b-generated Baml.baml.media.* structs;
  ns_media roundtrips pass (engine-minted values through typed calls).
- Cancellation: engine baml.panics.Cancelled -> Swift
  CancellationError (async-only, Python parity); Task-cancel and
  timeout-race tests assert the 0.5s fast-cancel bound.
  BamlCallContext remains unported (structured concurrency owns
  cancellation in Swift; noted in tests).
- Tests: TestGenerics, TestGenericMethods, TestGenericInference (35
  portable cases from both Python generic suites, Python-specific
  cases documented), TestMedia, TestStreams (companion types as
  values), TestCancellation, llm TestMain ($build_request header
  pipeline against real env keys) + TestStreamingE2E (6 cases) +
  ReplayHarness. llm_functions joins ENFORCED_FIXTURES.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	baml_language/Cargo.lock
#	baml_language/Cargo.toml
#	baml_language/crates/baml_cli/Cargo.toml
#	baml_language/crates/baml_cli/src/generate.rs
#	baml_language/crates/baml_codegen_types/src/generator_fields.rs
#	baml_language/sdk_tests/harness_runner/src/lib.rs
#	baml_language/sdk_tests/harness_setup/Cargo.toml
#	baml_language/sdk_tests/harness_setup/src/lib.rs
Canary standardized the bridge ABI onto a single versioned function
table (BamlApiV1) resolved through baml_get_api_v1(), replacing the
flat #[no_mangle] symbol surface the Swift runtime consumed in phases
0-5. Adopt it:

- Add BamlBridgeLanguage::Swift = 7 to bridge_cffi (registry, TryFrom,
  telemetry/display names, abi_layout discriminant assert) and
  regenerate the canonical header.
- New BamlBridge/Api.swift resolves the table once (fatalError on a
  null / not-V1-compatible library) and unwraps every required V1
  function pointer into a typed static; all native call sites
  (Runtime, Handle, Media, HostCallables, Decode) route through it.
- initialize() now calls register_bridge with the SDK's stamped
  version, so a generated SDK can never silently run against a
  different runtime release. sdkgen emits Baml.sdkVersion from
  baml_version::CANONICAL_VERSION and threads it into initialize().
- Swap the hand-maintained baml_bridge.h for a build-synced copy of
  the canonical crates/bridge_cffi/include/baml_cffi.h; the modulemap
  and xcframework script follow.
- Media.Kind values move onto the canonical BamlCffiMediaKind numbering
  (image=1..generic=5); status checks compare against BAML_CFFI_STATUS_OK.

Also fold in the canary merge fallout: the Ty family gained a per-variant
TyAttr, so alias/enum declaration names use bare_name() (fixing $stream
companion aliases) and `never` return types render as void functions.
Enforce the new unsupported_only fixture. Gate: 7/7, 0 skipped.
…mobile targets

The --all xcframework path failed linking aarch64-apple-ios: bex_cache
(new since the iOS feasibility spike) enabled reqwest's aws-lc-rs
provider, whose C objects target iOS 26.5 and reference
___chkstk_darwin. Give bex_cache the same aws-crypto/ring-crypto
feature split as sys_native/sys_llm, thread it through bex_project,
and have build-xcframework.sh build *-apple-ios* targets with
--no-default-features --features ring-crypto (the configuration the
on-device spike validated). macOS keeps the default backend.

Gate: 7/7. The glasses-vision demo app now builds for iOS simulator
against the full three-slice XCFramework.
…ped symbols

The fixpoint's skip posture meant an unsupported construct produced
absent API with no trace (the Ty::Never/DoPanic incident). Now every
skip is recorded with the concrete type that failed translation and
emitted into the generated output as _BamlSkipped.swift:

- reasons probe the failing signature in the renderer's own order
  (generic coverage, parameters, return type), so the reported cause
  is the one that made the renderer bail
- covers functions, classes, aliases, static/instance methods, and
  namespace-collision drops
- skips in the author's own (user) package raise a #warning at every
  build of the package; the stdlib/vendor baseline is listed under a
  separate section without warning — a warning that always fires
  would be ignored
- an empty manifest is emitted as the positive nothing-was-skipped
  signal

Gate: 7/7, 0 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Error implies Sendable; storing a bare `any BamlEncodable` fails
strict-concurrency checking on newer compilers (caught by the dist
repo's smoke gate on macos-15 — local toolchains let it slide). The
payload is now `any BamlEncodable & Sendable`: the honest constraint,
satisfied by every realistic thrown value (generated models are
Sendable value types), so no call-site changes.

Gate: 7/7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--all now self-provisions the pinned toolchain's Apple targets and
pins MACOSX/IPHONEOS_DEPLOYMENT_TARGET to the package minimums
(overridable via env), and grows a --zip <path> mode emitting the
deterministic zip + SwiftPM checksum — so distribution CI (baml-swift)
is pure orchestration with zero build knowledge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aller download)

Fat LTO's embed-bitcode leaves ~2/3 of each release archive as
__LLVM sections that ld64 provably discards at consumer link time
(consumer binary is byte-identical against stripped vs unstripped
archives; bitcode bundling is dead since Xcode 14). Strip with the
pinned toolchain's llvm-objcopy (self-provisioned via rustup
component add llvm-tools) before xcframework assembly.

Measured: macOS universal slice 476MB -> 156MB, iOS 234MB -> 75MB;
projected release zip 364MB -> ~120MB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	baml_language/.config/nextest.toml
#	baml_language/Cargo.toml
#	baml_language/crates/baml_cli/src/generate.rs
#	baml_language/crates/bridge_cffi/include/baml_cffi.h
#	baml_language/crates/bridge_cffi/src/ffi/runtime.rs
#	baml_language/sdk_tests/harness_runner/src/lib.rs
- sdks/swift/Sources/BamlBridge/Proto: generate-protos.sh writes an
  input-hash manifest (.generated-from) beside the generated .pb.swift
  sources; the Linux proto-sync job recomputes the source protos'
  sha256s instead of running protoc-gen-swift (Swift-toolchain-only) —
  protos changed without regeneration = dirty manifest = failure.
- sdks/swift/Sources/CBamlBridge/include/baml_cffi.h: re-copied from
  the canonical bridge_cffi header (itself byte-enforced by the
  header_generation test) so drift fails the same dirty-tree check.
- both paths added to the job's PATHS + the proto change-detection
  filter; regeneration command documented in bridge_ctypes/README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 22, 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 22, 2026 9:49pm
promptfiddle Ready Ready Preview, Comment Jul 22, 2026 9:49pm
promptfiddle2 Ready Ready Preview, Comment Jul 22, 2026 9:49pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds Swift SDK generation and runtime bridging, including C ABI and protobuf bindings, Swift codecs, host-callable and streaming support, generated fixture tests, XCFramework tooling, and CI synchronization checks.

Changes

Swift SDK pipeline

Layer / File(s) Summary
Generator and workspace integration
baml_language/Cargo.toml, crates/baml_cli/*, sdks/swift/rust/*
Adds Swift workspace membership, CLI generation dispatch, Swift bridge packaging, and code-generation support.
Swift code generation
sdks/swift/rust/sdkgen_swift/*
Generates Swift namespaces, types, callable wrappers, recursive unions, embedded bytecode, and skipped-symbol manifests.
Swift ABI and runtime
sdks/swift/Sources/BamlBridge/*, sdks/swift/Sources/CBamlBridge/*
Adds versioned ABI access, protobuf models, value codecs, runtime calls, errors, handles, streams, media, unions, and host callbacks.
Swift test harness
sdk_tests/crates/swift/*, sdk_tests/harness_setup/*, sdk_tests/harness_runner/*
Adds SwiftPM fixture generation, macOS setup, and broad XCTest coverage.
Proto and build synchronization
sdks/swift/scripts/*, .github/workflows/ci.yaml
Adds Swift regeneration scripts and CI checks for generated protobuf, ABI header, and Swift artifacts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant BamlCLI
  participant sdkgen_swift
  participant GeneratedSwift
  participant BamlRuntime
  participant bridge_swift
  BamlCLI->>sdkgen_swift: generate Swift sources and bytecode
  sdkgen_swift->>GeneratedSwift: write namespaces and runtime wrappers
  GeneratedSwift->>BamlRuntime: invoke generated callable
  BamlRuntime->>bridge_swift: call versioned C ABI
  bridge_swift->>BamlRuntime: return completion payload
  BamlRuntime->>GeneratedSwift: decode result or error
Loading

Poem

A bunny hops through Swift code bright,
With unions dancing left and right.
Proto carrots neatly sync in rows,
Streams wiggle where the runtime flows.
Tests thump happily, green and spry—
“Ship the bridge!” sings rabbit high.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.96% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a Swift bridge.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dhilan/swift-bridge-1

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.

@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.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 25.7 MB 10.9 MB file 25.3 MB +385.6 KB (+1.5%) OK
packed-program Linux 🔒 17.2 MB 7.1 MB file 17.0 MB +153.1 KB (+0.9%) OK
baml-cli macOS 🔒 19.9 MB 9.5 MB file 19.6 MB +314.2 KB (+1.6%) OK
packed-program macOS 🔒 13.4 MB 6.2 MB file 13.2 MB +198.3 KB (+1.5%) OK
baml-cli Windows 🔒 21.4 MB 9.7 MB file 21.1 MB +324.6 KB (+1.5%) OK
packed-program Windows 🔒 14.3 MB 6.3 MB file 14.2 MB +150.5 KB (+1.1%) OK
bridge_wasm WASM 16.3 MB 🔒 4.4 MB gzip 4.4 MB +19.2 KB (+0.4%) OK

🔒 = 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.


Generated by cargo size-gate · workflow run

The canary merge's keep-both splice in generate.rs orphaned
OutputType::Java's opening line and dropped its body — baml_cli did
not compile. Java's arm is restored verbatim from canary; Swift's arm
follows it. diff vs origin/canary now shows only the Swift addition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ATX24
ATX24 marked this pull request as ready for review July 22, 2026 17:37
unions-design.md and state-of-baml-swift.md move to external docs;
drop their markdown-whitelist entry.
sdk_test_swift was excluded from generic cargo-test jobs (like every
sdk_test_*) but had no dedicated matrix entry, so the 203-test parity
gate never ran in monorepo CI. One macOS entry; the nextest-bound
setup script already assembles the xcframework before the run.
@ATX24
ATX24 added this pull request to the merge queue Jul 22, 2026
@ATX24
ATX24 removed this pull request from the merge queue due to a manual request Jul 22, 2026
Accidental leftover from iOS/ring-crypto testing (24d2a93): the pin
was written when a resolve ran near the glasses-vision demo app. It was
never in Package.swift (only swift-protobuf is), so it was inert, but
it doesn't belong in the monorepo. Package.resolved now pins only the
declared swift-protobuf dependency.
@ATX24
ATX24 added this pull request to the merge queue Jul 22, 2026
Merged via the queue into canary with commit f3c8b6d Jul 22, 2026
67 checks passed
@ATX24
ATX24 deleted the dhilan/swift-bridge-1 branch July 22, 2026 21:59
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.

2 participants