Skip to content

quest: finish the compile-checks spike, reporting which Swift and C++ checks compile - #688

Merged
kixelated merged 2 commits into
mainfrom
quest/m2/compile-checks
Nov 10, 2026
Merged

kixelated merged 2 commits into
mainfrom
quest/m2/compile-checks

Conversation

@kixelated

@kixelated kixelated commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m2/compile-checks.md: a report on which of rust2-ffi's Swift and C++ run-time checks become compile errors when the generators emit each language's own ownership forms, and what that costs callers.

Problem

AGENTS.md lets a binding check at run time only the Rust rules its language can't express. The Swift and C++ bindings check every move, lend, and use after close at run time. Both languages can express some of these checks, and this spike finds which.

Approach

I prototyped the stricter output for the api fixture by rewriting the generated api.swift and api.hpp with scripts, posted in a comment below, rather than by changing the generators. I then rewrote ffi/tests/api.swift and api.cpp against the prototypes and compiled each test that breaks a contract on its own. Toolchains: Swift 6.3.3 and Clang 21.1.8 on x86_64 Linux, GCC 16.2 for portability, at main (ddf231a1).

Stricter forms prototyped:

  • Swift: each object that isn't Copy becomes a ~Copyable struct. self by value becomes consuming and &mut self becomes mutating. An object parameter becomes borrowing, or consuming when the call moves it in. An object lent to a closure becomes borrowing for &T and inout for &mut T.
  • C++: each object class gets Clang's typestate attributes, checked by -Wconsumed: consumable, callable_when, set_typestate(consumed) on its && methods, and return_typestate on its move. A closure lent &mut T takes api::borrow<T>, which reaches the object only through ->. api::view's range constructor and api::buf::{data, str} get [[clang::lifetimebound]]. All attributes sit behind macros, so GCC and MSVC see nothing.

Each broken contract in the tests today

Test Check today Stricter binding
Swift moved trap "Counter is closed" compile error: 'c' used after consume
Swift boxed moved trap "Node is closed" compile error: 'tail' used after consume
Swift lent moved Rust aborts "a lent handle's value cannot move out" compile error: missing reinitialization of inout parameter 'counter' after consume
Swift lent kept trap "Counter is closed" on the later use compile error: implicit conversion to 'Counter?' is consuming (storing a borrow)
Swift family Rust aborts on IPv6 where SocketAddrV4 is taken stays: a value check, not ownership
Swift stray, lent stray trap "a path from Rust is not UTF-8" stays: a value check, not ownership
Swift futures: an object dropped while a call awaits on it the handle's hold count keeps it alive compile-time guarantee: nothing can drop a value a task's capture borrows, so the test now only shows that the capture keeps it alive
C++ moved, moved from abort on a moved-out or null handle -Wconsumed error: invalid invocation of method 'get' on object 'c' while it is in the 'consumed' state
C++ shared moved from, shared moved out abort on a null or moved-out handle -Wconsumed error, same diagnostic for into_level
C++ lent moved Rust aborts compile error: into_label is &&-qualified and the borrow only yields an lvalue
C++ lent kept abort "a lent object was moved out of" compile error: api::borrow neither moves nor converts. std::move(*counter.operator->()) still compiles, so the run-time check stays as a backstop
C++ lent copied (a shared Gauge copied out of const Gauge &) abort "a lent object was copied past its call" stays: a shared object is copyable
C++ value, finished, null view abort stays: value and state checks Rust makes too
C++, not tested today undefined behavior: a view of a temporary buf -Wdangling error for api::view v = c.name(); and std::string_view s = c.name().str();

Totals: Swift turns 4 of its 7 broken-contract tests into compile errors, plus the futures liveness check. The other 3 aren't ownership. C++ turns 6 of its 10 into compile errors under -Wconsumed -Werror, plus a dangling class that is undefined behavior today. Of the other 4, 3 are value checks and 1 copies a shared object.

Every test that passes today still passes against the prototypes. The Swift transcript matches native Rust except for 6 lines from exports Swift 6.3 can't express (below). futures, owned, and the remaining broken cases behave as before. The C++ transcript matches exactly, and futures, lifetimes, and the remaining broken cases pass at C++17, 20, and 23. The whole C++ script builds warning-free under -Wconsumed -Wdangling -Werror, and under GCC.

What the stricter forms cost callers

Swift

  • A handle called through &mut self must be a var: 11 bindings in the script.
  • Swift 6.3's standard library takes no noncopyable conformer for Equatable, Hashable, CustomStringConvertible, or CustomDebugStringConvertible. So debug(c) becomes c.debugDescription and "\(url)" becomes url.description. == still works as a static operator. SE-0499 lifts this in Swift 6.4.
  • Neither Array nor a tuple holds a noncopyable value in Swift 6.3. That rules out 9 exports of the fixture: Board.new, Board.ranked, Counter.split, Token.twice, logCounts, counters, worth, entries, and numbered. It's the largest cost. A tuple can become a generated named ~Copyable struct. An array needs a noncopyable container: swift-collections' UniqueArray or a generated one.
  • An object thrown as an error (Refusal) stays a class, since Swift.Error requires Copyable.
  • moq-dev/rust2#678's Option<&T> gets worse. A borrowing Counter? parameter can't borrow an existing Counter, because wrapping it in an optional consumes it. Only a handle the caller already holds as Counter? can pass without moving. Keeping the shape would take an overload per optional parameter.
  • A caller's generic code over handles must opt in with T: ~Copyable, as the test's Cell and debug would.
  • An escaping closure may capture a handle let: the capture borrows it and keeps it alive, but can't consume it. A borrowed or inout lent handle can't escape at all, in Swift 5 language mode too.
  • Inside the generator: Native.view and Native.Handle.with need a ~Copyable result. Foundation's Data.withUnsafeBytes and String.withUTF8 return only a Copyable one, so a handle leaves them through an optional.

C++

  • A closure lent &mut T takes api::borrow<T> and calls through ->: one lambda in the script.
  • -Wconsumed is Clang-only, intraprocedural, and doesn't track members. It treats a reference parameter as unknown state, so methods are callable_when("unconsumed", "unknown"). Otherwise every lent object in a caller's lambda warns. The cost of that choice: a move on only one branch goes unflagged.
  • The consumer opts in with -Wconsumed -Wdangling. Nothing changes for one that doesn't, or for GCC or MSVC. MSVC's [[msvc::lifetimebound]] could map the same macro, untried.
  • lifetime_capture_by has no target in today's API. No export keeps a view or a reference past its call: a future owns a copy, and a closure is lent only for the call.

