rtfm_syntax/
check.rs

1use std::collections::{HashMap, HashSet};
2
3use proc_macro2::Span;
4use syn::parse;
5
6use crate::ast::App;
7
8pub fn app(app: &App) -> parse::Result<()> {
9    // Check that all referenced resources have been declared
10    // Check that resources are NOT `Exclusive`-ly shared between cores
11    let mut owners = HashMap::new();
12    for (core, _, name, access) in app.resource_accesses() {
13        if app.resource(name).is_none() {
14            return Err(parse::Error::new(
15                name.span(),
16                "this resource has NOT been declared",
17            ));
18        }
19
20        if access.is_exclusive() {
21            if let Some(owner) = owners.get(name) {
22                if core != *owner {
23                    return Err(parse::Error::new(
24                        name.span(),
25                        "resources can NOT be exclusively accessed (`&mut-`) from different cores",
26                    ));
27                }
28            } else {
29                owners.insert(name, core);
30            }
31        }
32    }
33
34    // Check that no resource has both types of access (`Exclusive` & `Shared`)
35    // TODO we want to allow this in the future (but behind a `Settings` feature gate)
36    // accesses from `init` are not consider `Exclusive` accesses because `init` doesn't use the
37    // `lock` API
38    let exclusive_accesses = app
39        .resource_accesses()
40        .filter_map(|(_, priority, name, access)| {
41            if priority.is_some() && access.is_exclusive() {
42                Some(name)
43            } else {
44                None
45            }
46        })
47        .collect::<HashSet<_>>();
48    for (_, _, name, access) in app.resource_accesses() {
49        if access.is_shared() && exclusive_accesses.contains(name) {
50            return Err(parse::Error::new(
51                name.span(),
52                "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`",
53            ));
54        }
55    }
56
57    // Check that init only has `Access::Exclusive` resources
58    // Check that late resources have NOT been assigned to `init`
59    for (name, access) in app.inits.values().flat_map(|init| &init.args.resources) {
60        if app.late_resources.contains_key(name) {
61            return Err(parse::Error::new(
62                name.span(),
63                "late resources can NOT be assigned to `init`",
64            ));
65        }
66
67        if access.is_shared() {
68            return Err(parse::Error::new(
69                name.span(),
70                "`init` has direct exclusive access to resources; use `x` instead of `&x` ",
71            ));
72        }
73    }
74
75    // Check that all late resources are covered by `init::LateResources`
76    let cores = app.args.cores;
77    let mut late_resources_set = app.late_resources.keys().collect::<HashSet<_>>();
78    if late_resources_set.is_empty() {
79        for init in app.inits.values() {
80            if init.returns_late_resources {
81                return Err(parse::Error::new(
82                    init.name.span(),
83                    "no late resources exist so this function must NOT return `LateResources`",
84                ));
85            }
86        }
87    } else {
88        if cores == 1 {
89            // the only core will initialize all the late resources
90            if let Some(init) = app.inits.get(&0) {
91                if !init.returns_late_resources {
92                    return Err(parse::Error::new(
93                        init.name.span(),
94                        "late resources exist so `#[init]` must return `init::LateResources`",
95                    ));
96                }
97            } else {
98                return Err(parse::Error::new(
99                    Span::call_site(),
100                    "late resources exist so a `#[init]` function must be defined",
101                ));
102            }
103        } else {
104            // this core will initialize the "rest" of late resources
105            let mut rest = None;
106
107            let mut initialized = HashMap::new();
108            for (core, init) in &app.inits {
109                if !init.returns_late_resources {
110                    continue;
111                }
112
113                if late_resources_set.is_empty() {
114                    return Err(parse::Error::new(
115                        init.name.span(),
116                        "no more late resources to initialize; \
117                         this function must NOT return `LateResources`",
118                    ));
119                }
120
121                if !init.args.late.is_empty() {
122                    for res in &init.args.late {
123                        if !app.late_resources.contains_key(res) {
124                            return Err(parse::Error::new(
125                                res.span(),
126                                "this is not a late resource",
127                            ));
128                        }
129
130                        if let Some(other) = initialized.get(res) {
131                            return Err(parse::Error::new(
132                                res.span(),
133                                &format!("this resource is initialized by core {}", other),
134                            ));
135                        } else {
136                            late_resources_set.remove(res);
137                            initialized.insert(res, core);
138                        }
139                    }
140                } else if let Some(rest) = rest {
141                    return Err(parse::Error::new(
142                        init.name.span(),
143                        &format!(
144                            "unclear how initialization of late resources is split between \
145                             cores {} and {}",
146                            rest, core,
147                        ),
148                    ));
149                } else {
150                    rest = Some(core);
151                }
152            }
153
154            if let Some(res) = late_resources_set.iter().next() {
155                if rest.is_none() {
156                    return Err(parse::Error::new(
157                        res.span(),
158                        "this resource is not being initialized",
159                    ));
160                }
161            }
162        }
163    }
164
165    // check that external interrupts are not used as hardware tasks
166    for task in app.hardware_tasks.values() {
167        let binds = &task.args.binds;
168
169        if let Some(extern_interrupts) = app.extern_interrupts.get(&task.args.core) {
170            if extern_interrupts.contains_key(binds) {
171                return Err(parse::Error::new(
172                    binds.span(),
173                    "`extern` interrupts can't be used as hardware tasks",
174                ));
175            }
176        }
177    }
178
179    // Check that all referenced tasks have been declared
180    for task in app.task_references() {
181        if app.hardware_tasks.contains_key(task) {
182            return Err(parse::Error::new(
183                task.span(),
184                "hardware tasks can NOT be spawned",
185            ));
186        } else if !app.software_tasks.contains_key(task) {
187            return Err(parse::Error::new(
188                task.span(),
189                "this software task has NOT been declared",
190            ));
191        }
192    }
193
194    Ok(())
195}