fix(derive): move to plain syn to fix darling issues

This commit is contained in:
Kat Marchán 2021-08-15 17:27:39 -07:00
parent 027c3b0a94
commit 9a78a94395
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
8 changed files with 376 additions and 225 deletions

View File

@ -14,4 +14,3 @@ proc-macro = true
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = "1.0.45" syn = "1.0.45"
darling = "0.13.0"

View File

@ -1,39 +1,44 @@
use std::fmt::Display;
use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{Lit, Meta, NestedMeta}; use syn::{
parenthesized,
parse::{Parse, ParseStream},
Token,
};
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant}; use crate::diagnostic::{Diagnostic, DiagnosticVariant};
#[derive(Debug)] #[derive(Debug)]
pub struct Code(String); pub struct Code(pub String);
impl Display for Code { impl Parse for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn parse(input: ParseStream) -> syn::Result<Self> {
write!(f, "{}", self.0) let ident = input.parse::<syn::Ident>()?;
} if ident == "code" {
} let la = input.lookahead1();
if la.peek(syn::token::Paren) {
impl FromMeta for Code { let content;
fn from_string(arg: &str) -> Result<Self, DarlingError> { parenthesized!(content in input);
Ok(Code(arg.into())) let la = content.lookahead1();
} if la.peek(syn::LitStr) {
let str = content.parse::<syn::LitStr>()?;
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> { Ok(Code(str.value()))
match &items[0] { } else {
NestedMeta::Meta(Meta::Path(p)) => Ok(Code( let path = content.parse::<syn::Path>()?;
p.segments Ok(Code(
.iter() path.segments
.map(|s| s.ident.to_string()) .iter()
.collect::<Vec<_>>() .map(|s| s.ident.to_string())
.join("::"), .collect::<Vec<_>>()
)), .join("::"),
NestedMeta::Lit(Lit::Str(code)) => Ok(Code(code.value())), ))
_ => Err(DarlingError::custom( }
"invalid code format. Only path::style and string literals are accepted", } else {
)), input.parse::<Token![=]>()?;
Ok(Code(input.parse::<syn::LitStr>()?.value()))
}
} else {
Err(syn::Error::new(ident.span(), "diagnostic code is required. Use #[diagnostic(code = ...)] or #[diagnostic(code(...))] to define one."))
} }
} }
} }
@ -41,16 +46,25 @@ impl FromMeta for Code {
impl Code { impl Code {
pub(crate) fn gen_enum( pub(crate) fn gen_enum(
_diag: &Diagnostic, _diag: &Diagnostic,
variants: &[&DiagnosticVariant], variants: &[DiagnosticVariant],
) -> Option<TokenStream> { ) -> Option<TokenStream> {
let code_pairs = variants.iter().map( let code_pairs = variants.iter().map(
|DiagnosticVariant { |DiagnosticVariant {
ref ident, ref ident,
ref code, ref code,
ref fields,
.. ..
}| { }| {
let code = code.to_string(); let code = &code.0;
quote! { Self::#ident => std::boxed::Box::new(#code), } match fields {
syn::Fields::Named(_) => {
quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
}
syn::Fields::Unnamed(_) => {
quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
}
syn::Fields::Unit => quote! { Self::#ident => std::boxed::Box::new(#code), },
}
}, },
); );
Some(quote! { Some(quote! {
@ -62,15 +76,8 @@ impl Code {
}) })
} }
pub(crate) fn gen_struct( pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
diag: &Diagnostic, let code = &self.0;
_fields: &Fields<&DiagnosticField>,
) -> Option<TokenStream> {
let code = diag
.code
.as_ref()
.expect("`code` attribute is required for diagnostics.")
.to_string();
Some(quote! { Some(quote! {
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> { fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
std::boxed::Box::new(#code) std::boxed::Box::new(#code)

View File

@ -0,0 +1,175 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{punctuated::Punctuated, DeriveInput, Token};
use crate::code::Code;
use crate::diagnostic_arg::DiagnosticArg;
use crate::help::Help;
use crate::severity::Severity;
pub enum Diagnostic {
Struct {
ident: syn::Ident,
generics: syn::Generics,
code: Code,
severity: Option<Severity>,
help: Option<Help>,
},
Enum {
ident: syn::Ident,
generics: syn::Generics,
variants: Vec<DiagnosticVariant>,
},
}
pub struct DiagnosticVariant {
pub ident: syn::Ident,
pub fields: syn::Fields,
pub code: Code,
pub severity: Option<Severity>,
pub help: Option<Help>,
}
impl Diagnostic {
pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
Ok(match input.data {
syn::Data::Struct(_) => {
if let Some(attr) = input.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
let args = attr.parse_args_with(
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
)?;
let mut code = None;
let mut severity = None;
let mut help = None;
for arg in args {
match arg {
DiagnosticArg::Code(new_code) => {
// TODO: error on multiple?
code = Some(new_code);
}
DiagnosticArg::Severity(sev) => {
severity = Some(sev);
}
DiagnosticArg::Help(hl) => {
help = Some(hl)
}
}
}
let ident = input.ident.clone();
Diagnostic::Struct {
ident: input.ident,
generics: input.generics,
code: code.ok_or_else(|| {
syn::Error::new(ident.span(), "Diagnostic code is required.")
})?,
help,
severity,
}
} else {
// Also handle when there's multiple `#[diagnostic]` attrs?
return Err(syn::Error::new(
input.ident.span(),
"#[diagnostic] attribute is required when deriving Diagnostic.",
));
}
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let mut vars = Vec::new();
for var in variants {
if let Some(attr) = var.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
let args = attr.parse_args_with(
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
)?;
let mut code = None;
let mut severity = None;
let mut help = None;
for arg in args {
match arg {
DiagnosticArg::Code(new_code) => {
// TODO: error on multiple?
code = Some(new_code);
}
DiagnosticArg::Severity(sev) => {
severity = Some(sev);
}
DiagnosticArg::Help(hl) => {
help = Some(hl);
}
}
}
let ident = input.ident.clone();
vars.push(DiagnosticVariant {
ident: var.ident,
fields: var.fields,
code: code.ok_or_else(|| {
syn::Error::new(ident.span(), "Diagnostic code is required.")
})?,
help,
severity,
});
} else {
// Also handle when there's multiple `#[diagnostic]` attrs?
return Err(syn::Error::new(
var.ident.span(),
"#[diagnostic] attribute is required on all enum variants when deriving Diagnostic.",
));
}
}
Diagnostic::Enum {
ident: input.ident,
generics: input.generics,
variants: vars,
}
}
syn::Data::Union(_) => {
return Err(syn::Error::new(
input.ident.span(),
"Can't derive Diagnostic for Unions",
))
}
})
}
pub fn gen(&self) -> TokenStream {
match self {
Self::Struct {
ident,
generics,
code,
severity,
help,
} => {
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
let code_body = code.gen_struct();
let help_body = help.as_ref().and_then(|x| x.gen_struct());
let sev_body = severity.as_ref().and_then(|x| x.gen_struct());
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
}
}
}
Self::Enum {
ident,
generics,
variants,
} => {
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
let code_body = Code::gen_enum(self, variants);
let help_body = Help::gen_enum(self, variants);
let sev_body = Severity::gen_enum(self, variants);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
}
}
}
}
}
}

View File

@ -0,0 +1,26 @@
use syn::parse::{Parse, ParseStream};
use crate::code::Code;
use crate::help::Help;
use crate::severity::Severity;
pub enum DiagnosticArg {
Code(Code),
Severity(Severity),
Help(Help),
}
impl Parse for DiagnosticArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.fork().parse::<syn::Ident>()?;
if ident == "code" {
Ok(DiagnosticArg::Code(input.parse()?))
} else if ident == "severity" {
Ok(DiagnosticArg::Severity(input.parse()?))
} else if ident == "help" {
Ok(DiagnosticArg::Help(input.parse()?))
} else {
Err(syn::Error::new(ident.span(), "Unrecognized diagnostic option"))
}
}
}

View File

@ -1,50 +1,47 @@
use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{Lit, NestedMeta}; use syn::{
parenthesized,
parse::{Parse, ParseStream},
Token,
};
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant}; use crate::diagnostic::{Diagnostic, DiagnosticVariant};
#[derive(Debug)]
pub struct Help { pub struct Help {
pub fmt: String, pub fmt: String,
pub args: Vec<NestedMeta>, pub args: Vec<syn::Expr>,
} }
impl FromMeta for Help { impl Parse for Help {
fn from_string(arg: &str) -> Result<Help, DarlingError> { fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Help { let ident = input.parse::<syn::Ident>()?;
fmt: arg.into(), if ident == "help" {
args: Vec::new(), let la = input.lookahead1();
}) if la.peek(syn::token::Paren) {
} let content;
parenthesized!(content in input);
fn from_list(items: &[NestedMeta]) -> Result<Help, DarlingError> { let str = content.parse::<syn::LitStr>()?;
match &items.get(0) { Ok(Help {
Some(NestedMeta::Lit(Lit::Str(fmt))) => Ok(Help { fmt: str.value(),
fmt: fmt.value(), args: Vec::new(),
args: items[1..] })
.iter() } else {
.map(|item| match item { input.parse::<Token![=]>()?;
NestedMeta::Meta(_) => Err(DarlingError::custom( Ok(Help {
"Only literals are supported for now. Sorry :(" fmt: input.parse::<syn::LitStr>()?.value(),
)), args: Vec::new(),
NestedMeta::Lit(_) => Ok(item.clone()), })
}) }
.collect::<Result<Vec<_>, DarlingError>>()?, } else {
}), Err(syn::Error::new(ident.span(), "not a help"))
None => Err(DarlingError::custom("Help format string is required")),
_ => Err(DarlingError::custom(
"First argument must be a literal format string",
)),
} }
} }
} }
impl Help { impl Help {
pub(crate) fn gen_enum( pub(crate) fn gen_enum(
_diag: &Diagnostic, _diag: &Diagnostic,
variants: &[&DiagnosticVariant], variants: &[DiagnosticVariant],
) -> Option<TokenStream> { ) -> Option<TokenStream> {
let help_pairs = variants let help_pairs = variants
.iter() .iter()
@ -53,12 +50,22 @@ impl Help {
|DiagnosticVariant { |DiagnosticVariant {
ref ident, ref ident,
ref help, ref help,
ref fields,
.. ..
}| { }| {
let help = &help.as_ref().unwrap(); let help = &help.as_ref().unwrap();
let fmt = &help.fmt; let fmt = &help.fmt;
let args = help.args.iter().map(|arg| quote! { #arg, }); let args = help.args.iter().map(|arg| quote! { #arg, });
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), } match fields {
syn::Fields::Named(_) => {
quote! { Self::#ident{..} => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }
}
syn::Fields::Unnamed(_) => {
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }
}
syn::Fields::Unit =>
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), },
}
}, },
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -76,17 +83,12 @@ impl Help {
} }
} }
pub(crate) fn gen_struct( pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
diag: &Diagnostic, let fmt = &self.fmt;
_fields: &Fields<&DiagnosticField>, let args = &self.args;
) -> Option<TokenStream> { Some(quote! {
diag.help.as_ref().map(|h| { fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
let fmt = &h.fmt; std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*)))
let args = &h.args;
quote! {
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*)))
}
} }
}) })
} }

