Skip to content

Feat/infallible and sealed - #4

Merged
rsansores merged 10 commits into
mainfrom
feat/infallible-and-sealed
Aug 22, 2026
Merged

rsansores merged 10 commits into
mainfrom
feat/infallible-and-sealed

Conversation

@rsansores

Copy link
Copy Markdown
Owner

No description provided.

Three changes, and a rename that should have happened at 0.1.

MapFrom/map_into were the fallible pair, which left every call site writing `?`
for a conversion that cannot fail and forced enclosing functions to return
Result for no reason. They are now TryMapFrom/try_map_into, mirroring
From/TryFrom, and the plain names belong to a new infallible pair.

`magic_map!(infallible ...)` emits it. The claim is checked structurally rather
than trusted: the expansion contains no `?`, so a field pair that only has a
TryMapFrom route fails to resolve. String -> Uuid parses, so claiming
infallible over it does not compile. Infallible mappings also get the fallible
half for free, written out rather than blanket-impl'd because
`impl<S, D: MapFrom<S>> TryMapFrom<S> for D` overlaps every generated impl and
coherence rejects it.

Sources may now be `&T`. The tuple form already took references in element
position; only the top-level parse refused, which left a borrowed source
spelled as a one-tuple.

`#[mapped]` is the attribute form of the derive, and `#[mapped(sealed)]` adds
#[non_exhaustive] plus a hidden all-fields constructor. From another crate the
type then has no struct expression, so a hand-rolled field-by-field copy stops
compiling — and so does `impl From<A> for T`, because that impl body cannot
construct its own output either. Forbidding the manual map forbids the manual
From with it; they are one mechanism, not two. It has to be an attribute
because a derive is additive-only and can never place an attribute on the item
it is deriving.

The constructor is public: an expansion holds no privilege a hand-written line
lacks, so this makes the wrong path ugly and greppable rather than impossible.
Sealing is skipped where it would only cost — unit and tuple structs, enums,
and field-less markers like a proto Empty.
README had the rename but none of the three features, and there was no upgrade
path written down. MIGRATING.md covers the sed, what each capability buys, and
— because it is the part people will get wrong — where sealing does and does
not reach: the boundary is the crate, not the directory, so a helper struct
declared next to its mapper is not protected and moving code between modules
changes nothing.
The struct path already branched on infallible; the enum path hardcoded
Ok(match ...), so an infallible enum-to-enum declaration failed with a
type mismatch whose span landed on the derive. Variant-to-variant over
unit enums cannot fail, so the bare match is the infallible body.
The infallible expansion funnelled every nested field through the global
MapFrom, which a foreign→foreign pair can never implement, so an
infallible fn-form mapping could not nest another one — in a layered
codebase that is most mappings. magic_map_scope! now plants LocalMapFrom
(infallible) beside the fallible funnel, which is renamed LocalTryMapFrom
to mirror the 0.4 trait split. Infallible fn-forms register in both.

Leaves gained fallibility: map_identity!/map_display! emit the MapFrom
twin (identity and Display cannot fail), DateTime<Utc>→String rfc3339 is
infallible, and magic_map_leaves!/scope leaves take an 'infallible'
prefix on custom pairs — one MapFrom impl then backs both funnels.
Sealing cannot reach types local to the mapping crate, conversions out of
a sealed type, or anything unsealed. The lint covers those three: a
syn-based binary that walks source trees and flags every impl of
From/Into/TryFrom/TryInto. Error conversions (either side named *Error)
are exempt — that is how ? bubbles between layers — and the allowlist
fails on stale entries, so it only shrinks.

MIGRATING.md gains the leaf-marking step and the lint section.
The attribute form parsed export = ".." and threw it away, and left the
#[magic_map(export)] helper dangling on the re-emitted item where no
derive remains to claim it. Both forms now feed schema_for the same
override, and prost trees can seal blanket-style while keeping their
per-type export disambiguations.
…ports

magic_map_scope! expanded ::uuid::Uuid and friends in the CALLING crate,
so a crate mapping only strings still needed uuid, rust_decimal, chrono
and serde_json as direct dependencies. The feature-gated groups now name
those types through $crate::__rx, which resolves against this crate's
own dependency set.
Version strings catch up to 0.4; the infallible section explains enum
mappings and fn-form composition through the local funnel; leaves gain
the fallibility model (map_identity!/map_display! emit the MapFrom twin,
'infallible' custom pairs back both funnels with one impl); the prost
recipe seals model packages path-scoped; and magic-map-lint gets its own
section — install, exemptions, shrink-only allowlist, and the division
of labour with sealing. MIGRATING adds the dependency dividend, the
parameter-construction bucket, and the sealed-pattern gotcha.
__magic_map_leaf_impl! was invoked nowhere and its emitted impl no
longer matched the renamed local trait — gone. mapped() re-implemented
the #[magic_map(export)] parser schema_for already owns; schema_for now
runs before the helper attr is stripped, so one loop reads it and a
helper beats the attribute argument. magic_map_lint drops an unused
quote dependency and syn's printing feature, and an unreadable --allow
path is a usage error (exit 2), not a panic.
@rsansores
rsansores merged commit 47dcc82 into main Aug 22, 2026
1 check passed
@rsansores
rsansores deleted the feat/infallible-and-sealed branch August 22, 2026 04:26
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