过程宏如何检查 Option<Option<T>> 的通用类型并将其扁平化为单个 Option?
How can a procedural macro check a generic type for Option<Option<T>> and flatten it down to a single Option?
我正在编写一个派生程序宏,其中所有值都转换为 Options
。问题是结构中的任何 Option
字段都可以包含在这些 Option
类型中。在我开始使用 serde 序列化数据之前,这本身并不是什么大问题。我希望能够跳过值为 None
的任何值,但在某些情况下它会变成 Some(None)
或 Some(CustomOption::None)
之类的东西。这两种情况都不比简单的 None
更有意义,但我不能只在派生字段上写 #[serde(skip_serializing_if = "Option::is_none")]
。当然,他们会以 JSON 格式输出 null
值。
基本上,我希望能够使用 syn 库来检查派生字段的内部值的类型是否为 Option
并将其展平为单数 Option<T>
在派生结构中,而不是 Option<Option<T>>
类型。我希望 Rust 在泛型上有基于类型的模式匹配,但这不是真的。
我能想到这个问题的两个解决方案,但我真的想不出如何实施它们。第一个是遍历所有字段并找到 Option
s,然后打开这些选项并重新包装它们,以便它们在外面只有一个 Option
。此解决方案的一个潜在问题是,在完成计算后,我可能必须将它们重新包装在另一个 Option
中。第二种解决方案是找到 Option
并相应地修改生成的代码,这样如果内部选项包含 None
,整个事情就变成 None
;基本上只有一个辅助函数,如果字段是 Option
,它会输出一个布尔值。关于如何实施这些或更好的解决方案有什么想法吗?
这是一个代码示例:
#[derive(Macro)]
struct X {
a: usize,
b: SomeType<String>,
c: Option<String>,
}
struct GeneratedX {
a: Option<usize>,
b: Option<SomeType<String>>,
c: Option<Option<String>>,
}
使用这样的函数来包装选项中的所有值:
pub fn wrap_typ_in_options(&self) -> TokenStream {
// self is a struct with the type Type in it along with some other items.
let typ: syn::Type = self.typ();
// attribute to check if should ignore a field.
if self.should_ignore() {
quote! { Option<#typ> }
} else {
quote! { Option<<#typ as module::Trait>::Type> }
}
}
我根据最初 post 中的第二个想法找到了解决此问题的方法。我使用这样的函数来判断令牌是否为 Option
:
let idents_of_path = path
.segments
.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())
然后我添加了一个名为 is_option
的新方法,如果 Type::Path
是一个选项,它 returns 一个布尔值。
pub fn is_option(&self) -> bool {
let typ = self.typ();
let opt = match typ {
Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()),
_ => None,
};
if let Some(o) = opt {
check_for_option(&o).is_some()
} else {
false
}
}
我根据此调用的结果修改了生成的代码,其方式与我处理各种属性的方式类似。所有这些都应该适用于我的特定 use-case,因为不会将任何别名 Option
引入该生态系统。它有点乱,但现在可以完成工作了。
我正在编写一个派生程序宏,其中所有值都转换为 Options
。问题是结构中的任何 Option
字段都可以包含在这些 Option
类型中。在我开始使用 serde 序列化数据之前,这本身并不是什么大问题。我希望能够跳过值为 None
的任何值,但在某些情况下它会变成 Some(None)
或 Some(CustomOption::None)
之类的东西。这两种情况都不比简单的 None
更有意义,但我不能只在派生字段上写 #[serde(skip_serializing_if = "Option::is_none")]
。当然,他们会以 JSON 格式输出 null
值。
基本上,我希望能够使用 syn 库来检查派生字段的内部值的类型是否为 Option
并将其展平为单数 Option<T>
在派生结构中,而不是 Option<Option<T>>
类型。我希望 Rust 在泛型上有基于类型的模式匹配,但这不是真的。
我能想到这个问题的两个解决方案,但我真的想不出如何实施它们。第一个是遍历所有字段并找到 Option
s,然后打开这些选项并重新包装它们,以便它们在外面只有一个 Option
。此解决方案的一个潜在问题是,在完成计算后,我可能必须将它们重新包装在另一个 Option
中。第二种解决方案是找到 Option
并相应地修改生成的代码,这样如果内部选项包含 None
,整个事情就变成 None
;基本上只有一个辅助函数,如果字段是 Option
,它会输出一个布尔值。关于如何实施这些或更好的解决方案有什么想法吗?
这是一个代码示例:
#[derive(Macro)]
struct X {
a: usize,
b: SomeType<String>,
c: Option<String>,
}
struct GeneratedX {
a: Option<usize>,
b: Option<SomeType<String>>,
c: Option<Option<String>>,
}
使用这样的函数来包装选项中的所有值:
pub fn wrap_typ_in_options(&self) -> TokenStream {
// self is a struct with the type Type in it along with some other items.
let typ: syn::Type = self.typ();
// attribute to check if should ignore a field.
if self.should_ignore() {
quote! { Option<#typ> }
} else {
quote! { Option<<#typ as module::Trait>::Type> }
}
}
我根据最初 post 中的第二个想法找到了解决此问题的方法。我使用这样的函数来判断令牌是否为 Option
:
let idents_of_path = path
.segments
.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())
然后我添加了一个名为 is_option
的新方法,如果 Type::Path
是一个选项,它 returns 一个布尔值。
pub fn is_option(&self) -> bool {
let typ = self.typ();
let opt = match typ {
Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()),
_ => None,
};
if let Some(o) = opt {
check_for_option(&o).is_some()
} else {
false
}
}
我根据此调用的结果修改了生成的代码,其方式与我处理各种属性的方式类似。所有这些都应该适用于我的特定 use-case,因为不会将任何别名 Option
引入该生态系统。它有点乱,但现在可以完成工作了。