v0.71.0
Fixed
-
Four independent trait-bridge stub defects, one per backend. Go emitted a bare identifier as
an enum's default return, which is valid only for a constant-backed enum — a sealed-interface or
struct-shaped enum has no such constant, so the identifier named a type and the compiler
rejected it as "not an expression"; the default is now constructed according to the enum's real
Go representation. Java computed its excluded-type set with an empty enum registry, which cannot
tell a real enum from one the crate's ownexclude_typesmarshals asString; it now passes the
IR enum names minus the configured exclusions. Swift skipped default-body methods when stubbing,
but the production backend declares every trait method as a required protocol member (alef cannot
carry a Rust default body through the IR), so the stub never conformed — and its
import Foundationheuristic matched only a constructor call, missing a bare type annotation.
TypeScript gated every import on a plain substring test, so an enum whose name is a prefix of
another correctly-used name was spuriously imported; import gating is now word-boundary aware. -
Zig snippet validation could not reach a debug-profile FFI library. Zig snippets are built
throughzig buildagainst the consumer's realbuild.zig, whoseffi_pathbuild option
defaults to the release profile. The synthesized snippet build only ever threaded
.target/.optimizeinto itsb.dependency("binding", .{…})call, and a top-level-Dcannot
set an option on a.pathdependency — so there was no mechanism at all to redirect that path.
Withalef buildproducing a debug artifact, every Zig snippet failed withunable to find dynamic system library. The validator now resolves the library itself — release preferred,
debug fallback, never crediting adeps/-only copy — and splices the resolved option into the
dependency call. When neither profile holds a real library it returns no override rather than
guessing a path. -
An
Iteratedocs operation resolved its per-item fields against the call's result type
instead of the collection's element type. When a per-item field name was also reachable as a
nested path from the result — e.g.content, reachable asresults[].content— the
result-anchored resolver reproduced that whole path underneath the already-peeled loop variable,
emittingfor result in result.results.iter() { println!("{}", result.results[0].content) }.
That does not compile in Rust and does not typecheck in TypeScript. Because every backend shares
one presentation layer, the same wrong accessor shipped in Rust, Python, TypeScript and WASM.
Per-item fields now resolve through an element-anchored resolver. -
A TypeScript trait-bridge stub declared
Promise<string>for a struct-typed method. A stub
returning a non-enum struct annotated itselfstringand returned a bare"{}", so it could not
satisfy the interface it was passed to regardless of its body. The stub now names the real struct,
casts throughas unknown as, and the import sweep reaches struct names referenced only by that
cast. -
alef buildstaged an FFI library from a profile it never built.find_built_artifact
hardcoded thereleaseprofile, so a plainalef build— which runscargo buildand produces
the debug artifact — could not find what it had just built. It instead fell through to
target/release/deps/, whose contents come from whatever other cargo invocation happened to
compile that crate, under that invocation's feature unification. The staged library was fresh but
feature-incomplete: linking a consumer program against it failed with dozens of undefined symbols.
find_built_artifactnow takes an explicitBuildProfileand searches only the two uplifted,
profile-scoped directories;deps/is consulted solely to name a rejected copy in the error.
StageFfiLibrarypasses the profile the current invocation actually built. Callers with no build
of their own (alef generate's post-build pass,alef test's e2e staging) try release then debug
explicitly. Packaging passesRelease, matching its always-release contract. -
A snippet run could report success while most of the corpus was never checked at the level it
asked for.RunSummarynow tracksfully_verified— results that reached their requested level
with no downgrade or capability cap — and the summary leads withChecked at requested level: N/Total (P%).alef snippets checknow fails, unconditionally rather than only under--strict,
when not one single result reached its requested level;alef docs/alef allwarn loudly on the
same condition instead of bailing, because that pipeline cannot guarantee a build ran in the same
invocation. Related: the Java validator no longer counts apackage does not existsymbol cascade
as real failures when the package was simply never built, the "no snippet session configured"
diagnostic now names the session target rather than the language, and Pythontypecheckruns
the interpreter's own compile check ahead ofpyrefly, so a hardIndentationErrorcan no longer
pass. -
Four generated-snippet type defects. Python field access now narrows an
Optionalbefore
subscripting it instead of indexing it bare; the C free-function call site routes omitted optional
arguments throughresolve_optional_sentinel, so an IR-declared handle parameter gets the0
sentinel rather thanNULL; aniterateoperation with an emptyfieldslist renders a fallback
print(item)rather than an empty — and therefore syntactically invalid — Python loop body; and a
Python adapter wrapper now converts its native return value whenoptions.pypublishes that type
only as a return-onlyTypedDict, matching the wrapper's own annotation. -
Generated output that is not formatter-canonical by construction. Three emitters produced
output a consumer's own formatter rewrites:binding.go's stdlib import block was assembled by
manual insert-position juggling that mis-ordered one real combination (declared errors, no sync
functions or non-static methods); e2emain_test.gohad an unsorted import block, two one-line
if err != nil { panic(err) }checks, a one-linego func() { for … } }()drain, and
gofmt-incorrect+spacing; and the ElixirGenServertemplate carried a double blank line and a
pre-joinedwhenclause one column pastmix format's limit. Each drift let a consumer's
gofmt -wormix formatrewrite the file after alef hashed and stamped it, permanently
stranding it outside alef's ownership. All three now round-trip byte-identically through the real
formatter, asserted by tests that invokegofmt/mix formatand self-skip when absent. -
Ownership markers alef itself refused to recognise. The PHP
install.sh, Rinstall.R, and
Node/napi e2e.npmrcemitters hand-spelled analef-generatedmarker string that alef's own
content_has_alef_markerguard does not match. All three aregenerated_header: false, so the
hand-written text was the only ownership signal — these files were permanently stranded as
unowned. They now source their marker fromhash::header/hash::STANDARD_HEADER_LINE, and each
has a test asserting through the real guard rather than a copied literal. -
package_dirno longer leaks a trailing slash into every path built from it.
ResolvedCrateConfig::package_dirreturned a user's configured[crates.output]/scaffold_output
string verbatim, so a trailing/produced double-slash paths thatalef adoptcould never match
against the real on-disk file. Fixed at the source, which protects the ~35format!("{pkg_dir}/…")
call sites acrossscaffold/languages/andpublish/;scaffold_license_filesalso now builds its
LICENSEpath withPath::join. -
Trait-bridge test stubs now satisfy the interfaces they claim to implement, in four
backends. Four independent causes, each a generator re-deriving or hardcoding a fact another
part of the pipeline already had:- Go dropped all four required super-trait methods whenever the super trait was declared in a
private module and re-exported, because the lookup matched on the extractedrust_pathrather
than the configured, publicly visible path. Java had already hit and fixed this; Go now has the
same synthetic fallback. - Java forced every enum-returning method to
String, because the exclusion helper it used
cannot see enums (they live in a separate registry). It now uses the enum-aware helper, as C#
already did. - Kotlin-Android fell back to a hardcoded, project-specific variant table and otherwise called a
bare constructor, which is invalid for both of Kotlin's enum lowerings. It now reads the real
enum registry and emitsType.CONSTANTorType.Variantas appropriate. - Dart took the first variant unconditionally and naively lowercased it, producing a constructor
tear-off when that variant carried fields and the wrong casing regardless.
The dispatcher also passed an empty enum slice to the Dart and Swift emitters, so their existing
enum-default lookups could never succeed at all. Where no fieldless variant exists, the stub now
warns naming the type and language instead of guessing a value the target compiler rejects.
- Go dropped all four required super-trait methods whenever the super trait was declared in a
-
Snippet session locks are keyed by fingerprint, not by config name.
alef.tomlcan point two
differently-named sessions (a language fallback such astypescriptand an explicit
binding-package target such asnode) at the samecwdand manifest. They resolve to one
physical workspace directory but each name got its ownMutex, so two batch groups that both
believed they held the session lock wrote into the samesnippet_batch_N.tsfiles concurrently.
The corruption was worse than the lost work it caused: a file cut mid-token silencestsc's
semantic diagnostics for every other file in the same program, so unrelated real failures
were reported as passes. Any TypeScript snippet count taken before this fix understates the
failures. -
TypeScript snippet checks no longer require
@types/nodeto read a file. The generated
await (await import("node:fs/promises")).readFile(...)form is emitted into every TypeScript
target, buttscdegrades an unresolvablenode:-prefixed dynamic import to a bare-identifier
lookup and reportsTS2591: Cannot find name 'node:fs/promises'. A browser/WASM package with no
@types/nodein its graph therefore failed every byte-payload snippet. The validator now writes
a minimal self-contained ambient declaration into each check, which merges cleanly with a real
@types/nodewhen one is present. -
Generated wasm TypeScript now constructs the classes wasm-bindgen actually exports. The wasm
backend lowers every struct with fields to a JS class with a positional constructor, never a
plain interface, but four places in the shared node/wasm e2e generator still assumed the NAPI
object shape: array-typedjson_objectarguments fell through to a bare object literal; the
transitive nested-class import walk was seeded only from a call'soptions_typeand missed a
class reachable solely through an argument's own fields; trait-bridge stub enum return types and
casts used the unprefixed IR name the wasm package does not export; and anIteratepresentation
path split onresults[0].spliced its tail segment in verbatim, referencing a snake_case member
against a binding that only exports the camelCased one (that last one affected node identically). -
alef buildnow restages the FFI shared library it just built. Staging into the Go, Java and
C# native-library directories only ever ran fromalef test --e2eandalef publish;alef buildrebuilt the cdylib, never copied it, and reported success, so the staged artifact rotted
silently until a consumer's cgo link failed on symbols that had been added weeks earlier. A
missing built artifact is now atracing::warn!naming the destination instead of a silent
no-op. Separately,find_built_artifact(FFI staging plus Zig/Go/C#/CLI/C-FFI packaging) now
also searches each candidate directory'sdeps/subdirectory, because a crate compiled only as
another crate's path-dependency is never uplifted totarget/release/and was therefore
reported absent while sitting intarget/release/deps/. -
Generated Go docs snippets now name the error type the Go binding actually declares. The
snippet generator used the raw Rust-side[crate] error_typevalue, while the Go backend's own
error generator strips a leading case-insensitive match of the package name from that same value
to avoid revive's stutter lint — so a snippet referencedpkg.SampleCrateErroragainst a binding
that declarespkg.Error. Both now derive the name throughgo_error_type_namein
src/codegen/naming.rs, alongside a newgo_package_name_from_modulewhose empty-module-path
fallback is now reachable (the previoussplit(..).next_back().unwrap_or("binding")could never
returnNone, so an empty module path yielded an empty package name). -
Generated TypeScript no longer splices a raw fixture string or array literal into a
Uint8Arrayfield. Two call sites each lowered abytesfixture value independently and both
got the string case wrong: the napi object-literal builder wrapped any value in
Uint8Array.from(...), which rejects astring, and the WASMdefault()+setter builder had no
string branch at all and emitted a bare quoted string. Both now ask one shared classifier, which
lowers a file path, inline text, base64 or a number array to the right expression. WASM
array-of-object arguments with a known IR element type also route through the typed builder as
node already did, so their elements construct real wasm-bindgen class instances instead of plain
object literals. -
Generated Rust docs snippets no longer move out of a plain collection field, and no longer
Display-format a field that does not implement it. TheIteratetemplate appended a borrow
adapter only when the collection wasOption-wrapped, so a plainVecfield behind an index
expression was moved out of (E0507); it now borrows in both cases. Separately the per-item
println!chose{}vs{:?}from the operation-leveldisplayflag with no reference to the
field's own type, so a field such asVec<Vec<String>>was formatted withDisplay. Per-item
fields are now checked individually against an allowlist ofString/char/numeric/bool
primitives and fall back to{:?}otherwise. -
VerifyFrbBridgeCoverageno longer passes silently on a gate naming an undeclared
feature. A#[cfg(feature = "...")]whose feature the siblingCargo.tomlnever declares at
all was treated exactly like one declared but left out ofdefault, so the function was
excluded from coverage and the build passed. That is the alef #135 scenario itself: the
ownership guard refuses to write a forwarding[features]entry into a pre-marker-convention
manifest, the facade gains a gated function the manifest can never activate, and the coverage
failure was the only signal that would have surfaced the refused write. An inactive gate is now
excluded only when the manifest declares every feature it names; an undeclared one stays a
coverage candidate, and the diagnostic names the undeclared feature and the manifest and points
atalef adopt <path>rather than at a stale bridge. -
Dart FRB
#[cfg]gates now attach across intervening attributes.cfg_gated_free_functions
associated a gate with its function only when thepub fnsat on the very line after the
#[cfg(...)], but the generated facade always emits#[frb](or#[frb(opaque)]) in between,
so the gate was never recorded. In a real facade only 3 of ~70 gated free functions were
followed directly by a signature, leaving ~96% of gates invisible. Two consequences are fixed
together:missing_bridge_functionsno longer reports a gated-and-disabled function as a
missing bridge entry and fails the build, andCarryFrbCfgGatesnow carries the gate into
frb_generated.rs's wire wrapper and dispatch arm. The scan now skips further attribute lines
(single- or multi-line) and doc comments, and still declines to attach to animpl, astruct,
a privatefn, or anything past a blank line. -
[e2e].fields_optionalis no longer blamed for optionality the IR derived.
with_ir_fieldsdeliberately merges IR-derivedOption<T>names into the optional set, but
declaring_config_keythen reportedfields_optionalas the source for those names too — so
the docs-snippet diagnostic told consumers to correct or delete a config entry that was never
in theiralef.toml. Config-declared provenance is now tracked in its own set that the merge
never touches. -
A fixture path extending past a
fields_method_calls-covered tagged union now resolves.
result_field_oracle_knowsrefused any path crossing a tagged-union field without consulting
fields_method_calls, so a path likemetadata.format.excel.sheet_countwas dropped from every
generated snippet even though the bindings expose it and the consumer had declared exactly how
to cross that union. Such a path now resolves against the variant's own payload type. A path
with no covering entry still refuses, and a segment the IR cannot judge still abstains. -
A host-owned
#[cfg]-gated enum variant keeps its match arm and gains a matching
#[cfg(...)]guard. Generated Rust glue named such a variant unconditionally, so a build with
the feature off failed withE0599. The shared
codegen::conversions::{gen_enum_from_binding_to_core_cfg, gen_enum_from_core_to_binding_cfg}
hard-codedcfg => Option::<&str>::Noneon every arm even though the
enum_from_binding_to_core/enum_from_core_to_bindingtemplates already accepted a per-arm
gate, which broke napi, magnus, rustler and wasm at once. The same omission is fixed in napi's
gen_tagged_enum_binding_to_core/gen_tagged_enum_core_to_binding, rustler's
gen_rustler_flat_data_enum_from_core/_to_core, php'sgen_flat_data_enum_from_implsand
gen_string_to_enum_expr, and pyo3's data-enum#[getter]accessors and#[staticmethod]
variant factories incodegen::generators::enums— pyo3 needs its own fix because
enum_has_data_variantsshort-circuits data enums out of the shared conversions path. The
trait-bridge visitor glue had the same hole one level down:VisitorResultVariantcarried no
cfgfield at all, so the magnus, napi, pyo3, rustler, wasm and phpvisitor_methodtemplates
emitted an unguarded reference to a gated callback-result variant. Fallback selection is
corrected with it — a_ =>default arm and php's no-default-variant fallback no longer elect a
cfg-gated variant as the always-available stand-in, and a catch-all arm is now emitted whenever
any variant is gated so the match stays exhaustive with the feature off. -
A
#[cfg]-gated enum variant merged in from a[[crates.source_crates]]crate has its arm
dropped entirely instead of gated. The generated binding crate never declares a Cargo feature
for a foreign crate's cfg —codegen::cfg::collect_cfg_gatesdeliberately skips a non-host
rust_pathwhen it builds the passthrough[features]table — so re-emitting the gate verbatim
producesunexpected cfg condition valuefor a feature the consumer cannot activate. Worse, a
gate of theany(test, feature = "testkit")shape is satisfied bycfg(test)under
cargo clippy --all-targets, so the arm still compiles and then failsE0599on a variant the
foreign crate was never built with; both were observed in a consumer's PHP crate.
codegen::cfg::is_host_owned_rust_pathis the single authority that decides host versus foreign,
and every emitter now asks it rather than re-deriving the comparison: dart'sFrom<Mirror>and
From<CoreType>enum impls, wasm's tagged-enum From impls (which already gated but never asked
about ownership), php's string-to-enum match, the sharedcodegen::conversions::enumsarms, and
napi, rustler and the visitor-result metadata walk. pyo3 drops both shapes — the accessor arm,
covered by the existing_ => Nonefallback, and the whole#[staticmethod]factory, which has
no arm to gate around. Every drop is announced throughtracing::warn!, and php no longer
advertises a dropped variant as an accepted string value. -
A type or enum wholly gated behind a Cargo feature carries that gate onto every generated item
that names its host path. Dart'srust_from_core_enum_open,rust_from_core_struct_open,
rust_from_mirror_enum_open,rust_from_mirror_struct_open,rust_opaque_wrapper_structand
rust_from_json_bridge_fntemplates were each passed asource_cfgand each ignored it, so a
build excluding the feature hitE0433on a module path that does not exist. The mirror struct
and enum declarations stay unconditional, since their fields are widened FRB-native types rather
than the host path; only the impls and functions that namecore_tyverbatim are gated. On the
FFI side,gen_enum_free,gen_enum_to_json,gen_enum_to_string,gen_enum_from_jsonand the
privatefrom_i32_rsreconstruction helper never threadedEnumDef::cfginto their templates
the waygen_type_free/gen_type_newalready threadedTypeDef::cfg, so an enum defined
inside a gated module got unconditional accessors —E0433for exactly the consumer that
declares the feature via[crates.ffi].extra_featureswithout enabling it by default. -
A stripping Jinja tag no longer welds the following generated line onto a
//comment.
trim_blockseats the newline after a tag and{%-eats the one before it, so a source line
followed directly by a stripping tag ingenerators/enums/enum_definition.jinjalost its line
ending and the next emitted line was appended to it. Where that line was a comment, the comment
swallowed an entireif let ... {, leaving its closing brace unmatched, and a consumer's
generated PyO3 crate did not parse. Every expected fragment was still textually present, just
commented out, so nocontains()assertion could see the defect; the regression test parses the
output withsyninstead. -
Generated e2e tests and doc snippets unwrap an
Option<Vec<T>>field reached through an
array-projected path.FieldResolver::ir_field_setsonly ever proves a bare field name
optional, by unanimity across every declaration of that name in the crate, while the
_with_optionalsaccessor renderers key their per-segment unwrap check by the full cumulative
path walked so far. A bare name therefore never matched once the path crossed more than one
segment, soentries[0].sections[0]andentries[0].sections.len()rendered against the
Optionunguarded —E0608and a missing method in Rust, an unguarded.first()/.sizeon a
nullable receiver in Kotlin, and the equivalent in the other backends. Every per-call resolver
now callswith_anchored_optional_pathsover the fixture's own assertion field paths, resolving
them through the IR's real(owner_type, field_name)walk the waypresentation.rsalready did
for doc snippets: rust, dart, kotlin, php, csharp, java, swift, typescript and zig. Kotlin needed
a second wire as well — its resolver never calledwith_ir_result_fields, leaving
ir_result_field_map.root_typeatNone, which makeswith_anchored_optional_pathsan
unconditional no-op whatever paths it is handed. -
Swift trait-bridge protocols are visible to code that imports only the umbrella module.
Swift{Trait}Bridgeprotocols are emitted intoSources/RustBridge/, so a doc snippet that
wroteclass Foo: SwiftEmbeddingBackendBridgeafterimport <Umbrella>alone failed with
"cannot find type ... in scope".gen_bridge_registration_overloads_filenow emits a
public typealias Swift{Trait}Bridge = RustBridge.Swift{Trait}Bridgeper configured bridge,
following the same per-symbol re-export idiom the main module file already uses for opaque handle
types rather than a blanket@_exported import. The SwiftPM compile gate gained a third
DocsSnippettarget that depends only on the umbrella module, reproducing the failure under a
realswift build. -
wasm resolves a core type's real module path for static and instance calls.
gen_method
composed{core_import}::{type_name}from the bare IR name, which only works for a type
re-exported at the core crate root; a type living under a private module produced
{core_import}::T::default()and rustc rejected it with "cannot findT", even with the type's
gating feature enabled. It now usescore_type_path, the existing shared authority that walks
TypeDef::rust_path. -
magnus no longer synthesizes an
impl Defaultit cannot satisfy.
gen_struct_default_impl_explicitemitted a whole-structDefaultas soon as any one field
carried its own default (a single#[serde(default)]was enough), then filled every remaining
required field through the untypeddefault_value_for_fieldfallback, which renders
{Type}::default()for aNamedfield whether or not that type implementsDefault. A struct
with a required field of a non-Defaulttype failed to compile with "no function or associated
item nameddefaultfound". The already-computeddefault_typesset is now consulted per field,
and the whole impl is skipped when a required field cannot be satisfied. -
The Java e2e stub always implements a super-trait bridge's
name()andversion().
trait_interface.jinjadeclares both abstract unconditionally whenever a bridge configures
super_trait, but the e2e stub derived them by matchingTraitBridgeConfig::super_traitagainst
the super-traitTypeDef'srust_pathand silently emitted neither when the lookup missed — as
it does for a super-trait declared in a private module and re-exported viapub use, whose
rust_pathneed not equal the configured value. Both sides now read the same
trait_bridge_naming::SUPER_TRAIT_REQUIRED_METHODSlist. -
C doc snippets derive trait-bridge register/unregister/clear symbols even for a
fixture-level-skipped fixture.resolve_fixture_call_infogated symbol derivation on
fixture.skip.languages, but that directive opts a fixture out of the executable harness only —
the docs-snippet generator renders a skipped fixture regardless — so the naive,
already-populatedcall.functionconfig text was left uncorrected and the snippets called a
pluralized symbol the generated header never declares, without its trailingout_errorparam.
Derivation is now gated on the call-levelskip_languages, the same authority the harness and
the docs generator's own inclusion filter already use for "this language cannot represent this
call at all". -
wasm doc snippets import nested classes the snippet body reaches only through the IR. The
standalone snippet import builder considered only the manually configurednested_typesmap,
unlikerender_test_file's builder, which also derives nested classes transitively via
collect_transitive_nested_types_for_wasm. A call with nonested_typesoverride — the common
case — could still emit a nestedSomeClass.default()construction through
ts_builder_expression_inner's own IR-derived lookup, leaving the snippet referencing an
undeclared symbol and failing to typecheck. -
Hand-authored
docs.showsanddocs.presentation.operationspaths are validated against the
IR. Only assertion-derived paths went through the existing existence check (shows_on_result),
so a stale or misspelled field name in authored docs config reached every snippet backend's
compiler identically. An iterate block's per-item fields are now checked against the collection's
own element type, resolved through a newly anchoredir_collection_map, rather than against the
call's result type, andresult_field_oracle_knowsrefuses a path that continues past a field
the IR knows it cannot walk into as a struct (the tagged-union/enum shape) instead of falling
through to a permissive flat check. A field with noNamedresolution at all — a
serde_json::Valueor other scalar, where continuing the path is unjudgeable rather than
impossible — is tracked separately and still accepted, so adocument.payload.anythingaccessor
keeps deriving as before. Only the IR may refute an authored path: the
[e2e].result_fieldsallow-list is incomplete by construction, so ahas_ir_result_evidence
gate keeps it from dropping the deliberately-documented virtual and namespaced paths an author
writesdocs.showsfor in the first place. -
A failed pipeline command reports its own output, not just its exit status.
run_run_command(post-buildRunCommandsteps, including the Swiftcargo buildstep) and
run_shell(the per-language e2e format override, including the default rustcargo fmt --all)
both reported a bare exit code, so'cargo' exited with status 101gave no hint that the real
cause was a macOS linker fixup error and an e2e formatter failure carried no diagnostic at all.
run_run_commandnow tees both streams throughprocess::capture::output_reader_tee, which
mirrors each chunk live so a long build still looks alive while capturing it, and quotes the last
~4KB of each stream on failure;run_shellmoves fromCommand::status()toCommand::output()
and quotes both streams the same wayrun_command_captured_with_envalready does. -
A snippet session key spelled exactly like its language wins claim resolution when another
candidate corroborates it as a deliberate alias.resolve_session_claimreported a target-less
snippet as ambiguous whenever its language had a genuinely different-directory second session,
even when one candidate was a bare-language-named key aliased onto another, already-present
candidate's own working directory.alias_default_claimlets the exact name win only when a
differently-named candidate already shares its directory; a standalone exactly-named candidate
still resolves as ambiguous, unchanged. -
The Dart FRB bridge-coverage check no longer reports a
#[cfg]-gated facade function as a
stale bridge.flutter_rust_bridge_codegenexpands against the dart Rust crate's own default
features, so a facade function behind a feature that is not indefaultis correctly absent from
the generated bridge — butmissing_bridge_functionswas a plain line scan with nocfg
awareness and counted every one of them as a function frb had failed to bridge, failing the dart
post-build stage on a bridge that was in fact freshly and correctly generated. It now filters on
cfg_feature_satisfiedagainst the feature set read from the facade's siblingCargo.toml
through the existingcodegen::cfg::read_default_enabled_cargo_featuresseam, and falls back to
the old unfiltered check when that manifest cannot be read rather than suppressing coverage
silently. -
That check's failure message states what was observed instead of asserting one cause. It
previously claimedflutter_rust_bridge_codegen did not (re)generate this bridge, which is one
of several explanations and was the wrong one in the case above. It now reports the facade
functions that have no bridge counterpart and lists the causes it cannot distinguish between. -
Hand-authored
docs.shows/docs.presentation.operationsfield paths are now validated against
the IR before rendering, matching the check already applied to paths derived fromassertions.
A fixture-authored typo or stale field name now drops the operation (falling back to
assertion-derived shows when every authored operation is dropped) instead of emitting a
non-compiling accessor identically across every generator that shares the snippet/e2e
presentation layer (Rust, Dart, Java, Swift, Kotlin, TypeScript, WASM, and the rest). -
A docs/e2e presentation path that continues past a field the IR can confirm is not a struct it
can walk further into (the tagged-union/enum shape) is now refused instead of silently falling
through to a permissive flat check that let the accessor renderer emit a plain field access into
an enum variant. -
An
Iterateoperation's per-itemfieldsare now validated against the collection's own
element type, resolved from the IR, instead of the call's result type. A per-item field name
that does not exist on the iterated element (e.g. a renamed struct field) is now dropped from the
operation instead of reaching every backend's snippet compiler. -
C#, Zig, Dart (
style = "ffi") and Kotlin/Native now consult the result-presence companion.
A scalarOptionreturn crosses the C ABI as a bare scalar, so absence and a legitimate zero are
the same bytes. C# matchedTypeRef::Optional(_)unconditionally and emitted
if (nativeResult == 0) { return null; }against anint64_t, which also shadowed the wrapper's
error check so a genuine FFI failure surfaced asnull. Zig and Kotlin/Native passed the raw C
value through and let the language coerce it into an optional as non-null. Dart'sdart:ffi
typedef declaredPointer<Void>where the FFI crate exportedint64_t— and readOption<bool>,
which crosses asi32, as an 8-byte pointer. TheConsumesCabiNotYetWiredledger in
backends::result_presence_stance_testsis now empty. The default Dartfrbstyle is unaffected. -
Go calls the trait-bridge register symbol the FFI backend actually exports. The FFI backend
names it{prefix}_{register_fn}from the bridge's configuredregister_fn; Go composed
{prefix}_register_{trait_snake}from the trait name, so any bridge whoseregister_fnspelled
anything else linked against a symbol exported nowhere. -
A return of
Option<Option<SomeType>>declares a handle on the C side. The FFI backend
declared*mut c_charfor that shape while its own emitted body handed backinsert_handle(..)
and its absent branch handed back the handle-shaped0— three answers to one question, of which
only the declaration reached the header and the consuming backends. -
Go names the trait registry in the configured unregister wrapper. It rendered
unregister_c_call.jinjawithouttrait_snake, so the undefined value resolved to the empty
string and the template emitted a bareRegistry.delete(name)— an identifier the generated
package never declares. -
napi, wasm and php honour
exclude_languagesfor trait bridges. All three emitted the bridge
wrapper struct and the register/unregister/clear entry points regardless, so a consumer writing
exclude_languages = ["wasm"]still got the bridge. Each backend's emitter, options-field wiring
and reported registration surface now read one shared predicate. The napi gate honours both
"node"and"napi". -
php, magnus and rustler no longer emit host wrappers for a trait absent from the API surface.
The Rust-side bridge emitter skips such a bridge; the host-side pass did not, so PHP emitted
wrapper methods forwarding tocrate::<register_fn>, magnus emitted
define_module_function("<register_fn>", …), and rustler emitted Elixir delegates calling
<AppModule>.Native.<fn>— each naming a symbol no pass generated.native.exlikewise declared
NIF stubs for those bridges. All now ask the same lookup. -
php and magnus type stubs no longer declare bridge entry points the bindings skip. The
.stub.phpand Ruby RBS emitters listed a bridge's methods offtrait_bridgesalone. -
extendr's
extendr_module!no longer registers a registration function that was never
generated.collect_trait_bridge_functionswiredregister_fninto the module macro without
checkingregistry_getter, whilegen_registration_fnwrites no#[extendr] pub fnwithout one
— a Rust compile error. -
pyo3: a public wrapper is annotated with the return type
options.pypublishes, and converts
the native value into it, instead of being annotated-> _rust.<Name>— the private extension
module's#[pyclass]. Under thetyped-dictoutput style the wrapper's annotation named a
different type than the one a consumer imports under the same word. -
pyo3: the keyword-omission unpack is no longer emitted for a field
options.pynever nulls.
A bare#[serde(default)]enum field renders as a literal default and can never be absent, so the
unpack was dead — and a type checker resolves an unpacked keyword against every remaining
parameter, costing one error per pair (three such unpacks in one constructor call produced six). -
alef setupandalef buildkill a timed-out command's whole process group, not just the
shwrapper, so ash -> gradlew -> daemontree no longer outlives its deadline and reparents to
PID 1. The drain that follows is bounded by a 5s grace rather than reading to end of stream, which
a descendant holding the inherited pipes never reaches; the captured helper also drains
concurrently with the wait, so a command that fills the OS pipe buffer no longer can only end by
timing out. -
Generated Rust no longer trips
redundant_field_names,collapsible_ifor
vec_init_then_pushunder a consumer's deny-level clippy. Struct literals use field-init
shorthand where the value is exactly the field identifier; the FFI*_freewrappers, the pyo3 DTO
alias helper and enum discriminant branch, the napi/wasm/extendr/php visitor result branches and
the Dart FRB loader build script use let-chains; the extendr and rustler visitor-context pair
lists arevec![]literals.extra_clippy_allowsremains available for consumer-owned code. -
e2e validator diagnostics are reported once per crate rather than once per render pass.
Changed
- Timed pipeline commands are spawned into their own process group and registered for
termination forwarding, so Ctrl-C still tears the whole tree down. Forwarding deliversSIGKILL,
matching the snippet validators. Untimed pipeline commands stay in the foreground group and are
unaffected. The process-group lifecycle moved fromsrc/snippets/validators/to a crate-level
src/process/module so both paths share one implementation. - Service-API and trait-bridge C symbols are spelled in one place,
codegen::c_consumer. Both
the FFI emitters that export them and the Go cgo call sites that consume them derive their names
from it. Templates receive whole symbols rather than fragments to interpolate. The service
family's two derivations agreed on every input, so that half is a drift guard, not a behaviour
change. register_fnwithoutregistry_getterwarns at config resolution. Every backend's
registration emitter needs a registry and emits nothing without one (the C FFI backend panics), so
the combination silently produced no registration function anywhere.- A trait bridge skipped because its trait is absent logs a
WARN; one skipped because
exclude_languagesnames the target logs atDEBUG, since that is an honoured request rather
than degradation.
Removed
- The unreachable
KotlinJvmBridgeGenerator. A Kotlin/JVM consumer calls the generated Java
bridge class directly, so it emitted nothing reachable.