1use super::encoding::{ObjCMethodSignature, ObjCQualifiedType, ObjCType, TypeQualifier};
8
9#[derive(Debug, Clone)]
11pub struct SignatureCompat {
12 pub compatible: bool,
14 pub findings: Vec<SignatureIssue>,
16}
17
18#[derive(Debug, Clone)]
20pub struct SignatureIssue {
21 pub severity: IssueSeverity,
23 pub message: String,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum IssueSeverity {
31 Error,
33 Warning,
35}
36
37pub fn compare_method_signatures(
45 target: &ObjCMethodSignature,
46 provider: &ObjCMethodSignature,
47) -> SignatureCompat {
48 let mut findings = Vec::new();
49
50 if target.arguments.len() != provider.arguments.len() {
52 findings.push(SignatureIssue {
53 severity: IssueSeverity::Error,
54 message: format!(
55 "argument count mismatch: target has {}, provider has {}",
56 target.arguments.len(),
57 provider.arguments.len(),
58 ),
59 });
60 } else {
61 for (i, (t_arg, p_arg)) in target.arguments.iter().zip(&provider.arguments).enumerate() {
63 if !types_compatible(&t_arg.ty, &p_arg.ty) {
64 let sev = if types_width_compatible(&t_arg.ty, &p_arg.ty) {
65 IssueSeverity::Warning
66 } else {
67 IssueSeverity::Error
68 };
69 findings.push(SignatureIssue {
70 severity: sev,
71 message: format!(
72 "argument {i} type mismatch: target '{}', provider '{}'",
73 t_arg.ty.render(),
74 p_arg.ty.render(),
75 ),
76 });
77 }
78
79 let qual_diff = qualifier_diff(&t_arg.ty.qualifiers, &p_arg.ty.qualifiers);
81 if !qual_diff.is_empty() {
82 findings.push(SignatureIssue {
83 severity: IssueSeverity::Warning,
84 message: format!("argument {i} qualifier difference: {qual_diff}"),
85 });
86 }
87 }
88 }
89
90 if !types_compatible(&target.return_type, &provider.return_type) {
92 let sev = if types_width_compatible(&target.return_type, &provider.return_type) {
93 IssueSeverity::Warning
94 } else {
95 IssueSeverity::Error
96 };
97 findings.push(SignatureIssue {
98 severity: sev,
99 message: format!(
100 "return type mismatch: target '{}', provider '{}'",
101 target.return_type.render(),
102 provider.return_type.render(),
103 ),
104 });
105 }
106
107 let compatible = !findings.iter().any(|f| f.severity == IssueSeverity::Error);
108
109 SignatureCompat {
110 compatible,
111 findings,
112 }
113}
114
115pub fn types_compatible(a: &ObjCQualifiedType, b: &ObjCQualifiedType) -> bool {
124 type_cores_compatible(&a.ty, &b.ty)
125}
126
127fn type_cores_compatible(a: &ObjCType, b: &ObjCType) -> bool {
128 match (a, b) {
129 (ObjCType::Void, ObjCType::Void)
131 | (ObjCType::Bool, ObjCType::Bool)
132 | (ObjCType::Char, ObjCType::Char)
133 | (ObjCType::UnsignedChar, ObjCType::UnsignedChar)
134 | (ObjCType::Short, ObjCType::Short)
135 | (ObjCType::UnsignedShort, ObjCType::UnsignedShort)
136 | (ObjCType::Int, ObjCType::Int)
137 | (ObjCType::UnsignedInt, ObjCType::UnsignedInt)
138 | (ObjCType::Long, ObjCType::Long)
139 | (ObjCType::UnsignedLong, ObjCType::UnsignedLong)
140 | (ObjCType::LongLong, ObjCType::LongLong)
141 | (ObjCType::UnsignedLongLong, ObjCType::UnsignedLongLong)
142 | (ObjCType::Float, ObjCType::Float)
143 | (ObjCType::Double, ObjCType::Double)
144 | (ObjCType::CString, ObjCType::CString)
145 | (ObjCType::CharPtr, ObjCType::CharPtr)
146 | (ObjCType::Selector, ObjCType::Selector)
147 | (ObjCType::Class, ObjCType::Class) => true,
148
149 (
151 ObjCType::Object {
152 class_name: a_cls,
153 is_block: a_blk,
154 ..
155 },
156 ObjCType::Object {
157 class_name: b_cls,
158 is_block: b_blk,
159 ..
160 },
161 ) => {
162 if *a_blk && *b_blk {
164 return true;
165 }
166 if a_cls.is_none() || b_cls.is_none() {
168 return true;
169 }
170 a_cls == b_cls
172 }
173
174 (ObjCType::Pointer(a_inner), ObjCType::Pointer(b_inner)) => {
176 types_compatible(a_inner, b_inner)
177 }
178
179 (ObjCType::Struct { name: a_name, .. }, ObjCType::Struct { name: b_name, .. }) => {
181 a_name == b_name
182 }
183 (ObjCType::Union { name: a_name, .. }, ObjCType::Union { name: b_name, .. }) => {
184 a_name == b_name
185 }
186
187 (
189 ObjCType::Array {
190 len: a_len,
191 element: a_el,
192 },
193 ObjCType::Array {
194 len: b_len,
195 element: b_el,
196 },
197 ) => a_len == b_len && types_compatible(a_el, b_el),
198
199 (ObjCType::BitField(a_bits), ObjCType::BitField(b_bits)) => a_bits == b_bits,
201
202 (ObjCType::CString, ObjCType::CharPtr) | (ObjCType::CharPtr, ObjCType::CString) => true,
204
205 _ => false,
207 }
208}
209
210fn types_width_compatible(a: &ObjCQualifiedType, b: &ObjCQualifiedType) -> bool {
219 if is_pointer_family(&a.ty) != is_pointer_family(&b.ty) {
222 return false;
223 }
224 if is_pointer_family(&a.ty) && is_pointer_family(&b.ty) {
226 return false;
227 }
228 let a_w = type_width(&a.ty);
229 let b_w = type_width(&b.ty);
230 match (a_w, b_w) {
231 (Some(aw), Some(bw)) => aw == bw,
232 _ => false,
233 }
234}
235
236fn is_pointer_family(ty: &ObjCType) -> bool {
237 matches!(
238 ty,
239 ObjCType::Pointer(_)
240 | ObjCType::Object { .. }
241 | ObjCType::CharPtr
242 | ObjCType::CString
243 | ObjCType::Selector
244 | ObjCType::Class
245 )
246}
247
248fn type_width(ty: &ObjCType) -> Option<usize> {
250 match ty {
251 ObjCType::Void => Some(0),
252 ObjCType::Bool | ObjCType::Char | ObjCType::UnsignedChar => Some(1),
253 ObjCType::Short | ObjCType::UnsignedShort => Some(2),
254 ObjCType::Int | ObjCType::UnsignedInt | ObjCType::Float => Some(4),
255 ObjCType::Long
256 | ObjCType::UnsignedLong
257 | ObjCType::LongLong
258 | ObjCType::UnsignedLongLong
259 | ObjCType::Double => Some(8),
260 ObjCType::Pointer(_)
261 | ObjCType::Object { .. }
262 | ObjCType::CharPtr
263 | ObjCType::CString
264 | ObjCType::Selector
265 | ObjCType::Class => Some(8),
266 _ => None,
267 }
268}
269
270fn qualifier_diff(a: &[TypeQualifier], b: &[TypeQualifier]) -> String {
272 let mut diffs = Vec::new();
273 for q in a {
274 if !b.contains(q) {
275 diffs.push(format!("target has {q:?}"));
276 }
277 }
278 for q in b {
279 if !a.contains(q) {
280 diffs.push(format!("provider has {q:?}"));
281 }
282 }
283 diffs.join(", ")
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::objc::encoding::ObjCMethodArg;
290
291 fn make_sig(ret: ObjCType, args: Vec<ObjCType>) -> ObjCMethodSignature {
292 ObjCMethodSignature {
293 return_type: ObjCQualifiedType {
294 qualifiers: vec![],
295 ty: ret,
296 },
297 return_offset: None,
298 self_type: None,
299 cmd_type: None,
300 arguments: args
301 .into_iter()
302 .map(|ty| ObjCMethodArg {
303 ty: ObjCQualifiedType {
304 qualifiers: vec![],
305 ty,
306 },
307 stack_offset: None,
308 })
309 .collect(),
310 }
311 }
312
313 #[test]
314 fn identical_signatures_compatible() {
315 let sig = make_sig(ObjCType::Void, vec![ObjCType::Int, ObjCType::Bool]);
316 let result = compare_method_signatures(&sig, &sig);
317 assert!(result.compatible);
318 assert!(result.findings.is_empty());
319 }
320
321 #[test]
322 fn arg_count_mismatch() {
323 let target = make_sig(ObjCType::Void, vec![ObjCType::Int]);
324 let provider = make_sig(ObjCType::Void, vec![ObjCType::Int, ObjCType::Bool]);
325 let result = compare_method_signatures(&target, &provider);
326 assert!(!result.compatible);
327 assert!(
328 result
329 .findings
330 .iter()
331 .any(|f| f.severity == IssueSeverity::Error)
332 );
333 }
334
335 #[test]
336 fn return_type_mismatch_is_error() {
337 let target = make_sig(ObjCType::Void, vec![]);
338 let provider = make_sig(ObjCType::Int, vec![]);
339 let result = compare_method_signatures(&target, &provider);
340 assert!(!result.compatible);
341 }
342
343 #[test]
344 fn same_width_mismatch_is_warning() {
345 let target = make_sig(ObjCType::Int, vec![]);
347 let provider = make_sig(ObjCType::UnsignedInt, vec![]);
348 let result = compare_method_signatures(&target, &provider);
349 assert!(result.compatible); assert!(!result.findings.is_empty());
351 }
352
353 #[test]
354 fn id_compatible_with_named_object() {
355 let id_type = ObjCType::Object {
356 class_name: None,
357 protocols: vec![],
358 is_block: false,
359 };
360 let named_type = ObjCType::Object {
361 class_name: Some("NSString".into()),
362 protocols: vec![],
363 is_block: false,
364 };
365 let target = make_sig(id_type.clone(), vec![named_type.clone()]);
366 let provider = make_sig(named_type, vec![id_type]);
367 let result = compare_method_signatures(&target, &provider);
368 assert!(result.compatible);
369 }
370
371 #[test]
372 fn pointer_depth_mismatch() {
373 let ptr = ObjCType::Pointer(Box::new(ObjCQualifiedType {
374 qualifiers: vec![],
375 ty: ObjCType::Int,
376 }));
377 let double_ptr = ObjCType::Pointer(Box::new(ObjCQualifiedType {
378 qualifiers: vec![],
379 ty: ptr.clone(),
380 }));
381 let target = make_sig(ObjCType::Void, vec![ptr]);
382 let provider = make_sig(ObjCType::Void, vec![double_ptr]);
383 let result = compare_method_signatures(&target, &provider);
384 assert!(!result.compatible);
385 }
386
387 #[test]
388 fn struct_name_mismatch() {
389 let a = ObjCType::Struct {
390 name: "CGRect".into(),
391 fields: vec![],
392 };
393 let b = ObjCType::Struct {
394 name: "CGSize".into(),
395 fields: vec![],
396 };
397 let target = make_sig(ObjCType::Void, vec![a]);
398 let provider = make_sig(ObjCType::Void, vec![b]);
399 let result = compare_method_signatures(&target, &provider);
400 assert!(!result.compatible);
401 }
402
403 #[test]
404 fn cstring_and_charptr_compatible() {
405 let target = make_sig(ObjCType::CString, vec![]);
406 let provider = make_sig(ObjCType::CharPtr, vec![]);
407 let result = compare_method_signatures(&target, &provider);
408 assert!(result.compatible);
409 }
410}