blob: 4bb272d818d59bb2b2b2623c43a299e363f74198 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! FOR_RELASE: Docs
chromium::import! {
"//mojo/public/rust/system";
}
use crate::ast::*;
use crate::pack::pack_mojom_type;
use ordered_float::OrderedFloat;
use std::collections::HashMap;
use system::message_pipe::MessageEndpoint;
use system::mojo_types::UntypedHandle;
/// This trait allows a type to be serialized/deserialized into a Mojom message.
pub trait MojomParse:
Into<MojomValue> + TryFrom<MojomValue, Error = anyhow::Error> + Sized + 'static
{
/// Returns the MojomType associated with this rust struct. This function
/// should always return the same value.
fn mojom_type() -> MojomType;
/// Returns the packed format for this type.
///
/// Logically, this just returns pack_mojom_type(T::mojom_type(), 0).
/// However, since we call this for every parse/deparse call, it caches the
/// results of every previous call.
///
/// FOR_RELEASE: We could reduce our complexity by using the generic_static
/// crate
fn wire_type() -> &'static MojomWireType {
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::{LazyLock, RwLock};
// The cache maps rust types to their corresponding packed format, using
// TypeId to represent them at runtime.
type WireTypeCache = HashMap<TypeId, &'static MojomWireType>;
// Sadly, we can't initialize an RwLock static directly because the initializer
// wouldn't be a constant expression, so have to wrap it in LazyLock.
static WIRE_TYPE: LazyLock<RwLock<WireTypeCache>> =
LazyLock::new(|| RwLock::new(WireTypeCache::new()));
// The read can only fail if a writer panicked at some point; packing never
// panics so we know it's safe to unwrap here.
// `cloned` transforms Option<&& MojomWireType> -> Option<& MojomWireType>
let contents: Option<&'static MojomWireType> =
WIRE_TYPE.read().unwrap().get(&TypeId::of::<Self>()).cloned();
match contents {
Some(wire_type_ref) => {
// Computed this before, return the cached result.
wire_type_ref
}
None => {
// No current entry, initialize it by packing the input type.
let wire_type_box = Box::new(pack_mojom_type(&Self::mojom_type()));
let wire_type_ref: &'static MojomWireType = Box::leak(wire_type_box);
WIRE_TYPE.write().unwrap().insert(TypeId::of::<Self>(), wire_type_ref);
wire_type_ref
}
}
}
}
// FOR_RELEASE: We could replace this with one of a number of crates. num_enum
// seems closest, though it doesn't have quite the API we want (we want
// from_primitive to return an option but respect default values if they exist)
pub trait PrimitiveEnum: Into<i32> + TryFrom<i32, Error = anyhow::Error> + Sized {
fn is_valid(value: i32) -> bool {
Self::try_from(value).is_ok()
}
}
impl<T: PrimitiveEnum> From<T> for MojomValue {
fn from(value: T) -> MojomValue {
MojomValue::Enum(value.into())
}
}
/***************************** */
// Implementations of the traits for various types.
// Note that for some types we derive instead, see parsing_attribute.rs.
/***************************** */
/// Implements the MojomParse trait for a leaf type. (Ab)uses the fact that
/// MojomType and MojomValue use identically-named variants.
macro_rules! mojomparse_leaf_impl {
($target_type:ty, $variant:ident) => {
impl MojomParse for $target_type {
fn mojom_type() -> MojomType {
MojomType::$variant
}
}
impl From<$target_type> for MojomValue {
fn from(value: $target_type) -> MojomValue {
MojomValue::$variant(value.into())
}
}
impl TryFrom<MojomValue> for $target_type {
type Error = anyhow::Error;
fn try_from(value: MojomValue) -> anyhow::Result<$target_type> {
if let MojomValue::$variant(v) = value {
return Ok(v.into());
} else {
anyhow::bail!(
"Cannot construct a value of type {} from this MojomValue: {:?}",
std::any::type_name::<$target_type>(),
value
);
}
}
}
};
}
mojomparse_leaf_impl!(u8, UInt8);
mojomparse_leaf_impl!(u16, UInt16);
mojomparse_leaf_impl!(u32, UInt32);
mojomparse_leaf_impl!(u64, UInt64);
mojomparse_leaf_impl!(i8, Int8);
mojomparse_leaf_impl!(i16, Int16);
mojomparse_leaf_impl!(i32, Int32);
mojomparse_leaf_impl!(i64, Int64);
// Floats can correspond either to themselves or their ordered version.
// The latter is only generated by the bindings when used as keys in a map.
mojomparse_leaf_impl!(f32, Float32);
mojomparse_leaf_impl!(f64, Float64);
mojomparse_leaf_impl!(OrderedFloat<f32>, Float32);
mojomparse_leaf_impl!(OrderedFloat<f64>, Float64);
mojomparse_leaf_impl!(bool, Bool);
mojomparse_leaf_impl!(String, String);
mojomparse_leaf_impl!(UntypedHandle, Handle);
mojomparse_leaf_impl!(MessageEndpoint, Handle);
// Implement MojomParse for any type that implements PrimitiveEnum and the other
// requirements for MojomParse. All requirements are derived by
// #[derive(PrimitiveEnum)]. Note that logically we could implement
// TryFrom<MojomValue> for T here, but the compiler won't let us since T is
// uncovered, so we derive it instead.
impl<T> MojomParse for T
where
T: PrimitiveEnum + TryFrom<MojomValue, Error = anyhow::Error> + 'static,
{
fn mojom_type() -> MojomType {
MojomType::Enum { is_valid: Predicate::new::<T>(&(Self::is_valid as fn(i32) -> bool)) }
}
}
// Implement MojomParse for arrays and vectors
// It would be neat to do this more generally, e.g. anything that can be cast
// to a slice, but rust doesn't have a way for us to prove that the different
// implementations of the trait are disjoint.
impl<T: MojomParse> From<Vec<T>> for MojomValue {
fn from(value: Vec<T>) -> MojomValue {
MojomValue::Array(value.into_iter().map(T::into).collect())
}
}
impl<T: MojomParse> TryFrom<MojomValue> for Vec<T> {
type Error = anyhow::Error;
fn try_from(value: MojomValue) -> anyhow::Result<Self> {
if let MojomValue::Array(v) = value {
v.into_iter().map(T::try_from).collect::<anyhow::Result<_>>()
} else {
anyhow::bail!(
"Cannot construct a value of type {} from this MojomValue: {:?}",
std::any::type_name::<Self>(),
value
);
}
}
}
impl<T: MojomParse> MojomParse for Vec<T> {
fn mojom_type() -> MojomType {
MojomType::Array { element_type: Box::new(T::mojom_type()), num_elements: None }
}
}
impl<T: MojomParse, const N: usize> From<[T; N]> for MojomValue {
fn from(value: [T; N]) -> MojomValue {
MojomValue::Array(value.into_iter().map(T::into).collect())
}
}
impl<T: MojomParse, const N: usize> TryFrom<MojomValue> for [T; N] {
type Error = anyhow::Error;
fn try_from(value: MojomValue) -> anyhow::Result<Self> {
let MojomValue::Array(v) = value else {
anyhow::bail!(
"Cannot construct a value of type {} from this MojomValue: {:?}",
std::any::type_name::<Self>(),
value
);
};
if v.len() != N {
anyhow::bail!(
"Wrong number of values to construct {} from this MojomValue: {:?}",
std::any::type_name::<Self>(),
MojomValue::Array(v)
)
};
// FOR_RELEASE: Get itertools approved and use collect_array here.
let vec_of_t: Vec<T> = v.into_iter().map(T::try_from).collect::<anyhow::Result<_>>()?;
// Unwrap will succeed because we just checked the length above.
// We can't just use `unwrap` because `T` may not be `Debug`.
let arr_of_t: [T; N] = Self::try_from(vec_of_t).unwrap_or_else(|_| unreachable!());
Ok(arr_of_t)
}
}
impl<T: MojomParse, const N: usize> MojomParse for [T; N] {
fn mojom_type() -> MojomType {
MojomType::Array { element_type: Box::new(T::mojom_type()), num_elements: Some(N) }
}
}
// Implement MojomParse for Maps
// Currently this is only implemented for HashMap, since that's
// probably what most users will want, but we can extend it to
// other map types if we need to.
impl<K, V> From<HashMap<K, V>> for MojomValue
where
K: MojomParse + Eq + std::hash::Hash,
V: MojomParse,
{
fn from(value: HashMap<K, V>) -> MojomValue {
let hashmap = value.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
MojomValue::Map(hashmap)
}
}
impl<K, V> TryFrom<MojomValue> for HashMap<K, V>
where
K: MojomParse + Eq + std::hash::Hash,
V: MojomParse,
{
type Error = anyhow::Error;
fn try_from(value: MojomValue) -> anyhow::Result<Self> {
if let MojomValue::Map(hashmap) = value {
let converted_map: Self = hashmap
.into_iter()
// Map to Result<(K, V)>
.map(|(k, v)| {
let ret: anyhow::Result<(K, V)> = Ok((k.try_into()?, v.try_into()?));
ret
})
// Fail if any of the conversions failed
// FOR_RELEASE: It would be nice to use Itertools::process_results
// instead of collecting here
.collect::<Result<_, _>>()?;
Ok(converted_map)
} else {
anyhow::bail!(
"Cannot construct a value of type {} from this MojomValue: {:?}",
std::any::type_name::<Self>(),
value
);
}
}
}
impl<K, V> MojomParse for HashMap<K, V>
where
K: MojomParse + Eq + std::hash::Hash,
V: MojomParse,
{
fn mojom_type() -> MojomType {
MojomType::Map {
key_type: Box::new(K::mojom_type()),
value_type: Box::new(V::mojom_type()),
}
}
}
// Implement MojomParse for Options
impl<T: MojomParse> From<Option<T>> for MojomValue {
fn from(value: Option<T>) -> MojomValue {
MojomValue::Nullable(value.map(|v| Box::new(v.into())))
}
}
impl<T: MojomParse> TryFrom<MojomValue> for Option<T> {
type Error = anyhow::Error;
fn try_from(value: MojomValue) -> anyhow::Result<Self> {
if let MojomValue::Nullable(opt) = value {
opt.map(|v| (*v).try_into()).transpose()
} else {
anyhow::bail!(
"Cannot construct a value of type {} from this MojomValue: {:?}",
std::any::type_name::<Self>(),
value
);
}
}
}
impl<T: MojomParse> MojomParse for Option<T> {
fn mojom_type() -> MojomType {
MojomType::Nullable { inner_type: Box::new(T::mojom_type()) }
}
}