View File

@ -1,101 +1,21 @@
use darling::{
ast::{self, Fields},
FromDeriveInput, FromField, FromVariant, ToTokens,
};
use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
use code::Code; use diagnostic::Diagnostic;
use help::Help;
use severity::Severity;
mod code; mod code;
mod diagnostic;
mod diagnostic_arg;
mod help; mod help;
mod severity; mod severity;
#[proc_macro_derive(Diagnostic, attributes(diagnostic))] #[proc_macro_derive(Diagnostic, attributes(diagnostic))]
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
let cmd = match Diagnostic::from_derive_input(&input) { let cmd = match Diagnostic::from_derive_input(input) {
Ok(cmd) => cmd, Ok(cmd) => cmd.gen(),
Err(err) => return err.write_errors().into(), Err(err) => return err.to_compile_error().into(),
}; };
// panic!("{:#}", cmd.to_token_stream()); // panic!("{:#}", cmd.to_token_stream());
quote!(#cmd).into() quote!(#cmd).into()
} }
#[derive(Debug, FromDeriveInput)]
#[darling(supports(any), attributes(diagnostic))]
struct Diagnostic {
ident: syn::Ident,
data: ast::Data<DiagnosticVariant, DiagnosticField>,
generics: syn::Generics,
#[darling(default)]
code: Option<Code>,
#[darling(default)]
severity: Option<Severity>,
#[darling(default)]
help: Option<Help>,
}
#[derive(Debug, FromField)]
struct DiagnosticField {
ident: Option<syn::Ident>,
ty: syn::Type,
}
#[derive(Debug, FromVariant)]
#[darling(attributes(diagnostic))]
struct DiagnosticVariant {
ident: syn::Ident,
code: Code,
#[darling(default)]
severity: Option<Severity>,
#[darling(default)]
help: Option<Help>,
}
impl ToTokens for Diagnostic {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ts = match self.data.as_ref() {
ast::Data::Enum(variants) => self.gen_enum(variants),
ast::Data::Struct(fields) => self.gen_struct(fields),
};
tokens.extend(ts);
}
}
impl Diagnostic {
fn gen_enum(&self, variants: Vec<&DiagnosticVariant>) -> TokenStream {
let ident = &self.ident;
let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl();
let code_body = Code::gen_enum(self, &variants);
let help_body = Help::gen_enum(self, &variants);
let sev_body = Severity::gen_enum(self, &variants);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
}
}
}
fn gen_struct(&self, fields: Fields<&DiagnosticField>) -> TokenStream {
let ident= &self.ident;
let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl();
let code_body = Code::gen_struct(self, &fields);
let help_body = Help::gen_struct(self, &fields);
let sev_body = Severity::gen_struct(self, &fields);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
}
}
}
}

