Skip to content

feat(ffi): export u128, const generics, a () parameter, and an empty enum - #675

Merged
kixelated merged 16 commits into
mainfrom
quest/m1/ffi-export/refusals/small-shapes
Nov 10, 2026
Merged

kixelated merged 16 commits into
mainfrom
quest/m1/ffi-export/refusals/small-shapes

Conversation

@kixelated

@kixelated kixelated commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Works on quest/m1/ffi-export/refusals/small-shapes.md.

Summary

  • A () parameter of a function is passed by nothing in C; the glue passes () to Rust. Clears the four mux::container::Producer<C>::set instances at the moq pin (R defaults to ()). A callback trait's () parameter stays refused, since the glue's trait implementation names every parameter.
  • An enum without variants is routed as an object. No value is one, so no export returns it, and since #671 the bindings leave it out and the census lists it, as "object no export returns". Clears transport::poll::Ungrouped's "enum without variants".
  • u128 exports by rule: <prefix>_u128 in C, a struct of its lo and hi 64-bit halves, and above it Python int, Kotlin java.math.BigInteger, Dart BigInt, Go *big.Int, Swift a U128 struct of high and low (with Comparable, Hashable, and its decimal description) until its deployment target allows UInt128, and C++ the C struct as u128. A value passed in out of range raises the language's error: Python OverflowError, Kotlin IllegalArgumentException, Dart ArgumentError, and a Go panic. Clears Timestamp::as_scale, as_millis, as_micros, and as_nanos.
  • Const generics export. A const argument is Type::Const(Literal) in the IR, beside type arguments. A generic type's const parameter infers its default, as a type parameter does, so frame::Buffer<const N: usize = 8> exports as Buffer, and any other value a signature names exports too (Batch<2> is api_Batch_2). A function's const parameter takes each value an instance of a generic type it passes the parameter to has, checked against each generic type the signature names once that type's parameters are all chosen, so group::Producer::write_frames and group::Consumer::read_frames export at N = 8, and a C name appends the value (api_drain_4, api_drain_2). The glue names a const argument in its turbofish. A const argument that is not a bool or an integer below 128 bits is refused, and so is a function's const parameter that nothing names.
  • A () parameter and a poll's context are both omitted from what C passes; the glue places each by its position among Rust's parameters, so a () before a Context still lends the context where Rust takes it.
  • A future whose result borrows what it holds, as read_frames's &'a mut [Frame] borrows its &mut Buffer argument, failed to compile: the glue lowered the result in the closure C polls, after the async block had dropped what it borrows. The block now clones a borrowed result first, as the synchronous path already does when it lowers.
  • Tests: reach(start, (), end), scaled(v: u128, by: u64) -> Option<u128>, Batch<const N: usize = 4> with pair() -> Batch<2>, drain<const N>(&mut Batch<N>), capacities<const N, const M>(&Batch<N>, &Batch<M>), and the async fill<const N>(&mut Batch<N>, v) -> &mut [u32] in the api fixture, called from every language against the Rust transcript; out-of-range checks in Python, Kotlin, and Dart; Swift's decimal description; enum Void {} in the api fixture's census. The reject fixture refuses a char const argument (Fixed<'a'>) and a const parameter nothing names (probe<const N>).
  • Swift reserves its runtime's U128, SocketAddr, and Comparable, so an export that takes one is refused at its source rather than generating Swift that fails to compile, and a unit test keeps every type the runtime declares, and every protocol one conforms to, reserved.
  • ffi/moq/ffi.toml drops write_frames and read_frames, which export, and lists frame::Buffer::drain (an arrayvec iterator, newly reached now that Buffer exports) and field@mux::codec::flac::Config::md5 (an array, reached since #678), as #693 also spells it.

Census at the moq pin after this: no moq-own item refused for unit, an empty enum, u128, or a const generic. Two arrays are left, tokio::tls::parse_fingerprint's [u8; 32] and flac::Config::md5's [u8; 16]: the quest expected narrowing the stand-in to drop the arrays, and it did not. The quest is rewritten to them, with the mapping left for the maintainer.

Decisions

Settled by the maintainer on 2026-11-09:

  1. u128: ✅ map it by rule as <prefix>_u128, two uint64_t halves in C, with each language's exact big integer above it (rejected: leave out). Implemented here across all seven generators. i128 stays refused: its sign is a conversion of its own in each language and no consumer uses it, so it is not the same rule at no extra cost.
  2. Not Send (clock::Entered, clock::Rng, catalog::Guard): owned by not-send's thread check, decided 2026-11-09; dropped from this PR.
  3. Const generics (group::Producer::write_frames, group::Consumer::read_frames over frame::Buffer<const N = 8>): ✅ do it in this PR once generics, moq-dev/rust2#670, lands, since both edit instance inference (rejected: a separate quest). feat(ffi): export generic parameters through blanket impls, and leave out traits C cannot implement #670 has merged; implemented here.
  4. Scope: ✅ rust2 asks moq for no source changes; changing moq is a non-goal. The quest's Plan says so; moq-dev/rust2#678 records the principle in the epic README.

Unattended picks, made for the maintainer on 2026-11-10 while they were away:

  1. Review round 3's design checkpoint: merging main put #679's poll contexts beside this PR's omitted () parameters, and a synchronous Context was matched by its place among Rust's parameters against the compacted C index. ✅ (a) keep both omitted kinds at their Rust place and have the glue map each C parameter to it before matching (rejected: (b) keeping () in params as a Type::Unit each of the seven generators skips; (c) refusing a () beside a context). Fixed in 99ab758; the api fixture's Channel::listen(&self, _: (), cx: &Context) covers it in every language.
  2. Arrays: the mapping stays a decision for the maintainer in the rewritten quest, recommending bytes with a length check; it is not a wait on this PR.
  3. ffi/moq/moq.cpp's C++ failure after feat(ffi)!: C++ checks ownership at compile time under Clang #694 belongs to #693, which keeps just ffi moq green; it is left out of this PR.

Choices made without asking:

  • C++ takes the C struct as u128 rather than unsigned __int128, which MSVC lacks.
  • Transcript values print as 32 hex digits, so C and C++ print them exactly without a decimal routine.
  • A function instance at a const value is named by the value, as one at a type is named by the type, even when only one instance exists (write_frames_8), so a later instance never renames it.
  • A const argument is limited to a bool or an integer below 128 bits, the values Literal already holds; a char one is refused, as no consumer has one.
  • The arrays left in the census stay in the quest rather than this PR: how [T; N] maps is a design decision the quest now asks the maintainer, recommending bytes.

Public API

C ABI: a function with a () parameter now exports, without that argument. A u128 parameter or return now exports as <prefix>_u128, and the runtime claims the C name <prefix>_u128. A const-generic type exports at its inferred instances, named by any const argument past its default (<prefix>_Batch_2), and a const-generic function at each inferred value, its C name ending with the value (<prefix>_drain_4). moq's C API gains moq_frame_Buffer and moq_group_Producer_write_frames_8 and moq_group_Consumer_read_frames_8. No item that exported before changes.

Follow-ups

  • Arrays: settle the mapping in the small-shapes quest; recommended bytes for [u8; N].
  • frame::Buffer::drain returns an impl ExactSizeIterator + '_ over an arrayvec Drain, excluded in ffi/moq/ffi.toml here; recommend planning it with the other borrowed iterators.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)

Checks

just ci passed locally on 99ab758, and the full ffi suite (every language's api fixture and the reject fixture).

just ffi moq fails on main in two places this PR does not touch:

With #693's ffi/src/kotlin.rs change applied locally, the moq export built and its C, Kotlin, Python, Swift, Go, and Dart programs passed.

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1c778-5888-70c0-8479-9648eee01db3, round 4
  • Head: 99ab758
  • Base: origin/main at efbfa66
  • Score: 10/10, The Context index mismatch is fixed, and code inspection found no new issues in the delta; builds and tests were not run as instructed.
  • Findings: none
  • The last round's findings, at e7b35b2:
    • Fixed: medium, ffi/src/collect.rs:2307: Adjust synchronous Context indices for omitted unit parameters; glue.rs now matches task.at against each parameter's original Rust position after skipping omitted arguments, and the Channel::listen fixture covers a unit argument preceding a synchronous Context.

@pullfrog

pullfrog Bot commented Nov 9, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

… enum as an object

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Small shapes export feat(ffi): export a () parameter and an empty enum Nov 9, 2026
kixelated and others added 4 commits November 9, 2026 20:43
# Conflicts:
#	ffi/README.md
#	ffi/fixtures/reject/expected.txt
#	ffi/fixtures/reject/src/lib.rs
#	quest/m1/ffi-export/refusals/small-shapes.md
…eger

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…census

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 4 commits November 10, 2026 10:31
…efusals/small-shapes

# Conflicts:
#	ffi/README.md
A const parameter of a generic type takes its default, as a type parameter does, and
a function's takes each value an instance it names has there. The IR carries a const
argument as Type::Const; a C name appends it, as api_drain_4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efusals/small-shapes

# Conflicts:
#	ffi/fixtures/reject/expected.txt
#	ffi/src/glue.rs
#	quest/m1/ffi-export/refusals/README.md
…shapes to its array

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 10, 2026 13:36
…ead_frames

A future's Done closure lowers its result when C polls, after the async block has
dropped what the result borrows; the block now clones a borrowed result first. The
api fixture's async fill<const N> covers it, as group::Consumer::read_frames needs.
ffi.toml drops write_frames and read_frames, which export, and lists Buffer::drain
and flac::Config::md5, which the census refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(ffi): export a () parameter and an empty enum feat(ffi): export u128, const generics, a () parameter, and an empty enum Nov 10, 2026
…erve Swift's runtime names

A function's parameter choice checked every generic type its signature names, one
naming a later parameter too, which no instance matches, so two const parameters
refused the function. Each type is now checked once its parameters are chosen; the
api fixture's capacities<N, M> covers it. Swift reserves the runtime's U128,
SocketAddr, and Comparable, and a test keeps every runtime declaration reserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 10, 2026 22:07
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 641630d8-240d-4eae-8148-ffcccb8c4119
Commits

Reviewing files that changed from the base of the PR and between efbfa66 and 99ab758.

Files selected for processing: 33

  • ffi/README.md
  • ffi/fixtures/api/examples/transcript.rs
  • ffi/fixtures/api/src/lib.rs
  • ffi/fixtures/reject/expected.txt
  • ffi/fixtures/reject/src/lib.rs
  • ffi/moq/ffi.toml
  • ffi/runtime/ffi_cgo.go
  • ffi/runtime/jni.rs
  • ffi/runtime/python.rs
  • ffi/runtime/rt.dart
  • ffi/runtime/rt.kt
  • ffi/runtime/rt.rs
  • ffi/runtime/rt.swift
  • ffi/src/abi.rs
  • ffi/src/c.rs
  • ffi/src/collect.rs
  • ffi/src/cpp.rs
  • ffi/src/dart.rs
  • ffi/src/glue.rs
  • ffi/src/go.rs
  • ffi/src/ir.rs
  • ffi/src/kotlin.rs
  • ffi/src/python.rs
  • ffi/src/swift.rs
  • ffi/tests/api.c
  • ffi/tests/api.cpp
  • ffi/tests/api.dart
  • ffi/tests/api.go
  • ffi/tests/api.kt
  • ffi/tests/api.py
  • ffi/tests/api.swift
  • ffi/tests/fixtures.rs
  • quest/m1/ffi-export/refusals/small-shapes.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

kixelated and others added 2 commits November 10, 2026 14:10
…efusals/small-shapes

# Conflicts:
#	ffi/README.md
#	ffi/moq/ffi.toml
#	ffi/src/glue.rs
#	ffi/tests/api.c
#	quest/m1/ffi-export/refusals/README.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A synchronous Context's task records its place among Rust's parameters, which a
() before it shifts from its C index. The glue now maps each C parameter to its
Rust place before matching. The api fixture's Channel::listen takes a () before
its context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit c2061e2 Nov 10, 2026
10 checks passed
@kixelated
kixelated deleted the quest/m1/ffi-export/refusals/small-shapes branch November 10, 2026 22:37
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