Kotlin, Go, Python, Dart have no such form, as the quest planned.

Speed: the Swift handle's run-time hold count

With the strict forms, Swift's compiler rules out each case Native.Handle's atomic hold count guards: a call after a move, a lend that outlives its call, and a drop during a call. A third variant ("lite") reduced Handle to its pointer and free function: no allocated state, no atomics. It passes the whole rewritten test. Median of 5 interleaved runs, -O, against the debug-built Rust library on a loaded host, so the shares would grow with a release library:

get() call create, call, free
today (class) 24.6 ns 124 ns
strict, hold count kept 24.7 ns 107 ns
strict, no hold count 17.9 ns 97 ns

The one hole is a data race on a non-Sendable class holding a handle, which Swift 5 language mode (moq's package's) doesn't diagnose. That race is already undefined behavior in Swift on today's class references, so the hold count doesn't make it defined.

Which of the parallel PRs' shapes benefit

  • moq-dev/rust2#678 (borrows): its proposed &mut in/out, option A, becomes a Swift inout. Swift then refuses overlapping access at compile time (bump(&c, &c): overlapping accesses to 'c'), with or without ~Copyable. Option<&mut Counter> becomes inout Counter?, exclusive at compile time. Option<&Counter> gets worse in Swift, as above. In C++, its pointers get nothing from typestate.
  • moq-dev/rust2#675 (() and u128): nothing. A u128 is a plain value with no ownership.

Impact

None: the report scopes later changes to the generated Swift and C++. This PR only removes the quest.

Alternatives

  • Only Swift's ownership modifiers on classes: not possible, since a class is always Copyable.
  • Keep Copyable for objects C++ treats as shared (Area, Gauge, Url), whose copy is indistinguishable from a clone, as C++'s detail::shared does. Consuming one would then clone, as C++'s own() does, at the cost of a run-time count for those types. I left this out of the prototype for one uniform rule. It's worth weighing for types moq stores in collections.
  • clang-tidy's bugprone-use-after-move catches what -Wconsumed does without annotations, but it isn't the compiler and wouldn't see a lent borrow.

Decisions

Settled by the maintainer on 2026-11-09.

  1. Swift handles: ✅ switch objects that aren't Copy or errors to non-copyable structs (~Copyable, consuming/borrowing), with tuples as generated named structs and arrays as swift-collections' UniqueArray (rejected: a generated list type of rust2's own; waiting for Swift's Array; no switch).
  2. Swift's per-handle atomic hold count: ✅ drop it once a release-build measurement confirms the gain (rejected: keep it as a backstop).
  3. C++: ✅ adopt all three behind macros: typestate attributes (-Wconsumed), Clang lifetime attributes, and api::borrow<T> for &mut lends, keeping the run-time lend check (rejected: attributes only; no change).

Follow-ups

Two quests for /quest-complete to plan once this PR is queued, each recommended to plan:

  • Swift generator: non-copyable handles (decisions 1 and 2), sized M to L. Emit ~Copyable structs with consuming, borrowing, mutating, and inout; tuples as generated named structs; arrays as UniqueArray; drop the hold count after a release-build measurement. Fold in Swift 6.4 conformances under #if compiler(>=6.4) and a check on Apple platforms (back deployment of Optional<~Copyable> metadata). Interaction with moq-dev/rust2#678: its &mut in/out becomes inout, exclusive at compile time, and Option<&mut T> becomes inout T?; its Option<&T> needs an overload per optional parameter, since wrapping a borrowed handle in an optional consumes it. Whichever lands second adapts to the other.
  • C++ header: typestate, lifetime, and borrow attributes (decision 3), sized S. Interaction with moq-dev/rust2#678: api::borrow<T> is the shape for &mut lends, so its &mut in/out should take or yield it; its Option<&T> pointers get nothing from typestate and keep the run-time check.

Review

Skipped at 2c5b9463f2b99f100b47db552cd13fb068238f98: the branch only deletes quest/m2/compile-checks.md; the report and decisions live in this body.

Models

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

Checks: just ci passed at this head. The prototypes were built and run by hand against the fixture as exported from main. The repo's own test suite did not run: the branch changes only the quest tree.

(Written by Opus 5.5 with medium)

🤖 Generated with Claude Code

Quest-Claim: 19c89b4028b0761d
@pullfrog

pullfrog Bot commented Nov 10, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

…++ checks compile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Spike: the target language's compiler enforces Rust's rules quest: finish the compile-checks spike, reporting which Swift and C++ checks compile Nov 10, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Evidence for the report: the two test scripts rewritten against the stricter bindings, and the scripts that make those bindings from main's generated output (ddf231a1), so the prototype can be rebuilt.

To reproduce: export the api fixture (cargo test --test fixtures -- api_swift writes it to target/tmp/api/out), then run python3 -I strict.py swift/api.swift include/api.h > strict.swift and python3 -I strict_hpp.py include/api.hpp > strict/api.hpp, then rewrite.py and rewrite_cpp.py on the tests. Each broken case lives in its own file: Swift's compile with swiftc -emit-sil, C++'s with clang++ -c -Wconsumed -Wdangling -Werror (Clang 21 skips -Wconsumed under -fsyntax-only).

swift-test.diff
--- ffi/tests/api.swift
+++ api.swift
@@ -149,7 +149,7 @@
 	let d = Point.default()
 	print("default \(d.x) \(d.y)")
 
-	let c = Counter.new("c", 5)
+	var c = Counter.new("c", 5)
 	print("get \(c.get())")
 	print("name \(c.name())")
 	print("add ok \(try! c.add(10))")
@@ -170,16 +170,14 @@
 	c.rename("d")
 	print("name \(c.name())")
 	print("is d \(c.`is`("d"))")
-	let copy = c.clone()
+	var copy = c.clone()
 	_ = try! copy.add(1)
 	print("clone \(copy.get()) original \(c.get())")
-	print("debug \(debug(c))")
+	print("debug \(c.debugDescription)")
 	let label = c.intoLabel(p)
 	print("label \(debug(label))")
 	print("note \(hasText(Note(text: ""))) \(hasText(Note(text: nil)))")
 
-	let (below, above) = Counter.new("s", 7).split(5)
-	print("split \(below.name()) \(below.get()) \(above.name()) \(above.get())")
 	let one = only(Counter.new("o", 3))
 	print("only \(one.name()) \(one.get())")
 	let (w8, w16, w32, w64) = widths(98305)
@@ -208,10 +206,6 @@
 	}
 	let statRows = rows([Point(x: 1, y: 2), Point(x: 3, y: 3)])
 	print("rows \(statRows.map { "\($0.0) \(debug($0.1)) \($0.2.x) \($0.2.y)" }.joined(separator: ", "))")
