Skip to content

feat(ffi)!: keep every clashing name apart instead of failing the export - #696

Open
kixelated wants to merge 7 commits into
mainfrom
quest/m1/ffi-export/names
Open

kixelated wants to merge 7 commits into
mainfrom
quest/m1/ffi-export/names

Conversation

@kixelated

@kixelated kixelated commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m1/ffi-export/names.md: no name clash fails an export any more. Each language keeps apart only the names that clash in it, and renames nothing in other languages. C names stay Rust's wherever they don't clash.

Summary

  • C (abi.rs): every C name is still registered, but each claim now records the item it belongs to. A clash renames items until no clash is left:
    • Items whose own names clash each keep a doubled _ at every split after the segments they share, so audio::encoder_finish and audio_encoder::finish become api_clash_audio__encoder_finish and api_clash_audio_encoder__finish. One whose remaining path is a single segment keeps its name, the same rule apart uses for Pascal case.
    • An item that still clashes takes a trailing _, as do the names made from it. This covers a clash with a runtime name, with a name made from a type (a method free beside <type>_free, an inherent to_string beside Display's), or between two tuples (((u8, u16), u32) and ((u8, u16, u32),)). The keeper comes first by rank (the runtime, then names made from an item, then items' own names), then by what claims it.
    • Export::named carries the unrenamed C name, which the other generators name members by. So a C-only rename (Store_free_) stays free() in Kotlin, Python, Swift, Go, and Dart. C++ keeps naming by C names, as its README says.
  • Types in every Pascal-case language (classes): after apart and Go's module segment (moq-dev/rust2#683), names that still match go through abi::unique. A name that matches by case (Tone/TONE), or that the runtime or a range's class takes, gets a trailing _. The type whose last segment is already in Pascal case keeps the plain name, then the rest go by C name. Types are registered before any other top-level name, so they win.
  • Other top-level names (functions, constants, exception and error classes): abi::claim adds a trailing _ where a type, the runtime, or an earlier name already has the name. This applies in Kotlin, Python, Swift (by full name), Dart, and Go.
  • Members: a method named as a field, or as a member its type declares before it, gets a trailing _ in Swift, Go, Dart, Kotlin, and Python. distinct stays as an internal check, and its message no longer tells the author to rename.
  • Derived names that are referenced elsewhere are computed up front:
    • Dart: data-enum variant classes.
    • Go: variant names and error types per package.
    • Dart's glue namespace <prefix>_dart gains a _ while any C name of the export starts with it. Before, such a C name was refused.
  • Fixture: the api fixture gains clash and Cancelled. They cover types apart only by case, a method named as its field, inherent to_string beside Display, methods free and close, a name Dart's runtime declares, the audio split, and the nested tuples. All 7 languages call them and match the native transcript. The reject fixture drops its two clash cases.

Public API

Breaking only for names that failed to export before. Every name that exported before is unchanged, with two exceptions:

  • A Go or Pascal-case type literally named as a range's class (RangeU64) now takes a _.
  • The Dart glue's symbols move only when a C name of the export starts with <prefix>_dart_. Before, that export failed.

Choices made without asking

  • C split uses __ (the quest's recommendation). C++ reserves identifiers containing __, and the C++ wrapper both includes and re-declares these names. GCC and Clang accept them under -Wall -Wextra -pedantic, but strictly this is undefined behavior. The alternative is a plain trailing _ for C too, which hides where the paths split. I kept __ because only clashing names ever get it.
  • Functions in camel/Pascal-case languages take a trailing _ instead of a split _. clashAudioEncoderFinish and clashAudioEncoderFinish_: those generators name functions from the full joined path, so apart's split would need a second naming path for functions.
  • Kotlin/Python/Swift/Dart range classes are taken inside classes. Go shares that code, so a Go type named like a range class also gets a _, even though Go keeps ranges in rt.
  • Go fields give way to interface methods. A record field that would take String, GoString, Equal, or Error (on an error) gets a trailing _, so the method keeps the name its interface needs.

Remaining edges, not reachable from moq

  • In C, two names one item makes alike still fail with "are both … in C". Example: an enum with variants A and a, which needs #[allow(non_camel_case_types)]. Renaming the enum cannot separate its own variants.

Suggested follow-ups

  • Drop: per-variant renames for the enum residue above. Rust lints both variants, and no consumer has them.

Caused by

No single PR. The generators refused clashes by design from the start.

Models

Size: M
Implementer: Claude Code, Opus 5.5, medium effort

(Written by Claude Opus 5.5 with medium)

@pullfrog

pullfrog Bot commented Nov 10, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 4 commits November 10, 2026 13:48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ffi/fixtures/reject/expected.txt
#	ffi/fixtures/reject/src/lib.rs
#	ffi/src/abi.rs
#	quest/m1/ffi-export/README.md
@kixelated kixelated changed the title No name clash fails an export feat(ffi)!: keep every clashing name apart instead of failing the export Nov 10, 2026
kixelated and others added 2 commits November 10, 2026 14:04
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate callback tables with the types

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 10, 2026 22:18
@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 22 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: e71b214c-0a44-4953-8f2e-4183a1648f9b
Commits

Reviewing files that changed from the base of the PR and between efbfa66 and 23125e2.

Files selected for processing: 21

  • 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/src/abi.rs
  • ffi/src/cpp.rs
  • ffi/src/dart.rs
  • ffi/src/go.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
  • quest/m1/ffi-export/names.md
  • quest/m1/ffi-export/refusals/README.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

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