1use crate::error::{Error, Result};
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct ObjCQualifiedType {
7 pub qualifiers: Vec<TypeQualifier>,
9 pub ty: ObjCType,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
16pub enum ObjCType {
17 Void,
19 Bool,
21 Char,
23 UnsignedChar,
25 Short,
27 UnsignedShort,
29 Int,
31 UnsignedInt,
33 Long,
35 UnsignedLong,
37 LongLong,
39 UnsignedLongLong,
41 Float,
43 Double,
45 CharPtr,
47 Selector,
49 Class,
51 Object {
53 class_name: Option<String>,
55 protocols: Vec<String>,
57 is_block: bool,
59 },
60 CString,
62 Pointer(Box<ObjCQualifiedType>),
64 Array {
66 len: usize,
68 element: Box<ObjCQualifiedType>,
70 },
71 Struct {
73 name: String,
75 fields: Vec<ObjCQualifiedType>,
77 },
78 Union {
80 name: String,
82 fields: Vec<ObjCQualifiedType>,
84 },
85 BitField(usize),
87 Unknown(char),
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[non_exhaustive]
94pub enum TypeQualifier {
95 Const,
97 In,
99 InOut,
101 Out,
103 ByCopy,
105 ByRef,
107 OneWay,
109 Atomic,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ObjCMethodArg {
116 pub ty: ObjCQualifiedType,
118 pub stack_offset: Option<usize>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ObjCMethodSignature {
125 pub return_type: ObjCQualifiedType,
127 pub return_offset: Option<usize>,
129 pub self_type: Option<ObjCMethodArg>,
131 pub cmd_type: Option<ObjCMethodArg>,
133 pub arguments: Vec<ObjCMethodArg>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Default)]
138pub struct ObjCPropertyAttributes {
140 pub ty: Option<ObjCQualifiedType>,
142 pub readonly: bool,
144 pub nonatomic: bool,
146 pub dynamic: bool,
148 pub weak: bool,
150 pub copy: bool,
152 pub strong: bool,
154 pub getter: Option<String>,
156 pub setter: Option<String>,
158 pub ivar: Option<String>,
160 pub old_type_encoding: Option<String>,
162 pub unknown_flags: Vec<String>,
164}
165
166impl ObjCQualifiedType {
167 pub fn parse(encoding: &str) -> Result<Self> {
169 let mut parser = Parser::new(encoding);
170 let ty = parser.parse_qualified_type()?;
171 parser.skip_digits();
172 parser.expect_eof()?;
173 Ok(ty)
174 }
175
176 pub fn render(&self) -> String {
178 match &self.ty {
179 ObjCType::Pointer(inner) => {
180 let inner = inner.render();
181 let pointer = if inner.ends_with('*') {
182 format!("{inner}*")
183 } else {
184 format!("{inner} *")
185 };
186 self.qualify(pointer)
187 }
188 ObjCType::Array { len, element } => {
189 self.qualify(format!("{}[{len}]", element.render()))
190 }
191 _ => self.qualify(self.ty.render_base()),
192 }
193 }
194
195 pub fn render_named(&self, name: &str) -> String {
197 match &self.ty {
198 ObjCType::BitField(bits) => self.qualify(attach_declarator(
199 "unsigned int".to_string(),
200 format!("{name} : {bits}"),
201 )),
202 ObjCType::Pointer(inner) => {
203 if let ObjCType::Array { len, element } = &inner.ty {
204 return self.qualify(attach_declarator(
205 element.render(),
206 format!("(*{name})[{len}]"),
207 ));
208 }
209
210 let inner_rendered = inner.render();
211 let declarator = if inner_rendered.ends_with('*') {
212 format!("*{name}")
213 } else {
214 format!(" *{name}")
215 };
216 self.qualify(format!("{inner_rendered}{declarator}"))
217 }
218 ObjCType::Array { len, element } => self.qualify(attach_declarator(
219 element.render(),
220 format!("{name}[{len}]"),
221 )),
222 _ => self.qualify(attach_declarator(self.ty.render_base(), name.to_string())),
223 }
224 }
225
226 fn qualify(&self, rendered: String) -> String {
227 if self.qualifiers.is_empty() {
228 rendered
229 } else {
230 format!(
231 "{} {rendered}",
232 self.qualifiers
233 .iter()
234 .map(|qualifier| qualifier.as_str())
235 .collect::<Vec<_>>()
236 .join(" ")
237 )
238 }
239 }
240}
241
242impl ObjCMethodSignature {
243 pub fn parse(encoding: &str) -> Result<Self> {
245 let mut parser = Parser::new(encoding);
246
247 let return_type = parser.parse_qualified_type()?;
248 let return_offset = parser.parse_number();
249 let self_type = parser.parse_method_arg()?;
250 let cmd_type = parser.parse_method_arg()?;
251
252 let mut arguments = Vec::new();
253 while !parser.is_eof() {
254 let Some(arg) = parser.parse_method_arg()? else {
255 break;
256 };
257 arguments.push(arg);
258 }
259
260 Ok(Self {
261 return_type,
262 return_offset,
263 self_type,
264 cmd_type,
265 arguments,
266 })
267 }
268}
269
270impl ObjCPropertyAttributes {
271 pub fn parse(attrs: &str) -> Self {
273 let mut parsed = Self::default();
274
275 for component in split_property_attribute_components(attrs) {
276 if component.is_empty() {
277 continue;
278 }
279
280 let mut chars = component.chars();
281 let Some(flag) = chars.next() else {
282 continue;
283 };
284 let value = chars.as_str();
285
286 match flag {
287 'T' => parsed.ty = ObjCQualifiedType::parse(value).ok(),
288 'R' => parsed.readonly = true,
289 'N' => parsed.nonatomic = true,
290 'D' => parsed.dynamic = true,
291 'W' => parsed.weak = true,
292 'C' => parsed.copy = true,
293 '&' => parsed.strong = true,
294 'G' if !value.is_empty() => parsed.getter = Some(value.to_string()),
295 'S' if !value.is_empty() => parsed.setter = Some(value.to_string()),
296 'V' if !value.is_empty() => parsed.ivar = Some(value.to_string()),
297 't' if !value.is_empty() => parsed.old_type_encoding = Some(value.to_string()),
298 _ => parsed.unknown_flags.push(component.to_string()),
299 }
300 }
301
302 parsed
303 }
304
305 pub fn effective_type(&self) -> Option<ObjCQualifiedType> {
307 let legacy = self
308 .old_type_encoding
309 .as_deref()
310 .and_then(|encoding| ObjCQualifiedType::parse(encoding).ok());
311
312 match (&self.ty, legacy) {
313 (Some(current), Some(legacy)) if current.is_less_specific_than(&legacy) => Some(legacy),
314 (Some(current), _) => Some(current.clone()),
315 (None, Some(legacy)) => Some(legacy),
316 (None, None) => None,
317 }
318 }
319}
320
321impl fmt::Display for ObjCQualifiedType {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 write!(f, "{}", self.render())
324 }
325}
326
327impl fmt::Display for ObjCType {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "{}", self.render_base())
330 }
331}
332
333impl TypeQualifier {
334 pub(crate) fn as_str(self) -> &'static str {
335 match self {
336 Self::Const => "const",
337 Self::In => "in",
338 Self::InOut => "inout",
339 Self::Out => "out",
340 Self::ByCopy => "bycopy",
341 Self::ByRef => "byref",
342 Self::OneWay => "oneway",
343 Self::Atomic => "_Atomic",
344 }
345 }
346}
347
348impl fmt::Display for TypeQualifier {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.write_str(self.as_str())
351 }
352}
353
354impl ObjCType {
355 fn render_base(&self) -> String {
356 match self {
357 Self::Void => "void".to_string(),
358 Self::Bool => "BOOL".to_string(),
359 Self::Char => "char".to_string(),
360 Self::UnsignedChar => "unsigned char".to_string(),
361 Self::Short => "short".to_string(),
362 Self::UnsignedShort => "unsigned short".to_string(),
363 Self::Int => "int".to_string(),
364 Self::UnsignedInt => "unsigned int".to_string(),
365 Self::Long => "long".to_string(),
366 Self::UnsignedLong => "unsigned long".to_string(),
367 Self::LongLong => "long long".to_string(),
368 Self::UnsignedLongLong => "unsigned long long".to_string(),
369 Self::Float => "float".to_string(),
370 Self::Double => "double".to_string(),
371 Self::CharPtr | Self::CString => "char *".to_string(),
372 Self::Selector => "SEL".to_string(),
373 Self::Class => "Class".to_string(),
374 Self::Object {
375 class_name,
376 protocols,
377 is_block,
378 } => {
379 if *is_block {
380 "id /* block */".to_string()
381 } else if let Some(name) = class_name {
382 if protocols.is_empty() {
383 format!("{name} *")
384 } else {
385 format!("{name}<{}> *", protocols.join(", "))
386 }
387 } else if protocols.is_empty() {
388 "id".to_string()
389 } else {
390 format!("id<{}>", protocols.join(", "))
391 }
392 }
393 Self::Pointer(inner) => {
394 let inner = inner.render();
395 if inner.ends_with('*') {
396 format!("{inner}*")
397 } else {
398 format!("{inner} *")
399 }
400 }
401 Self::Array { len, element } => format!("{}[{len}]", element.render()),
402 Self::Struct { name, .. } => {
403 if name == "?" || name.is_empty() {
404 "struct ?".to_string()
405 } else {
406 format!("struct {name}")
407 }
408 }
409 Self::Union { name, .. } => {
410 if name == "?" || name.is_empty() {
411 "union ?".to_string()
412 } else {
413 format!("union {name}")
414 }
415 }
416 Self::BitField(bits) => format!("unsigned int : {bits}"),
417 Self::Unknown(code) => format!("unknown /* {code} */"),
418 }
419 }
420
421 fn is_less_specific_than(&self, other: &Self) -> bool {
422 match self {
423 Self::Unknown(_) => !matches!(other, Self::Unknown(_)),
424 Self::Object {
425 class_name: None,
426 protocols,
427 is_block: false,
428 } => match other {
429 Self::Object {
430 class_name,
431 protocols: other_protocols,
432 ..
433 } => class_name.is_some() || other_protocols.len() > protocols.len(),
434 _ => false,
435 },
436 _ => false,
437 }
438 }
439}
440
441impl ObjCQualifiedType {
442 fn is_less_specific_than(&self, other: &Self) -> bool {
443 self.qualifiers == other.qualifiers && self.ty.is_less_specific_than(&other.ty)
444 }
445}
446
447fn attach_declarator(base: String, declarator: String) -> String {
448 if declarator.is_empty() {
449 base
450 } else if base.ends_with('*') {
451 format!("{base}{declarator}")
452 } else {
453 format!("{base} {declarator}")
454 }
455}
456
457struct Parser<'a> {
458 input: &'a str,
459 pos: usize,
460}
461
462impl<'a> Parser<'a> {
463 fn new(input: &'a str) -> Self {
464 Self { input, pos: 0 }
465 }
466
467 fn is_eof(&self) -> bool {
468 self.pos >= self.input.len()
469 }
470
471 fn expect_eof(&self) -> Result<()> {
472 if self.is_eof() {
473 Ok(())
474 } else {
475 Err(Error::format(format!(
476 "unexpected trailing ObjC encoding at byte {}: {}",
477 self.pos,
478 &self.input[self.pos..]
479 )))
480 }
481 }
482
483 fn peek(&self) -> Option<char> {
484 self.input[self.pos..].chars().next()
485 }
486
487 fn bump(&mut self) -> Option<char> {
488 let ch = self.peek()?;
489 self.pos += ch.len_utf8();
490 Some(ch)
491 }
492
493 fn consume(&mut self, expected: char) -> Result<()> {
494 match self.bump() {
495 Some(ch) if ch == expected => Ok(()),
496 Some(ch) => Err(Error::format(format!(
497 "expected '{}' in ObjC encoding, found '{}'",
498 expected, ch
499 ))),
500 None => Err(Error::format(format!(
501 "expected '{}' in ObjC encoding, found EOF",
502 expected
503 ))),
504 }
505 }
506
507 fn parse_method_arg(&mut self) -> Result<Option<ObjCMethodArg>> {
508 if self.is_eof() {
509 return Ok(None);
510 }
511 let ty = self.parse_qualified_type()?;
512 let stack_offset = self.parse_number();
513 Ok(Some(ObjCMethodArg { ty, stack_offset }))
514 }
515
516 fn parse_qualified_type(&mut self) -> Result<ObjCQualifiedType> {
517 let mut qualifiers = Vec::new();
518 while let Some(qualifier) = self.parse_qualifier() {
519 qualifiers.push(qualifier);
520 }
521 let ty = self.parse_type()?;
522 Ok(ObjCQualifiedType { qualifiers, ty })
523 }
524
525 fn parse_qualifier(&mut self) -> Option<TypeQualifier> {
526 let qualifier = match self.peek()? {
527 'r' => TypeQualifier::Const,
528 'n' => TypeQualifier::In,
529 'N' => TypeQualifier::InOut,
530 'o' => TypeQualifier::Out,
531 'O' => TypeQualifier::ByCopy,
532 'R' => TypeQualifier::ByRef,
533 'V' => TypeQualifier::OneWay,
534 'A' => TypeQualifier::Atomic,
535 _ => return None,
536 };
537 self.bump();
538 Some(qualifier)
539 }
540
541 fn parse_type(&mut self) -> Result<ObjCType> {
542 let Some(ch) = self.bump() else {
543 return Err(Error::format("unexpected EOF in ObjC type encoding"));
544 };
545
546 match ch {
547 'v' => Ok(ObjCType::Void),
548 'B' => Ok(ObjCType::Bool),
549 'c' => Ok(ObjCType::Char),
550 'C' => Ok(ObjCType::UnsignedChar),
551 's' => Ok(ObjCType::Short),
552 'S' => Ok(ObjCType::UnsignedShort),
553 'i' => Ok(ObjCType::Int),
554 'I' => Ok(ObjCType::UnsignedInt),
555 'l' => Ok(ObjCType::Long),
556 'L' => Ok(ObjCType::UnsignedLong),
557 'q' => Ok(ObjCType::LongLong),
558 'Q' => Ok(ObjCType::UnsignedLongLong),
559 'f' => Ok(ObjCType::Float),
560 'd' => Ok(ObjCType::Double),
561 '*' => Ok(ObjCType::CharPtr),
562 ':' => Ok(ObjCType::Selector),
563 '#' => Ok(ObjCType::Class),
564 '%' => Ok(ObjCType::CString),
565 '@' => self.parse_object_type(),
566 '^' => Ok(ObjCType::Pointer(Box::new(self.parse_qualified_type()?))),
567 '[' => self.parse_array_type(),
568 '{' => self.parse_record_type('}', true),
569 '(' => self.parse_record_type(')', false),
570 'b' => Ok(ObjCType::BitField(self.parse_number().unwrap_or(0))),
571 '?' => Ok(ObjCType::Unknown('?')),
572 _ => Ok(ObjCType::Unknown(ch)),
573 }
574 }
575
576 fn parse_object_type(&mut self) -> Result<ObjCType> {
577 match self.peek() {
578 Some('?') => {
579 self.bump();
580 Ok(ObjCType::Object {
581 class_name: None,
582 protocols: Vec::new(),
583 is_block: true,
584 })
585 }
586 Some('"') => {
587 self.bump();
588 let quoted = self.take_until('"')?;
589 self.consume('"')?;
590 let (class_name, protocols) = parse_quoted_object_metadata("ed);
591 Ok(ObjCType::Object {
592 class_name,
593 protocols,
594 is_block: false,
595 })
596 }
597 _ => Ok(ObjCType::Object {
598 class_name: None,
599 protocols: Vec::new(),
600 is_block: false,
601 }),
602 }
603 }
604
605 fn parse_array_type(&mut self) -> Result<ObjCType> {
606 let len = self.parse_number().unwrap_or(0);
607 let element = self.parse_qualified_type()?;
608 self.consume(']')?;
609 Ok(ObjCType::Array {
610 len,
611 element: Box::new(element),
612 })
613 }
614
615 fn parse_record_type(&mut self, terminator: char, is_struct: bool) -> Result<ObjCType> {
616 let name = self.take_record_name(terminator);
617 let mut fields = Vec::new();
618
619 if self.peek() == Some('=') {
620 self.bump();
621 while self.peek() != Some(terminator) {
622 self.skip_quoted_field_name();
623 fields.push(self.parse_qualified_type()?);
624 }
625 }
626
627 self.consume(terminator)?;
628 if is_struct {
629 Ok(ObjCType::Struct { name, fields })
630 } else {
631 Ok(ObjCType::Union { name, fields })
632 }
633 }
634
635 fn take_record_name(&mut self, terminator: char) -> String {
636 let start = self.pos;
637 while let Some(ch) = self.peek() {
638 if ch == '=' || ch == terminator {
639 break;
640 }
641 self.bump();
642 }
643 self.input[start..self.pos].to_string()
644 }
645
646 fn skip_quoted_field_name(&mut self) {
647 if self.peek() != Some('"') {
648 return;
649 }
650 let _ = self.bump();
651 while let Some(ch) = self.bump() {
652 if ch == '"' {
653 break;
654 }
655 }
656 }
657
658 fn take_until(&mut self, terminator: char) -> Result<String> {
659 let start = self.pos;
660 while let Some(ch) = self.peek() {
661 if ch == terminator {
662 return Ok(self.input[start..self.pos].to_string());
663 }
664 self.bump();
665 }
666 Err(Error::format(format!(
667 "unterminated quoted ObjC encoding fragment starting at byte {}",
668 start.saturating_sub(1)
669 )))
670 }
671
672 fn parse_number(&mut self) -> Option<usize> {
673 let start = self.pos;
674 while matches!(self.peek(), Some('0'..='9')) {
675 self.bump();
676 }
677 if self.pos == start {
678 None
679 } else {
680 self.input[start..self.pos].parse().ok()
681 }
682 }
683
684 fn skip_digits(&mut self) {
685 while matches!(self.peek(), Some('0'..='9')) {
686 self.bump();
687 }
688 }
689}
690
691fn parse_quoted_object_metadata(quoted: &str) -> (Option<String>, Vec<String>) {
692 if quoted.is_empty() {
693 return (None, Vec::new());
694 }
695
696 let mut class_name = String::new();
697 let mut protocols = Vec::new();
698 let mut rest = quoted;
699
700 while let Some(start) = rest.find('<') {
701 if class_name.is_empty() {
702 class_name.push_str(rest[..start].trim());
703 }
704 let Some(end) = rest[start + 1..].find('>') else {
705 break;
706 };
707 let proto = &rest[start + 1..start + 1 + end];
708 for proto in proto
709 .split(',')
710 .map(str::trim)
711 .filter(|proto| !proto.is_empty())
712 {
713 protocols.push(proto.to_string());
714 }
715 rest = &rest[start + 2 + end..];
716 }
717
718 if class_name.is_empty() {
719 let trimmed = rest.trim();
720 if !trimmed.is_empty() {
721 class_name.push_str(trimmed);
722 }
723 }
724
725 let class_name = if class_name.is_empty() || class_name == "<>" {
726 None
727 } else {
728 Some(class_name)
729 };
730
731 (class_name, protocols)
732}
733
734fn split_property_attribute_components(attrs: &str) -> Vec<&str> {
735 let mut parts = Vec::new();
736 let mut start = 0;
737 let mut in_quotes = false;
738 let mut angle_depth = 0usize;
739 let mut brace_depth = 0usize;
740 let mut paren_depth = 0usize;
741 let mut bracket_depth = 0usize;
742
743 for (idx, ch) in attrs.char_indices() {
744 match ch {
745 '"' => in_quotes = !in_quotes,
746 '<' if !in_quotes => angle_depth += 1,
747 '>' if !in_quotes && angle_depth > 0 => angle_depth -= 1,
748 '{' if !in_quotes => brace_depth += 1,
749 '}' if !in_quotes && brace_depth > 0 => brace_depth -= 1,
750 '(' if !in_quotes => paren_depth += 1,
751 ')' if !in_quotes && paren_depth > 0 => paren_depth -= 1,
752 '[' if !in_quotes => bracket_depth += 1,
753 ']' if !in_quotes && bracket_depth > 0 => bracket_depth -= 1,
754 ',' if !in_quotes
755 && angle_depth == 0
756 && brace_depth == 0
757 && paren_depth == 0
758 && bracket_depth == 0 =>
759 {
760 parts.push(&attrs[start..idx]);
761 start = idx + ch.len_utf8();
762 }
763 _ => {}
764 }
765 }
766
767 parts.push(&attrs[start..]);
768 parts
769}
770
771#[cfg(test)]
772mod tests {
773 use super::{
774 ObjCMethodSignature, ObjCPropertyAttributes, ObjCQualifiedType, ObjCType, TypeQualifier,
775 };
776
777 #[test]
778 fn parses_named_object_type() {
779 let ty = ObjCQualifiedType::parse("@\"NSString\"").expect("parse object type");
780 assert_eq!(
781 ty.ty,
782 ObjCType::Object {
783 class_name: Some("NSString".into()),
784 protocols: Vec::new(),
785 is_block: false,
786 }
787 );
788 assert_eq!(ty.to_string(), "NSString *");
789 }
790
791 #[test]
792 fn parses_protocol_qualified_object_type() {
793 let ty = ObjCQualifiedType::parse("@\"NSObject<NSCopying><NSSecureCoding>\"")
794 .expect("parse qualified object");
795 assert_eq!(ty.to_string(), "NSObject<NSCopying, NSSecureCoding> *");
796 }
797
798 #[test]
799 fn parses_nested_pointer_and_struct_type() {
800 let ty = ObjCQualifiedType::parse("^{CGRect={CGPoint=dd}{CGSize=dd}}")
801 .expect("parse struct pointer");
802 assert_eq!(ty.to_string(), "struct CGRect *");
803 }
804
805 #[test]
806 fn parses_qualified_scalar_type() {
807 let ty = ObjCQualifiedType::parse("ri").expect("parse qualified scalar");
808 assert_eq!(ty.qualifiers, vec![TypeQualifier::Const]);
809 assert_eq!(ty.to_string(), "const int");
810 }
811
812 #[test]
813 fn parses_method_signature_with_offsets() {
814 let sig =
815 ObjCMethodSignature::parse("v24@0:8@\"NSString\"16").expect("parse method signature");
816 assert_eq!(sig.return_type.to_string(), "void");
817 assert_eq!(sig.return_offset, Some(24));
818 assert_eq!(sig.arguments.len(), 1);
819 assert_eq!(sig.arguments[0].ty.to_string(), "NSString *");
820 assert_eq!(sig.arguments[0].stack_offset, Some(16));
821 }
822
823 #[test]
824 fn parses_method_signature_with_block_argument() {
825 let sig = ObjCMethodSignature::parse("v32@0:8@?16q24").expect("parse block signature");
826 assert_eq!(sig.arguments.len(), 2);
827 assert_eq!(sig.arguments[0].ty.to_string(), "id /* block */");
828 assert_eq!(sig.arguments[1].ty.to_string(), "long long");
829 }
830
831 #[test]
832 fn parses_property_attributes() {
833 let attrs = ObjCPropertyAttributes::parse(
834 "T@\"NSString\",&,N,GcustomTitle,SsetCustomTitle:,V_title",
835 );
836 assert_eq!(attrs.ty.expect("typed property").to_string(), "NSString *");
837 assert!(attrs.strong);
838 assert!(attrs.nonatomic);
839 assert_eq!(attrs.getter.as_deref(), Some("customTitle"));
840 assert_eq!(attrs.setter.as_deref(), Some("setCustomTitle:"));
841 assert_eq!(attrs.ivar.as_deref(), Some("_title"));
842 }
843
844 #[test]
845 fn parses_property_attributes_with_commas_inside_quoted_type_metadata() {
846 let attrs =
847 ObjCPropertyAttributes::parse("T@\"NSObject<NSCopying, NSSecureCoding>\",&,N,V_value");
848 assert_eq!(
849 attrs.effective_type().expect("typed property").to_string(),
850 "NSObject<NSCopying, NSSecureCoding> *"
851 );
852 assert!(attrs.strong);
853 assert!(attrs.nonatomic);
854 assert_eq!(attrs.ivar.as_deref(), Some("_value"));
855 }
856
857 #[test]
858 fn prefers_more_specific_legacy_property_type() {
859 let attrs = ObjCPropertyAttributes {
860 ty: Some(ObjCQualifiedType::parse("@").expect("parse id type")),
861 old_type_encoding: Some("@\"NSString\"".into()),
862 ..Default::default()
863 };
864 assert_eq!(
865 attrs.effective_type().expect("effective type").to_string(),
866 "NSString *"
867 );
868 }
869
870 #[test]
871 fn renders_named_nested_pointer_types_without_extra_spaces() {
872 let ty = ObjCQualifiedType::parse("^^@\"NSError\"").expect("parse nested pointer");
873 assert_eq!(ty.to_string(), "NSError ***");
874 assert_eq!(ty.render_named("error"), "NSError ***error");
875 }
876
877 #[test]
878 fn renders_pointer_to_array_declarators() {
879 let ty = ObjCQualifiedType::parse("^[4i]").expect("parse pointer to array");
880 assert_eq!(ty.render_named("matrix"), "int (*matrix)[4]");
881 }
882
883 #[test]
884 fn renders_bitfield_declarators() {
885 let ty = ObjCQualifiedType::parse("b3").expect("parse bitfield");
886 assert_eq!(ty.render_named("flags"), "unsigned int flags : 3");
887 }
888}