thread_aware_macros_impl/
field_attrs.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use syn::{Attribute, Expr, Type};
5
6/// Configuration for field attributes.
7#[derive(Default, Debug)]
8pub struct FieldAttrCfg {
9    /// Whether to skip this field in thread-aware processing.
10    pub skip: bool,
11}
12
13/// Parses the `thread_aware` attributes on a field.
14#[expect(clippy::missing_errors_doc, reason = "syn::internal API, no need for docs")]
15pub fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<FieldAttrCfg> {
16    let mut cfg = FieldAttrCfg::default();
17    for attr in attrs.iter().filter(|a| a.path().is_ident("thread_aware")) {
18        let parsed = attr.parse_args_with(|input: syn::parse::ParseStream| {
19            if input.is_empty() {
20                return Ok(None);
21            }
22            let expr: Expr = input.parse()?;
23            Ok(Some(expr))
24        })?;
25        if let Some(expr) = parsed {
26            match expr {
27                Expr::Path(p) if p.path.is_ident("skip") => {
28                    if cfg.skip {
29                        return Err(syn::Error::new_spanned(p, "duplicate 'skip'"));
30                    }
31                    cfg.skip = true;
32                }
33                other => {
34                    return Err(syn::Error::new_spanned(
35                        other,
36                        "unknown thread_aware attribute (only 'skip' is supported)",
37                    ));
38                }
39            }
40        }
41    }
42    Ok(cfg)
43}
44
45/// Checks if the given type is `PhantomData`.
46#[must_use]
47pub fn is_phantom_data(ty: &Type) -> bool {
48    if let Type::Path(tp) = ty
49        && let Some(seg) = tp.path.segments.last()
50    {
51        return seg.ident == "PhantomData";
52    }
53    false
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use syn::parse_quote;
60
61    #[test]
62    fn test_parse_field_attrs_no_attrs() {
63        // Test with no attributes at all
64        let attrs: Vec<Attribute> = vec![];
65        let result = parse_field_attrs(&attrs).unwrap();
66        assert!(!result.skip);
67    }
68
69    #[test]
70    fn test_parse_field_attrs_skip() {
71        // Test with skip attribute
72        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware(skip)] }];
73        let result = parse_field_attrs(&attrs).unwrap();
74        assert!(result.skip);
75    }
76
77    #[test]
78    fn test_parse_field_attrs_empty_thread_aware() {
79        // Test with empty thread_aware attribute
80        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware()] }];
81        let result = parse_field_attrs(&attrs).unwrap();
82        assert!(!result.skip);
83    }
84
85    #[test]
86    fn test_parse_field_attrs_duplicate_skip() {
87        // Test that duplicate skip attributes are rejected
88        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware(skip)] }, parse_quote! { #[thread_aware(skip)] }];
89        let result = parse_field_attrs(&attrs);
90        assert!(result.is_err());
91        assert!(result.unwrap_err().to_string().contains("duplicate 'skip'"));
92    }
93
94    #[test]
95    fn test_parse_field_attrs_unknown_attribute() {
96        // Test that unknown attributes are rejected
97        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware(unknown)] }];
98        let result = parse_field_attrs(&attrs);
99        assert!(result.is_err());
100        assert!(result.unwrap_err().to_string().contains("unknown thread_aware attribute"));
101    }
102
103    #[test]
104    fn test_parse_field_attrs_unknown_attribute_with_value() {
105        // Test that unknown attributes with values are rejected (covers line 30-33)
106        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware(skip = helper)] }];
107        let result = parse_field_attrs(&attrs);
108        assert!(result.is_err());
109        assert!(result.unwrap_err().to_string().contains("unknown thread_aware attribute"));
110    }
111
112    #[test]
113    fn test_parse_field_attrs_non_thread_aware() {
114        // Test that non-thread_aware attributes are ignored
115        let attrs: Vec<Attribute> = vec![parse_quote! { #[derive(Debug)] }, parse_quote! { #[serde(skip)] }];
116        let result = parse_field_attrs(&attrs).unwrap();
117        assert!(!result.skip);
118    }
119
120    #[test]
121    fn test_parse_field_attrs_mixed_attributes() {
122        // Test that thread_aware attributes are parsed correctly alongside other attributes
123        let attrs: Vec<Attribute> = vec![
124            parse_quote! { #[derive(Debug)] },
125            parse_quote! { #[thread_aware(skip)] },
126            parse_quote! { #[serde(skip)] },
127        ];
128        let result = parse_field_attrs(&attrs).unwrap();
129        assert!(result.skip);
130    }
131
132    #[test]
133    fn test_is_phantom_data_simple() {
134        // Test with simple PhantomData type
135        let ty: Type = parse_quote! { PhantomData<T> };
136        assert!(is_phantom_data(&ty));
137    }
138
139    #[test]
140    fn test_is_phantom_data_with_std() {
141        // Test with std::marker::PhantomData
142        let ty: Type = parse_quote! { std::marker::PhantomData<T> };
143        assert!(is_phantom_data(&ty));
144    }
145
146    #[test]
147    fn test_is_phantom_data_with_core() {
148        // Test with core::marker::PhantomData
149        let ty: Type = parse_quote! { core::marker::PhantomData<T> };
150        assert!(is_phantom_data(&ty));
151    }
152
153    #[test]
154    fn test_is_phantom_data_fully_qualified() {
155        // Test with fully qualified path
156        let ty: Type = parse_quote! { ::std::marker::PhantomData<T> };
157        assert!(is_phantom_data(&ty));
158    }
159
160    #[test]
161    fn test_is_phantom_data_multiple_generics() {
162        // Test with multiple generic parameters
163        let ty: Type = parse_quote! { PhantomData<(T, U, V)> };
164        assert!(is_phantom_data(&ty));
165    }
166
167    #[test]
168    fn test_is_phantom_data_not_phantom() {
169        // Test with non-PhantomData types
170        let ty: Type = parse_quote! { String };
171        assert!(!is_phantom_data(&ty));
172
173        let ty: Type = parse_quote! { Vec<u8> };
174        assert!(!is_phantom_data(&ty));
175
176        let ty: Type = parse_quote! { Option<T> };
177        assert!(!is_phantom_data(&ty));
178    }
179
180    #[test]
181    fn test_is_phantom_data_reference() {
182        // Test with reference types (not a Type::Path)
183        let ty: Type = parse_quote! { &PhantomData<T> };
184        assert!(!is_phantom_data(&ty));
185    }
186
187    #[test]
188    fn test_is_phantom_data_tuple() {
189        // Test with tuple types (not a Type::Path)
190        let ty: Type = parse_quote! { (PhantomData<T>,) };
191        assert!(!is_phantom_data(&ty));
192    }
193
194    #[test]
195    fn test_is_phantom_data_array() {
196        // Test with array types (not a Type::Path)
197        let ty: Type = parse_quote! { [PhantomData<T>; 1] };
198        assert!(!is_phantom_data(&ty));
199    }
200
201    #[test]
202    fn test_field_attr_cfg_default() {
203        // Test that FieldAttrCfg::default() works correctly
204        let cfg = FieldAttrCfg::default();
205        assert!(!cfg.skip);
206    }
207
208    #[test]
209    fn test_parse_field_attrs_covers_line_27() {
210        // This test specifically covers line 27: if cfg.skip check
211        // by attempting to set skip twice
212        let attrs: Vec<Attribute> = vec![parse_quote! { #[thread_aware(skip)] }, parse_quote! { #[thread_aware(skip)] }];
213        let result = parse_field_attrs(&attrs);
214        assert!(result.is_err());
215        let err_msg = result.unwrap_err().to_string();
216        assert!(err_msg.contains("duplicate"));
217    }
218}