safety_parser/safety/
mod.rs1use 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 let args = attr.parse_args()?;
43 Ok(SafetyAttr { attr, args })
44 }
45}
46
47pub 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 let _: Token![,] = input.parse()?;
104 }
105 if input.peek(Token![:]) {
106 let _: Token![:] = input.parse()?;
107 let s: LitStr = input.parse()?;
109 desc = Some(s.value().into());
110 break;
111 }
112 if input.peek(Token![;]) {
113 break;
115 }
116 }
117 Ok(PropertiesAndReason { tags: tags.into(), desc })
118 }
119}
120
121impl PropertiesAndReason {
122 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 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 pub tag: TagNameType,
206 pub args: Box<[Expr]>,
208}
209
210impl Property {
211 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 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 let args_len = self.args.len().min(defined_tag.args.len());
232
233 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 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 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#[derive(Debug)]
256pub struct TagNameType {
257 typ: Option<TagType>,
261 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 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 pub fn check_type(&self) {
298 let (name, typ) = self.name_type();
299 if name == ANY {
300 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 pub fn get_spec(&self) -> Option<&'static Tag> {
324 get_tag_opt(&self.name)
325 }
326}