-	// Each handle in a tuple moves in, as one taken by value does.
-	let board = Board.new([("low", Counter.new("l", 1)), ("high", Counter.new("h", 9))])
-	let ranked = board.ranked()
-	print("ranked \(ranked.map { "\($0.0) \($0.1.name()) \($0.1.get())" }.joined(separator: ", "))")
 	// A map crosses as its pairs and a set as its items, which Rust collects in order, each
 	// key once, the last kept.
 	let atlas = Atlas(points: [("b", Point(x: 1, y: 1)), ("ab", Point(x: 2, y: 3)), ("b", Point(x: 4, y: 4))], ids: [3, 1, 3])
@@ -245,13 +239,13 @@
 	print("title Some((\"\(title(true)!)\",)) \(title(false) == nil ? "None" : "Some") named Some((\"\(named("x")!)\",))")
 
 	// Each lent counter is a new handle to a clone.
-	let registry = Registry.new(Counter.new("first", 1))
+	var registry = Registry.new(Counter.new("first", 1))
 	print("last \(registry.last() != nil)")
 	registry.push(Counter.new("last", 2))
-	let first = registry.firstGet
+	var first = registry.firstGet
 	_ = try! first.add(10)
 	print("first \(registry.firstGet.get()) lent \(first.get())")
-	let last = registry.last()!
+	var last = registry.last()!
 	_ = try! last.add(10)
 	print("last \(registry.last()!.get()) lent \(last.get())")
 	// A closure lent for the call edits the first counter through a handle.
@@ -270,8 +264,8 @@
 	print("shift \(shift({ 7 }, 4))")
 
 	let name = Name.owned("a")
-	let joined = name.join(Name.owned("b"))
-	print("joined \(joined.asStr()) \(debug(joined)) \(joined == name)")
+	var joined = name.join(Name.owned("b"))
+	print("joined \(joined.asStr()) \(joined.debugDescription) \(joined == name)")
 	joined.rename("c")
 	print("renamed \(joined.asStr())")
 	let long = longer(joined, Name.owned("dd"))
@@ -331,7 +325,7 @@
 	print("secs \(stamp.secs())")
 	print("later \(Stamp.fromMillis(3000).later(stamp))")
 	print("secs \(stamp.secs())")
-	let message = Message.new(stamp, Data("body".utf8))
+	var message = Message.new(stamp, Data("body".utf8))
 	print("payload \(text(message.payloadGet))")
 	print("stamp \(debug(message.stampGet!))")
 	print("sent \(message.sent())")
@@ -394,7 +388,7 @@
 	channel.send(Data("rung".utf8))
 	print("woken \(wakes.value)")
 
-	let list = Node.new(1, nil)
+	var list = Node.new(1, nil)
 	list.push(2)
 	print("node \(list.sum())")
 
@@ -420,7 +414,7 @@
 	let beside = PairU32(left: 258, right: 9).besideSize(sized.left)
 	print("beside \(beside.left) \(debug(beside))")
 	// At its default, named as the type itself.
-	let stack = Stack.new()
+	var stack = Stack.new()
 	for (i, n) in ["a", "b"].enumerated() {
 		stack.push(Counter.new(n, UInt64(i) + 1))
 	}
@@ -466,7 +460,7 @@
 	print("dropped \(dropped())")
 
 	// A field and the method of its name, each its own member.
-	let retry = Retry.new(5)
+	var retry = Retry.new(5)
 	let limit = retry.limitGet
 	print("retry \(limit != nil ? 1 : 0) \(limit ?? 0) \(retry.limit()) \(retry.delay())")
 	// The renamed field's setter, named as its getter.
@@ -476,9 +470,9 @@
 
 	// Foreign types: a URL parsed from text, socket addresses, OS paths, and `Vec`s.
 	let base = try! Url.parse("https://example.com/a/b")
-	print("url \(base) host \(host(base).map { "Some(\"\($0)\")" } ?? "None")")
+	print("url \(base.description) host \(host(base).map { "Some(\"\($0)\")" } ?? "None")")
 	let url = try! resolve(base.clone(), "c?d")
-	print("resolve \(url) \(url == base)")
+	print("resolve \(url.description) \(url == base)")
 	do {
 		_ = try Url.parse("no scheme")
 	} catch let e as StringError {
@@ -500,10 +494,6 @@
 	print("sentence \(sentence(words)) \(sentence(nil))")
 	print("sentence \(sentence([]))|")
 	print("chunks \(chunks(Data("abcde".utf8), 2).map { Array($0) })")
-	let counters = counters(["x", "y"])
-	print("counters \(counters.map { "\($0.name()) \($0.get())" }.joined(separator: ", "))")
-	// Each counter moves in, so Swift closes it.
-	print("worth \(worth([counters[0]], [counters[1]])) \(worth(nil, []))")
 	print("held \([heldCount(Counter.new("h", 4)), heldCount(nil)].map { $0.map { "Some(\($0))" } ?? "None" }.joined(separator: " "))")
 	print("boxed \(boxedName(boxedCounter("b")).map { "Some(\"\($0)\")" } ?? "None") \(boxedName(nil) ?? "None") \(boxedCounter(nil) == nil)")
 	print("less \(less(5, 2))")
@@ -517,9 +507,6 @@
 	print("timed text \(timedText(doc))")
 	let tagged = tagged(doc, "t")
 	print("tagged \(tagged.toText())")
-	// A JSON object's entries cross as pairs of a key and its document, each moved in.
-	let jsonEntries = entries([("b", try! Json.parse("[1]")), ("a", try! Json.parse("2"))], "t")
-	print("entries \(jsonEntries.map { "\($0.0) \($0.1.toText())" }.joined(separator: ", "))")
 	do {
 		_ = try Json.parse("{\"a\":")
 	} catch let e as StringError {
@@ -528,17 +515,15 @@
 		check(false, "parse throws its message")
 	}
 	let site = Site.new(base)
-	print("site \(site.homeGet) \(site.homeGet)")
+	print("site \(site.homeGet.description) \(site.homeGet.description)")
 
 	// Slices, passed in and returned as clones, and the socket address of each family.
 	print("shapes \(Shape.ALL) greetings \(GREETINGS)")
-	let trail = Trail.new([Point(x: 1, y: 2), Point(x: 3, y: 4)], ["a", "b"])
+	var trail = Trail.new([Point(x: 1, y: 2), Point(x: 3, y: 4)], ["a", "b"])
 	print("trail \(trail.points()) \(trail.labels())")
 	// Returned iterators, each collected at the call: drained labels are gone after.
 	print("iterated \(trail.iter()) \(trail.names()) \(Array(trail.bytes()))")
 	print("drained \(trail.drain()) then \(trail.labels())")
-	let numbered = numbered(["p", "q"])
-	print("numbered \(numbered.map { "\($0.name()) \($0.get())" }.joined(separator: ", "))")
 	let (next4, next6) = nexts(SocketAddr(ip: [127, 0, 0, 1], port: 8080), v6)
 	print("nexts \(next4.ip.map(String.init).joined(separator: ".")) \(next4.port) \(next6.port) \(next6.scopeId)")
 }
