rialo-s-msg 0.18.0

Solana msg macro.
Documentation
#![allow(unsafe_code)]

/// Print a message to the log.
///
/// Supports simple strings as well as Rust [format strings][fs]. When passed a
/// single expression it will be passed directly to [`rlo_log`]. The expression
/// must have type `&str`, and is typically used for logging static strings.
/// When passed something other than an expression, particularly
/// a sequence of expressions, the tokens will be passed through the
/// [`format!`] macro before being logged with `rlo_log`.
///
/// [fs]: https://doc.rust-lang.org/std/fmt/
/// [`format!`]: https://doc.rust-lang.org/std/fmt/fn.format.html
///
/// Note that Rust's formatting machinery is relatively CPU-intensive
/// for constrained environments like the Solana VM.
///
/// # Examples
///
/// ```
/// use rialo_s_msg::msg;
///
/// // The fast form
/// msg!("verifying multisig");
///
/// // With formatting
/// let err = "not enough signers";
/// msg!("multisig failed: {}", err);
/// ```
///
/// The fast form does not run `format!`, so a lone literal cannot interpolate.
/// Inline captures in it are rejected at compile time rather than logged with
/// their braces intact:
///
/// ```compile_fail
/// use rialo_s_msg::msg;
///
/// let err = "not enough signers";
/// msg!("multisig failed: {err}"); // error: use msg!("multisig failed: {}", err)
/// ```
#[macro_export]
macro_rules! msg {
    // Allocation-free fast path for string literals: the literal reaches the
    // syscall as-is, with no `format!` and no `String`.
    //
    // Guarded, because the fast path cannot interpolate: a literal carrying a
    // format placeholder (`msg!("fee={fee}")`) would otherwise log the braces
    // verbatim, silently — the defect this arm exists to make impossible. The
    // guard turns it into a compile error pointing at the formatting form.
    //
    // The guard sees the *cooked* string, so escapes whose braces belong to
    // Rust's lexer rather than to `format!` (`"\u{2014}"`) pass unaffected.
    ($msg:literal) => {{
        const _: () = $crate::assert_literal_has_no_format_placeholder($msg);
        $crate::rlo_log($msg)
    }};
    ($msg:expr) => {
        $crate::rlo_log($msg)
    };
    ($($arg:tt)*) => ($crate::rlo_log(&format!($($arg)*)));
}

/// Compile-time guard behind [`msg!`]'s allocation-free literal fast path.
///
/// Aborts compilation if `message` contains `{` or `}`. Called only from a
/// `const` item inside `msg!`, so a violation is a build failure rather than a
/// log line with visible braces.
///
/// Not intended to be called directly; it is public only because `msg!`
/// expands at the call site.
pub const fn assert_literal_has_no_format_placeholder(message: &str) {
    let bytes = message.as_bytes();
    let mut index = 0;
    while index < bytes.len() {
        if bytes[index] == b'{' || bytes[index] == b'}' {
            panic!(
                "this `msg!` literal contains `{{` or `}}`, but the single-literal form is \
                 allocation-free and does not run `format!`, so the braces would be logged \
                 verbatim. Pass the values as arguments instead: `msg!(\"fee={{}}\", fee)`. \
                 For a literal brace, use the argument form as well."
            );
        }
        index += 1;
    }
}

#[cfg(target_os = "solana")]
pub mod syscalls;

/// Print a string to the log.
#[inline]
pub fn rlo_log(message: &str) {
    #[cfg(target_os = "solana")]
    unsafe {
        syscalls::rlo_log_(message.as_ptr(), message.len() as u64);
    }

    #[cfg(not(target_os = "solana"))]
    println!("{message}");
}

#[cfg(test)]
mod tests {
    use super::*;

    /// `const` context, so these are checked at compile time: were the guard to
    /// reject any of them, this module would fail to build.
    #[test]
    fn guard_accepts_literals_without_format_placeholders() {
        const _: () = assert_literal_has_no_format_placeholder("");
        const _: () = assert_literal_has_no_format_placeholder("verifying multisig");
        // Braces belonging to a lexer escape, not to `format!` — the guard sees
        // the cooked string, so this is an em dash and passes.
        const _: () = assert_literal_has_no_format_placeholder("reconnecting \u{2014} retrying");
        const _: () = assert_literal_has_no_format_placeholder("percent % and backslash \\ pass");
    }

    /// The rejecting direction is covered by the `compile_fail` doctest on
    /// [`msg!`]; a runtime call here would abort the test process rather than
    /// fail an assertion, since the guard panics.
    #[test]
    fn fast_path_and_formatting_path_both_log() {
        let err = "not enough signers";
        let owned = String::from("owned");
        msg!("verifying multisig");
        msg!("multisig failed: {}", err);
        msg!(&owned);
        msg!("reconnecting \u{2014} retrying");
    }
}