| // 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_RELEASE: Docs |
| |
| use quote::quote; |
| use syn::{parse_macro_input, DeriveInput}; |
| |
| #[proc_macro_derive(MojomParse)] |
| pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| let input = parse_macro_input!(input as DeriveInput); |
| let name = input.ident; |
| |
| let struct_fields = match input.data { |
| syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { |
| syn::Fields::Named(syn::FieldsNamed { named, .. }) => named, |
| _ => todo!("Gotta have named fields, give an error message"), |
| }, |
| _ => todo!("Only structs supported at the moment"), |
| }; |
| |
| let num_fields = struct_fields.len(); |
| |
| // As far as I know, quote can only iterate over vectors of things that can |
| // be directly converted to tokens. Notably, this means they have to be |
| // single values. So if we want to write something like #name = #val, in a |
| // loop, we first have to combine each pair of names and values into a single |
| // token stream, and then can we iterate over that in the quote. |
| |
| // The names of the fields in the struct. |
| let field_idents: Vec<&syn::Ident> = |
| struct_fields.iter().map(|field| field.ident.as_ref().unwrap()).collect(); |
| |
| // A bunch of entries for a MojomType::Struct |
| let mojom_type_fields: Vec<proc_macro2::TokenStream> = struct_fields |
| .iter() |
| .map(|field| { |
| let ty = &field.ty; |
| let name = field.ident.as_ref().unwrap().to_string(); |
| quote! { (#name.to_string(), #ty::mojom_type()) } |
| }) |
| .collect(); |
| |
| // A bunch of entries for a MojomValue::Struct |
| let to_mojom_value_fields: Vec<proc_macro2::TokenStream> = struct_fields |
| .iter() |
| .map(|field| { |
| let name = field.ident.as_ref().unwrap(); |
| let name_str = name.to_string(); |
| quote! { (#name_str.to_string(), value.#name.into()) } |
| }) |
| .collect(); |
| |
| // The body of a struct value, converting each field from a MojomValue with |
| // the same name as the field. |
| let from_mojom_value_fields: Vec<proc_macro2::TokenStream> = struct_fields |
| .iter() |
| .map(|field| { |
| let name = field.ident.as_ref().unwrap(); |
| quote! { #name: #name.try_into()? } |
| }) |
| .collect(); |
| |
| // We wrap the `impl` blocks in an anonymous scope so that we can |
| // import mojom_parser_core without polluting the caller's namespace. |
| let quoted = quote! { |
| const _: () = { |
| chromium::import! { |
| "//mojo/public/rust/mojom_parser:mojom_parser_core"; |
| } |
| |
| use mojom_parser_core::*; |
| |
| impl MojomParse for #name { |
| fn mojom_type() -> MojomType { |
| let fields : Vec<(String, MojomType)> = vec![ |
| #(#mojom_type_fields),* |
| ]; |
| MojomType::Struct { fields } |
| } |
| } |
| |
| impl From<#name> for MojomValue { |
| fn from(value: #name) -> MojomValue { |
| let fields : Vec<(String, MojomValue)> = vec![ |
| #(#to_mojom_value_fields),* |
| ]; |
| MojomValue::Struct ( fields ) |
| } |
| } |
| |
| impl TryFrom<MojomValue> for #name { |
| type Error = ::anyhow::Error; |
| |
| fn try_from(value : MojomValue) -> ::anyhow::Result<Self> { |
| use ::anyhow::Context; |
| // FOR_RELEASE: Don't clone here |
| if let MojomValue::Struct(fields) = value.clone() { |
| // Drop the strings, we don't care about them here |
| let fields : Vec<MojomValue> = fields.into_iter().map(|field| field.1).collect(); |
| // Try to extract all the field values at once |
| let fields : [MojomValue; #num_fields] = fields.try_into() |
| .or(Err(::anyhow::anyhow!( |
| "Wrong number of fields to construct a value of type {} from MojomValue {:?}", |
| std::any::type_name::<#name>(), |
| value)))?; |
| let [#(#field_idents),*] = fields; |
| return Ok(Self { |
| #(#from_mojom_value_fields),* |
| }) |
| } else { |
| ::anyhow::bail!( |
| "Cannot construct a value of type {} from non-struct MojomValue {:?}", |
| std::any::type_name::<#name>(), |
| value |
| ); |
| } |
| } |
| } |
| }; |
| }; |
| |
| // Excellent for debugging, prints out the entire generated code |
| // println!("{}", "ed); |
| return proc_macro::TokenStream::from(quoted); |
| } |