@@ -646,12 +631,14 @@
 	// An object whose last reference goes while a call awaits on it stays alive until the
 	// call ends.
 	do {
-		let other = Cell<Channel?>(Channel.new())
-		let sender = other.value!.clone()
 		let got = Cell<String?>(nil)
-		q.launch { got.value = text(try await other.value!.recv()) }
-		q.drain()
-		other.value = nil
+		let sender: Channel
+		do {
+			let other = Channel.new()
+			sender = other.clone()
+			q.launch { got.value = text(try await other.recv()) }
+			q.drain()
+		}
 		sender.send(Data("held".utf8))
 		q.drain()
 		check(got.value == "held", "the call awaiting on an object holds it")
@@ -688,34 +675,9 @@
 	print("owned ok")
 }
 
-/// Holds a lent counter past the call it was lent for.
-final class Kept: @unchecked Sendable {
-	var counter: Counter?
-}
-
 // Each breaks a contract, which must fail with a message naming it.
 func broken(_ which: String) {
 	switch which {
-	case "moved":
-		let c = Counter.new("c", 0)
-		_ = c.intoLabel(nil)
-		_ = c.get()
-	case "boxed moved":
-		let tail = Node.new(1, nil)
-		_ = Node.new(2, tail)
-		_ = tail.sum()
-	case "lent moved":
-		_ = Registry.new(Counter.new("c", 0)).edit { counter in
-			_ = counter.intoLabel(nil)
-			return false
-		}
-	case "lent kept":
-		let kept = Kept()
-		_ = Registry.new(Counter.new("c", 0)).edit { counter in
-			kept.counter = counter
-			return false
-		}
-		_ = kept.counter!.get()
 	// An IPv6 address where Rust takes `SocketAddrV4`.
 	case "family":
 		let any = SocketAddr(ip: Array(repeating: 0, count: 16), port: 1)
cpp-test.diff
--- ffi/tests/api.cpp
+++ api.cpp
@@ -321,9 +321,9 @@
 	}
 	// A closure lent for the call edits the first counter through its class.
 	std::uint64_t by = 5;
-	bool edited = registry.edit([&](api::Counter &counter) {
-		(void)counter.add(by);
-		return counter.edited() && counter.get() == 6;
+	bool edited = registry.edit([&](api::borrow<api::Counter> counter) {
+		(void)counter->add(by);
+		return counter->edited() && counter->get() == 6;
 	});
 	std::printf("edit %s %llu\n", yes(edited), ull(registry.first_get().get()));
 	// A setter moves an object in, as assignment does.
@@ -1112,23 +1112,7 @@
 
 // Each breaks a contract, which must abort with a message naming the function.
 int broken(const char *which) {
-	if (std::strcmp(which, "moved") == 0) {
-		api::Counter c = api::Counter::new_("c", 0);
-		api::Label label = std::move(c).into_label(std::nullopt);
-		c.get();
-	} else if (std::strcmp(which, "moved from") == 0) {
-		api::Counter c = api::Counter::new_("c", 0);
-		api::Counter d = std::move(c);
-		c.get();
-	} else if (std::strcmp(which, "shared moved from") == 0) {
-		api::Gauge g = api::Gauge::new_(0);
-		api::Gauge h = std::move(g);
-		std::move(g).into_level();
-	} else if (std::strcmp(which, "shared moved out") == 0) {
-		api::Gauge g = api::Gauge::new_(0);
-		std::move(g).into_level();
-		std::move(g).into_level();
-	} else if (std::strcmp(which, "lent copied") == 0) {
+	if (std::strcmp(which, "lent copied") == 0) {
 		static std::optional<api::Gauge> kept;
 		api::watch(api::Gauge::new_(0), [](const api::Gauge &lent) {
 			kept.emplace(lent);
@@ -1146,19 +1130,6 @@
 		recv.poll(waker);
 	} else if (std::strcmp(which, "null view") == 0) {
 		api::len(api::view(nullptr, 1));
-	} else if (std::strcmp(which, "lent moved") == 0) {
-		api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0));
-		registry.edit([](api::Counter &counter) {
-			api::Label label = std::move(counter).into_label(std::nullopt);
-			return false;
-		});
-	} else if (std::strcmp(which, "lent kept") == 0) {
-		static std::optional<api::Counter> kept;
-		api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0));
-		registry.edit([](api::Counter &counter) {
-			kept.emplace(std::move(counter));
-			return false;
-		});
 	}
 	return 0;
 }
lite.diff
--- strict/api.swift
+++ lite/api.swift
@@ -266,83 +266,26 @@
 	/// A handle an object owns, or is lent for a call: each call holds it, so `close`,
 	/// once a call moves its value out or a lend ends, frees it only once no call does, and
 	/// a call after `close` traps.
+	/// A handle an object owns, or is lent for a call. The type system orders every call
+	/// before the move or the end of the lend, so nothing is counted at run time.
 	struct Handle {
 		let raw: OpaquePointer?
-		/// The holds, one for the owner until closed and one per call, shifted past a bit
-		/// set once closed.
-		let state: UnsafeMutablePointer<Int>
 		/// Null for a handle Rust lends, which it frees itself.
 		let free: (@convention(c) (OpaquePointer?) -> Void)?
-		let type: StaticString
-
-		static let closed = 1
-		static let held = 2
 
 		init(_ raw: OpaquePointer?, _ free: (@convention(c) (OpaquePointer?) -> Void)?, _ type: StaticString) {
 			self.raw = raw
 			self.free = free
-			self.type = type
-			state = UnsafeMutablePointer<Int>.allocate(capacity: 1)
-			state.initialize(to: Handle.held)
-		}
-
-		func hold() -> OpaquePointer? {
-			var n = load(state)
-			while true {
-				if n & Handle.closed != 0 {
-					fatalError("\(type) is closed")
-				}
-				let seen = swap(state, n, n + Handle.held)
-				if seen == n {
-					return raw
-				}
-				n = seen
-			}
-		}
-
-		func release() {
-			if add(state, -Handle.held) < Handle.held {
-				free?(raw)
-			}
-		}
-
-		/// Run `body` with the raw handle, held until it returns.
-		@inline(__always)
-		func with<R: ~Copyable>(_ body: (OpaquePointer?) throws -> R) rethrows -> R {
-			let raw = hold()
-			defer { release() }
-			return try body(raw)
-		}
-
-		func close() {
-			var n = load(state)
-			while n & Handle.closed == 0 {
-				let seen = swap(state, n, (n | Handle.closed) - Handle.held)
-				if seen == n {
-					if (n | Handle.closed) - Handle.held < Handle.held {
-						free?(raw)
-					}
-					return
-				}
-				n = seen
-			}
 		}
 
-		/// End a lend, closing the handle, which Rust frees next: a call still holding it
-		/// would reach the freed handle, so it traps, as Rust's own check does once a call
-		/// borrowed.
-		func end() {
-			close()
-			if load(state) != Handle.closed {
-				fatalError("\(type): a call still holds it when its lend ends")
-			}
-		}
-
-		/// Close it, for its owner's `deinit`, when no call can hold it.
-		func drop() {
-			close()
-			state.deallocate()
-		}
+		@inline(__always) func hold() -> OpaquePointer? { raw }
+		@inline(__always) func release() {}
+		@inline(__always) func with<R: ~Copyable>(_ body: (OpaquePointer?) throws -> R) rethrows -> R { try body(raw) }
+		@inline(__always) func close() {}
+		@inline(__always) func end() {}
+		/// Free it, for its owner's `deinit`: after a call moved the value out, Rust left
+		/// the handle empty for this to free.
+		func drop() { free?(raw) }
 	}
 
 	/// Run `body` with the raw handle of `handle`, or null for none, held until it returns.
strict.py
"""Rewrite the generated api.swift into its stricter form: each object that is not `Copy`
becomes a `~Copyable` struct, `self` by value `consuming`, `&mut self` `mutating`, an object
parameter `borrowing` or, when the call moves it in, `consuming`, and an object lent to a
closure `borrowing` or, for `&mut`, `inout`. What Swift 6.3 cannot express is commented out
and listed on stderr.

usage: python3 -I strict.py api.swift api.h > strict.swift
"""

import re
import sys

src = open(sys.argv[1]).read()
header = open(sys.argv[2]).read()

classes = re.findall(r"^public final class (\w+):", src, re.M)
COPY = {"Stamp", "Refusal"}
objects = [c for c in classes if c not in COPY]
obj = r"\b(?:" + "|".join(objects) + r")\b"


def const_self(cfn):
    m = re.search(r"\b" + cfn + r"\((const )?api_\w+ \*self", header)
    return m is not None and m.group(1) is not None


def callback_const(cfn):
    """Whether each object a C function's callback is lent is const."""
    m = re.search(r"\b" + cfn + r"\(([^;]*)\);", header)
    if not m:
        return []
    cb = re.search(r"\(\*\w+\)\(void \*data([^)]*)\)", m.group(1))
    if not cb:
        return []
    return [p.strip().startswith("const ") for p in cb.group(1).split(",")[1:]]


lines = src.split("\n")
out = []
dropped = []


def block_end(i):
    """The index of the line closing the declaration opened on line i."""
    depth = 0
    for j in range(i, len(lines)):
        depth += lines[j].count("{") - lines[j].count("}")
        if depth == 0 and j > i or (depth == 0 and "{" in lines[j]):
            return j
    raise ValueError(i)


def split_params(sig):
    """The top-level parameters of a signature's parameter list."""
    start = sig.index("(")
    depth = 0
    for k in range(start, len(sig)):
        if sig[k] in "([<":
            depth += 1
        elif sig[k] in ")]>" and not (sig[k] == ">" and sig[k - 1] == "-"):
            depth -= 1
            if depth == 0:
                end = k
                break
    params, depth, cur = [], 0, ""
    for ch in sig[start + 1 : end]:
        if ch in "([<":
            depth += 1
        elif ch in ")]>":
            depth -= 1
        if ch == "," and depth == 0:
            params.append(cur)
            cur = ""
        else:
            cur += ch
    if cur.strip():
        params.append(cur)
    return start, end, params


def unsupported(t):
    """A type naming an object inside an array or tuple, which Swift 6.3 cannot hold."""
    return re.search(r"\[[^\]]*" + obj, t) or re.search(r"\((?:[^()]*,)?\s*[^()]*" + obj + r"[^()]*,", t) or re.search(r"\([^()]*,[^()]*" + obj, t)


i = 0
current = None
while i < len(lines):
    line = lines[i]
    m = re.match(r"^public final class (\w+): (.*) \{$", line)
    if m:
        current = m.group(1)
        if current in objects:
            out.append(f"public struct {current}: ~Copyable, @unchecked Sendable {{")
        else:
            out.append(line)
        i += 1
        continue
    if line == "}":
        current = None
    fm = re.match(r"^(\t?)(public )?(static )?func ([^(]+)\(", line)
    if fm and (fm.group(1) == "" or current is not None):
        end = block_end(i)
        body = "\n".join(lines[i : end + 1])
        sig = line
        _, close, params = split_params(sig)
        ret = sig[close + 1 :]
        name = fm.group(4).strip()
        if unsupported(ret) or any(unsupported(p.split(":", 1)[1]) for p in params if ":" in p and "->" not in p.split(":", 1)[1]):
            dropped.append(f"{current + '.' if current else ''}{name}{sig[sig.index('('):close + 1]}{ret.rstrip(' {')}")
            out.extend("// " + l for l in lines[i : end + 1])
            i = end + 1
            continue
        new = []
        swaps = []
        for p in params:
            label, _, ty = p.partition(":")
            pname = label.split()[-1]
            t = ty.strip()
            if re.fullmatch(obj + r"\??", t):
                moved = re.search(r"\b" + pname + r"\??\.handle\.close\(\)", body) or re.search(r"if let (\w+) = " + pname + r" \{ \1\.handle\.close\(\)", body)
                new.append(f"{label}: {'consuming' if moved else 'borrowing'} {t}")
            elif "->" in t and re.search(obj, t):
                cfn = re.search(r"\b(api_\w+)\(", body).group(1)
                consts = callback_const(cfn)
                inner = re.match(r"(@Sendable )?\((.*?)\) ->", t)
                args = [a.strip() for a in inner.group(2).split(",")]
                k = 0
                fixed = []
                for a in args:
                    if re.fullmatch(obj, a):
                        c = consts[k] if k < len(consts) else True
                        fixed.append(("borrowing " if c else "inout ") + a)
                        if not c:
                            body_new = None
                    else:
                        fixed.append(a)
                    k += 1
                t2 = t.replace("(" + inner.group(2) + ")", "(" + ", ".join(fixed) + ")", 1)
                new.append(f"{label}: {t2}")
                swaps.append((t, t2, [k for k, a in enumerate(fixed) if a.startswith("inout ")]))
            else:
                new.append(p.strip() if not new else p.strip())
        sig2 = sig[: sig.index("(") + 1] + ", ".join(new) + sig[close:]
        if current in objects and (fm.group(2) or fm.group(1)) and not fm.group(3) and not name.startswith("=="):
            if "defer { self.handle.close() }" in body:
                sig2 = sig2.replace("func ", "consuming func ", 1)
            else:
                cm = re.search(r"\b(api_\w+)\(h_", body)
                if cm and not const_self(cm.group(1)):
                    sig2 = sig2.replace("func ", "mutating func ", 1)
        if name.startswith("==") and current in objects:
            sig2 = re.sub(r"\(a: (\w+), b: (\w+)\)", r"(a: borrowing \1, b: borrowing \2)", sig2)
        out.append(sig2)
        # A closure lent an object for `&mut` takes it inout.
        rest = "\n".join(lines[i + 1 : end + 1])
        for old, new_t, inouts in swaps:
            rest = rest.replace(old, new_t)
            for k in inouts:
                rest = re.sub(r"let l" + str(k) + r"_ = ", "var l" + str(k) + "_ = ", rest)
                rest = re.sub(r"f_\((.*?)\bl" + str(k) + r"_\b", lambda m: "f_(" + m.group(1) + "&l" + str(k) + "_", rest)
        out.extend(rest.split("\n"))
        i = end + 1
        continue
    out.append(line)
    i += 1

text = "\n".join(out)
# The runtime's helpers return whatever their body does, a handle included.
text = re.sub(r"func (view|with)<R>\(", r"func \1<R: ~Copyable>(", text)
# Foundation's closures return only a copyable result, so a handle leaves through an
# optional the closure fills.
text = text.replace(
    """		try bytes.withUnsafeBytes { raw in
			let ptr = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) ?? UnsafePointer(empty)
			return try body(Buf(ptr: ptr, len: raw.count, owner: nil))
		}""",
    """		var r: R? = nil
		try bytes.withUnsafeBytes { raw in
			let ptr = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) ?? UnsafePointer(empty)
			r = try body(Buf(ptr: ptr, len: raw.count, owner: nil))
		}
		return r.take()!""",
)
text = text.replace(
    """		return try text.withUTF8 { utf8 in
			try body(Buf(ptr: utf8.baseAddress ?? UnsafePointer(empty), len: utf8.count, owner: nil))
		}""",
    """		var r: R? = nil
		try text.withUTF8 { utf8 in
			r = try body(Buf(ptr: utf8.baseAddress ?? UnsafePointer(empty), len: utf8.count, owner: nil))
		}
		return r.take()!""",
)


# Optional.map takes only a copyable value: a switch borrows it instead.
def unmap(m):
    if not re.search(obj, m.group(0)) and m.group(1) not in params_of_objects:
        return m.group(0)
    return (
        f"{{ () -> {m.group(3)} in switch {m.group(1)} {{ case .some(let {m.group(2)}): "
        f"return {m.group(3)}(some: true, value: {m.group(4)}); case .none: return {m.group(5)}() }} }}()"
    )


params_of_objects = set(re.findall(r"_ (\w+): (?:consuming|borrowing) " + obj + r"\?", text))
text = re.sub(
    r"\((\w+)\.map \{ (\w+) in (api_option_\w+)\(some: true, value: (.*?)\) \} \?\? (api_option_\w+)\(\)\)",
    unmap,
    text,
)
# `if let` consumes an optional, which a `defer` cannot: a switch borrows it.
text = re.sub(
    r"if let (\w+) = (\w+) \{ \1\.handle\.close\(\) \}",
    r"switch \2 { case .some(let \1): \1.handle.close(); case .none: break }",
    text,
)
sys.stdout.write(text)
for d in dropped:
    print("unsupported:", d, file=sys.stderr)
rewrite.py
import re, sys
s = open(sys.argv[1]).read()
def sub(old, new, count=1):
    global s
    if old not in s:
        sys.exit(f"missing: {old!r}")
    s = s.replace(old, new, count)
# Handles called through `&mut self` are vars.
for v in ["c = Counter.new(\"c\", 5)", "copy = c.clone()", "registry = Registry.new(", "first = registry.firstGet",
          "last = registry.last()!", "joined = name.join(", "message = Message.new(", "list = Node.new(",
          "stack = Stack.new()", "retry = Retry.new(", "trail = Trail.new("]:
    sub("let " + v, "var " + v)
# No stdlib protocol takes a noncopyable conformer before Swift 6.4: Debug and Display
# are members.
sub('print("debug \\(debug(c))")', 'print("debug \\(c.debugDescription)")')
sub('\\(debug(joined))', '\\(joined.debugDescription)')
sub('print("url \\(base) host', 'print("url \\(base.description) host')
sub('print("resolve \\(url) \\(url == base)")', 'print("resolve \\(url.description) \\(url == base)")')
sub('print("site \\(site.homeGet) \\(site.homeGet)")', 'print("site \\(site.homeGet.description) \\(site.homeGet.description)")')
# An array or tuple holds no noncopyable value in Swift 6.3: what returns or takes one is
# left out of the export.
sub('''	let (below, above) = Counter.new("s", 7).split(5)
	print("split \\(below.name()) \\(below.get()) \\(above.name()) \\(above.get())")
''', '')
sub('''	// Each handle in a tuple moves in, as one taken by value does.
	let board = Board.new([("low", Counter.new("l", 1)), ("high", Counter.new("h", 9))])
	let ranked = board.ranked()
	print("ranked \\(ranked.map { "\\($0.0) \\($0.1.name()) \\($0.1.get())" }.joined(separator: ", "))")
''', '')
sub('''	let counters = counters(["x", "y"])
	print("counters \\(counters.map { "\\($0.name()) \\($0.get())" }.joined(separator: ", "))")
	// Each counter moves in, so Swift closes it.
	print("worth \\(worth([counters[0]], [counters[1]])) \\(worth(nil, []))")
''', '')
sub('''	// A JSON object's entries cross as pairs of a key and its document, each moved in.
	let jsonEntries = entries([("b", try! Json.parse("[1]")), ("a", try! Json.parse("2"))], "t")
	print("entries \\(jsonEntries.map { "\\($0.0) \\($0.1.toText())" }.joined(separator: ", "))")
''', '')
sub('''	let numbered = numbered(["p", "q"])
	print("numbered \\(numbered.map { "\\($0.name()) \\($0.get())" }.joined(separator: ", "))")
''', '')
# An object a task awaits on: the task's capture keeps it alive, and nothing can drop it
# while the task borrows it, so the case is a compile-time guarantee, not a check.
sub('''		let other = Cell<Channel?>(Channel.new())
		let sender = other.value!.clone()
		let got = Cell<String?>(nil)
		q.launch { got.value = text(try await other.value!.recv()) }
		q.drain()
		other.value = nil
		sender.send''', '''		let got = Cell<String?>(nil)
		let sender: Channel
		do {
			let other = Channel.new()
			sender = other.clone()
			q.launch { got.value = text(try await other.recv()) }
			q.drain()
		}
		sender.send''')
# A registry an edit borrows mutably is a var.
s = s.replace('_ = Registry.new(Counter.new("c", 0)).edit {', 'var registry = Registry.new(Counter.new("c", 0))\n\t\t_ = registry.edit {')
open(sys.argv[2], "w").write(s)
# The ownership contracts no longer compile: each is its own file, which swiftc must refuse.
s = open(sys.argv[2]).read()
cases = {
    "moved": '''	let c = Counter.new("c", 0)
	_ = c.intoLabel(nil)
	_ = c.get()''',
    "boxed moved": '''	let tail = Node.new(1, nil)
	_ = Node.new(2, tail)
	_ = tail.sum()''',
    "lent moved": '''	var registry = Registry.new(Counter.new("c", 0))
	_ = registry.edit { counter in
		_ = counter.intoLabel(nil)
		return false
	}''',
    "lent kept": '''	let kept = Kept()
	var registry = Registry.new(Counter.new("c", 0))
	_ = registry.edit { counter in
		kept.counter = counter
		return false
	}
	_ = kept.counter!.get()''',
}
start = s.index('\tcase "moved":')
end = s.index('\t// An IPv6 address where Rust takes `SocketAddrV4`.')
s = s[:start] + s[end:]
s = s.replace('''/// Holds a lent counter past the call it was lent for.
final class Kept: @unchecked Sendable {
	var counter: Counter?
}

''', '')
open(sys.argv[2], "w").write(s)
for name, body in cases.items():
    extra = "final class Kept: @unchecked Sendable {\n\tvar counter: Counter?\n}\n\n" if name == "lent kept" else ""
    open("case-" + name.replace(" ", "-") + ".swift", "w").write(f"import Api\n\n{extra}func broken() {{\n{body}\n}}\n")
strict_hpp.py
"""Rewrite the generated api.hpp into its stricter form for Clang: each object a
`consumable` whose moved-from state -Wconsumed tracks, a lent `&T` passed as `const T &`, a
lent `&mut T` as a borrow exposing only `->`, and a view or buffer's bytes `lifetimebound`.

usage: python3 -I strict_hpp.py api.hpp > strict.hpp
"""

import re
import sys

src = open(sys.argv[1]).read()

LB = "API_LIFETIMEBOUND"
prelude = f"""#if defined(__clang__)
#define {LB} [[clang::lifetimebound]]
#define API_CONSUMABLE [[clang::consumable(unconsumed)]]
#define API_CALLABLE [[clang::callable_when("unconsumed", "unknown")]]
#define API_CONSUMES [[clang::callable_when("unconsumed", "unknown"), clang::set_typestate(consumed)]]
#define API_UNCONSUMED [[clang::return_typestate(unconsumed)]]
#define API_MOVED_FROM [[clang::return_typestate(consumed)]]
#else
#define {LB}
#define API_CONSUMABLE
#define API_CALLABLE
#define API_CONSUMES
#define API_UNCONSUMED
#define API_MOVED_FROM
#endif
"""


def rep(old, new):
    global src
    if old not in src:
        sys.exit(f"missing: {old!r}")
    src = src.replace(old, new, 1)


rep("namespace api {", prelude + "\nnamespace api {")

# A view of a temporary dangles.
rep("view(const R &range) noexcept : view(range.data(), range.size()) {}",
    f"view(const R &range {LB}) noexcept : view(range.data(), range.size()) {{}}")
# The bytes and text of a buffer live as long as it does.
for decl in ["const ::std::uint8_t *data() const noexcept {\n\t\treturn detail::bytes(raw_.ptr, raw_.len);",
             "::std::string_view str() const noexcept {\n\t\treturn view(*this).str();"]:
    head, _, rest = decl.partition(" noexcept {")
    rep(decl, f"{head} noexcept {LB} {{{rest}")

rep("// Bytes lent to a call: text or binary", """// An object lent for a call as `&mut`: its methods through `->`, never a move out of it.
template <class T>
class borrow {
	T &object_;

public:
	explicit borrow(T &object) noexcept : object_(object) {}
	borrow(const borrow &) = delete;
	borrow &operator=(const borrow &) = delete;
	T *operator->() const noexcept {
		return &object_;
	}
};

// Bytes lent to a call: text or binary""")

# Objects: a consumable whose `&&` methods and moves leave it consumed.
objects = re.findall(r"^class (\w+) \{\n\t::api::detail::(?:unique|shared)<", src, re.M)
for name in objects:
    m = re.search(r"^class " + name + r" \{\n(.*?)^\};", src, re.M | re.S)
    body = m.group(1)
    shared = "detail::shared<" in body
    lines = []
    public = False
    for line in body.split("\n"):
        if line == "public:":
            public = True
            lines.append(line)
            moves = [
                f"\tAPI_UNCONSUMED {name}({name} &&other API_MOVED_FROM) noexcept = default;",
                f"\t{name} &operator=({name} &&other) noexcept = default;",
            ]
            if shared:
                moves += [f"\tAPI_UNCONSUMED {name}(const {name} &other) = default;",
                          f"\t{name} &operator=(const {name} &other) = default;"]
            lines += moves
            continue
        dm = re.match(r"^\t(?![\t}]|static |friend |explicit |///|template)(.+\)( const)?( &&)?;)$", line)
        if public and dm and "operator" not in line:
            attr = "API_CONSUMES" if line.endswith("&&;") else "API_CALLABLE"
            lines.append(f"\t{attr} {dm.group(1)}")
            continue
        if public and line.startswith("\ttemplate <") is False and re.match(r"^\ttemplate <", line):
            lines.append(line)
            continue
        lines.append(line)
    src = src.replace(m.group(0), f"class API_CONSUMABLE {name} {{\n" + "\n".join(lines) + "};", 1)

# Templates (methods taking a lent closure) are callable only unconsumed too.
src = re.sub(r"^\ttemplate <(class F\d+(?:, class F\d+)*)>\n\t(?!static )(\S.*\);)$",
             r"\ttemplate <\1>\n\tAPI_CALLABLE \2", src, flags=re.M)

# A lent `&T` is passed as `const T &`, and a lent `&mut T` as a borrow.
src = re.sub(r"(::api::detail::lent<(::api::\w+), const ::api_\w+> (l\d+_)\(p\d+_\);\n(.*?))\b\3\.get\(\)",
             lambda m: m.group(1) + f"static_cast<const {m.group(2)} &>({m.group(3)}.get())", src, flags=re.S)
src = re.sub(r"(::api::detail::lent<(::api::\w+), ::api_\w+> (l\d+_)\(p\d+_\);\n(.*?))\b\3\.get\(\)",
             lambda m: m.group(1) + f"::api::borrow<{m.group(2)}>({m.group(3)}.get())", src, flags=re.S)

sys.stdout.write(src)
print("objects:", len(objects), file=sys.stderr)
rewrite_cpp.py
import sys
s = open(sys.argv[1]).read()
def sub(old, new):
    global s
    if old not in s:
        sys.exit(f"missing: {old!r}")
    s = s.replace(old, new, 1)
# A closure lent `&mut` takes a borrow, calling through `->`.
sub('''	bool edited = registry.edit([&](api::Counter &counter) {
		(void)counter.add(by);
		return counter.edited() && counter.get() == 6;
	});''', '''	bool edited = registry.edit([&](api::borrow<api::Counter> counter) {
		(void)counter->add(by);
		return counter->edited() && counter->get() == 6;
	});''')
# The contracts Clang now refuses: each its own file, compiled with -Wconsumed -Werror.
cases = {}
for name, start, end in [
    ("moved", '\tif (std::strcmp(which, "moved") == 0) {\n', '\t} else if (std::strcmp(which, "moved from") == 0) {\n'),
    ("moved from", '\t} else if (std::strcmp(which, "moved from") == 0) {\n', '\t} else if (std::strcmp(which, "shared moved from") == 0) {\n'),
    ("shared moved from", '\t} else if (std::strcmp(which, "shared moved from") == 0) {\n', '\t} else if (std::strcmp(which, "shared moved out") == 0) {\n'),
    ("shared moved out", '\t} else if (std::strcmp(which, "shared moved out") == 0) {\n', '\t} else if (std::strcmp(which, "lent copied") == 0) {\n'),
    ("lent moved", '\t} else if (std::strcmp(which, "lent moved") == 0) {\n', '\t} else if (std::strcmp(which, "lent kept") == 0) {\n'),
    ("lent kept", '\t} else if (std::strcmp(which, "lent kept") == 0) {\n', '\t}\n\treturn 0;\n}\n'),
]:
    a = s.index(start) + len(start)
    b = s.index(end, a)
    cases[name] = s[a:b]
    s = s[:s.index(start)] + (start.replace("\t} else if", "\tif") if False else "") + s[b:]
# The first case removed leaves the chain starting at `} else if`: restore its head.
s = s.replace('int broken(const char *which) {\n\t} else if (std::strcmp(which, "lent copied") == 0) {', 'int broken(const char *which) {\n\tif (std::strcmp(which, "lent copied") == 0) {')
s = s.replace('''	} else if (std::strcmp(which, "null view") == 0) {
		api::len(api::view(nullptr, 1));
	}''', '''	} else if (std::strcmp(which, "null view") == 0) {
		api::len(api::view(nullptr, 1));
	}
	return 0;
}''') if False else s
open(sys.argv[2], "w").write(s)
for name, body in cases.items():
    open("cpp/case-" + name.replace(" ", "-") + ".cpp", "w").write(
        "#include <optional>\n#include \"api.hpp\"\n\nvoid broken() {\n" + body.replace("\n\t\t", "\n\t").lstrip("\t").join(["\t", ""]) + "}\n")
bench.swift
import Api

@main
struct Bench {
	static func main() {
		let clock = ContinuousClock()
		var sum: UInt64 = 0
		let c = Counter.new("c", 1)
		let calls = 20_000_000
		var start = clock.now
		for _ in 0..<calls { sum &+= c.get() }
		let get = (clock.now - start) / calls
		let objects = 2_000_000
		start = clock.now
		for i in 0..<objects {
			let x = Counter.new("x", UInt64(i))
			sum &+= x.get()
		}
		let made = (clock.now - start) / objects
		print("get \(get) new+get+free \(made) (\(sum))")
	}
}
case-lent-moved.cpp (by hand)
#include <optional>
#include "api.hpp"

void broken() {
	api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0));
	registry.edit([](api::borrow<api::Counter> counter) {
		api::Label label = std::move(counter)->into_label(std::nullopt);
		return false;
	});
}
case-lent-kept.cpp (by hand)
#include <optional>
#include "api.hpp"

