damask 0.10.1

Damask: React-like, compile-time components for Rust.
Documentation
//! Typestate and conversions for the prop builder the `Component` derive
//! generates.
//!
//! A call site in a template names only the props the author wrote, and it is
//! compiled in a different crate from the struct it is building — so it cannot
//! know which fields it left out, nor what those fields default to. The derive
//! knows both, and answers by generating a builder: one setter per field, and a
//! `__damask_build` that is reachable only once every *required* prop has been set.
//!
//! Nothing here is meant to be named by hand.
//!
//! # Which props may be skipped
//!
//! The builder carries one type parameter per required prop. Setting the prop
//! swaps that parameter from a marker the derive named after it to [`Set`], and
//! `__damask_build` requires every one of them to be [`Provided`]. A forgotten prop
//! is therefore a trait-bound error naming the prop, at the call site.
//!
//! A prop whose type is `Option<_>` needs no marker: leaving it out yields
//! `None`. Neither does any prop of a struct marked `#[component(default)]`,
//! whose builder starts from `Default::default()`.
//!
//! # Reaching an `Option` prop with a quoted value
//!
//! A setter takes its prop's type exactly, so that passing a value to it behaves
//! as assigning to the field did — `&Vec<T>` still coerces to a `&[T]` prop,
//! `count={2 + 8}` still infers to whatever integer the prop is. That leaves the
//! conversion a quoted value needs to happen on the *value* side, where the
//! prop's type is what the result is inferred from:
//!
//! ```text
//! detail: Option<String>     detail="check the log"   → Some("check the log")
//!                            detail="row {self.n}"    → Some("row 3")
//!                            detail={self.detail}     → passed through
//!                            (omitted)                → None
//! ```
//!
//! An interpolated value is already a `String`, and `String` reaches both a
//! `String` prop and an `Option<String>` one through `Into`. Static text is the
//! case `Into` cannot serve — no `From<&'static str> for Option<String>` exists,
//! and adding one is not ours to do — so [`literal`] stands in for it.

/// A required prop that has been supplied.
pub struct Set;

/// Satisfied only by [`Set`] — the bound `__damask_build` places on each required
/// prop's marker.
///
/// The unsatisfied case is the diagnostic, so it is phrased here: the failing
/// type is the marker the derive named after the missing prop, and `{Self}`
/// puts that name in the message.
#[diagnostic::on_unimplemented(
    message = "missing a required prop: {Self}",
    label = "a required prop was not given",
    note = "a prop is required unless its type is `Option<_>`, which makes leaving it out mean `None`",
    note = "`#[component(default)]` on the struct makes every prop skippable, filling the rest from its `Default`"
)]
pub trait Provided {}

impl Provided for Set {}

/// A string type buildable from *either* form a quoted attribute value arrives
/// in: static text, or an interpolated `String`.
///
/// Requiring both is not incidental. It is what makes [`literal`] resolve to
/// exactly one conversion, because it excludes — by construction rather than by
/// a list — the one type that would otherwise fit both of [`FromLiteral`]'s
/// impls: `&'static str`, which no `String` converts into.
///
/// Blanket-implemented, so `String`, `Cow<'static, str>`, `Box<str>`, `Rc<str>`,
/// `Arc<str>` and any type of your own with both conversions qualify.
pub trait FromText: From<&'static str> + From<String> {}

impl<T: From<&'static str> + From<String>> FromText for T {}

/// The value is the prop's own type.
pub struct Direct;
/// The value is what the prop's `Option` wraps.
pub struct Wrapped;

/// How static attribute text becomes a prop, given the prop's type.
///
/// `M` is what keeps the two impls from overlapping — a prop is reached either
/// as itself or through its `Option` — and is always inferred.
#[diagnostic::on_unimplemented(
    message = "a quoted attribute value cannot become `{Self}`",
    label = "no conversion from text to this prop's type",
    note = "a quoted value needs `From<&'static str>` for the prop's type (or for what its `Option` wraps); pass a `{{ … }}` value instead"
)]
pub trait FromLiteral<M>: Sized {
    fn from_literal(text: &'static str) -> Self;
}

impl<T: From<&'static str>> FromLiteral<Direct> for T {
    fn from_literal(text: &'static str) -> Self {
        T::from(text)
    }
}

