Fixups

From docs/fixups.md at 72c38f3 View on GitHub

A generated API is not always the API you want to ship. A fixup is a declarative edit applied to the model after the drivers have run and before anything is emitted, so the result is still generated code you never hand-edit.

fixups:
  - match: { symbol: "Client.*Legacy*" }
    hide: true
  - match: { platform: android, symbol: "com.example.Client#connect$2" }
    rename: connectWithTimeout

Fixups apply in the order they are written, each to whatever the previous ones left, so a rename followed by a match on the new name works and a match on the old one no longer does.

Matching

match.symbol is a glob, in one of three spellings:

SpellingMatches
Client or com.example.Clienta declaration, by its Dart name or its native identifier
Client.connectmembers of declarations named Client
com.example.Client#connectmembers, with the declaration matched by native identifier

* stands for any run of characters and ? for one. Everything else is literal, including the $ in a jnigen overload suffix, so com.example.Client#connect$2 needs no escaping.

match.platform restricts a fixup to one platform. Leave it out and the fixup applies to every platform that has the symbol — which is usually what you want for a rename, and rarely what you want for a threading annotation.

What a fixup can do

KeyEffect
renameGive the declaration or member a different Dart name.
hideKeep it in the model, marked as dropped, and never emit it.
threadingmain or any: declare which thread the symbol has to be called on. On a declaration this applies to every member.
nullabilityreturns: nonnull | nullable | unknown, overriding what the driver inferred.
ackRemove the verify markers: a human has reviewed this symbol.

Recipes

Hide a deprecated family. Hiding keeps the symbol in bindsmith dump with a marker saying a fixup hid it, so a reviewer can see the API was trimmed rather than never generated.

- match: { symbol: "*_deprecated_*" }
  hide: true

Fix a name a generator had to mangle. jnigen numbers overloads, and connect$2 tells a reader nothing:

- match: { platform: android, symbol: "com.example.Client#connect$2" }
  rename: connectWithTimeout

Declare a main-thread requirement. Nothing in a C or Objective-C header says a function must be called on the platform thread, but the documentation does, and calling it elsewhere fails in ways that look like corruption:

- match: { platform: ios, symbol: "AVPlayer.play" }
  threading: main

Tighten a nullability a driver had to guess. An Objective-C header with no audited regions yields unknown, which becomes nullable plus a verify marker. When you have read the implementation, say so:

- match: { symbol: "Client.fetch" }
  nullability: { returns: nonnull }

Acknowledge a marker you have reviewed. ack removes the verify markers from one symbol and nothing else, which is what stops bindsmith verify failing on it. Use it per symbol, never with a broad glob — the markers are the only record that a translation was inexact, and a pattern like symbol: "*" with ack: true silently discards every one of them.

- match: { symbol: "Client.onEvent" }
  ack: true

What a fixup is not for

A fixup edits the model, not the semantics. It cannot change a parameter type, add a method, or make a callback safe to invoke from another thread — it can only record what you already know to be true. If you need a different shape at the boundary, that is a wrapper: a generated @objc Swift bridge, a generated Kotlin bridge, or a C++ extern "C" shim, each committed and fed back through the ordinary driver.

The one thing to be careful with is ack. Everything else here is visible in the generated code; ack removes a warning, and it is the one fixup that can make a real problem invisible.