Skip to main content

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