View File

@ -1,33 +1,47 @@
use darling::{ast::Fields, error::Error as DarlingError, FromMeta}; use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{Lit, LitStr, Meta, NestedMeta, Path}; use syn::{
parenthesized,
parse::{Parse, ParseStream},
Token,
};
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant}; use crate::diagnostic::{Diagnostic, DiagnosticVariant};
#[derive(Debug)] pub struct Severity(pub syn::Path);
pub struct Severity(pub Path);
impl FromMeta for Severity { impl Parse for Severity {
fn from_string(arg: &str) -> Result<Self, DarlingError> { fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Severity(LitStr::new(arg, Span::call_site()).parse()?)) let ident = input.parse::<syn::Ident>()?;
} if ident == "severity" {
let la = input.lookahead1();
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> { if la.peek(syn::token::Paren) {
match &items[0] { let content;
NestedMeta::Meta(Meta::Path(p)) => Ok(Severity(p.clone())), parenthesized!(content in input);
NestedMeta::Lit(Lit::Str(sev)) => Ok(Severity(sev.parse()?)), let la = content.lookahead1();
_ => Err(DarlingError::custom( if la.peek(syn::LitStr) {
"invalid severity format. Only literal names and string literals are accepted", let str = content.parse::<syn::LitStr>()?;
)), Ok(Severity(str.parse()?))
} else {
let path = content.parse::<syn::Path>()?;
Ok(Severity(path))
}
} else {
input.parse::<Token![=]>()?;
Ok(Severity(input.parse::<syn::LitStr>()?.parse()?))
}
} else {
Err(syn::Error::new(
ident.span(),
"not a severity level.",
))
} }
} }
} }
impl Severity { impl Severity {
pub(crate) fn gen_enum( pub(crate) fn gen_enum(
_diag: &Diagnostic, _diag: &Diagnostic,
variants: &[&DiagnosticVariant], variants: &[DiagnosticVariant],
) -> Option<TokenStream> { ) -> Option<TokenStream> {
let sev_pairs = variants let sev_pairs = variants
.iter() .iter()
@ -55,13 +69,11 @@ impl Severity {
} }
} }
pub(crate) fn gen_struct(diag: &Diagnostic, _fields: &Fields<&DiagnosticField>) -> Option<TokenStream> { pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
diag.severity.as_ref().map(|sev| { let sev = &self.0;
let sev = &sev.0; Some(quote! {
quote! { fn severity(&self) -> std::option::Option<miette::Severity> {
fn severity(&self) -> std::option::Option<miette::Severity> { Some(miette::Severity::#sev)
Some(miette::Severity::#sev)
}
} }
}) })
} }

View File

@ -24,7 +24,6 @@ fn basic_struct() {
#[test] #[test]
fn basic_enum() { fn basic_enum() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
enum Foo { enum Foo {
@ -35,14 +34,39 @@ fn basic_enum() {
)] )]
X, X,
#[diagnostic(code = "foo::y")] #[diagnostic(code = "foo::y")]
Y, Y(usize),
#[diagnostic(code = "foo::z")]
Z { prop: String },
} }
assert_eq!("foo::x".to_string(), Foo::X.code().to_string()); assert_eq!("foo::x".to_string(), Foo::X.code().to_string());
assert_eq!("foo::y".to_string(), Foo::Y.code().to_string()); assert_eq!("foo::y".to_string(), Foo::Y(1).code().to_string());
assert_eq!(
"foo::z".to_string(),
Foo::Z { prop: "bar".into() }.code().to_string()
);
assert_eq!(Some(Severity::Warning), Foo::X.severity()); assert_eq!(Some(Severity::Warning), Foo::X.severity());
assert_eq!(None, Foo::Y.severity()); assert_eq!(None, Foo::Y(1).severity());
}
#[test]
fn paren_code() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code("foo::bar::baz"))]
struct FooStruct;
assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
enum FooEnum {
#[diagnostic(code("foo::x"))]
X,
}
assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
} }
#[test] #[test]
@ -68,10 +92,7 @@ fn path_code() {
fn path_severity() { fn path_severity() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
#[diagnostic( #[diagnostic(code(foo::bar::baz), severity(Warning))]
code(foo::bar::baz),
severity(Warning)
)]
struct FooStruct; struct FooStruct;
assert_eq!(Some(Severity::Warning), FooStruct.severity()); assert_eq!(Some(Severity::Warning), FooStruct.severity());
@ -79,10 +100,7 @@ fn path_severity() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
enum FooEnum { enum FooEnum {
#[diagnostic( #[diagnostic(code(foo::x), severity(Warning))]
code(foo::x),
severity(Warning),
)]
X, X,
} }
@ -93,10 +111,7 @@ fn path_severity() {
fn list_help() { fn list_help() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
#[diagnostic( #[diagnostic(code(foo::bar::baz), help("try doing it better"))]
code(foo::bar::baz),
help("try doing it better"),
)]
struct FooStruct; struct FooStruct;
assert_eq!( assert_eq!(
@ -107,10 +122,7 @@ fn list_help() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
enum FooEnum { enum FooEnum {
#[diagnostic( #[diagnostic(code(foo::x), help("try doing it better"))]
code(foo::x),
help("try doing it better"),
)]
X, X,
} }
@ -120,8 +132,6 @@ fn list_help() {
); );
} }
// TODO: Darling doesn't support this, apparently:
// https://github.com/TedDriggs/darling/issues/145
/* /*
#[test] #[test]
fn fmt_help() { fn fmt_help() {
@ -129,9 +139,9 @@ fn fmt_help() {
#[error("welp")] #[error("welp")]
#[diagnostic( #[diagnostic(
code(foo::bar::baz), code(foo::bar::baz),
help("{} {}", 1, "bar"), help("{} {}", 1, self.0),
)] )]
struct FooStruct; struct FooStruct(String);
assert_eq!( assert_eq!(
"1 bar".to_string(), "1 bar".to_string(),