adze_common_syntax_core/
lib.rs1#![forbid(unsafe_op_in_unsafe_fn)]
4#![deny(missing_docs)]
5#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
6#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
7#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
8#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
9
10use std::collections::HashSet;
11
12use syn::{
13 parse::{Parse, ParseStream},
14 punctuated::Punctuated,
15 *,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct NameValueExpr {
24 pub path: Ident,
26 pub eq_token: Token![=],
28 pub expr: Expr,
30}
31
32impl Parse for NameValueExpr {
33 fn parse(input: ParseStream) -> syn::Result<Self> {
34 Ok(NameValueExpr {
35 path: input.parse()?,
36 eq_token: input.parse()?,
37 expr: input.parse()?,
38 })
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct FieldThenParams {
48 pub field: Field,
50 pub comma: Option<Token![,]>,
52 pub params: Punctuated<NameValueExpr, Token![,]>,
54}
55
56impl Parse for FieldThenParams {
57 fn parse(input: ParseStream) -> syn::Result<Self> {
58 let field = Field::parse_unnamed(input)?;
59 let comma: Option<Token![,]> = input.parse()?;
60 let params = if comma.is_some() {
61 Punctuated::parse_terminated_with(input, NameValueExpr::parse)?
62 } else {
63 Punctuated::new()
64 };
65
66 Ok(FieldThenParams {
67 field,
68 comma,
69 params,
70 })
71 }
72}
73
74pub fn try_extract_inner_type(
85 ty: &Type,
86 inner_of: &str,
87 skip_over: &HashSet<&str>,
88) -> (Type, bool) {
89 if let Type::Path(p) = &ty {
90 let type_segment = p.path.segments.last().unwrap();
91 if type_segment.ident == inner_of {
92 match &type_segment.arguments {
93 PathArguments::AngleBracketed(p) => {
94 if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
95 (t, true)
96 } else {
97 panic!("Argument in angle brackets must be a type")
98 }
99 }
100 _ => (ty.clone(), false),
101 }
102 } else if skip_over.contains(type_segment.ident.to_string().as_str()) {
103 match &type_segment.arguments {
104 PathArguments::AngleBracketed(p) => {
105 if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
106 let (inner, extracted) = try_extract_inner_type(&t, inner_of, skip_over);
107 if extracted {
108 (inner, true)
109 } else {
110 (ty.clone(), false)
111 }
112 } else {
113 panic!("Argument in angle brackets must be a type")
114 }
115 }
116 _ => (ty.clone(), false),
117 }
118 } else {
119 (ty.clone(), false)
120 }
121 } else {
122 (ty.clone(), false)
123 }
124}
125
126pub fn filter_inner_type(ty: &Type, skip_over: &HashSet<&str>) -> Type {
136 if let Type::Path(p) = &ty {
137 let type_segment = p.path.segments.last().unwrap();
138 if skip_over.contains(type_segment.ident.to_string().as_str()) {
139 match &type_segment.arguments {
140 PathArguments::AngleBracketed(p) => {
141 if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
142 filter_inner_type(&t, skip_over)
143 } else {
144 panic!("Argument in angle brackets must be a type")
145 }
146 }
147 _ => ty.clone(),
148 }
149 } else {
150 ty.clone()
151 }
152 } else {
153 ty.clone()
154 }
155}
156
157pub fn wrap_leaf_type(ty: &Type, skip_over: &HashSet<&str>) -> Type {
167 let mut ty = ty.clone();
168 if let Type::Path(p) = &mut ty {
169 let type_segment = p.path.segments.last_mut().unwrap();
170 if skip_over.contains(type_segment.ident.to_string().as_str()) {
171 match &mut type_segment.arguments {
172 PathArguments::AngleBracketed(args) => {
173 for a in args.args.iter_mut() {
174 if let syn::GenericArgument::Type(t) = a {
175 *t = wrap_leaf_type(t, skip_over);
176 }
177 }
178
179 ty
180 }
181 _ => ty,
182 }
183 } else {
184 parse_quote!(adze::WithLeaf<#ty>)
185 }
186 } else {
187 parse_quote!(adze::WithLeaf<#ty>)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use syn::parse_quote;
195
196 #[test]
197 fn test_parse_name_value_expr() {
198 let input: NameValueExpr = parse_quote!(key = "value");
199 assert_eq!(input.path.to_string(), "key");
200
201 let input: NameValueExpr = parse_quote!(precedence = 5);
202 assert_eq!(input.path.to_string(), "precedence");
203 }
204
205 #[test]
206 fn test_parse_field_then_params() {
207 let input: FieldThenParams = parse_quote!(Type);
208 assert!(input.comma.is_none());
209 assert!(input.params.is_empty());
210
211 let input: FieldThenParams = parse_quote!(Type, name = "test", value = 42);
212 assert!(input.comma.is_some());
213 assert_eq!(input.params.len(), 2);
214 assert_eq!(input.params[0].path.to_string(), "name");
215 assert_eq!(input.params[1].path.to_string(), "value");
216 }
217}