Skip to content

Commit

Permalink
refactor(swarm-derive): better error reporting for invalid attributes
Browse files Browse the repository at this point in the history
This PR refactors the error reporting away from panicking to returning `syn::Result` and adds two unit tests for the parsing of attributes that users interact with.

Pull-Request: #3922.
  • Loading branch information
thomaseizinger authored May 15, 2023
1 parent 0bc724a commit adcc10b
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 62 deletions.
88 changes: 26 additions & 62 deletions swarm-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,48 @@
#![recursion_limit = "256"]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

mod syn_ext;

use crate::syn_ext::RequireStrLit;
use heck::ToUpperCamelCase;
use proc_macro::TokenStream;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
parse_macro_input, Data, DataStruct, DeriveInput, Expr, ExprLit, Lit, Meta, MetaNameValue,
Token,
};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Meta, Token};

/// Generates a delegating `NetworkBehaviour` implementation for the struct this is used for. See
/// the trait documentation for better description.
#[proc_macro_derive(NetworkBehaviour, attributes(behaviour))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
build(&ast)
build(&ast).unwrap_or_else(|e| e.to_compile_error().into())
}

/// The actual implementation.
fn build(ast: &DeriveInput) -> TokenStream {
fn build(ast: &DeriveInput) -> syn::Result<TokenStream> {
match ast.data {
Data::Struct(ref s) => build_struct(ast, s),
Data::Enum(_) => unimplemented!("Deriving NetworkBehaviour is not implemented for enums"),
Data::Union(_) => unimplemented!("Deriving NetworkBehaviour is not implemented for unions"),
Data::Enum(_) => Err(syn::Error::new_spanned(
ast,
"Cannot derive `NetworkBehaviour` on enums",
)),
Data::Union(_) => Err(syn::Error::new_spanned(
ast,
"Cannot derive `NetworkBehaviour` on union",
)),
}
}

/// The version for structs
fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (_, ty_generics, where_clause) = ast.generics.split_for_impl();
let BehaviourAttributes {
prelude_path,
user_specified_out_event,
deprecation_tokenstream,
} = match parse_attributes(ast) {
Ok(attrs) => attrs,
Err(e) => return e,
};
} = parse_attributes(ast)?;

let multiaddr = quote! { #prelude_path::Multiaddr };
let trait_to_impl = quote! { #prelude_path::NetworkBehaviour };
Expand Down Expand Up @@ -849,7 +852,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
}
};

final_quote.into()
Ok(final_quote.into())
}

struct BehaviourAttributes {
Expand All @@ -859,7 +862,7 @@ struct BehaviourAttributes {
}

/// Parses the `value` of a key=value pair in the `#[behaviour]` attribute into the requested type.
fn parse_attributes(ast: &DeriveInput) -> Result<BehaviourAttributes, TokenStream> {
fn parse_attributes(ast: &DeriveInput) -> syn::Result<BehaviourAttributes> {
let mut attributes = BehaviourAttributes {
prelude_path: syn::parse_quote! { ::libp2p::swarm::derive_prelude },
user_specified_out_event: None,
Expand All @@ -871,33 +874,13 @@ fn parse_attributes(ast: &DeriveInput) -> Result<BehaviourAttributes, TokenStrea
.iter()
.filter(|attr| attr.path().is_ident("behaviour"))
{
let nested = attr
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.expect("`parse_args_with` never fails when parsing nested meta");
let nested = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;

for meta in nested {
if meta.path().is_ident("prelude") {
match meta {
Meta::Path(_) => unimplemented!(),
Meta::List(_) => unimplemented!(),
Meta::NameValue(MetaNameValue {
value:
Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}),
..
}) => {
attributes.prelude_path = syn::parse_str(&s.value()).unwrap();
}
Meta::NameValue(name_value) => {
return Err(syn::Error::new_spanned(
name_value.value,
"`prelude` value must be a quoted path",
)
.to_compile_error()
.into());
}
}
let value = meta.require_name_value()?.value.require_str_lit()?;

attributes.prelude_path = syn::parse_str(&value)?;

continue;
}
Expand All @@ -912,29 +895,10 @@ fn parse_attributes(ast: &DeriveInput) -> Result<BehaviourAttributes, TokenStrea

attributes.deprecation_tokenstream = quote::quote! { #warning };
}
match meta {
Meta::Path(_) => unimplemented!(),
Meta::List(_) => unimplemented!(),

Meta::NameValue(MetaNameValue {
value:
Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}),
..
}) => {
attributes.user_specified_out_event =
Some(syn::parse_str(&s.value()).unwrap());
}
Meta::NameValue(name_value) => {
return Err(syn::Error::new_spanned(
name_value.value,
"`to_swarm` value must be a quoted type",
)
.to_compile_error()
.into());
}
}

let value = meta.require_name_value()?.value.require_str_lit()?;

attributes.user_specified_out_event = Some(syn::parse_str(&value)?);

continue;
}
Expand Down
16 changes: 16 additions & 0 deletions swarm-derive/src/syn_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use syn::{Expr, ExprLit, Lit};

pub(crate) trait RequireStrLit {
fn require_str_lit(&self) -> syn::Result<String>;
}

impl RequireStrLit for Expr {
fn require_str_lit(&self) -> syn::Result<String> {
match self {
Expr::Lit(ExprLit {
lit: Lit::Str(str), ..
}) => Ok(str.value()),
_ => Err(syn::Error::new_spanned(self, "expected a string literal")),
}
}
}
11 changes: 11 additions & 0 deletions swarm/tests/ui/fail/prelude_not_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use libp2p_ping as ping;

#[derive(libp2p_swarm::NetworkBehaviour)]
#[behaviour(prelude = libp2p_swarm::derive_prelude)]
struct Foo {
ping: ping::Behaviour,
}

fn main() {

}
5 changes: 5 additions & 0 deletions swarm/tests/ui/fail/prelude_not_string.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: expected a string literal
--> tests/ui/fail/prelude_not_string.rs:4:23
|
4 | #[behaviour(prelude = libp2p_swarm::derive_prelude)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 changes: 19 additions & 0 deletions swarm/tests/ui/fail/to_swarm_not_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use libp2p_ping as ping;

#[derive(libp2p_swarm::NetworkBehaviour)]
#[behaviour(out_event = FooEvent, prelude = "libp2p_swarm::derive_prelude")]
struct Foo {
ping: ping::Behaviour,
}

struct FooEvent;

impl From<ping::Event> for FooEvent {
fn from(_: ping::Event) -> Self {
unimplemented!()
}
}

fn main() {

}
5 changes: 5 additions & 0 deletions swarm/tests/ui/fail/to_swarm_not_string.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: expected a string literal
--> tests/ui/fail/to_swarm_not_string.rs:4:25
|
4 | #[behaviour(out_event = FooEvent, prelude = "libp2p_swarm::derive_prelude")]
| ^^^^^^^^

0 comments on commit adcc10b

Please sign in to comment.