Skip to main content

Phase 3.4. List of records

FieldValue
MEPMEP-49 §Phases · Phase 3.4
StatusLANDED
Started2026-05-28 13:40 (GMT+7)
Landed2026-05-28 13:40 (GMT+7)
Tracking issue
Tracking PR

Gate

TestPhase3ListOfRecords: 20 fixtures green on Swift 6.0 and 6.1, linux-x64. TestSwiftcClean remains green.

Goal-alignment audit

Phase 3.4 proves that the collection operations from 3.1-3.3 compose correctly with record types from Phase 4. Because Swift generics are reified (not erased), [MyRecord].map(f) works without monomorphisation in the Go lowerer -- Swift's type system handles specialisation at the compiler level. Phase 3.4 validates this end-to-end. This is the bridge from "collections of scalars" to "collections of structured data."

Sub-phases

#ScopeStatusCommit
3.4.0list<Record> literals; map, filter over record lists; record fields in closuresNOT STARTED
3.4.1map<string, Record> literals; map access returning a record; record update in mapNOT STARTED
3.4.2Typed empty literals: [] as [MyRecord], OrderedDictionary<String, MyRecord>()NOT STARTED
3.4.3Nested collections: list<list<T>>, map<K, list<V>>, list<map<K,V>>NOT STARTED

Sub-phase 3.4.0 -- List of records

Decisions made (3.4.0)

No monomorphisation in Go lowerer: Swift's generics are reified. [MyRecord].map { $0.field } is compiled by swiftc with full type information; the lowerer does not need to generate specialised Go code per element type. This contrasts with the C backend (which hand-expands templates) and the JVM backend (which needs boxing for primitive types).

Record type in list literal: [MyRecord] type annotation is explicit in the let binding when the list is the first expression containing records. Subsequent references infer from context.

// Mochi: let users = [{ name: "alice", age: 30 }, { name: "bob", age: 25 }]
let users: [User] = [User(name: "alice", age: Int64(30)), User(name: "bob", age: Int64(25))]

map over record list: The closure receives the full record struct. Fields are accessed via .field (dot notation on the Swift struct).

// Mochi: let names = users.map(fun(u) => u.name)
let names: [String] = users.map { u in u.name }

filter over record list: The closure receives the record and returns Bool.

// Mochi: let adults = users.filter(fun(u) => u.age >= 18)
let adults: [User] = users.filter { u in u.age >= Int64(18) }

Nested field access in sort: sort_by on a list of records, sorting by a nested field.

// Mochi: let sorted = users.sort_by(fun(u) => u.age)
let sorted: [User] = users.sorted(by: { a, b in a.age < b.age })

Sub-phase 3.4.1 -- Map of records

Decisions made (3.4.1)

map<string, Record> literal: Similar to plain map literals but with record values.

// Mochi: let db = { "alice": { age: 30 }, "bob": { age: 25 } }
let db: OrderedDictionary<String, User> = OrderedDictionary(uniqueKeysWithValues: [
("alice", User(name: "alice", age: Int64(30))),
("bob", User(name: "bob", age: Int64(25))),
])

Map access returning a record: db.get_or("alice", default_user)db["alice"] ?? default_user.

Record update in map: Functional update of a record stored in a map requires extracting, updating, and reinserting:

// Mochi: let db2 = db.set("alice", { db.get_or("alice", default_user) with age: 31 })
var __tmp_db2 = db
let __alice = db["alice"] ?? defaultUser
__tmp_db2["alice"] = User(name: __alice.name, age: Int64(31))
let db2 = __tmp_db2

The with update syntax for records is lowered in Phase 4; Phase 3.4 covers the map wrapping around it.

Sub-phase 3.4.2 -- Typed empty literals

Decisions made (3.4.2)

Typed empty list: Mochi [] in a context expecting list<MyRecord>[MyRecord]() with the explicit type. The lowerer determines the expected type from the let binding annotation or function parameter type.

Typed empty map: Mochi {} in a map<K, Record> context → OrderedDictionary<K, Record>().

Typed empty set: Mochi {} in a set<Record> context → OrderedSet<Record>().

Inference failure: If the context type is not determinable (e.g., passed to a generic function without type annotation), the lowerer emits a type-annotated form with a compiler comment. The TestSwiftcClean gate catches any resulting ambiguity warnings.

Sub-phase 3.4.3 -- Nested collections

Decisions made (3.4.3)

list<list<T>>: → [[T]]. All collection operations compose naturally. xs.map { inner in inner.filter { x in ... } } works without special handling.

map<K, list<V>>: → OrderedDictionary<K, [V]>. Building such a map from a list (group-by) is deferred to Phase 7 (query DSL group by). Phase 3.4 only covers direct literals and access.

list<map<K,V>>: → [OrderedDictionary<K, V>]. Each element is an OrderedDictionary. Indexing into the list gives an OrderedDictionary; subscripting that gives V?.

Nesting depth: Phase 3.4 tests up to two levels of nesting. Deeper nesting is theoretically unlimited but not explicitly tested; TestSwiftcClean catches any type inference issues.

Files changed

FilePurpose
transpiler3/swift/lower/lower.goTyped empty literal lowering; nested collection type annotation
transpiler3/swift/lower/types.goswiftTypeOf(mochiType) helper: translates Mochi type → sxtree TypeSyntax
transpiler3/swift/build/phase03_records_test.goTestPhase3ListOfRecords: 20 fixtures
tests/transpiler3/swift/fixtures/phase03-list-of-records/20 fixture directories

Test set

  • TestPhase3ListOfRecords -- 20 fixtures covering: list_of_record_literal, list_of_record_map, list_of_record_filter, list_of_record_sort, list_of_record_fold, list_of_record_contains, map_of_record_literal, map_of_record_get, map_of_record_update, map_of_record_map_values, set_of_record, typed_empty_list, typed_empty_map, typed_empty_set, nested_list_list, nested_map_list, nested_list_map, nested_list_of_list_map, record_in_multiple_collections, collection_of_option_record.

Deferred work

  • group_by on record lists (builds map<K, list<V>>). Deferred to Phase 7.
  • list<option<Record>>. Deferred to Phase 5 (option types).
  • list<union_type>. Deferred to Phase 5 (sum types).