Rust macro to format arguments over multiple formats - rust

TL;DR
I'm trying to write a macro that will do the following transformation:
magic_formatter!(["_{}", "{}_", "_{}_"], "foo") ==
[format!("_{}", "foo"),
format!("{}_", "foo"),
format!("_{}_", "foo")]
(a solution that will give ["_foo", "foo_", "_foo_"] and works for varargs is also welcome)
Full story:
I'm writing a parser, and many of it's tests do stuff like this:
let ident = identifier().parse("foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
so I tried to reduce repetition by doing this:
for f in [" {}", "{} ", " {} "] {
let inp = format!(f, "foo");
let ident = identifier().parse(inp).unwrap();
assert_eq!(ident, Syntax::ident("foo"));
}
which of course doesn't compile.
However, it seems to me that there isn't really any unknown information preventing from the whole array to be generated at compile time, so I searched the webz, hoping that this has been solved somewhere already, but my google-fu can't seem to find anything that just does what I want.
So I thought I'd get my hands dirty and write an actually useful rust macro for the first time(!).
I read the macro chapter of Rust by Example, and failed for a while.
Then I tried reading the actual reference which I feel that got me a few steps further but I still couldn't get it right.
Then I really got into it and found this cool explanation and thought that I actually had it this time, but I still can't seem to get my macro to work properly and compile at the same time.
my latest attempt looks is this:
macro_rules! map_fmt {
(#accum () -> $($body:tt),*) => { map_fmt!(#as_expr [$($body),*]) };
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> $($body:tt),*) => {
map_fmt!(#accum ([$($fs),*], $args) -> (format!($f, $args) $($body),*))
};
(#as_expr $e:expr) => { $e };
([$f:literal, $($fs:literal),*], $args:expr) => {
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
};
}
I'll appreciate if someone could help me understand what is my macro missing? and how to fix it, if even possible? and if not is there some other technique I could/should use to reduce the repetition in my tests?
Edit:
this is the final solution I'm using, which is the correct answer provided by #finomnis, which I slightly modified to support variadic arguments in the format! expression
macro_rules! map_fmt {
(#accum ([$f:literal], $($args:tt),*) -> ($($body:tt)*)) => { [$($body)* format!($f, $($args),*)] };
(#accum ([$f:literal, $($fs:literal),*], $($args:tt),*) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $($args),*) -> ($($body)* format!($f, $($args),*),))
};
([$f:literal, $($fs:literal),*], $($args:expr),*) => {
map_fmt!(#accum ([$f, $($fs),*], $($args),*) -> ())
};
}

format!() doesn't work, because it generates the code at compiletime and therefore needs an actual string literal formatter.
str::replace(), however, works:
fn main() {
for f in [" {}", "{} ", " {} "] {
let inp = f.replace("{}", "foo");
println!("{:?}", inp);
}
}
" foo"
"foo "
" foo "
I don't think there is any reason why doing this at runtime is a problem, especially as your format!() call in the macro is also a runtime replacement, but nonetheless I think this is an interesting challenge to learn more about macros.
There are a couple of problems with your macro.
For one, the () case should be ([], $_:tt) instead.
But the main problem with your macro is that [$f:literal, $($fs:literal),*] does not match [""] (the case where only one literal is left) because it doesn't match the required comma. This one would match: ["",].
This can be solved by converting the $(),* into $(),+ (meaning, they have to carry at least one element) and then replacing the [] (no elements left) case with [$f:literal] (one element left). This then handles the special case where only one element is left and the comma doesn't match.
The way you select your intermediate results has minor bugs in several places. At some places, you forgot the () around it, and the arguments may be in the wrong order. Further, it's better to transport them as $(tt)* instead of $(tt),*, as the tt contains the comma already.
Your $as_expr case doesn't serve much purpose according to the newer macro book, so I would remove it.
This is how your code could look like after fixing all those things:
macro_rules! map_fmt {
(#accum ([$f:literal], $args:tt) -> ($($body:tt)*)) => {
[$($body)* format!($f, $args)]
};
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $args) -> ($($body)* format!($f, $args),))
};
([$f:literal, $($fs:literal),*], $args:expr) => {
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
}
["_foo", "foo_", "_foo_"]
However, if you use cargo expand to print what the macro resolves to, this is what you get:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
let fmt = [
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["", "_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
{
let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
&["_", "_"],
&[::core::fmt::ArgumentV1::new_display(&"foo")],
));
res
},
];
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["", "\n"],
&[::core::fmt::ArgumentV1::new_debug(&fmt)],
));
};
}
What you can clearly see here is that the format! is still a runtime call. So I don't think that the macro actually creates any kind of speedup.
You could fix that with the const_format crate:
macro_rules! map_fmt {
(#accum ([$f:literal], $args:tt) -> ($($body:tt)*)) => {
[$($body)* ::const_format::formatcp!($f, $args)]
};
(#accum ([$f:literal, $($fs:literal),*], $args:tt) -> ($($body:tt)*)) => {
map_fmt!(#accum ([$($fs),*], $args) -> ($($body)* ::const_format::formatcp!($f, $args),))
};
([$f:literal, $($fs:literal),*], $args:expr) => {{
map_fmt!(#accum ([$f, $($fs),*], $args) -> ())
}};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
print_type_of(&fmt);
}
["_foo", "foo_", "_foo_"]
[&str; 3]
You can now see that the type is &'static str, meaning, it is now being formatted at compile time and stored in the binary as a static string.
That all said, I think the entire recursion in the macro is quite pointless. It seems like it can be done with a single repetition:
macro_rules! map_fmt {
([$($fs:literal),*], $args:expr) => {{
[$(format!($fs, $args)),*]
}};
}
fn main() {
let fmt = map_fmt!(["_{}", "{}_", "_{}_"], "foo");
println!("{:?}", fmt);
}
["_foo", "foo_", "_foo_"]
If you want to support an arbitrary number of arguments for format!(), then you could do:
macro_rules! map_fmt {
(#format $f:literal, ($($args:expr),*)) => {
format!($f, $($args),*)
};
([$($fs:literal),*], $args:tt) => {{
[$(map_fmt!(#format $fs, $args)),*]
}};
}
fn main() {
let fmt = map_fmt!(["_{}_{}", "{}__{}", "{}_{}_"], ("foo", "bar"));
println!("{:?}", fmt);
}
["_foo_bar", "foo__bar", "foo_bar_"]

Related

Match on pair of enum when both are of the same kind

I want to get rid of the repeated code here, and not have to list each enum kind:
use Geometry::*;
let geometry = match (&self.geometry, &other.geometry) {
(Point(a), Point(b)) => Point(*a.interpolate(b, t)),
(Curve(a), Curve(b)) => Curve(*a.interpolate(b, t)),
(EPBox(a), EPBox(b)) => EPBox(*a.interpolate(b, t)),
(_, _) => unimplemented!("Kinds not matching")
};
The types inside the kinds all implement the Interpolate trait which has the interpolate method:
enum Geometry {
Point(Point),
Curve(Curve),
EPBox(EPBox)
}
trait Interpolate {
fn interpolate(&self, other: &Self, t: f64) -> Box<Self> {
// ...
}
}
impl Interpolate for Point {}
impl Interpolate for Curve {}
impl Interpolate for EPBox {}
What I want to do is something like this:
let geometry = match (&self.geometry, &other.geometry) {
(x(a), x(b)) => x(*a.interpolate(b, t)), // and check that a and b impls Interpolate
(_, _) => unimplemented!("Kinds not matching")
};
I'm not sure how many of these operations you want to implement, and how many enum variants you actually have. Depending on that, the best answer changes quite a bit, I think. If you really only have one operation (interpolate), and three variants: type it out, anything else will be a lot less simple and maintainable.
That being said, I might use
let geometry = binop!(match (&self.geometry, &other.geometry) {
(a, b) => *a.interpolate(b, t)
});
based on
macro_rules! binop {
(match ($av:expr, $bv:expr) { ($a:ident, $b:ident) => $op:expr }) => {{
use Geometry::*;
binop!($av, $bv, $a, $b, $op, Point, Curve, EPBox)
}};
($av:expr, $bv:expr, $a:ident, $b:ident, $op:expr, $($var:path),*) => {
match ($av, $bv) {
$(($var($a), $var($b)) => $var($op),)*
_ => unimplemented!("Kinds not matching")
}
};
}
Playground
You'll have to list up the enum variants once. If you don't want that, you'll have to go for a proc macro. I think that would be overkill.
With your current code, you can't really do this. Checking if the enum variants are the same variant is fairly simple with the use of core::mem::discriminant, but you can't access the contained value without match or if let statements. What you can do is use type parameters for the variables and type IDs. This is a really tacky way to do things, and should be avoided really, but it works. I've given an example to show you how you could implement it
use std::any::Any;
trait SomeTrait {
fn some_function(&self, other: Self) -> &Self;
}
#[derive(Clone)]
struct Var0 {
eg: i64
}
#[derive(Clone)]
struct Var1 {
other_eg: f64
}
fn check<T: 'static, B: 'static>(lhs: T, rhs: B) -> Option<T> {
if lhs.type_id() == rhs.type_id() {
Some(lhs.some_function(rhs))
} else {
None
}
}
fn main() {
let a = Var0{ eg: 2 };
let b = Var0{ eg: 9 };
let c = Var1 { other_eg: -3f64 };
assert!(check(a.clone(), b).is_some());
assert!(check(a, c).is_none());
}
impl SomeTrait for Var0 { /* ... */ }
impl SomeTrait for Var1 { /* ... */ }
Please note though: as I said before this is messy and pretty hacky and not very nice to read. If I were writing this I would use the match statement over this, but it's up to you what you do since this does require some reworking to implement. I'm also not sure if you can use different lifetimes for the check function other than `static which might be a problem.
I came up with the following solution, which is easy to extend with new Interpolate types:
macro_rules! geometries {
( $( $x:ident ),+ ) => {
enum Geometry {
$(
$x($x),
)*
}
fn interpolate_geometries(a: &Geometry, b: &Geometry, t: f64) -> Geometry {
use Geometry::*;
match (a, b) {
$(
($x(a), $x(b)) => $x(a.interpolate(b, t)),
)*
_ => unimplemented!("Kinds not matching")
}
}
$(
impl Interpolate for $x {}
)*
};
}
geometries!(Point, Curve, EPBox);
Which make it possible to later do:
let geometry = interpolate_geometries(&self.geometry, &other.geometry, t);
Thanks for all answers and comments! Especially the one with the binop macro, which is quite similar to my solution. After reading up on macros (thanks to comments here) I came up with this solution.

Why can't Rust find method for enum generated using proc_macro_attribute?

I am trying to write procedural macros that will accept a Rust enum like
#[repr(u8)]
enum Ty {
A,
B
}
and generate a method for the enum that will let me convert an u8 into an allowed variant like this
fn from_byte(byte: u8) -> Ty {
match {
0 => Ty::A,
1 => Ty::B,
_ => unreachable!()
}
}
This is what I have implemented using proc_macro lib. (no external lib)
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_quote)]
extern crate proc_macro;
use proc_macro::{TokenStream, Diagnostic, Level, TokenTree, Ident, Group, Literal};
use proc_macro::quote;
fn report_error(tt: TokenTree, msg: &str) {
Diagnostic::spanned(tt.span(), Level::Error, msg).emit();
}
fn variants_from_group(group: Group) -> Vec<Ident> {
let mut iter = group.stream().into_iter();
let mut res = vec![];
while let Some(TokenTree::Ident(id)) = iter.next() {
match iter.next() {
Some(TokenTree::Punct(_)) | None => res.push(id),
Some(tt) => {
report_error(tt, "unexpected variant. Only unit variants accepted.");
return res
}
}
}
res
}
#[proc_macro_attribute]
pub fn procmac(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = args;
let mut res = TokenStream::new();
res.extend(input.clone());
let mut iter = input.into_iter()
.skip_while(|tt| if let TokenTree::Punct(_) | TokenTree::Group(_) = tt {true} else {false})
.skip_while(|tt| tt.to_string() == "pub");
match iter.next() {
Some(tt # TokenTree::Ident(_)) if tt.to_string() == "enum" => (),
Some(tt) => {
report_error(tt, "unexpected token. this should be only used with enums");
return res
},
None => return res
}
match iter.next() {
Some(tt) => {
let variants = match iter.next() {
Some(TokenTree::Group(g)) => {
variants_from_group(g)
}
_ => return res
};
let mut match_arms = TokenStream::new();
for (i, v) in variants.into_iter().enumerate() {
let lhs = TokenTree::Literal(Literal::u8_suffixed(i as u8));
if i >= u8::MAX as usize {
report_error(lhs, "enum can have only u8::MAX variants");
return res
}
let rhs = TokenTree::Ident(v);
match_arms.extend(quote! {
$lhs => $tt::$rhs,
})
}
res.extend(quote!(impl $tt {
pub fn from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
}
_ => ()
}
res
}
And this is how I am using it.
use helper_macros::procmac;
#[procmac]
#[derive(Debug)]
#[repr(u8)]
enum Ty {
A,
B
}
fn main() {
println!("TEST - {:?}", Ty::from_byte(0))
}
The problem is this causing an error from the compiler. The exact error being
error[E0599]: no variant or associated item named `from_byte` found for enum `Ty` in the current scope
--> main/src/main.rs:91:32
|
85 | enum Ty {
| ------- variant or associated item `from_byte` not found here
...
91 | println!("TEST - {:?}", Ty::from_byte(0))
| ^^^^^^^^^ variant or associated item not found in `Ty`
Running cargo expand though generate the proper code. And running that code directly works as expected. And so I am stumped. It could be I am missing something about how proc_macros should be used since this is the first time I am playing with them and I don't see anything that would cause this error. I am following the sorted portion of the proc_macro_workshop0. Only change is, I am using TokenStream directly instead of using syn and quote crates. Also, if I mistype the method name, the rust compiler does suggest that a method with similar name exists.
Here is a Playground repro: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=02c1ee77bcd80c68967834a53c011e41
So, indeed what you mention is true: the expanded code could be copy-pasted and it would work. When this happens (having behavior from macro expansion and "manual copy-pasted expansion" differ), there are two possibilities:
macro_rules! metavariables
When emitting code using macro_rules! special captures, some of these captures are wrapped with special invisible parenthesis that already tell the parser how the thing inside should be parsed, which make it illegal to use in other places (for instance, one may capture a $Trait:ty, and then doing impl $Trait for ... will fail (it will parse $Trait as a type, thus leading to it being interpreted as a trait object (old syntax)); see also https://github.com/danielhenrymantilla/rust-defile for other examples.
This is not your case, but it's good to keep in mind (e.g. my initial hunch was that when doing $tt::$rhs if $tt was a :path-like capture, then that could fail).
macro hygiene/transparency and Spans
Consider, for instance:
macro_rules! let_x_42 {() => (
let x = 42;
)}
let_x_42!();
let y = x;
This expands to code that, if copy-pasted, does not fail to compile.
Basically the name x that the macro uses is "tainted" to be different from any x used outside the macro body, precisely to avoid misinteractions when the macro needs to define helper stuff such as variables.
And it turns out that this is the same thing that has happened with your from_byte identifier: your code was emitting a from_byte with private hygiene / a def_site() span, which is something that normally never happens for method names when using classic macros, or classic proc-macros (i.e., when not using the unstable ::proc_macro::quote! macro). See this comment: https://github.com/rust-lang/rust/issues/54722#issuecomment-696510769
And so the from_byte identifier is being "tainted" in a way that allows Rust to make it invisible to code not belonging to that same macro expansion, such as the code in your fn main.
The solution, at this point, is easy: forge a from_bytes Identifier with an explicit non-def_site() Span (e.g., Span::call_site(), or even better: Span::mixed_site() to mimic the rules of macro_rules! macros) so as to prevent it from getting that default def_site() Span that ::proc_macro::quote! uses:
use ::proc_macro::Span;
// ...
let from_byte = TokenTree::from(Ident::new("from_byte", Span::mixed_site()));
res.extend(quote!(impl $tt {
// use an interpolated ident rather than a "hardcoded one"
// vvvvvvvvvv
pub fn $from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
Playground

Simplifying a `match` using a Rust macro

There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.
I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):
fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
let begin = Instant::now();
let output: T = question_func();
let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
println!("{}", output);
println!("{:.6}s taken", elapsed_secs);
}
fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }
fn main() {
// ...
match question_num {
1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
_ => {
println!("Question doesn't exist.");
},
}
}
I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:
error: variable 'question_num' is still repeating at this depth
I'm rather stumped too how I can print the Question doesn't exist. as a default case.
#[macro_export]
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
{
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
}
};
}
The way I'd like to use it, is (or anything just as short is fine as well):
run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
I read a bit of the Rust book, but there aren't exactly that many examples of macros.
How would I go about doing this?
Rather than many if statements, I just reproduced the match statement
with a repetition $( ... )* for all the available branches.
It seems to behave like the extensive match expression.
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
match $chosen_question {
$($question_num => run_question($question_mod),)*
_ => {
println!("Question doesn't exist.");
}
}
};
}
The error message explained:
macro_rules! run_questions {
($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:
$(if $chosen_question == $question_num {
run_question($question_mod::solve);
})*
Although as pointed out by #prog-fh's answer, better to use a match in the macro, same as in the straight code:
match $chosen_question {
$($question_num => run_question ($question_mod::solve),)*
_ => println!("Question doesn't exist.")
};

Rust macro error: local ambiguity: multiple parsing options

The following rust code does not compile because of the macro error
error: local ambiguity: multiple parsing options: built-in NTs stmt ('s') or 1 other option.
macro A is fine. Macro B shows the error.
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $($s: stmt)*; $e: expr) => {
println!("hello");
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; 5];
}
This minimal reproducible example in B is exactly what I need. I want the macro to accept multiple let statements and terminate by some other expression.
What is the ambiguity that is being referred to?
Is there a way around it?
Of those tokens accepted after statement fragments I have tried several combinations yet none appear to make a difference. Neither does swapping the statement with a token tree.
Expressions are statements, so $($s: stmt)*; $e: expr is ambiguous because the compiler can't decide between using s or e when it encounters one.
Since you only expect bindings, you can easily expand them yourself:
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $(let $p:pat = $v:expr)*; $e: expr) => {
$(let $p = $v);*
println!("hello: {}", $e);
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; x+2];
}
Note that this doesn't support including types in the binding (let a: i32 = 42;) because pat can't be followed by :.

How can I get the T from an Option<T> when using syn?

I'm using syn to parse Rust code. When I read a named field's type using field.ty, I get a syn::Type. When I print it using quote!{#ty}.to_string() I get "Option<String>".
How can I get just "String"? I want to use #ty in quote! to print "String" instead of "Option<String>".
I want to generate code like:
impl Foo {
pub set_bar(&mut self, v: String) {
self.bar = Some(v);
}
}
starting from
struct Foo {
bar: Option<String>
}
My attempt:
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let data: Data = ast.data;
match data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| {
let name = &field.ident.clone().unwrap();
let ty = &field.ty;
quote!{
impl Foo {
pub set_bar(&mut self, v: #ty) {
self.bar = Some(v);
}
}
};
});
}
_ => {}
},
_ => panic!("You can derive it only from struct"),
}
My updated version of the response from #Boiethios, tested and used in a public crate, with support of several syntaxes for Option:
Option
std::option::Option
::std::option::Option
core::option::Option
::core::option::Option
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
// TODO store (with lazy static) the vec of string
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec!["Option|", "std|option|Option|", "core|option|Option|"]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|path_seg| {
let type_params = &path_seg.arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
You should do something like this untested example:
use syn::{GenericArgument, PathArguments, Type};
fn extract_type_from_option(ty: &Type) -> Type {
fn path_is_option(path: &Path) -> bool {
leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(typepath) if typepath.qself.is_none() && path_is_option(typepath.path) => {
// Get the first segment of the path (there is only one, in fact: "Option"):
let type_params = typepath.path.segments.iter().first().unwrap().arguments;
// It should have only on angle-bracketed param ("<String>"):
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.iter().first().unwrap(),
_ => panic!("TODO: error handling"),
};
// This argument must be a type:
match generic_arg {
GenericArgument::Type(ty) => ty,
_ => panic!("TODO: error handling"),
}
}
_ => panic!("TODO: error handling"),
}
}
There's not many things to explain, it just "unrolls" the diverse components of a type:
Type -> TypePath -> Path -> PathSegment -> PathArguments -> AngleBracketedGenericArguments -> GenericArgument -> Type.
If there is an easier way to do that, I would be happy to know it.
Note that since syn is a parser, it works with tokens. You cannot know for sure that this is an Option. The user could, for example, type std::option::Option, or write type MaybeString = std::option::Option<String>;. You cannot handle those arbitrary names.

Resources