Repository navigation
2.2 swift: type-mapping spec (step 1) - #55
Draft
cunninghamcard-bit wants to merge 8 commits into
Draft
cunninghamcard-bit wants to merge 8 commits into
cunninghamcard-bit wants to merge 8 commits into
Conversation
docs/plan/2.2-swift-mapping.md maps every wire IR construct (wire.rs:22-129) and everything the Go emitter handles (emit/go.rs) to a Swift shape: primitives and widths, JSONValue, arrays/maps/Nullable, Vec<u8> as [UInt8] (no Data, which JSONEncoder writes as base64; D33 pending), records and the presence matrix (encodeNil vs encodeIfPresent, TriState), aliases/proxies, concrete generics, flatten, open/closed string enums, and internal/external/adjacent/untagged unions (declaration-order strict decode, null rejected like serde). It also covers reuse of the emit::write guard, the test/doc/verdict mirror of Go (wire_diff_swift.rs, 2.2-diff-swift.md, 2.2-diff-swift-verdicts.json), toolchain status (swift not installed on the box) and open questions.
- D33 pending; typealias WireBytes = [UInt8], switched in one place later. - The generated JSONValue gets Int64/UInt64 cases (as serde_json::Value keeps integers). The hand-written Double-only storage loses precision above 2^53, which is 手写错. A big-integer fixture (2^53+1, u64::MAX) is planned. - Open enums keep the received string, the same as Go. - The diff runs on local Linux and is mandatory: a missing toolchain fails the test, never skips it. Toolchain: official Ubuntu 24.04 tarball (not installed here). The macOS CI Swift job is supplementary; Foundation differences are recorded and non-blocking. - No swift-format: compile check plus --check only. - Hand-written type names come from swiftc -dump-parse. Tests cover nested types, extensions and multiple declarations on one line; a line-by-line reader is shown to miss them. The open-questions section is now empty.
…, counts) - A non-Option #[serde(default)] field (omittable only, wire.rs:348) decodes only when the key is present (contains ? decode : default), so a null is rejected as in serde and Go rejectNull (go.rs:158). Planned null-alignment fixture: DecisionCapabilities supports_images: null (decision_model.rs:362-363). - JSONValue decodes Bool -> Int64 -> UInt64 -> Double inside JSONDecoder (no custom parser). Big-integer precision is a hard requirement. 1.0, 1e3 and -0.0 may come back as integers, a possible known difference from a Foundation limitation, to be measured first after the install. Fixture list and the drop-int/uint mutation are in the doc. - Fixture count is 223 (14 + 124 + 80 + 5), not 224. - Swift >= 6.0. First measurements: u64::MAX and 2^53+1 on Linux. - 'Byte-identical' is replaced by 'equal as serde_json::Value' (preserve_order, Cargo.toml:53). - IR union counts: internal 30 including the AiMuxError proxy, untagged 12, adjacent 0; all 19 external enums are unit-only (source: Go output from the IR at df2a90d). - Planned: an IR premise test (no untagged Unit/Json variant), a synthetic external-payload unit test, and emitter mutations (encodeNil, open enum, int overflow/fraction, u128). The missing-toolchain failure will be shown once in the PR body. - Minor: ImageResult example marked 节选 (image_model.rs:183); TriState only with a codec (wire.rs:358-360); TextStreamPart is defined at stream_part.rs:176 (aimux-ffi/src/lib.rs:2304 is a call site) and is in the Swift comparison set (hand Types.swift:1163).
The verdict schema becomes {手写错, 生成器错, 已知差异}. 已知差异 is only for generated code that cannot match serde because of a platform limitation. The sole candidate is Foundation JSONDecoder number parsing (1.0/1e3/-0.0), and only if measured. Its reason must name the limitation source and cite PHASES.md:307, and it is registered per language.
The test fails when a 已知差异 row actually matches serde (no stale exemptions). Planned mutations: a matching 0.5 fixture marked 已知差异 goes red; a real generator difference marked 已知差异 still fails as a generator error. Generator errors are still caught before the table, as in Go, and never appear in it. Open-enum string keeping is a design choice and is excluded. The 'schema gains that value later' note is removed.
…ance, A/B points)
- skip_if_empty non-nullable decodes with contains ? decode : empty (wire.rs:348,351), so null is rejected; the null-alignment fixture and its mutation cover it.
- JSONValue first measurements add 0, 1 and true (Bool/Int exclusivity); non-blocking until the toolchain is installed.
- New docs/plan/2.2-known-differences.json, machine-readable: id, language, limitationSource, a phasesExcerpt quoting aimux-plan PHASES.md:307 verbatim (the in-repo excerpt is authoritative), and a scope of (fixture, leaf path). An entry applies only on label + path match with both values numbers. Three-way checks (ID registered with matching language, row in scope, every ID referenced) plus the stale check each have a planned mutation, red test and expected error; the second mutation uses Usage inputTokens.total ('out of scope'). KD-SWIFT-NUM-1 is planned and unmeasured.
- New docs/plan/2.2-provenance.md: in-repo excerpts of PHASES.md:307 and DECISIONS D33. Swift byte reasons will cite it. The Go verdicts (#54) still cite out-of-repo DECISIONS D33; switching them is noted as a 2.2 follow-up.
- IR premise scan expands aliases first (DecisionState, TextContentBinding, ToolChoice are Alias(Json)) and treats Value(Nullable) as null-accepting.
- Stated as not verified: internal tag position, since the comparison ignores key order; no assertion added.
- String enums counted from the IR rule (wire.rs:244-248) = 19, with the exact command. Go's 21 includes the Size and AspectRatio aliases.
- Every planned mutation names its red test and expected error.
…ests, doc fixes - emit/swift.rs: records with explicit CodingKeys/init(from:)/encode(to:) per the presence matrix, open/closed string enums, internal/external/ adjacent/untagged unions, JSONValue (Bool->Int64->UInt64->Double), WireBytes, TriState only with a codec; Swift::check refuses u128/i128, variant-level untagged in tagged unions, by-value record cycles. - Emitter::check (default Ok) runs in emit::write before any file. - wire.rs StringEnum carries variant aliases (ModelType); Go ignores them. - tests/emit_swift_test.rs: ir_has_19_string_enums, untagged premise (aliases expanded, Nullable counted), u128 error, synthetic external payload and adjacent unions, presence rows. - fixtures moved unchanged to tests/common/fixtures.rs (shared). - doc: alias sentence factual (decision_model.rs:15, tool.rs:214, wire.rs:290-292), :71 key-order clause removed, KD reason cites 2.2-provenance.md, IR test replaces the counting one-liner, final KD conditions incl. f64 equality and their mutations.
… record - tests/wire_diff_swift.rs: SwiftPM scratch harness (generated vs copies of the hand Swift), shared fixtures + 12 JSONValue number fixtures, reject-parity cases (default-field null, skip_if_empty null, u32 overflow/negative/fraction), open-enum unknown + alias, null parity, binary, wire-name mutation, swiftc -dump-parse type-name reader. Fails (not skips) when swift/swiftc are missing. - Known differences: label AND path in scope AND both numbers AND equal as f64; three-way checks, stale check; each mutation asserted. - null_parity_cases moved unchanged into tests/common/fixtures.rs. - docs: 2.2-diff-swift-verdicts.json ([]), 2.2-diff-swift.md placeholder (not generated: no toolchain), 2.2-swift-mutations.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
2.2 (Swift): step 1, the type-mapping spec
Stacked on #54 (
exp/2.2-codegen-go, df2a90d). This PR adds onlydocs/plan/2.2-swift-mapping.md. There is no emitter code yet, andbindings/,aimux-core/and ts-rs are untouched.Mapping decisions
target/codegen-out/swift/only through the unchangedemit::writeguard.CodingKeysand hand-emittedinit(from:)/encode(to:)for every record. The presence matrix:Optionwithout skip usesencodeNil(serde writesnull).Optionwith skip usesencodeIfPresent.#[serde(default)]and skip_if_empty fields decode withcontains(k) ? decode : default/empty, so a presentnullis rejected as in serde.TriState<T>(absent/null/value).Vec<u8>maps to[UInt8]behindtypealias WireBytes, which encodes as a number array.Datais never emitted, because JSONEncoder writes it as base64 (the same trap as Go[]byte). D33 is pending.nulland[null]are rejected by JSONDecoder itself; the Go null-parity fixtures will verify this, with a mutation.type X stringlines include the Size/AspectRatio aliases); the{"name": payload}path gets a synthetic unit test. Adjacent tagging is supported but unused.#[serde(other)]): aRawRepresentablestruct that keeps the received string, as Go does. Variant aliases are accepted on decode.Size/AspectRatiobecomeString. StreamPart is instantiated per itsts(concrete)(stream_part.rs:28). Flatten is already expanded in the IR.JSONValueenum like the hand binding (Types.swift:29-58).Plan
tests/wire_diff_swift.rsmirrorswire_diff_go.rs: the same 223 serde fixtures (14 + 124 + 80 + 5) and the same tests (coverage, empty-value, both directions, null, tri-state, proxy/generic, null parity, binary AudioData/VideoData equal as serde_json::Value, mutations).docs/plan/2.2-diff-swift.mdis generated and stale-checked.docs/plan/2.2-diff-swift-verdicts.jsonuses the same{type, field, verdict ∈ {手写错, 生成器错, 已知差异}, reason}schema.Rulings (aimux RFC, folded into the spec)
typealias WireBytes = [UInt8]is the one place to switch later.JSONValuehasInt64/UInt64cases and decodes Bool → Int64 → UInt64 → Double inside JSONDecoder, so big integers keep exact precision. It is not claimed to matchserde_json::Valueeverywhere:1.0/1e3/-0.0may come back as integers (a possible known difference, measured after install). The hand-written Double-only storage loses precision above 2^53 and gets the verdict 手写错. A big-integer fixture (2^53+1, u64::MAX) is planned to prove it.swiftmakes the test fail, not skip.Swift testsjob is supplementary; Linux/macOS Foundation differences are recorded and non-blocking.--check.swiftc -dump-parse. Tests cover nested types, extensions and multiple declarations on one line, and a line-by-line reader is shown by mutation to miss them.Spec corrections (6c3dbbc)
#[serde(default)]fields decode only when the key is present (contains ? decode : default), so anullis rejected, as in serde and Go'srejectNull. A null-alignment fixture is planned.Bool → Int64 → UInt64 → Doubleinside JSONDecoder, with no custom parser, and big-integer precision is a hard requirement.1.0,1e3and-0.0may come back as integers, a possible known difference caused by a Foundation limitation. It is measured first after the install. If confirmed, the diff stays strict and those fixtures get the verdict 已知差异. The fixtures are -1, i64::MIN, below i64::MIN, 2^53+1, u64::MAX, 0.5, 1.0, 1e3 and -0.0, with a mutation that drops the int/uint cases.serde_json::Value(preserve_order, Cargo.toml:53), not byte identity. Map equality ignores key order, so the internal tag's key position is not verified, and no assertion is added for it.Third verdict 已知差异 (bcd3e63, aimux-plan PHASES.md:307)
wire_diff_swift.rsfails if a 已知差异 row actually matches serde. Planned mutation: mark the matching0.5fixture as 已知差异 and the test goes red.Final mapping revision (102abf8)
docs/plan/2.2-known-differences.json, machine-readable, so tests never parse Markdown.id,language,limitationSource, a verbatimphasesExcerptof aimux-plan PHASES.md:307 (the in-repo copy is authoritative) and ascopeof{fixture, leaf path}.Usage inputTokens.totalgives "out of scope"); every ID is referenced. The stale-exemption check is kept.KD-SWIFT-NUM-1is planned and unmeasured.docs/plan/2.2-provenance.mdholds in-repo excerpts of PHASES.md:307 and DECISIONS D33. Swift byte reasons will cite it. 2.2: wire IR + Go emitter; generated Go vs hand-written diff (on #53) #54's Go verdicts still cite out-of-repo DECISIONS D33; switching them is a 2.2 follow-up.contains ? decode : empty, and null is rejected (wire.rs:348,351).Value(Nullable)as null-accepting.0,1andtrue(Bool/Int exclusivity). These are non-blocking until the toolchain is installed.Toolchain status
swiftis not installed on the dev box (Debian 13, x86_64). The install size is an estimate (about 1 GB download, 3 GB on disk) and has not been measured.Open questions
None. All six earlier questions are ruled above.
Step 2: the Swift emitter (head a3fee77)
Commits:
115cf75cemitteremit/swift.rs,Emitter::check(runs inemit::writebefore any file), IRStringEnumnow carries variant aliases,tests/emit_swift_test.rs(15), fixtures moved unchanged totests/common/fixtures.rs, plus the doc fixes (alias sentence cites decision_model.rs:15 / tool.rs:214 / wire.rs:290-292; the :71 key-order clause is deleted; the KD reason cites2.2-provenance.md;ir_has_19_string_enumsreplaces the counting one-liner; final KD conditions with f64 equality).262e8f61tests/wire_diff_swift.rsharness (21 tests), known-difference checks,2.2-diff-swift-verdicts.json([]),2.2-diff-swift.mdplaceholder,2.2-swift-mutations.md.a3fee776regenerate closure.md (the new file is now tracked).No Swift toolchain on the box (install pending Trey's approval), so nothing Swift has been compiled or run. The generated Swift has never gone through
swiftc. Tests that needswift/swiftcfail; they are not skipped.Tests (aimux-codegen): lib 11, codegen 8, emit_go 15, emit_relative_root 1, emit_swift 15/15, extract_prereq 17, manifest 16, wire_diff_go 12, wire_diff_swift 4 pass / 17 fail. Of the 17 failures, 16 are "swift missing" and 1 is
known_differences_are_registered_scoped_and_used(KD-SWIFT-NUM-1 is not referenced by any verdict). That one is intentionally red until measurement, with no exemption. clippy-D warningsclean, fmt clean,aimux-codegen --checkandgen_closure_inventory.py --checkup to date.Output of
wire_diff_swiftwith swift absent from PATH (one run):Mutations: every mutation is listed in
docs/plan/2.2-swift-mutations.md, run on the Rust side (22 rows, all red as expected) or marked not run (9 rows, need swift).Spec deviations, all recorded in the doc:
X<Variant>(Go naming), not nestedX.<Variant>. A nested struct would shadow a same-named top-level type inside the enum.#[serde(default)]field of a Named record or union type has no default in the IR, so it is kept asT?(16 fields). Decoding usescontains ? decode : nil(a present null is still rejected), and encoding usesencodeIfPresent.import CAimuxFFIremoved. Whether they compile without FFI is unverified.swiftc -dump-parseoutput format was assumed (S-expression(struct_decl … "Name") and has not been checked against a real dump.