feat(bridges): add swift bridge - #4122
Conversation
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.
…roring the Python doc
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe 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. ChangesSwift SDK pipeline
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
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks passed✅ 7 passed
Generated by |
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>
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.
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.
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,BamlUnionNfamily, streams, handles, host callables. Consumes the canonical V1 C ABI (baml_get_api_v1table, registered asBridgeLanguage::Swift = 8), statically linked.sdks/swift/rust/sdkgen_swift/— the generator (~1,600 lines): supported-types fixpoint, cycle boxing, namespace routing, real typedfuncbodies (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.swiftmanifest (#warningwhen 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:
BamlUnion2..8— the cross-team no-generated-names design: positional cases in wire order, type-directed convenience layer, labeledmatch, and metadata-first decode using the wire'svalue_option_name(exact arm selection where Python guesses structurally)._types=machinery. Return-only TypeVars are skipped pending a wire type-hint hook.Packaging (dev channel, already live)
BoundaryML/baml-swiftpublishes from this branch automatically: immutable checksum-pinned XCFramework release assets (5 Apple slices,release-bridge-swiftprofile, LLVM bitcode stripped — 104MB download, ~26MB linked into an app), stamped rootPackage.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
sdks/swiftgenerated files (input-hash manifest for the protoc-gen-swift outputs, canonical-header copy check).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)Completeness matrix
Full feature table — status + Swift representation (✅ gate-enforced · 🚧 partial · ❌ not yet)
func f(...) throws -> R; caller parks on a semaphore (deadlock-safe: completions always arrive on engine threads)_asyncsibling of every function; continuation in the pending table("self", self)kwarg 0BamlOptional<T> = .unset—.unsetomits (engine default),.some(nil)explicit nullwithTaskCancellationHandler→ reserve-basedcancel_function_call; engineCancelled→ nativeCancellationError. No sync cancellation;BamlCallContextunported by design$build_request/$parse/$parse_stream/$stream)$→_in Swift spellingsSwift.String/Int/Double/Bool,Foundation.Data— always fully qualifiedEquatable, Sendablestructs carrying their own_bamlEncode/_bamlDecode; fields present-as-null on the wiretypealias; recursive union aliases → nominal indirect enum under the user's name; nullable ones keep?at reference sitesBamlUnion2..8<T0,…>generic indirect enums — no generated names. Positional cases in wire order + type-directed layer (value(as:),holds, init overloads) + labeledmatch+ nativeswitch. Metadata-first decode via the wire'svalue_option_name(exact where Python guesses structurally)"a" | "b",42)@BamlIndirect(CoW); unions break cycles free viaindirect enum; cyclic graphs unconstructible (value semantics)[K: V]/[T], element-wise codec recursionBamlMedia.fromBase64/URL/fileover canonicalBamlCffiMediaKind; tagged engine handleBamlHandle: u64 key;deinitreleases, encode clones the key (no double-release)Int; noBamlBigIntyetstruct Box<T: BamlCodableValue>; non-BAML type args are compile errorstype_args, no_types=(35 inference cases enforced)parse_as<T>)_BamlSkipped.swift); needs a wire type-hint hookthrows→ SwiftBamlError { message, className, bamlTrace, payload }; typed access decode-on-demand viavalue(as:); thrown types as- Throws:docsBamlPanic;Cancelled→ nativeCancellationError(async); exit-panicsexit()throw BamlThrownValue(model)— payloadany BamlEncodable & SendableTypeMismatch→ native errorBamlErrorfor now (Python maps toTypeError)complete_host_callexactly once on every path; async closures first-class; engine-driven lifetimeBamlStream<Partial, Final>;next()→.value/.finishedenum (mid-stream partials can be null); partials instream_typeswith all-optional fields; replay-server e2e enforcedAny-typed; every skip + failing type recorded in_BamlSkipped.swift(#warningfor user-package skips)BamlEncodableparams)baml_get_api_v1()verified/unwrapped once;BridgeLanguage::Swift = 8; exact-versionregister_bridgeat initCodableBoundaryML/baml-swift: immutable checksum-pinned XCFramework releases (bitcode-stripped, 104 MB download / ~26 MB linked), auto-published, smoke-gatedSummary by CodeRabbit