use proc_macro2::TokenStream;
use quote::quote;
use syn::Index;
use crate::input::{FormatArgProjectionMethodCall, FormatArgProjectionSegment};
pub fn append_projection_segments(
mut expr: TokenStream,
segments: &[FormatArgProjectionSegment]
) -> TokenStream {
for segment in segments {
expr = append_projection_segment(expr, segment);
}
expr
}
fn append_projection_segment(
expr: TokenStream,
segment: &FormatArgProjectionSegment
) -> TokenStream {
match segment {
FormatArgProjectionSegment::Field(ident) => quote!((#expr).#ident),
FormatArgProjectionSegment::Index {
index,
span
} => {
let index_token = Index {
index: *index as u32,
span: *span
};
quote!((#expr).#index_token)
}
FormatArgProjectionSegment::MethodCall(call) => append_method_call(expr, call)
}
}
pub fn append_method_call(expr: TokenStream, call: &FormatArgProjectionMethodCall) -> TokenStream {
let method = &call.method;
let args = &call.args;
if let Some(turbofish) = &call.turbofish {
let colon2 = turbofish.colon2_token;
let generics = &turbofish.generics;
quote!((#expr).#method #colon2 #generics (#args))
} else {
quote!((#expr).#method(#args))
}
}
#[cfg(test)]
mod tests {
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{punctuated::Punctuated, token::Comma};
use super::*;
#[test]
fn test_append_projection_segments_single_field() {
let expr = quote!(self);
let segments = vec![FormatArgProjectionSegment::Field(format_ident!("foo"))];
let result = append_projection_segments(expr, &segments);
let expected = quote!((self).foo);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_multiple_fields() {
let expr = quote!(self);
let segments = vec![
FormatArgProjectionSegment::Field(format_ident!("foo")),
FormatArgProjectionSegment::Field(format_ident!("bar")),
];
let result = append_projection_segments(expr, &segments);
let expected = quote!(((self).foo).bar);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_single_index() {
let expr = quote!(self);
let segments = vec![FormatArgProjectionSegment::Index {
index: 0,
span: Span::call_site()
}];
let result = append_projection_segments(expr, &segments);
let expected = quote!((self).0);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_multiple_indices() {
let expr = quote!(self);
let segments = vec![
FormatArgProjectionSegment::Index {
index: 0,
span: Span::call_site()
},
FormatArgProjectionSegment::Index {
index: 1,
span: Span::call_site()
},
];
let result = append_projection_segments(expr, &segments);
let expected = quote!(((self).0).1);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_field_then_index() {
let expr = quote!(self);
let segments = vec![
FormatArgProjectionSegment::Field(format_ident!("foo")),
FormatArgProjectionSegment::Index {
index: 0,
span: Span::call_site()
},
];
let result = append_projection_segments(expr, &segments);
let expected = quote!(((self).foo).0);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_index_then_field() {
let expr = quote!(self);
let segments = vec![
FormatArgProjectionSegment::Index {
index: 0,
span: Span::call_site()
},
FormatArgProjectionSegment::Field(format_ident!("bar")),
];
let result = append_projection_segments(expr, &segments);
let expected = quote!(((self).0).bar);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_method_call_no_args() {
let expr = quote!(self);
let segments = vec![FormatArgProjectionSegment::MethodCall(
FormatArgProjectionMethodCall {
method: format_ident!("foo"),
turbofish: None,
args: Punctuated::new(),
span: Span::call_site()
}
)];
let result = append_projection_segments(expr, &segments);
let expected = quote!((self).foo());
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_method_call_with_args() {
let expr = quote!(self);
let mut args = Punctuated::<syn::Expr, Comma>::new();
args.push(syn::parse_quote!(42));
args.push(syn::parse_quote!("test"));
let segments = vec![FormatArgProjectionSegment::MethodCall(
FormatArgProjectionMethodCall {
method: format_ident!("process"),
turbofish: None,
args,
span: Span::call_site()
}
)];
let result = append_projection_segments(expr, &segments);
let expected = quote!((self).process(42, "test"));
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_method_call_with_turbofish() {
let expr = quote!(self);
let turbofish = crate::input::FormatArgMethodTurbofish {
colon2_token: syn::parse_quote!(::),
generics: syn::parse_quote!(<String>)
};
let segments = vec![FormatArgProjectionSegment::MethodCall(
FormatArgProjectionMethodCall {
method: format_ident!("into"),
turbofish: Some(turbofish),
args: Punctuated::new(),
span: Span::call_site()
}
)];
let result = append_projection_segments(expr, &segments);
let expected = quote!((self).into::<String>());
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_complex_chain() {
let expr = quote!(self);
let mut args = Punctuated::<syn::Expr, Comma>::new();
args.push(syn::parse_quote!(10));
let segments = vec![
FormatArgProjectionSegment::Field(format_ident!("inner")),
FormatArgProjectionSegment::Index {
index: 0,
span: Span::call_site()
},
FormatArgProjectionSegment::MethodCall(FormatArgProjectionMethodCall {
method: format_ident!("get"),
turbofish: None,
args,
span: Span::call_site()
}),
FormatArgProjectionSegment::Field(format_ident!("value")),
];
let result = append_projection_segments(expr, &segments);
let expected = quote!(((((self).inner).0).get(10)).value);
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_projection_segments_empty() {
let expr = quote!(self);
let segments = vec![];
let result = append_projection_segments(expr.clone(), &segments);
assert_eq!(result.to_string(), expr.to_string());
}
#[test]
fn test_append_method_call_simple() {
let expr = quote!(value);
let call = FormatArgProjectionMethodCall {
method: format_ident!("to_string"),
turbofish: None,
args: Punctuated::new(),
span: Span::call_site()
};
let result = append_method_call(expr, &call);
let expected = quote!((value).to_string());
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn test_append_method_call_with_turbofish_and_args() {
let expr = quote!(value);
let turbofish = crate::input::FormatArgMethodTurbofish {
colon2_token: syn::parse_quote!(::),
generics: syn::parse_quote!(<i32>)
};
let mut args = Punctuated::<syn::Expr, Comma>::new();
args.push(syn::parse_quote!(5));
let call = FormatArgProjectionMethodCall {
method: format_ident!("parse"),
turbofish: Some(turbofish),
args,
span: Span::call_site()
};
let result = append_method_call(expr, &call);
let expected = quote!((value).parse::<i32>(5));
assert_eq!(result.to_string(), expected.to_string());
}
}