playdate_bindgen/gen/fixes/
mod.rs1use 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 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
101fn 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 },
154 Fix::Deref => {
155 },
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 if key != "graphics.getDebugBitmap" {
171 field.ty = ty.to_owned();
172 }
173 },
174 Type::Ptr(ty) => {
175 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}