// Reached only where the prop's `Option` wraps a type static text converts into
// *and* an interpolated one would — the pair that rules out `&'static str`,
// whose `Option` the impl above already serves through `From<T> for Option<T>`.
impl<T: FromText> FromLiteral<Wrapped> for Option<T> {
    fn from_literal(text: &'static str) -> Self {
        Some(T::from(text))
    }
}

/// Convert the static text of a quoted attribute into the prop it is being
/// passed to. Emitted by the template lowering.
pub fn literal<M, T: FromLiteral<M>>(text: &'static str) -> T {
    T::from_literal(text)
}

/// How the assembled text of an *interpolating* quoted attribute becomes a prop.
///
/// The same shape as [`FromLiteral`] and for the same reason: the value arrives
/// as a `String`, and the prop it is going to is either that type or an `Option`
/// of it. `M` is what keeps those two cases from overlapping.
#[diagnostic::on_unimplemented(
    message = "an interpolated attribute value cannot become `{Self}`",
    label = "no conversion from text to this prop's type",
    note = "an interpolated value needs `From<String>` for the prop's type (or for what its `Option` wraps); pass a `{{ … }}` value instead"
)]
pub trait FromInterpolated<M>: Sized {
    fn from_interpolated(text: String) -> Self;
}

// [`FromText`] rather than `From<String>` alone, and for the same reason
// `FromLiteral` asks for it: `Option<String>` converts from a `String` — that is
// `From<T> for Option<T>` — so a bound of `From<String>` here would let an
// `Option` prop match this impl as readily as the one below, and neither would
// win. No `Option` converts from static text, which is what tells the two apart.
impl<T: FromText> FromInterpolated<Direct> for T {
    fn from_interpolated(text: String) -> Self {
        T::from(text)
    }
}

impl<T: From<String>> FromInterpolated<Wrapped> for Option<T> {
    fn from_interpolated(text: String) -> Self {
        Some(T::from(text))
    }
}

/// The channel every attribute a component does not declare travels through.
///
/// Implemented by the derive on the prop builder, and only when the component
/// has a `#[prop(rest)]` field to put them in. That is what makes the bag
/// opt-in: a component without one still rejects an attribute it does not
/// name, so a typo is a build failure rather than something rendered into the
/// page.
///
/// # How a call site reaches it
///
/// `<Hidden data-cover-target="input"/>` cannot be resolved by the template
/// lowering, which is compiled somewhere else and cannot see `Hidden`'s fields.
/// So the lowering does not decide. It emits the setter call it always emitted
/// and, next to it, a fallback trait carrying the same method name, and lets
/// **method resolution** choose: an inherent setter — a declared prop — wins,
/// and only a name the builder has no setter for falls through to here.
///
/// A name that could not be a method at all, which is every hyphenated one,
/// skips the fallback and is emitted as a call to this trait directly.
///
/// Nothing here is meant to be named by hand.
#[diagnostic::on_unimplemented(
    message = "this component takes only the props it declares",
    label = "there is no `#[prop(rest)]` field to carry an attribute the component does not name",
    note = "give the component one — `#[prop(rest)] pub attrs: damask::Attrs` — and spread it in its template with `{{...self.attrs}}`"
)]
pub trait Rest: Sized {
    /// `name={expr}`, and the interpolated `name="a {b}"` — both arrive as a
    /// value whose type decides whether the attribute appears at all.
    fn __damask_rest<V: crate::attr::IntoAttrValue>(self, name: &'static str, value: V) -> Self;

    /// `name="text"`, where both halves are template source and stay borrowed.
    fn __damask_rest_static(self, name: &'static str, value: &'static str) -> Self;

    /// A bare `name`, written with no value.
    fn __damask_rest_bare(self, name: &'static str) -> Self;

    /// `{...expr}` on a component: a whole set, folded in where it was written.
    fn __damask_rest_spread<A: crate::attr::AttrSet + ?Sized>(self, attrs: &A) -> Self;
}