Skip to content

v0.71.0

Choose a tag to compare

@Goldziher Goldziher released this 26 Aug 23:51
· 70 commits to main since this release
v0.71.0
bcc7425

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 own exclude_types marshals as String; 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 Foundation heuristic 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
    through zig build against the consumer's real build.zig, whose ffi_path build option
    defaults to the release profile. The synthesized snippet build only ever threaded
    .target/.optimize into its b.dependency("binding", .{…}) call, and a top-level -D cannot
    set an option on a .path dependency — so there was no mechanism at all to redirect that path.
    With alef build producing a debug artifact, every Zig snippet failed with unable to find dynamic system library. The validator now resolves the library itself — release preferred,
    debug fallback, never crediting a deps/-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 Iterate docs 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 as results[].content — the
    result-anchored resolver reproduced that whole path underneath the already-peeled loop variable,
    emitting for 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 itself string and 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 through as unknown as, and the import sweep reaches struct names referenced only by that
    cast.

  • alef build staged an FFI library from a profile it never built. find_built_artifact
    hardcoded the release profile, so a plain alef build — which runs cargo build and 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_artifact now takes an explicit BuildProfile and searches only the two uplifted,
    profile-scoped directories; deps/ is consulted solely to name a rejected copy in the error.
    StageFfiLibrary passes 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 passes Release, 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.
    RunSummary now tracks fully_verified — results that reached their requested level
    with no downgrade or capability cap — and the summary leads with Checked at requested level: N/Total (P%). alef snippets check now fails, unconditionally rather than only under --strict,
    when not one single result reached its requested level; alef docs/alef all warn 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 a package does not exist symbol 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 Python typecheck runs
    the interpreter's own compile check ahead of pyrefly, so a hard IndentationError can no longer
    pass.

  • Four generated-snippet type defects. Python field access now narrows an Optional before
    subscripting it instead of indexing it bare; the C free-function call site routes omitted optional
    arguments through resolve_optional_sentinel, so an IR-declared handle parameter gets the 0
    sentinel rather than NULL; an iterate operation with an empty fields list 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 when options.py publishes that type
    only as a return-only TypedDict, 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); e2e main_test.go had an unsorted import block, two one-line
    if err != nil { panic(err) } checks, a one-line go func() { for … } }() drain, and
    gofmt-incorrect + spacing; and the Elixir GenServer template carried a double blank line and a
    pre-joined when clause one column past mix format's limit. Each drift let a consumer's
    gofmt -w or mix format rewrite 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 invoke gofmt/mix format and self-skip when absent.

  • Ownership markers alef itself refused to recognise. The PHP install.sh, R install.R, and
    Node/napi e2e .npmrc emitters hand-spelled an alef-generated marker string that alef's own
    content_has_alef_marker guard does not match. All three are generated_header: false, so the
    hand-written text was the only ownership signal — these files were permanently stranded as
    unowned. They now source their marker from hash::header/hash::STANDARD_HEADER_LINE, and each
    has a test asserting through the real guard rather than a copied literal.

  • package_dir no longer leaks a trailing slash into every path built from it.
    ResolvedCrateConfig::package_dir returned a user's configured [crates.output]/scaffold_output
    string verbatim, so a trailing / produced double-slash paths that alef adopt could never match
    against the real on-disk file. Fixed at the source, which protects the ~35 format!("{pkg_dir}/…")
    call sites across scaffold/languages/ and publish/; scaffold_license_files also now builds its
    LICENSE path with Path::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 extracted rust_path rather
      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 emits Type.CONSTANT or Type.Variant as 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.
  • Snippet session locks are keyed by fingerprint, not by config name. alef.toml can point two
    differently-named sessions (a language fallback such as typescript and an explicit
    binding-package target such as node) at the same cwd and manifest. They resolve to one
    physical workspace directory but each name got its own Mutex, so two batch groups that both
    believed they held the session lock wrote into the same snippet_batch_N.ts files concurrently.
    The corruption was worse than the lost work it caused: a file cut mid-token silences tsc'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/node to read a file. The generated
    await (await import("node:fs/promises")).readFile(...) form is emitted into every TypeScript
    target, but tsc degrades an unresolvable node:-prefixed dynamic import to a bare-identifier
    lookup and reports TS2591: Cannot find name 'node:fs/promises'. A browser/WASM package with no
    @types/node in 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/node when 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-typed json_object arguments fell through to a bare object literal; the
    transitive nested-class import walk was seeded only from a call's options_type and 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 an Iterate presentation
    path split on results[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 build now restages the FFI shared library it just built. Staging into the Go, Java and
    C# native-library directories only ever ran from alef test --e2e and alef publish; alef build rebuilt 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 a tracing::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's deps/ subdirectory, because a crate compiled only as
    another crate's path-dependency is never uplifted to target/release/ and was therefore
    reported absent while sitting in target/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_type value, 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 referenced pkg.SampleCrateError against a binding
    that declares pkg.Error. Both now derive the name through go_error_type_name in
    src/codegen/naming.rs, alongside a new go_package_name_from_module whose empty-module-path
    fallback is now reachable (the previous split(..).next_back().unwrap_or("binding") could never
    return None, so an empty module path yielded an empty package name).

  • Generated TypeScript no longer splices a raw fixture string or array literal into a
    Uint8Array field.
    Two call sites each lowered a bytes fixture value independently and both
    got the string case wrong: the napi object-literal builder wrapped any value in
    Uint8Array.from(...), which rejects a string, and the WASM default()+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.
    The Iterate template appended a borrow
    adapter only when the collection was Option-wrapped, so a plain Vec field 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-level display flag with no reference to the
    field's own type, so a field such as Vec<Vec<String>> was formatted with Display. Per-item
    fields are now checked individually against an allowlist of String/char/numeric/bool
    primitives and fall back to {:?} otherwise.

  • VerifyFrbBridgeCoverage no longer passes silently on a gate naming an undeclared
    feature.
    A #[cfg(feature = "...")] whose feature the sibling Cargo.toml never declares at
    all was treated exactly like one declared but left out of default, 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
    at alef 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 the pub fn sat 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_functions no longer reports a gated-and-disabled function as a
    missing bridge entry and fails the build, and CarryFrbCfgGates now 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 an impl, a struct,
    a private fn, or anything past a blank line.

  • [e2e].fields_optional is no longer blamed for optionality the IR derived.
    with_ir_fields deliberately merges IR-derived Option<T> names into the optional set, but
    declaring_config_key then reported fields_optional as the source for those names too — so
    the docs-snippet diagnostic told consumers to correct or delete a config entry that was never
    in their alef.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_knows refused any path crossing a tagged-union field without consulting
    fields_method_calls, so a path like metadata.format.excel.sheet_count was 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 with E0599. The shared
    codegen::conversions::{gen_enum_from_binding_to_core_cfg, gen_enum_from_core_to_binding_cfg}
    hard-coded cfg => Option::<&str>::None on every arm even though the
    enum_from_binding_to_core / enum_from_core_to_binding templates 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's gen_flat_data_enum_from_impls and
    gen_string_to_enum_expr, and pyo3's data-enum #[getter] accessors and #[staticmethod]
    variant factories in codegen::generators::enums — pyo3 needs its own fix because
    enum_has_data_variants short-circuits data enums out of the shared conversions path. The
    trait-bridge visitor glue had the same hole one level down: VisitorResultVariant carried no
    cfg field at all, so the magnus, napi, pyo3, rustler, wasm and php visitor_method templates
    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_gates deliberately skips a non-host
    rust_path when it builds the passthrough [features] table — so re-emitting the gate verbatim
    produces unexpected cfg condition value for a feature the consumer cannot activate. Worse, a
    gate of the any(test, feature = "testkit") shape is satisfied by cfg(test) under
    cargo clippy --all-targets, so the arm still compiles and then fails E0599 on a variant the
    foreign crate was never built with; both were observed in a consumer's PHP crate.
    codegen::cfg::is_host_owned_rust_path is the single authority that decides host versus foreign,
    and every emitter now asks it rather than re-deriving the comparison: dart's From<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 shared codegen::conversions::enums arms, and
    napi, rustler and the visitor-result metadata walk. pyo3 drops both shapes — the accessor arm,
    covered by the existing _ => None fallback, and the whole #[staticmethod] factory, which has
    no arm to gate around. Every drop is announced through tracing::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's rust_from_core_enum_open, rust_from_core_struct_open,
    rust_from_mirror_enum_open, rust_from_mirror_struct_open, rust_opaque_wrapper_struct and
    rust_from_json_bridge_fn templates were each passed a source_cfg and each ignored it, so a
    build excluding the feature hit E0433 on 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 name core_ty verbatim are gated. On the
    FFI side, gen_enum_free, gen_enum_to_json, gen_enum_to_string, gen_enum_from_json and the
    private from_i32_rs reconstruction helper never threaded EnumDef::cfg into their templates
    the way gen_type_free / gen_type_new already threaded TypeDef::cfg, so an enum defined
    inside a gated module got unconditional accessors — E0433 for exactly the consumer that
    declares the feature via [crates.ffi].extra_features without enabling it by default.

  • A stripping Jinja tag no longer welds the following generated line onto a // comment.
    trim_blocks eats the newline after a tag and {%- eats the one before it, so a source line
    followed directly by a stripping tag in generators/enums/enum_definition.jinja lost its line
    ending and the next emitted line was appended to it. Where that line was a comment, the comment
    swallowed an entire if 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 no contains() assertion could see the defect; the regression test parses the
    output with syn instead.

  • Generated e2e tests and doc snippets unwrap an Option<Vec<T>> field reached through an
    array-projected path.
    FieldResolver::ir_field_sets only ever proves a bare field name
    optional, by unanimity across every declaration of that name in the crate, while the
    _with_optionals accessor 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, so entries[0].sections[0] and entries[0].sections.len() rendered against the
    Option unguarded — E0608 and a missing method in Rust, an unguarded .first()/.size on a
    nullable receiver in Kotlin, and the equivalent in the other backends. Every per-call resolver
    now calls with_anchored_optional_paths over the fixture's own assertion field paths, resolving
    them through the IR's real (owner_type, field_name) walk the way presentation.rs already did
    for doc snippets: rust, dart, kotlin, php, csharp, java, swift, typescript and zig. Kotlin needed
    a second wire as well — its resolver never called with_ir_result_fields, leaving
    ir_result_field_map.root_type at None, which makes with_anchored_optional_paths an
    unconditional no-op whatever paths it is handed.

  • Swift trait-bridge protocols are visible to code that imports only the umbrella module.
    Swift{Trait}Bridge protocols are emitted into Sources/RustBridge/, so a doc snippet that
    wrote class Foo: SwiftEmbeddingBackendBridge after import <Umbrella> alone failed with
    "cannot find type ... in scope". gen_bridge_registration_overloads_file now emits a
    public typealias Swift{Trait}Bridge = RustBridge.Swift{Trait}Bridge per 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
    DocsSnippet target that depends only on the umbrella module, reproducing the failure under a
    real swift 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 find T", even with the type's
    gating feature enabled. It now uses core_type_path, the existing shared authority that walks
    TypeDef::rust_path.

  • magnus no longer synthesizes an impl Default it cannot satisfy.
    gen_struct_default_impl_explicit emitted a whole-struct Default as soon as any one field
    carried its own default (a single #[serde(default)] was enough), then filled every remaining
    required field through the untyped default_value_for_field fallback, which renders
    {Type}::default() for a Named field whether or not that type implements Default. A struct
    with a required field of a non-Default type failed to compile with "no function or associated
    item named default found". The already-computed default_types set 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() and version().
    trait_interface.jinja declares both abstract unconditionally whenever a bridge configures
    super_trait, but the e2e stub derived them by matching TraitBridgeConfig::super_trait against
    the super-trait TypeDef's rust_path and silently emitted neither when the lookup missed — as
    it does for a super-trait declared in a private module and re-exported via pub use, whose
    rust_path need not equal the configured value. Both sides now read the same
    trait_bridge_naming::SUPER_TRAIT_REQUIRED_METHODS list.

  • C doc snippets derive trait-bridge register/unregister/clear symbols even for a
    fixture-level-skipped fixture.
    resolve_fixture_call_info gated 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-populated call.function config text was left uncorrected and the snippets called a
    pluralized symbol the generated header never declares, without its trailing out_error param.
    Derivation is now gated on the call-level skip_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 configured nested_types map,
    unlike render_test_file's builder, which also derives nested classes transitively via
    collect_transitive_nested_types_for_wasm. A call with no nested_types override — the common
    case — could still emit a nested SomeClass.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.shows and docs.presentation.operations paths 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 anchored ir_collection_map, rather than against the
    call's result type, and result_field_oracle_knows refuses 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 no Named resolution at all — a
    serde_json::Value or other scalar, where continuing the path is unjudgeable rather than
    impossible — is tracked separately and still accepted, so a document.payload.anything accessor
    keeps deriving as before. Only the IR may refute an authored path: the
    [e2e].result_fields allow-list is incomplete by construction, so a has_ir_result_evidence
    gate keeps it from dropping the deliberately-documented virtual and namespaced paths an author
    writes docs.shows for in the first place.

  • A failed pipeline command reports its own output, not just its exit status.
    run_run_command (post-build RunCommand steps, including the Swift cargo build step) and
    run_shell (the per-language e2e format override, including the default rust cargo fmt --all)
    both reported a bare exit code, so 'cargo' exited with status 101 gave no hint that the real
    cause was a macOS linker fixup error and an e2e formatter failure carried no diagnostic at all.
    run_run_command now tees both streams through process::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_shell moves from Command::status() to Command::output()
    and quotes both streams the same way run_command_captured_with_env already does.

  • A snippet session key spelled exactly like its language wins claim resolution when another
    candidate corroborates it as a deliberate alias.
    resolve_session_claim reported 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_claim lets 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_codegen expands against the dart Rust crate's own default
    features, so a facade function behind a feature that is not in default is correctly absent from
    the generated bridge — but missing_bridge_functions was a plain line scan with no cfg
    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_satisfied against the feature set read from the facade's sibling Cargo.toml
    through the existing codegen::cfg::read_default_enabled_cargo_features seam, 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 claimed flutter_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.operations field paths are now validated against
    the IR before rendering
    , matching the check already applied to paths derived from assertions.
    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 Iterate operation's per-item fields are 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 scalar Option return crosses the C ABI as a bare scalar, so absence and a legitimate zero are
    the same bytes. C# matched TypeRef::Optional(_) unconditionally and emitted
    if (nativeResult == 0) { return null; } against an int64_t, which also shadowed the wrapper's
    error check so a genuine FFI failure surfaced as null. Zig and Kotlin/Native passed the raw C
    value through and let the language coerce it into an optional as non-null. Dart's dart:ffi
    typedef declared Pointer<Void> where the FFI crate exported int64_t — and read Option<bool>,
    which crosses as i32, as an 8-byte pointer. The ConsumesCabiNotYetWired ledger in
    backends::result_presence_stance_tests is now empty. The default Dart frb style 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 configured register_fn; Go composed
    {prefix}_register_{trait_snake} from the trait name, so any bridge whose register_fn spelled
    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_char for that shape while its own emitted body handed back insert_handle(..)
    and its absent branch handed back the handle-shaped 0 — 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.jinja without trait_snake, so the undefined value resolved to the empty
    string and the template emitted a bare Registry.delete(name) — an identifier the generated
    package never declares.

  • napi, wasm and php honour exclude_languages for 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 to crate::<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.ex likewise 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.php and Ruby RBS emitters listed a bridge's methods off trait_bridges alone.

  • extendr's extendr_module! no longer registers a registration function that was never
    generated.
    collect_trait_bridge_functions wired register_fn into the module macro without
    checking registry_getter, while gen_registration_fn writes no #[extendr] pub fn without one
    — a Rust compile error.

  • pyo3: a public wrapper is annotated with the return type options.py publishes, and converts
    the native value into it, instead of being annotated -> _rust.<Name> — the private extension
    module's #[pyclass]. Under the typed-dict output 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.py never 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 setup and alef build kill a timed-out command's whole process group, not just the
    sh wrapper, so a sh -> gradlew -> daemon tree 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_if or
    vec_init_then_push
    under a consumer's deny-level clippy. Struct literals use field-init
    shorthand where the value is exactly the field identifier; the FFI *_free wrappers, 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 are vec![] literals. extra_clippy_allows remains 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 delivers SIGKILL,
    matching the snippet validators. Untimed pipeline commands stay in the foreground group and are
    unaffected. The process-group lifecycle moved from src/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_fn without registry_getter warns 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_languages names the target logs at DEBUG, 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.