codama_syn_helpers/extensions/
expr.rs

1use super::ToTokensExtension;
2use syn::{Expr, ExprLit, ExprPath, ExprUnary};
3
4pub trait ExprExtension {
5    fn get_self(&self) -> &Expr;
6
7    /// Returns the integer value of the expression if it is a literal unsigned integer.
8    fn as_unsigned_integer<T>(&self) -> syn::Result<T>
9    where
10        T: std::str::FromStr,
11        T::Err: std::fmt::Display,
12    {
13        let this = self.get_self();
14        match this {
15            Expr::Lit(ExprLit {
16                lit: syn::Lit::Int(value),
17                ..
18            }) => value.base10_parse::<T>(),
19            _ => Err(this.error("expected an unsigned integer")),
20        }
21    }
22
23    /// Returns the integer value of the expression if it is a literal signed integer.
24    fn as_signed_integer<T>(&self) -> syn::Result<T>
25    where
26        T: std::str::FromStr + std::ops::Neg<Output = T>,
27        T::Err: std::fmt::Display,
28    {
29        let this = self.get_self();
30        let result = match this {
31            Expr::Unary(ExprUnary {
32                op: syn::UnOp::Neg(_),
33                expr: unsigned_expr,
34                ..
35            }) => unsigned_expr
36                .as_unsigned_integer::<T>()
37                .map(|value| value.neg()),
38            _ => this.as_unsigned_integer::<T>(),
39        };
40
41        result.map_err(|_| this.error("expected a signed integer"))
42    }
43
44    fn as_unsigned_float<T>(&self) -> syn::Result<T>
45    where
46        T: std::str::FromStr,
47        T::Err: std::fmt::Display,
48    {
49        let this = self.get_self();
50        match this {
51            Expr::Lit(ExprLit {
52                lit: syn::Lit::Float(value),
53                ..
54            }) => value.base10_parse::<T>(),
55            _ => Err(this.error("expected an unsigned float")),
56        }
57    }
58
59    fn as_float<T>(&self) -> syn::Result<T>
60    where
61        T: std::str::FromStr + std::ops::Neg<Output = T>,
62        T::Err: std::fmt::Display,
63    {
64        let this = self.get_self();
65        let result = match this {
66            Expr::Unary(ExprUnary {
67                op: syn::UnOp::Neg(_),
68                expr: float_expr,
69                ..
70            }) => float_expr.as_unsigned_float::<T>().map(|value| value.neg()),
71            _ => this.as_unsigned_float::<T>(),
72        };
73
74        result.map_err(|_| this.error("expected a float"))
75    }
76
77    /// Returns the string value of the expression if it is a literal string.
78    fn as_string(&self) -> syn::Result<String> {
79        let this = self.get_self();
80        match this {
81            Expr::Lit(ExprLit {
82                lit: syn::Lit::Str(value),
83                ..
84            }) => Ok(value.value()),
85            _ => Err(this.error("expected a string")),
86        }
87    }
88
89    /// Returns the boolean value of the expression if it is a literal bool.
90    fn as_bool(&self) -> syn::Result<bool> {
91        let this = self.get_self();
92        match this {
93            Expr::Lit(ExprLit {
94                lit: syn::Lit::Bool(value),
95                ..
96            }) => Ok(value.value()),
97            _ => Err(this.error("expected a boolean")),
98        }
99    }
100
101    /// Returns the path of the expression if it is a path.
102    fn as_path(&self) -> syn::Result<&syn::Path> {
103        let this = self.get_self();
104        match this {
105            Expr::Path(ExprPath { path, .. }) => Ok(path),
106            _ => Err(this.error("expected a path")),
107        }
108    }
109}
110
111impl ExprExtension for Expr {
112    fn get_self(&self) -> &Expr {
113        self
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::extensions::*;
121
122    #[test]
123    fn as_unsigned_integer_ok() {
124        let expr: Expr = syn::parse_quote! { 42 };
125        let result = expr.as_unsigned_integer::<usize>().unwrap();
126        assert_eq!(result, 42usize);
127    }
128
129    #[test]
130    fn as_unsigned_integer_err() {
131        let expr: Expr = syn::parse_quote! { -42 };
132        let error = expr.as_unsigned_integer::<usize>().unwrap_err();
133        assert_eq!(error.to_string(), "expected an unsigned integer");
134    }
135
136    #[test]
137    fn as_signed_integer_ok() {
138        let expr: Expr = syn::parse_quote! { -42 };
139        let result = expr.as_signed_integer::<isize>().unwrap();
140        assert_eq!(result, -42isize);
141    }
142
143    #[test]
144    fn as_signed_integer_ok_with_unsigned() {
145        let expr: Expr = syn::parse_quote! { 42 };
146        let result = expr.as_signed_integer::<isize>().unwrap();
147        assert_eq!(result, 42isize);
148    }
149
150    #[test]
151    fn as_signed_integer_err() {
152        let expr: Expr = syn::parse_quote! { -42.5 };
153        let error = expr.as_signed_integer::<isize>().unwrap_err();
154        assert_eq!(error.to_string(), "expected a signed integer");
155    }
156
157    #[test]
158    fn as_float_ok() {
159        let expr: Expr = syn::parse_quote! { 1.5 };
160        let result = expr.as_float::<f64>().unwrap();
161        assert_eq!(result, 1.5f64);
162    }
163
164    #[test]
165    fn as_float_ok_negative() {
166        let expr: Expr = syn::parse_quote! { -1.5 };
167        let result = expr.as_float::<f64>().unwrap();
168        assert_eq!(result, -1.5f64);
169    }
170
171    #[test]
172    fn as_float_err() {
173        let expr: Expr = syn::parse_quote! { "3.14" };
174        let error = expr.as_float::<f64>().unwrap_err();
175        assert_eq!(error.to_string(), "expected a float");
176    }
177
178    #[test]
179    fn as_string_ok() {
180        let expr: Expr = syn::parse_quote! { "hello" };
181        let result = expr.as_string().unwrap();
182        assert_eq!(result, "hello");
183    }
184
185    #[test]
186    fn as_string_err() {
187        let expr: Expr = syn::parse_quote! { 40 + 2 };
188        let error = expr.as_string().unwrap_err();
189        assert_eq!(error.to_string(), "expected a string");
190    }
191
192    #[test]
193    fn as_bool_ok() {
194        let expr: Expr = syn::parse_quote! { true};
195        let result = expr.as_bool().unwrap();
196        assert!(result);
197    }
198
199    #[test]
200    fn as_bool_err() {
201        let expr: Expr = syn::parse_quote! { 42 };
202        let error = expr.as_bool().unwrap_err();
203        assert_eq!(error.to_string(), "expected a boolean");
204    }
205
206    #[test]
207    fn as_path_ok() {
208        let expr: Expr = syn::parse_quote! { hello::world };
209        let result = expr.as_path().unwrap().to_string();
210        assert_eq!(result, "hello::world");
211    }
212
213    #[test]
214    fn as_path_err() {
215        let expr: Expr = syn::parse_quote! { 40 + 2 };
216        let error = expr.as_path().unwrap_err();
217        assert_eq!(error.to_string(), "expected a path");
218    }
219}