You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
alef docs parsed docs.snippets.required_languages through a fence-tag-only parser while alef snippets gaps parsed the same key through a session-target-aware one. An entry of node, wasm or kotlin_android was therefore accepted by one command and rejected by the other, so alef all aborted with unknown language: node on a config its own sibling command had already validated. The resolver is now a single authority in snippets::types that both call sites use. The abort also short-circuited docs/snippet validation, so it was masking every finding behind it.
napi, wasm: a payload-bearing variant on a default-representation enum silently dropped its payload. An enum with no #[serde(tag/content/untagged)] — for example enum Label { A, B, Custom(String) } — was emitted by napi as a #[napi(string_enum)] with a bare Custom, variant and by wasm as a plain C-style Custom = 1,, discarding the field in both. Both backends now route such enums through the same discriminated-object emitter an explicitly tagged enum already used, so the payload round-trips in both directions. pyo3 was already correct, which is why Python preserved payloads while Node and WASM did not.
Generated DTOs for pyo3, napi, magnus, rustler and extendr now deserialize a container-level #[serde(from/into/try_from/transparent)] struct by delegating to the core type's own Deserialize and converting via Into. The derived field-by-field object Deserialize silently disagreed with the real wire shape, which is commonly a positional array. Delegation makes no positional assumption; a struct whose fields cannot round-trip (an unwrapped opaque field, a sanitized non-Cow field, a cfg-gated field) falls back to the derived impl rather than guessing. wasm and the seven FFI-derived backends were already unaffected. Serialize symmetry is deliberately unchanged.
The C ABI now emits a has_<field> presence companion for every optional struct field whose return type has no null representation — Option<i32/u64/f32/f64/bool/…> and Option<Duration>. Previously None and a legitimate zero-valued Some both returned the same 0/0.0 sentinel with no way to tell them apart, so a field meaning "explicitly disabled" was indistinguishable from one meaning "apply the default". Pointer-shaped types already had a real null and are unchanged.
PHP now throws, naming both the field and the offending value, when a string-backed enum field does not match a known variant. It previously substituted the default or first variant, which a consumer's own core-side validate() then ran against and could not detect — an unknown value became a real, plausible, wrong one.
Swift's generated enum-from-string helper returns Result instead of panicking. An unrecognised wire string used to panic! inside __alef_<enum>_from_swift_string, unwinding across the swift-bridge FFI boundary, which is undefined behaviour. Every call site now propagates the error, forcing an otherwise-infallible wrapper's return type to Result<_, String> so the failure has somewhere to go.
Generated binding↔core conversions no longer silently drop Vec elements that fail to (de)serialize. A failed element keeps its slot instead of vanishing and shifting every later index — a shrinking collection is undetectable from the output shape, while a preserved slot is at least positionally honest.
alef snippets check no longer tells you to run alef build for a language that has no docs.snippets.sessions target configured at all. That advice was false — no build could change the result — and it was why running alef build and then alef snippets check produced byte-identical unresolved-dependency counts. The rollup now separates "No session configured" from the real build-ordering case, with distinct remediation for each.
Fixed-size arrays of primitives ([u8; N], [f64; N]) now resolve losslessly to Bytes/Vec<T> instead of being sanitized to a lossy String placeholder. resolve_type had no syn::Type::Array arm at all, so every fixed array fell through to a stringified Named type.
Sanitized public-API diagnostics are now driven by the sanitizer's recorded rewrite rather than by pattern-matching the String placeholder, so a field genuinely declared String is never conflated with one rewritten to String. Sanitized parameters also record original_type for the first time, which several backends already gated on and which had therefore been inert.
The Zig reference pages document []const u8/[]u8 at the wrapper boundary instead of a DTO's Rust type name, by asking the Zig emitter rather than re-deriving the shape. The same mapping error had Zig strings documented as [:0]const u8 — a sentinel-terminated slice that appears nowhere in generated output — for parameters, returns and struct fields alike.
A trait implemented from a foreign crate no longer contributes methods to the binding surface. An impl SomeFramework::Trait for Config { fn schema() … } — written to serve OpenAPI generation, a serializer, or any other tool — had its methods lifted into the public binding API, where they sanitized lossily and aborted generation. The trait filter was a denylist of std traits, so every other trait passed. A fully-qualified trait path rooted in a crate that is neither the one being extracted nor any crate contributing a type to the surface is now foreign. A single-segment path (an imported trait) is deliberately unchanged: resolving it depends on module visit order and would drop real methods.
The C ABI from_i32 reconstruction helper now carries a variant's #[cfg]. A variant behind #[cfg(feature = "…")] does not exist in a build without that feature, so an ungated match arm naming it was a hard compile error in the consumer's crate. Discriminants stay reserved, so numbering is stable across feature subsets.
C# no longer declares a scalar optional return as IntPtr. A function returning Option<u64> is exported by the FFI crate as a raw u64; C# declared the same symbol as IntPtr, read the integer bit pattern as a UTF-8 string pointer and passed it to FreeString — an arbitrary-address free. The pointer-vs-scalar decision now has one owner in ffi::type_map that C# asks; the private copy it replaced had already drifted on Option<Option<Duration>>. A direct Option<scalar> return still cannot distinguish None from Some(0); a presence channel for that position is not yet implemented.
kotlin_android documentation snippets no longer silently drop every field of a call's result. It was the one language backend without a render_snippet_body_with_functions override, so it fell to a trait default that discards the function registry; the call then anchored to an unrelated struct sharing its name and the field oracle correctly rejected everything beneath it.
Generated reference pages no longer corrupt a URL followed by punctuation. Proxy URL (e.g. "http://proxy:8080", …) rendered as <http://proxy:8080\",> because quotes were not excluded from the autolink match. Balanced parentheses and trailing slashes are still treated as part of the URL.
Cross-page reference links are no longer hardcoded to a .md suffix. New [docs].reference_link_style selects suffixed (default, unchanged) or extensionless for documentation sites that route without file extensions.
A fenced code block naming a language alef does not generate bindings for — astro, mdx, hcl — no longer fails documentation validation. It is reported at warning severity instead, naming the tag, so a typo like pythn stays visible rather than passing silently. A tag that claims a real binding target and still fails to resolve remains an error.
MCP prompts and resources constructed at runtime can now be declared in configuration.docs.mcp.declared covers surfaces built through calls like Prompt::new(…) rather than declared by attribute, which attribute extraction cannot see and therefore reported as nothing missing. Attribute-derived surfaces win on a name collision, and every dropped duplicate is reported once with a count. Consumers who declare nothing see identical output.
Sanitized method and function parameters now record original_type, mirroring the field path, so a diagnostic can name the original Rust type. Several backends already gated behaviour on that value and had been inert for parameters.
Added
A regression test proving that the functions alef generate actually consults to decide skip-vs-regenerate report a cache miss for byte-identical inputs once the compiled-in alef version changes. Prior coverage only compared two cache keys in isolation and never tied that difference to the real on-disk read path.