rtic_syntax/parse/
util.rs

1use syn::{
2    bracketed,
3    parse::{self, ParseStream},
4    punctuated::Punctuated,
5    spanned::Spanned,
6    Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path,
7    PathArguments, ReturnType, Token, Type, Visibility,
8};
9
10use crate::{
11    ast::{Access, Local, LocalResources, SharedResources, TaskLocal},
12    Map,
13};
14
15pub fn abi_is_rust(abi: &Abi) -> bool {
16    match &abi.name {
17        None => true,
18        Some(s) => s.value() == "Rust",
19    }
20}
21
22pub fn attr_eq(attr: &Attribute, name: &str) -> bool {
23    attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && {
24        let segment = attr.path.segments.first().unwrap();
25        segment.arguments == PathArguments::None && *segment.ident.to_string() == *name
26    }
27}
28
29/// checks that a function signature
30///
31/// - has no bounds (like where clauses)
32/// - is not `async`
33/// - is not `const`
34/// - is not `unsafe`
35/// - is not generic (has no type parameters)
36/// - is not variadic
37/// - uses the Rust ABI (and not e.g. "C")
38pub fn check_fn_signature(item: &ItemFn) -> bool {
39    item.vis == Visibility::Inherited
40        && item.sig.constness.is_none()
41        && item.sig.asyncness.is_none()
42        && item.sig.abi.is_none()
43        && item.sig.unsafety.is_none()
44        && item.sig.generics.params.is_empty()
45        && item.sig.generics.where_clause.is_none()
46        && item.sig.variadic.is_none()
47}
48
49#[allow(dead_code)]
50pub fn check_foreign_fn_signature(item: &ForeignItemFn) -> bool {
51    item.vis == Visibility::Inherited
52        && item.sig.constness.is_none()
53        && item.sig.asyncness.is_none()
54        && item.sig.abi.is_none()
55        && item.sig.unsafety.is_none()
56        && item.sig.generics.params.is_empty()
57        && item.sig.generics.where_clause.is_none()
58        && item.sig.variadic.is_none()
59}
60
61pub struct FilterAttrs {
62    pub cfgs: Vec<Attribute>,
63    pub docs: Vec<Attribute>,
64    pub attrs: Vec<Attribute>,
65}
66
67pub fn filter_attributes(input_attrs: Vec<Attribute>) -> FilterAttrs {
68    let mut cfgs = vec![];
69    let mut docs = vec![];
70    let mut attrs = vec![];
71
72    for attr in input_attrs {
73        if attr_eq(&attr, "cfg") {
74            cfgs.push(attr);
75        } else if attr_eq(&attr, "doc") {
76            docs.push(attr);
77        } else {
78            attrs.push(attr);
79        }
80    }
81
82    FilterAttrs { cfgs, docs, attrs }
83}
84
85pub fn extract_lock_free(attrs: &mut Vec<Attribute>) -> parse::Result<bool> {
86    if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) {
87        attrs.remove(pos);
88        Ok(true)
89    } else {
90        Ok(false)
91    }
92}
93
94pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result<SharedResources> {
95    let inner;
96    bracketed!(inner in content);
97
98    let mut resources = Map::new();
99    for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
100        let err = Err(parse::Error::new(
101            e.span(),
102            "identifier appears more than once in list",
103        ));
104        let (access, path) = match e {
105            Expr::Path(e) => (Access::Exclusive, e.path),
106
107            Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr {
108                Expr::Path(e) => (Access::Shared, e.path.clone()),
109
110                _ => return err,
111            },
112
113            _ => return err,
114        };
115
116        let ident = extract_resource_name_ident(path)?;
117
118        if resources.contains_key(&ident) {
119            return Err(parse::Error::new(
120                ident.span(),
121                "resource appears more than once in list",
122            ));
123        }
124
125        resources.insert(ident, access);
126    }
127
128    Ok(resources)
129}
130
131fn extract_resource_name_ident(path: Path) -> parse::Result<Ident> {
132    if path.leading_colon.is_some()
133        || path.segments.len() != 1
134        || path.segments[0].arguments != PathArguments::None
135    {
136        Err(parse::Error::new(
137            path.span(),
138            "resource must be an identifier, not a path",
139        ))
140    } else {
141        Ok(path.segments[0].ident.clone())
142    }
143}
144
145pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result<LocalResources> {
146    let inner;
147    bracketed!(inner in content);
148
149    let mut resources = Map::new();
150
151    for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
152        let err = Err(parse::Error::new(
153            e.span(),
154            "identifier appears more than once in list",
155        ));
156
157        let (name, local) = match e {
158            // local = [IDENT],
159            Expr::Path(path) => {
160                if !path.attrs.is_empty() {
161                    return Err(parse::Error::new(
162                        path.span(),
163                        "attributes are not supported here",
164                    ));
165                }
166
167                let ident = extract_resource_name_ident(path.path)?;
168                // let (cfgs, attrs) = extract_cfgs(path.attrs);
169
170                (ident, TaskLocal::External)
171            }
172
173            // local = [IDENT: TYPE = EXPR]
174            Expr::Assign(e) => {
175                let (name, ty, cfgs, attrs) = match *e.left {
176                    Expr::Type(t) => {
177                        // Extract name and attributes
178                        let (name, cfgs, attrs) = match *t.expr {
179                            Expr::Path(path) => {
180                                let name = extract_resource_name_ident(path.path)?;
181                                let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs);
182
183                                (name, cfgs, attrs)
184                            }
185                            _ => return err,
186                        };
187
188                        let ty = t.ty;
189
190                        // Error check
191                        match &*ty {
192                            Type::Array(_) => {}
193                            Type::Path(_) => {}
194                            Type::Ptr(_) => {}
195                            Type::Tuple(_) => {}
196                            _ => return Err(parse::Error::new(
197                                ty.span(),
198                                "unsupported type, must be an array, tuple, pointer or type path",
199                            )),
200                        };
201
202                        (name, ty, cfgs, attrs)
203                    }
204                    e => return Err(parse::Error::new(e.span(), "malformed, expected a type")),
205                };
206
207                let expr = e.right; // Expr
208
209                (
210                    name,
211                    TaskLocal::Declared(Local {
212                        attrs,
213                        cfgs,
214                        ty,
215                        expr,
216                    }),
217                )
218            }
219
220            expr => {
221                return Err(parse::Error::new(
222                    expr.span(),
223                    "malformed, expected 'IDENT: TYPE = EXPR'",
224                ))
225            }
226        };
227
228        resources.insert(name, local);
229    }
230
231    Ok(resources)
232}
233
234type ParseInputResult = Option<(Box<Pat>, Result<Vec<PatType>, FnArg>)>;
235
236pub fn parse_inputs(inputs: Punctuated<FnArg, Token![,]>, name: &str) -> ParseInputResult {
237    let mut inputs = inputs.into_iter();
238
239    match inputs.next() {
240        Some(FnArg::Typed(first)) => {
241            if type_is_path(&first.ty, &[name, "Context"]) {
242                let rest = inputs
243                    .map(|arg| match arg {
244                        FnArg::Typed(arg) => Ok(arg),
245                        _ => Err(arg),
246                    })
247                    .collect::<Result<Vec<_>, _>>();
248
249                Some((first.pat, rest))
250            } else {
251                None
252            }
253        }
254
255        _ => None,
256    }
257}
258
259pub fn type_is_bottom(ty: &ReturnType) -> bool {
260    if let ReturnType::Type(_, ty) = ty {
261        matches!(**ty, Type::Never(_))
262    } else {
263        false
264    }
265}
266
267fn extract_init_resource_name_ident(ty: Type) -> Result<Ident, ()> {
268    match ty {
269        Type::Path(path) => {
270            let path = path.path;
271
272            if path.leading_colon.is_some()
273                || path.segments.len() != 1
274                || path.segments[0].arguments != PathArguments::None
275            {
276                Err(())
277            } else {
278                Ok(path.segments[0].ident.clone())
279            }
280        }
281        _ => Err(()),
282    }
283}
284
285/// Checks Init's return type, return the user provided types for analysis
286pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> {
287    match ty {
288        ReturnType::Default => Err(()),
289
290        ReturnType::Type(_, ty) => match &**ty {
291            Type::Tuple(t) => {
292                // return should be:
293                // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics)
294                //
295                // We check the length and the last one here, analysis checks that the user
296                // provided structs are correct.
297                if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) {
298                    return Ok((
299                        extract_init_resource_name_ident(t.elems[0].clone())?,
300                        extract_init_resource_name_ident(t.elems[1].clone())?,
301                    ));
302                }
303
304                Err(())
305            }
306
307            _ => Err(()),
308        },
309    }
310}
311
312pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool {
313    match ty {
314        Type::Path(tpath) if tpath.qself.is_none() => {
315            tpath.path.segments.len() == segments.len()
316                && tpath
317                    .path
318                    .segments
319                    .iter()
320                    .zip(segments)
321                    .all(|(lhs, rhs)| lhs.ident == **rhs)
322        }
323
324        _ => false,
325    }
326}
327
328pub fn type_is_unit(ty: &ReturnType) -> bool {
329    if let ReturnType::Type(_, ty) = ty {
330        if let Type::Tuple(ref tuple) = **ty {
331            tuple.elems.is_empty()
332        } else {
333            false
334        }
335    } else {
336        true
337    }
338}