playdate_bindgen/gen/fixes/
mod.rs

1use std::cell::Cell;
2use std::collections::HashMap;
3use bindgen_cfg::Target;
4use syn::spanned::Spanned;
5use syn::token;
6use syn::token::Not;
7use syn::token::RArrow;
8use syn::Item;
9use syn::Lifetime;
10use syn::Type;
11use syn::ItemStruct;
12use syn::TypeReference;
13
14use crate::Result;
15
16
17pub type FixMap = HashMap<String, Fix>;
18
19pub enum Fix {
20	ReturnNever,
21	Unwrap,
22	Deref,
23}
24
25
26pub fn engage(bindings: &mut syn::File, root: &str, _target: &Target, docs: &FixMap) -> Result<()> {
27	// TODO: preserve bindings.attrs
28	let items = Cell::from_mut(&mut bindings.items[..]);
29	let items_cells = items.as_slice_of_cells();
30	if let Some(root) = find_struct(items_cells, root) {
31		walk_struct(items_cells, None, root, docs);
32	}
33
34	Ok(())
35}
36
37
38fn find_struct<'t>(items: &'t [Cell<Item>], name: &str) -> Option<&'t mut ItemStruct> {
39	items.iter().find_map(|item| {
40		            match unsafe { item.as_ptr().as_mut() }.expect("cell is null, impossible") {
41			            syn::Item::Struct(entry) if entry.ident == name => Some(entry),
42		               _ => None,
43		            }
44	            })
45}
46
47
48fn walk_struct(items: &[Cell<Item>],
49               this: Option<&str>,
50               structure: &mut ItemStruct,
51               fixes: &HashMap<String, Fix>) {
52	let prefix = this.map(|s| format!("{s}.")).unwrap_or("".to_owned());
53	for field in structure.fields.iter_mut() {
54		let field_name = field.ident.as_ref().expect("field name");
55
56		match &mut field.ty {
57			syn::Type::Ptr(entry) => {
58				match entry.elem.as_mut() {
59					syn::Type::Path(path) => {
60						if let Some(ident) = path.path.get_ident() {
61							if let Some(ty) = find_struct(items, &ident.to_string()) {
62								let key = format!("{prefix}{field_name}");
63								walk_struct(items, Some(&key), ty, fixes);
64								apply_all(&key, field, fixes, None);
65							}
66						}
67					},
68					_ => unimplemented!("unknown ty: {}", quote::quote!(#{{field.ty}})),
69				}
70			},
71
72			syn::Type::Path(path) => {
73				if let Some(ident) = path.path.get_ident() {
74					unimplemented!("unexpected struct: '{}'", quote::quote!(#ident))
75				} else if let Some(ty) = extract_type_from_option(&field.ty) {
76					match ty {
77						Type::BareFn(_) => {
78							let key = format!("{prefix}{field_name}");
79							apply_all(&key, field, fixes, Some(ty.to_owned()));
80						},
81						_ => unimplemented!("unexpected ty: '{}'", quote::quote!(#ty)),
82					}
83				} else {
84					unimplemented!("unexpected ty: '{}'", quote::quote!(#&path))
85				}
86			},
87
88			_ty => {
89				#[cfg(feature = "log")]
90				println!(
91				         "unknown: {prefix}{}: {}",
92				         field.ident.as_ref().expect("field.ident"),
93				         quote::quote!(#_ty)
94				);
95			},
96		}
97	}
98}
99
100
101// This is the really bad solution to extract the type from an Option<T>.
102fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
103	use syn::{GenericArgument, Path, PathArguments, PathSegment};
104
105	fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
106		match *ty {
107			syn::Type::Path(ref tp) if tp.qself.is_none() => Some(&tp.path),
108			_ => None,
109		}
110	}
111
112	fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
113		let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| {
114			                                         acc.push_str(&v.ident.to_string());
115			                                         acc.push('|');
116			                                         acc
117		                                         });
118		vec!["Option|", "std|option|Option|", "core|option|Option|"].into_iter()
119		                                                            .find(|s| idents_of_path == *s)
120		                                                            .and_then(|_| path.segments.last())
121	}
122
123	extract_type_path(ty).and_then(|path| extract_option_segment(path))
124	                     .and_then(|path_seg| {
125		                     let type_params = &path_seg.arguments;
126		                     match *type_params {
127			                     PathArguments::AngleBracketed(ref params) => params.args.first(),
128		                        _ => None,
129		                     }
130	                     })
131	                     .and_then(|generic_arg| {
132		                     match *generic_arg {
133			                     GenericArgument::Type(ref ty) => Some(ty),
134		                        _ => None,
135		                     }
136	                     })
137}
138
139
140fn apply(_key: &str, field: &mut syn::Field, fix: &Fix, _underlying: Option<Type>) {
141	match fix {
142		Fix::ReturnNever => {
143			if let Type::BareFn(ref mut ty) = &mut field.ty {
144				ty.output =
145					syn::ReturnType::Type(
146					                      RArrow(ty.output.span()),
147					                      Box::new(syn::TypeNever { bang_token: Not(ty.output.span()), }.into()),
148					);
149			}
150		},
151		Fix::Unwrap => {
152			// TODO: Fix::Unwrap
153		},
154		Fix::Deref => {
155			// TODO: Fix::Deref
156		},
157	}
158}
159
160
161fn apply_all(key: &str, field: &mut syn::Field, fixes: &FixMap, ty: Option<Type>) {
162	let ty = ty.as_ref()
163	           .or_else(|| extract_type_from_option(&field.ty))
164	           .unwrap_or(&field.ty);
165
166
167	match ty {
168		Type::BareFn(_) => {
169			// apply default unwrap:
170			if key != "graphics.getDebugBitmap" {
171				field.ty = ty.to_owned();
172			}
173		},
174		Type::Ptr(ty) => {
175			// apply default deref:
176			match ty.elem.as_ref() {
177				Type::Path(path) => {
178					let lifetime = Lifetime::new("'static", ty.star_token.span());
179					field.ty = Type::Reference(TypeReference { and_token: token::And(ty.star_token.span()),
180					                                           lifetime: Some(lifetime),
181					                                           mutability: ty.mutability,
182					                                           elem: Type::Path(path.to_owned()).into() })
183				},
184				_ => unimplemented!(),
185			}
186		},
187		_ => unimplemented!(),
188	}
189
190	if let Some(fix) = fixes.get(key) {
191		apply(key, field, fix, None);
192	}
193}