void broken() {
	static std::optional<api::Counter> kept;
	api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0));
	registry.edit([](api::borrow<api::Counter> counter) {
		kept.emplace(std::move(counter));
		return false;
	});
}
case-lent-kept-raw.cpp (by hand)
#include <optional>
#include "api.hpp"

// The borrow's pointer still reaches the object: moving through it compiles, and the
// lend's run-time check refuses it.
void broken() {
	static std::optional<api::Counter> kept;
	api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0));
	registry.edit([](api::borrow<api::Counter> counter) {
		kept.emplace(std::move(*counter.operator->()));
		return false;
	});
}
case-dangling-view.cpp (by hand)
#include "api.hpp"

// A view of a temporary buffer, and the text of one: both dangle at the semicolon.
std::size_t broken() {
	api::view name = api::Counter::new_("c", 0).name();
	std::string_view text = api::Counter::new_("c", 0).name().str();
	return name.size() + text.size();
}

(Written by Opus 5.5 with medium)

@kixelated
kixelated marked this pull request as ready for review November 10, 2026 04:34
@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 49 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: 1158b5c0-8131-4f8a-bb79-b3a61cecc4dd
Commits

Reviewing files that changed from the base of the PR and between ddf231a and 2c5b946.

Files selected for processing: 2

  • quest/m2/README.md
  • quest/m2/compile-checks.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
kixelated added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit 6c1394b Nov 10, 2026
13 checks passed
@kixelated
kixelated deleted the quest/m2/compile-checks branch November 10, 2026 04:56
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