playdate_bindgen/gen/docs/
gen.rs

1use std::cell::Cell;
2use std::collections::HashMap;
3use syn::Item;
4use syn::Type;
5use syn::ItemStruct;
6
7use crate::Result;
8use super::DocsMap;
9
10
11pub fn engage(bindings: &mut syn::File, root: &str, docs: &DocsMap) -> Result<()> {
12	// TODO: preserve bindings.attrs
13	let items = Cell::from_mut(&mut bindings.items[..]);
14	let items_cells = items.as_slice_of_cells();
15	if let Some(root) = find_struct(items_cells, root) {
16		walk_struct(items_cells, None, root, docs);
17	}
18
19	Ok(())
20}
21
22
23fn find_struct<'t>(items: &'t [Cell<Item>], name: &str) -> Option<&'t mut ItemStruct> {
24	items.iter().find_map(|item| {
25		            match unsafe { item.as_ptr().as_mut() }.expect("cell is null, impossible") {
26			            syn::Item::Struct(entry) if entry.ident == name => Some(entry),
27		               _ => None,
28		            }
29	            })
30}
31
32
33fn walk_struct(items: &[Cell<Item>],
34               this: Option<&str>,
35               structure: &mut ItemStruct,
36               docs: &HashMap<String, String>) {
37	let prefix = this.map(|s| format!("{s}.")).unwrap_or("".to_owned());
38	for field in structure.fields.iter_mut() {
39		let field_name = field.ident.as_ref().expect("field name");
40
41		match &mut field.ty {
42			syn::Type::Ptr(entry) => {
43				match entry.elem.as_mut() {
44					syn::Type::Path(path) => {
45						if let Some(ident) = path.path.get_ident() {
46							if let Some(ty) = find_struct(items, &ident.to_string()) {
47								let key = format!("{prefix}{field_name}");
48								walk_struct(items, Some(&key), ty, docs);
49							}
50						}
51					},
52					_ => unimplemented!("unknown ty: {}", quote::quote!(#{{field.ty}})),
53				}
54			},
55
56			syn::Type::Path(path) => {
57				if let Some(ident) = path.path.get_ident() {
58					unimplemented!("unexpected struct: '{}'", quote::quote!(#ident))
59				} else if let Some(ty) = extract_type_from_option(&field.ty) {
60					match ty {
61						Type::BareFn(_) => {
62							let key = format!("{prefix}{field_name}");
63							if let Some(doc) = docs.get(&key) {
64								let attr: syn::Attribute = syn::parse_quote! { #[doc = #doc] };
65								field.attrs.push(attr);
66							} else {
67								#[cfg(feature = "log")]
68								println!("cargo::warning=Doc not found for '{key}'");
69							}
70						},
71						_ => unimplemented!("unexpected ty: '{}'", quote::quote!(#ty)),
72					}
73				} else {
74					unimplemented!("unexpected ty: '{}'", quote::quote!(#&path))
75				}
76			},
77
78			ty => {
79				println!(
80				         "unknown: {prefix}{}: {}",
81				         field.ident.as_ref().expect("field.ident"),
82				         quote::quote!(#ty)
83				);
84			},
85		}
86	}
87}
88
89
90// This is the really bad solution to extract the type from an Option<T>.
91fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
92	use syn::{GenericArgument, Path, PathArguments, PathSegment};
93
94	fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
95		match *ty {
96			syn::Type::Path(ref tp) if tp.qself.is_none() => Some(&tp.path),
97			_ => None,
98		}
99	}
100
101	fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
102		let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| {
103			                                         acc.push_str(&v.ident.to_string());
104			                                         acc.push('|');
105			                                         acc
106		                                         });
107		vec!["Option|", "std|option|Option|", "core|option|Option|"].into_iter()
108		                                                            .find(|s| idents_of_path == *s)
109		                                                            .and_then(|_| path.segments.last())
110	}
111
112	extract_type_path(ty).and_then(|path| extract_option_segment(path))
113	                     .and_then(|path_seg| {
114		                     let type_params = &path_seg.arguments;
115		                     match *type_params {
116			                     PathArguments::AngleBracketed(ref params) => params.args.first(),
117		                        _ => None,
118		                     }
119	                     })
120	                     .and_then(|generic_arg| {
121		                     match *generic_arg {
122			                     GenericArgument::Type(ref ty) => Some(ty),
123		                        _ => None,
124		                     }
125	                     })
126}