blob: 108694b8a95ac744a25c7821cb7b1903254363da [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Do not depend on this crate. Instead, use the `fuchsia_inspect_derive` crate.
#![recursion_limit = "128"]
extern crate proc_macro;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, DeriveInput, Error};
struct UnitField {
/// Name of the original and the inspect data field.
name: syn::Ident,
/// The type path of the original field. For instance a `u32` has a single segment
/// `u32`, and `my::Yak` has two segments `my` and `Yak`.
type_path: syn::TypePath,
/// Field attribute arguments, e.g. `skip`
attr_args: FieldAttrArgs,
}
impl UnitField {
/// Parse a syn::Field into a unit field. Returns an error if the field is not named
/// and Ok(None) if the field should be skipped.
fn try_from_field(f: &syn::Field) -> Result<Option<Self>, Error> {
let name = f.ident.as_ref().expect("internal error: expected named field").clone();
let attr_args = get_field_attrs(f)?;
attr_args.validate_for_unit()?;
if attr_args.skip {
Ok(None)
} else {
let type_path = to_type_path(&f.ty)?;
Ok(Some(UnitField { name, type_path, attr_args }))
}
}
/// Get a string literal containing the name of the field.
fn literal(&self) -> syn::LitStr {
self.attr_args
.rename
.clone()
.unwrap_or_else(|| syn::LitStr::new(self.name.to_string().as_ref(), self.name.span()))
}
/// Convenience method to get the fully qualified path to the Unit trait.
fn unit_path(&self) -> TokenStream {
let type_path = &self.type_path;
quote! { <#type_path as ::fuchsia_inspect_derive::Unit> }
}
/// Creates a single field declaration for the inspect data struct declaration.
fn struct_decl(&self) -> TokenStream {
let name = &self.name;
let span = self.type_path.span();
let unit = self.unit_path();
quote_spanned! { span=> #name: #unit::Data }
}
/// Creates a single field assignment for the inspect data struct initialization.
///
/// Note that the local variable `inspect_node` must be defined.
fn create_struct_expr(&self) -> TokenStream {
let literal = self.literal();
let name = &self.name;
let unit = self.unit_path();
quote! { #name: #unit::inspect_create(&self.#name, &inspect_node, #literal) }
}
/// Creates a single field update assignment statement.
fn update_stmt(&self) -> TokenStream {
let name = &self.name;
let unit = self.unit_path();
quote! {#unit::inspect_update(&self.#name, &mut data.#name)}
}
}
struct InspectField {
/// Name of the original and the inspect data field.
name: syn::Ident,
/// Field attribute arguments, e.g. `skip`
attr_args: FieldAttrArgs,
}
impl InspectField {
/// Parse a syn::Field into an inspect field. Returns an error if the field is not named
/// and Ok(None) if the field should be skipped.
fn try_from_field(f: &syn::Field) -> Result<Option<Self>, Error> {
let name = f.ident.as_ref().expect("internal error: expected named field").clone();
let attr_args = get_field_attrs(f)?;
attr_args.validate_for_inspect()?;
if attr_args.skip {
Ok(None)
} else {
Ok(Some(InspectField { name, attr_args }))
}
}
/// Get a string literal containing the name of the field.
fn literal(&self) -> syn::LitStr {
self.attr_args
.rename
.clone()
.unwrap_or_else(|| syn::LitStr::new(self.name.to_string().as_ref(), self.name.span()))
}
/// Creates an iattach assignment.
fn iattach_stmt(&self, has_inspect_node: bool) -> Result<TokenStream, Error> {
let name = &self.name;
let span = name.span();
match (self.attr_args.forward, has_inspect_node) {
// attach to parent
(false, false) => {
let literal = self.literal();
Ok(quote_spanned! { span=> self.#name.iattach(parent, #literal)? })
}
// attach to self.inspect_node
(false, true) => {
let literal = self.literal();
Ok(quote_spanned! { span=> self.#name.iattach(&self.inspect_node, #literal)? })
}
// forward attachment to inner field
(true, false) => Ok(quote_spanned! { span=> self.#name.iattach(parent, &name)? }),
// forward and attach to inspect_node would cause a name collision
(true, true) => Err(Error::new_spanned(
name,
"inspect_node and inspect(forward) cannot be enabled simultaneously",
)),
}
}
}
/// Convenience method to convert a type to a type path. Errors if not a type path.
fn to_type_path(ty: &syn::Type) -> Result<syn::TypePath, Error> {
match ty {
syn::Type::Path(ref p) => Ok(p.clone()),
_ => Err(Error::new_spanned(ty, "cannot derive inspect for this type")),
}
}
/// Parsed inspect attributes for an individual field.
struct FieldAttrArgs {
/// The span of the field, for errors.
span: Span,
/// The field should not be inspected.
/// Example: [inspect(skip)]
skip: bool,
/// Forwards inspect attachments to this field. Only available for types
/// without an `inspect_node`. As a result, the name of this field will go
/// unused. Useful for wrapper types.
/// Example: [inspect(forward)]
forward: bool,
/// Renames the inspect field to a different name.
/// Example: [inspect(rename = "foo")]
rename: Option<syn::LitStr>,
}
impl FieldAttrArgs {
/// Create from a span used for error messages. All fields assume their default values.
fn new(span: Span) -> Self {
Self { span, skip: false, forward: false, rename: None }
}
/// Validate that attributes are valid for `Unit`
fn validate_for_unit(&self) -> Result<(), Error> {
self.validate_skip()?;
if self.forward {
Err(Error::new(self.span, "inspect(forward) is not defined for `Unit`"))
} else {
Ok(())
}
}
/// Validate that attributes are valid for `Inspect`
fn validate_for_inspect(&self) -> Result<(), Error> {
self.validate_skip()?;
if self.forward && self.rename.is_some() {
Err(Error::new(
self.span,
concat!(
"inspect(rename) cannot be used with inspect(forward) ",
"since forward ignores any name provided"
),
))
} else {
Ok(())
}
}
/// Validate that if `skip` is provided, no other attributes are.
fn validate_skip(&self) -> Result<(), Error> {
if self.skip && (self.forward || self.rename.is_some()) {
Err(Error::new(
self.span,
"inspect(skip) cannot be specified together with other attributes",
))
} else {
Ok(())
}
}
}
/// Given an `inspect` field attribute, parse its arguments. Errors out if
/// arguments are malformed.
fn parse_field_attr_args(attr: &syn::Attribute, args: &mut FieldAttrArgs) -> Result<(), Error> {
return match attr.parse_meta()? {
syn::Meta::List(syn::MetaList { ref nested, .. }) => {
for nested_meta in nested {
match nested_meta {
syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => {
if path.is_ident("skip") {
args.skip = true;
} else if path.is_ident("forward") {
args.forward = true;
} else {
return Err(Error::new_spanned(
path,
"unrecognized attribute argument",
));
}
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ref name_value)) => {
if name_value.path.is_ident("rename") {
if let syn::Lit::Str(ref lit_str) = name_value.lit {
args.rename = Some(lit_str.clone());
} else {
return Err(Error::new_spanned(
&name_value.lit,
"rename value must be string literal",
));
}
} else {
return Err(Error::new_spanned(
name_value,
"unrecognized attribute argument",
));
}
}
_ => {
return Err(Error::new_spanned(
nested_meta,
"unrecognized attribute argument",
))
}
}
}
Ok(())
}
meta @ _ => Err(Error::new_spanned(meta, "unrecognized attribute arguments")),
};
}
/// Returns the field attribute args on a structured form, or an error upon unrecognized arguments.
/// Ignores any attributes that are not `inspect`.
fn get_field_attrs(f: &syn::Field) -> Result<FieldAttrArgs, Error> {
let mut args = FieldAttrArgs::new(f.span());
for attr in &f.attrs {
if attr.path.is_ident("inspect") {
parse_field_attr_args(attr, &mut args)?;
}
}
Ok(args)
}
/// Checks that no `inspect` attributes are set on the container.
fn check_container_attrs(d: &syn::DeriveInput) -> Result<(), Error> {
for attr in &d.attrs {
if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "inspect" {
return Err(Error::new_spanned(attr, "inspect does not support container attributes"));
}
}
Ok(())
}
/// The `Unit` derive macro. Requires that the type is a named struct.
/// Type- and lifetime parameters are supported if they are ignored.
///
/// The only supported field-level attribute is `inspect(skip)`.
// TODO(fxbug.dev/50504): Add support for more types, such as enums.
#[proc_macro_derive(Unit, attributes(inspect))]
pub fn derive_unit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
match derive_unit_inner(ast) {
Ok(token_stream) => token_stream,
Err(err) => err.to_compile_error(),
}
.into()
}
fn derive_unit_inner(ast: DeriveInput) -> Result<TokenStream, Error> {
let name = &ast.ident;
check_container_attrs(&ast)?;
let fields = match ast.data {
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
..
}) => Ok(named),
_ => Err(Error::new_spanned(&ast, "can only derive Unit on named structs")),
}?;
let mut unit_fields = Vec::new();
for field in fields {
if let Some(data_field) = UnitField::try_from_field(field)? {
unit_fields.push(data_field);
}
}
let inspect_data_ident = format_ident!("_{}InspectData", name);
let struct_decls = unit_fields.iter().map(|f| f.struct_decl());
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let create_struct_exprs = unit_fields.iter().map(|f| f.create_struct_expr());
let update_stmts = unit_fields.iter().map(|f| f.update_stmt());
Ok(quote! {
#[derive(Default)]
struct #inspect_data_ident {
#(#struct_decls,)*
inspect_node: ::fuchsia_inspect_derive::InspectNode,
}
impl #impl_generics ::fuchsia_inspect_derive::Unit for #name #ty_generics #where_clause {
type Data = #inspect_data_ident;
fn inspect_create(
&self,
parent: &::fuchsia_inspect_derive::InspectNode,
name: impl AsRef<str>
) -> Self::Data {
let inspect_node = parent.create_child(name);
#inspect_data_ident {
#(#create_struct_exprs,)*
inspect_node,
}
}
fn inspect_update(&self, data: &mut Self::Data) {
#(#update_stmts;)*
}
}
})
}
/// The `Inspect` derive macro. Requires that the type is a named struct.
///
/// If an `inspect_node` field is present, an inspect node will be created
/// and used for all properties of this node. It must be of type
/// `fuchsia_inspect::Node`. If `inspect_node` is NOT present, all
/// `Inspect` fields of this type will be merged into the parent node
/// (similar to serde's "flatten" attribute).
/// As a result, the name provided by the parent will be ignored (unless
/// `inspect(forward)` is used, see below).
/// This is suitable when an intermediate layer should be omitted. Care should
/// be taken to (a) avoid name collisions between nested inspect fields of
/// this type and the parent node's own children and (b) ensure that the parent
/// node outlives instances of this type, to avoid premature detachment from the
/// inspect tree.
///
/// Supported field-level attributes:
/// - `inspect(skip)`: Ignore this field in inspect entirely.
/// - `inspect(rename = "foo")`: Use a different name for the inspect node or
/// property of this field. By default, the field identifier is used.
/// - `inspect(forward)`: Forward attachments directly to a child field. Only
/// a single field can be forwarded, and `inspect_node` must be absent. As
/// a result the name of the field will be unused. Useful for wrapper types.
/// Note that other non-forward `Inspect` fields are allowed and their
/// semantics are unaffected.
///
/// Type- and lifetime parameters are supported if they are ignored.
#[proc_macro_derive(Inspect, attributes(inspect))]
pub fn derive_inspect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
match derive_inspect_inner(ast) {
Ok(token_stream) => token_stream,
Err(err) => err.to_compile_error(),
}
.into()
}
fn derive_inspect_inner(ast: DeriveInput) -> Result<TokenStream, Error> {
let name = &ast.ident;
check_container_attrs(&ast)?;
let fields = match ast.data {
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
..
}) => Ok(named),
_ => Err(Error::new_spanned(&ast, "can only derive Inspect on named structs")),
}?;
let mut inspect_fields = Vec::new();
let mut has_inspect_node = false;
for field in fields {
if field.ident.as_ref().expect("internal error: expected named field") == "inspect_node" {
has_inspect_node = true;
continue;
}
if let Some(data_field) = InspectField::try_from_field(field)? {
inspect_fields.push(data_field);
}
}
let forward_count = inspect_fields.iter().filter(|f| f.attr_args.forward).count();
if forward_count > 1 {
return Err(Error::new_spanned(&ast, "only one inspect(forward) is allowed"));
}
let node_setup_stmt = if has_inspect_node {
quote! {
self.inspect_node = parent.create_child(name);
}
} else {
TokenStream::new()
};
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let iattach_stmts = inspect_fields
.iter()
.map(|f| f.iattach_stmt(has_inspect_node))
.collect::<Result<Vec<_>, _>>()?;
Ok(quote! {
impl #impl_generics ::fuchsia_inspect_derive::Inspect for &mut #name #ty_generics
#where_clause
{
fn iattach(
self,
parent: &::fuchsia_inspect_derive::InspectNode,
name: impl AsRef<str>
) -> std::result::Result<(), fuchsia_inspect_derive::AttachError> {
#node_setup_stmt
#(#iattach_stmts;)*
Ok(())
}
}
})
}