safety_parser/safety/
mod.rs

1use crate::{
2    Str,
3    configuration::{ANY, Tag, TagType, doc_option, env::need_check, get_tag, get_tag_opt},
4};
5use indexmap::IndexMap;
6use proc_macro2::TokenStream;
7use quote::quote;
8use syn::{
9    parse::{Parse, ParseStream},
10    punctuated::Punctuated,
11    token::{Brace, Paren},
12    *,
13};
14
15mod utils;
16
17#[cfg(test)]
18mod tests;
19
20#[derive(Debug)]
21pub struct SafetyAttr {
22    pub attr: Attribute,
23    pub args: SafetyAttrArgs,
24}
25
26impl Parse for SafetyAttr {
27    fn parse(input: ParseStream) -> Result<Self> {
28        let mut attrs = Attribute::parse_outer(input)?;
29
30        if attrs.len() != 1 {
31            return Err(syn::Error::new(
32                input.span(),
33                "Given input must be a single #[safety] attribute.",
34            ));
35        }
36        let attr = attrs.remove(0);
37        drop(attrs);
38
39        // We don't check attribute name. Normally, it's #[safety { ... }],
40        // but it can be #[path::to::safety {}], or #[reexported {}], or #[rapx::inner {}].
41
42        let args = attr.parse_args()?;
43        Ok(SafetyAttr { attr, args })
44    }
45}
46
47/// Parse a full attribute such as `#[rapx::inner { ... }]` to get properties.
48pub fn parse_attr_and_get_properties(attr: &str) -> Box<[PropertiesAndReason]> {
49    let Ok(attr) = parse_str::<SafetyAttr>(attr) else { return Box::new([]) };
50    attr.args.args.into_iter().collect()
51}
52
53#[derive(Debug)]
54pub struct SafetyAttrArgs {
55    pub args: Punctuated<PropertiesAndReason, Token![;]>,
56}
57
58impl Parse for SafetyAttrArgs {
59    fn parse(input: ParseStream) -> Result<Self> {
60        Ok(SafetyAttrArgs { args: Punctuated::parse_terminated(input)? })
61    }
62}
63
64impl SafetyAttrArgs {
65    pub fn property_reason(&self) -> impl Iterator<Item = (&Property, Option<&str>)> {
66        self.args.iter().flat_map(|arg| arg.tags.iter().map(|prop| (prop, arg.desc.as_deref())))
67    }
68}
69
70#[derive(Debug)]
71pub struct PropertiesAndReason {
72    pub tags: Box<[Property]>,
73    pub desc: Option<Str>,
74}
75
76impl Parse for PropertiesAndReason {
77    fn parse(input: ParseStream) -> Result<Self> {
78        let mut tags = Vec::<Property>::new();
79        let mut desc = None;
80
81        while !input.cursor().eof() {
82            let tag: TagNameType = input.parse()?;
83            if need_check() {
84                tag.check_type();
85            }
86            let args = if input.peek(Paren) {
87                let content;
88                parenthesized!(content in input);
89                let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
90                args.into_iter().collect()
91            } else if input.peek(Brace) {
92                let content;
93                braced!(content in input);
94                let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
95                args.into_iter().collect()
96            } else {
97                Default::default()
98            };
99            tags.push(Property { tag, args });
100
101            if input.peek(Token![,]) {
102                // consume `,` in multiple tags
103                let _: Token![,] = input.parse()?;
104            }
105            if input.peek(Token![:]) {
106                let _: Token![:] = input.parse()?;
107                // `:` isn't in args, thus parse desc
108                let s: LitStr = input.parse()?;
109                desc = Some(s.value().into());
110                break;
111            }
112            if input.peek(Token![;]) {
113                // new grouped SPs
114                break;
115            }
116        }
117        Ok(PropertiesAndReason { tags: tags.into(), desc })
118    }
119}
120
121impl PropertiesAndReason {
122    /// Generate
123    ///
124    /// ```text
125    /// /// Grouped desc
126    /// /// * SP1: desc
127    /// /// * SP2: desc
128    /// ```
129    pub fn gen_doc(&self) -> TokenStream {
130        let mut ts = TokenStream::default();
131
132        if doc_option().heading_safety_title && self.need_gen_doc() {
133            ts.extend(quote! { #[doc = "# Safety\n\n"] });
134        }
135
136        if let Some(desc) = self.desc.as_deref() {
137            ts.extend(quote! { #[doc = #desc] });
138        }
139
140        let heading_tag = doc_option().heading_tag;
141
142        for tag in &self.tags {
143            let name = tag.tag.name();
144            let tokens = match (heading_tag, tag.gen_doc()) {
145                (true, None) => quote! { #[doc = concat!("* ", #name)] },
146                (true, Some(desc)) => quote! { #[doc = concat!("* ", #name, ": ", #desc)] },
147                (false, None) => quote! {},
148                (false, Some(desc)) => quote! { #[doc = concat!("* ", #desc)] },
149            };
150            ts.extend(tokens);
151        }
152        ts
153    }
154
155    /// Safety doc rendered through LSP hover.
156    pub fn gen_hover_doc(&self) -> Box<str> {
157        use std::fmt::Write;
158
159        let mut doc = String::with_capacity(512);
160
161        if let Some(desc) = &self.desc {
162            doc.push_str(desc);
163            doc.push_str("\n\n");
164        }
165
166        for tag in &self.tags {
167            let name = tag.tag.name();
168            if let Some(desc) = tag.gen_doc() {
169                _ = writeln!(&mut doc, "* {name}: {desc}");
170            } else {
171                _ = writeln!(&mut doc, "* {name}");
172            }
173        }
174
175        doc.into()
176    }
177
178    fn gen_sp_in_any_doc(&self) -> String {
179        let mut doc = String::new();
180        let heading_tag = doc_option().heading_tag;
181
182        for tag in &self.tags {
183            let name = tag.tag.name();
184            let item = match (heading_tag, tag.gen_doc()) {
185                (true, None) => format!("    * {name}"),
186                (true, Some(desc)) => format!("    * {name}: {desc}"),
187                (false, None) => String::new(),
188                (false, Some(desc)) => format!("    * {desc}"),
189            };
190            doc.push_str(&item);
191            doc.push('\n');
192        }
193        doc.pop();
194        doc
195    }
196
197    pub fn need_gen_doc(&self) -> bool {
198        self.desc.is_some() || !self.tags.is_empty()
199    }
200}
201
202#[derive(Debug)]
203pub struct Property {
204    /// `SP` or `type.SP`. The type of single `SP` is unkown until queried from definition.
205    pub tag: TagNameType,
206    /// Args in `SP(args)` such as `arg1, arg2`.
207    pub args: Box<[Expr]>,
208}
209
210impl Property {
211    /// Generate `#[doc]` for this property from its desc string interpolation.
212    /// None means SP is not defined with desc, thus nothing to generate.
213    pub fn gen_doc(&self) -> Option<String> {
214        let name = self.tag.name();
215
216        if name == ANY {
217            if self.args.is_empty() {
218                return None;
219            }
220            let mut doc =
221                "Only one of the following properties requires being satisfied:\n".to_owned();
222            // validate SPs in `any(SP1, SP2, ...)` exist
223            for prop in utils::parse_args_in_any_tag(&self.args) {
224                doc.push_str(&prop.gen_sp_in_any_doc());
225            }
226            return Some(doc);
227        }
228
229        let defined_tag = get_tag_opt(name)?;
230        // NOTE: this tolerates missing args, but position matters.
231        let args_len = self.args.len().min(defined_tag.args.len());
232
233        // map defined arg names to user inputs
234        let defined_args = defined_tag.args[..args_len].iter().map(|s| &**s);
235        let input_args = self.args[..args_len].iter().map(utils::expr_to_string);
236        let mut map_defined_arg_input_arg: IndexMap<_, _> = defined_args.zip(input_args).collect();
237        // if input arg is missing, defined arg will be an empty string
238        for defined_arg in &defined_tag.args {
239            if !map_defined_arg_input_arg.contains_key(&**defined_arg) {
240                map_defined_arg_input_arg.insert(defined_arg, String::new());
241            }
242        }
243
244        defined_tag.desc.as_deref().map(|desc| utils::template(desc, &map_defined_arg_input_arg))
245    }
246
247    /// SPs in `any` tag. None means the tag is not `any` or empty args.
248    pub fn args_in_any_tag(&self) -> Option<Vec<PropertiesAndReason>> {
249        (self.tag.name() == ANY && !self.args.is_empty())
250            .then(|| utils::parse_args_in_any_tag(&self.args))
251    }
252}
253
254/// Typed SP: `type.SP`
255#[derive(Debug)]
256pub struct TagNameType {
257    /// Default tag type is the one in single defined_types.
258    //
259    /// Deserialization will fill the default tag type as Precond.
260    typ: Option<TagType>,
261    /// Single ident string.
262    name: Str,
263}
264
265impl Parse for TagNameType {
266    fn parse(input: ParseStream) -> Result<Self> {
267        let ident: Ident = input.parse()?;
268        let first = ident.to_string();
269        Ok(if input.peek(Token![.]) {
270            let _: Token![.] = input.parse()?;
271            let second: Ident = input.parse()?;
272            let name = second.to_string().into();
273            TagNameType { name, typ: Some(TagType::new(&first)) }
274        } else {
275            TagNameType { name: first.into(), typ: None }
276        })
277    }
278}
279
280impl TagNameType {
281    pub fn name(&self) -> &str {
282        &self.name
283    }
284
285    // FIXME: no pinned default tag, because we want default tag to be
286    // the one in single defined_types. Deserialization will fill the
287    // default tag type as Precond.
288    pub fn typ(&self) -> Option<TagType> {
289        self.typ
290    }
291
292    pub fn name_type(&self) -> (&str, Option<TagType>) {
293        (&self.name, self.typ)
294    }
295
296    /// Check if the tag in macro is wrongly specified.
297    pub fn check_type(&self) {
298        let (name, typ) = self.name_type();
299        if name == ANY {
300            // FIXME: check SP args here
301            return;
302        }
303        let defined_types = &get_tag(name).types;
304        if let Some(typ) = typ {
305            assert!(
306                defined_types.contains(&typ),
307                "For tag {name:?}, defined_types is {defined_types:?}, \
308                 while user's {typ:?} doesn't exist."
309            );
310        } else {
311            assert_eq!(
312                defined_types.len(),
313                1,
314                "For tag {name:?} without explicit type, \
315                 the default type is the single defined type.\n\
316                 But defined_types has multiple types: {defined_types:?}.\n\
317                 So choose a type to be `type.{name}`."
318            );
319        }
320    }
321
322    /// Get specification of the tag in TOML.
323    pub fn get_spec(&self) -> Option<&'static Tag> {
324        get_tag_opt(&self.name)
325    }
326}