Skip to main content

swiftui_native/
mangle.rs

1//! Swift type manglings, built rather than counted.
2//!
3//! A mangled type name is length-prefixed: `SwiftUI.ContentTransition` is
4//! `7SwiftUI17ContentTransitionV`, where the `7` and the `17` are the lengths of
5//! the two identifiers and the `V` says it is a struct. Writing those by hand is
6//! arithmetic in a string literal, and getting one wrong does not fail to
7//! compile — it resolves to nothing, or to the wrong type, at run time. This
8//! crate shipped `14decimalDigits` for a name thirteen characters long, and
9//! found out through a panic in an unrelated modifier.
10//!
11//! So the lengths are counted by the compiler's `len()` instead of by eye:
12//!
13//! ```ignore
14//! swift_type!(SwiftUI.ContentTransition)        // 7SwiftUI17ContentTransitionV
15//! swift_type!(enum SwiftUI.Font.Design)         // 7SwiftUI4FontV6DesignO
16//! swift_type!(opt SwiftUI.Color)                // 7SwiftUI5ColorVSg
17//! swift_type!([SwiftUI.EdgeInsets])             // Say7SwiftUI10EdgeInsetsVG
18//! ```
19//!
20//! Symbols — functions, getters, witness tables — are *not* covered. Their
21//! mangling encodes argument types, substitutions and calling conventions, and
22//! is read off the export table rather than constructed; see
23//! [`crate::resolve`].
24
25/// What kind of nominal type is being named.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum Kind {
28    /// A struct — the `V` suffix.
29    Struct,
30    /// An enum — the `O` suffix.
31    Enum,
32    /// A class — the `C` suffix.
33    Class,
34}
35
36impl Kind {
37    const fn suffix(self) -> char {
38        match self {
39            Kind::Struct => 'V',
40            Kind::Enum => 'O',
41            Kind::Class => 'C',
42        }
43    }
44}
45
46/// A nominal type, named by its module and the path to it.
47///
48/// `nominal(&["SwiftUI", "Font", "Design"], Kind::Enum)` is
49/// `7SwiftUI4FontV6DesignO` — note that the *outer* type keeps its own `V`, so a
50/// nested type carries the kind of everything containing it.
51pub fn nominal(path: &[&str], kind: Kind) -> String {
52    let mut out = String::new();
53    for (i, seg) in path.iter().enumerate() {
54        out.push_str(&seg.len().to_string());
55        out.push_str(seg);
56        // every segment but the last is a containing type, and a module has no
57        // suffix at all
58        if i > 0 && i + 1 < path.len() {
59            out.push(Kind::Struct.suffix());
60        }
61    }
62    out.push(kind.suffix());
63    out
64}
65
66/// A type imported from Objective-C or C.
67///
68/// These carry no module: `So` stands in for "the Clang importer", so
69/// `NSItemProvider` is `So14NSItemProviderC` and `CGRect` is `So6CGRectV`,
70/// however many frameworks away they actually live.
71pub fn imported(name: &str, kind: Kind) -> String {
72    format!("So{}{}{}", name.len(), name, kind.suffix())
73}
74
75/// A SwiftUI type written where the module is already in the substitution table.
76///
77/// Swift does not repeat itself: once `7SwiftUI` has been mangled, every later
78/// type from that module is `AA` followed by its own name. So the second
79/// SwiftUI type in a mangling looks nothing like the first —
80/// `7SwiftUI15ModifiedContentV` then `AA7AnyViewV`.
81pub fn again(name: &str, kind: Kind) -> String {
82    format!("AA{}{}{}", name.len(), name, kind.suffix())
83}
84
85/// `ModifiedContent<AnyView, M>` — the result type of most modifiers.
86///
87/// `modifier` is already in substituted form, because it is the third SwiftUI
88/// type in the name; [`again`] builds it.
89pub fn modified_content(modifier: &str) -> String {
90    format!(
91        "{}y{}{modifier}G",
92        nominal(&["SwiftUI", "ModifiedContent"], Kind::Struct),
93        again("AnyView", Kind::Struct),
94    )
95}
96
97/// A standard-library type with no shorthand, such as `Result`.
98///
99/// The module is `s`, which is a shorthand of its own and carries no length.
100pub fn stdlib(name: &str, kind: Kind) -> String {
101    format!("s{}{}{}", name.len(), name, kind.suffix())
102}
103
104/// A protocol existential — `any Error` is `s5Error_p`.
105pub fn existential(module_and_name: &str) -> String {
106    format!("{module_and_name}_p")
107}
108
109/// `Range<T>`.
110pub fn range(inner: &str) -> String {
111    format!("Sny{inner}G")
112}
113
114/// A type nested inside one of the shorthand types, such as `String.Index`.
115///
116/// The outer type is already a shorthand — `SS` — so only the inner name is
117/// length-prefixed.
118pub fn nested(shorthand: &str, name: &str, kind: Kind) -> String {
119    format!("{shorthand}{}{}{}", name.len(), name, kind.suffix())
120}
121
122/// `Optional<T>`, which is `T` with `Sg` after it.
123pub fn optional(inner: &str) -> String {
124    format!("{inner}Sg")
125}
126
127/// `Array<T>`.
128pub fn array(inner: &str) -> String {
129    format!("Say{inner}G")
130}
131
132/// `Set<T>`, which is `Sh` applied to one argument.
133pub fn set(inner: &str) -> String {
134    format!("Shy{inner}G")
135}
136
137/// A generic type applied to arguments — `Binding<Bool>` is
138/// `bound(BINDING, &[BOOL])`.
139pub fn bound(generic: &str, args: &[&str]) -> String {
140    format!("{generic}y{}G", args.concat())
141}
142
143/// `Swift.String`.
144pub const STRING: &str = "SS";
145/// `Swift.Int`.
146pub const INT: &str = "Si";
147/// `Swift.Bool`.
148pub const BOOL: &str = "Sb";
149/// `Swift.Double`.
150pub const DOUBLE: &str = "Sd";
151/// `Foundation.Date` — one `Double` of seconds since the reference date.
152pub const DATE: &str = "10Foundation4DateV";
153/// `Swift.Never`.
154pub const NEVER: &str = "s5NeverO";
155
156/// Turn a mangled name back into something readable, for diagnostics.
157pub fn demangle(mangled: &str) -> String {
158    // The runtime's demangler wants the `$s` a bare type mangling omits.
159    let full = if mangled.starts_with("$s") { mangled.to_string() } else { format!("$s{mangled}") };
160    match std::ffi::CString::new(full.clone()) {
161        Ok(c) => swift_runtime::demangle::demangle(&c).unwrap_or(full),
162        Err(_) => full,
163    }
164}
165
166/// Build a mangled Swift type name.
167///
168/// See the [module documentation](self) for the forms.
169macro_rules! swift_type {
170    (opt $($rest:tt)+)   => { $crate::mangle::optional(&swift_type!($($rest)+)) };
171    ([$($rest:tt)+])     => { $crate::mangle::array(&swift_type!($($rest)+)) };
172    (again $name:ident)       => { $crate::mangle::again(stringify!($name), $crate::mangle::Kind::Struct) };
173    (again enum $name:ident)  => { $crate::mangle::again(stringify!($name), $crate::mangle::Kind::Enum) };
174    (stdlib enum $name:ident) => { $crate::mangle::stdlib(stringify!($name), $crate::mangle::Kind::Enum) };
175    (objc $name:ident)      => { $crate::mangle::imported(stringify!($name), $crate::mangle::Kind::Class) };
176    (objc struct $name:ident) => { $crate::mangle::imported(stringify!($name), $crate::mangle::Kind::Struct) };
177    (enum $($seg:ident).+)  => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Enum) };
178    (class $($seg:ident).+) => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Class) };
179    ($($seg:ident).+)       => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Struct) };
180}
181
182pub(crate) use swift_type;
183
184/// Declare the mangled result types of a group of modifiers.
185///
186/// Each is built by the compressor at first use rather than written out, so the
187/// length prefixes and substitutions are the machine's arithmetic rather than
188/// anyone's:
189///
190/// ```ignore
191/// result_types! {
192///     /// `.blur(radius:)`
193///     &BLUR_T = modified(ty("_BlurEffect"));
194///     /// `.clipShape(RoundedRectangle(...))`
195///     CLIP_ROUNDED_T = modified(ty("_ClipEffect").of(vec![ty("RoundedRectangle")]));
196/// }
197/// ```
198macro_rules! result_types {
199    ($(
200        $(#[$meta:meta])*
201        $vis:vis $name:ident = $build:expr;
202    )*) => {$(
203        $(#[$meta])*
204        $vis static $name: $crate::views::types::CachedType =
205            $crate::views::types::CachedType::new(std::sync::LazyLock::new(|| {
206                #[allow(unused_imports)]
207                use $crate::mangle::{modified, opt, ty, ty_class, ty_enum, Kind, Type};
208                $build.mangle().into_bytes()
209            }));
210    )*};
211}
212
213pub(crate) use result_types;
214
215/// Declare the result type of a *modifier*, by naming the modifier alone.
216///
217/// `result_types!` names a whole concrete type. A modifier cannot: its result is
218/// `ModifiedContent<Self, Modifier>`, and `Self` is settled at the call, not
219/// here. So these entries carry the modifier and compose the rest against
220/// whatever they are applied to — see [`crate::views::types::ModifierType`].
221macro_rules! modifier_types {
222    ($(
223        $(#[$meta:meta])*
224        $vis:vis $name:ident = $build:expr;
225    )*) => {$(
226        $(#[$meta])*
227        $vis static $name: $crate::views::types::ModifierType =
228            $crate::views::types::ModifierType::new(std::sync::LazyLock::new(|| {
229                #[allow(unused_imports)]
230                use $crate::mangle::{modified, opt, ty, ty_class, ty_enum, Kind, Type};
231                $build.mangle().into_bytes()
232            }));
233    )*};
234}
235
236pub(crate) use modifier_types;
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    /// Every mangling this crate writes by hand, rebuilt by counting.
243    ///
244    /// The point is not that the macro works — it is that these strings are
245    /// what SwiftUI actually exports, so a mismatch means one of the two is
246    /// wrong and the test says which.
247    #[test]
248    fn matches_the_hand_written_manglings() {
249        assert_eq!(swift_type!(SwiftUI.EmptyVisualEffect), "7SwiftUI17EmptyVisualEffectV");
250        assert_eq!(swift_type!(SwiftUI.ContentTransition), "7SwiftUI17ContentTransitionV");
251        assert_eq!(swift_type!(SwiftUI.EnvironmentValues), "7SwiftUI17EnvironmentValuesV");
252        assert_eq!(swift_type!(SwiftUI.UnitPoint), "7SwiftUI9UnitPointV");
253        assert_eq!(swift_type!(SwiftUI.EdgeInsets), "7SwiftUI10EdgeInsetsV");
254        assert_eq!(swift_type!(SwiftUI.TextSelection), "7SwiftUI13TextSelectionV");
255        assert_eq!(swift_type!(SwiftUI.LinearGradient), "7SwiftUI14LinearGradientV");
256        assert_eq!(swift_type!(SwiftUI.LocalizedStringKey), "7SwiftUI18LocalizedStringKeyV");
257        assert_eq!(swift_type!(SwiftUI.PinnedScrollableViews), "7SwiftUI21PinnedScrollableViewsV");
258        assert_eq!(swift_type!(Foundation.URL), "10Foundation3URLV");
259        assert_eq!(swift_type!(UniformTypeIdentifiers.UTType), "22UniformTypeIdentifiers6UTTypeV");
260        assert_eq!(swift_type!(objc NSItemProvider), "So14NSItemProviderC");
261        assert_eq!(swift_type!(objc struct CGRect), "So6CGRectV");
262
263        assert_eq!(swift_type!(enum SwiftUI.Axis), "7SwiftUI4AxisO");
264        assert_eq!(swift_type!(enum SwiftUI.Font.Design), "7SwiftUI4FontV6DesignO");
265        assert_eq!(swift_type!(SwiftUI.Namespace.ID), "7SwiftUI9NamespaceV2IDV");
266
267        assert_eq!(swift_type!(opt SwiftUI.Color), "7SwiftUI5ColorVSg");
268        assert_eq!(swift_type!(opt enum SwiftUI.Axis), "7SwiftUI4AxisOSg");
269        assert_eq!(swift_type!(opt SwiftUI.Namespace.ID), "7SwiftUI9NamespaceV2IDVSg");
270        assert_eq!(swift_type!(opt enum SwiftUI.Font.Design), "7SwiftUI4FontV6DesignOSg");
271
272        assert_eq!(swift_type!([SwiftUI.EdgeInsets]), "Say7SwiftUI10EdgeInsetsVG");
273        assert_eq!(array(STRING), "SaySSG");
274        assert_eq!(array(INT), "SaySiG");
275        assert_eq!(optional(STRING), "SSSg");
276        assert_eq!(optional(BOOL), "SbSg");
277        assert_eq!(optional(DOUBLE), "SdSg");
278
279        assert_eq!(bound("7SwiftUI7BindingV", &[BOOL]), "7SwiftUI7BindingVySbG");
280        assert_eq!(bound("7SwiftUI7BindingV", &[INT]), "7SwiftUI7BindingVySiG");
281        assert_eq!(bound("7Combine5EmptyV", &[INT, NEVER]), "7Combine5EmptyVySis5NeverOG");
282
283        // substituted forms
284        assert_eq!(swift_type!(again AnyView), "AA7AnyViewV");
285        assert_eq!(swift_type!(again _BlurEffect), "AA11_BlurEffectV");
286        assert_eq!(
287            modified_content(&swift_type!(again _BlurEffect)),
288            "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_BlurEffectVG"
289        );
290        assert_eq!(
291            modified_content(&bound(&swift_type!(again _ClipEffect), &[&swift_type!(again RoundedRectangle)])),
292            "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA16RoundedRectangleVGG"
293        );
294
295        // standard library corners
296        assert_eq!(range(&nested(STRING, "Index", Kind::Struct)), "SnySS5IndexVG");
297        assert_eq!(
298            bound(&swift_type!(stdlib enum Result), &[&swift_type!(Foundation.URL), &existential("s5Error")]),
299            "s6ResultOy10Foundation3URLVs5Error_pG"
300        );
301    }
302
303    /// The compressor, against the strings SwiftUI actually exports.
304    ///
305    /// Each of these was copied out of the framework's export table; the
306    /// builder has to reproduce it character for character, including every
307    /// word substitution and every `A`-indexed back-reference. If Swift ever
308    /// changes the scheme, this is where it shows.
309    ///
310    /// These are the only mangled type names left written out in the crate —
311    /// production builds every one of them — so this is also the last place a
312    /// hand-counted length prefix could hide.
313    #[test]
314    fn compressor_reproduces_swiftui() {
315        use crate::mangle;
316        fn check(readable: &str, built: &mangle::Type, expected: &str) {
317            assert_eq!(built.mangle(), expected, "building {readable}");
318        }
319        // 77 types SwiftUI exports, built rather than copied
320        check("SecureField<Text>", &mangle::ty("SecureField").of(vec![mangle::ty("Text")]), "7SwiftUI11SecureFieldVyAA4TextVG");
321        check("ToolbarItem<(), AnyView>", &mangle::ty("ToolbarItem").of(vec![mangle::Type::Void, mangle::ty("AnyView")]), "7SwiftUI11ToolbarItemVyytAA7AnyViewVG");
322        check("ProgressView<EmptyView, EmptyView>", &mangle::ty("ProgressView").of(vec![mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI12ProgressViewVyAA05EmptyD0VAEG");
323        check("TimelineView<AnimationTimelineSchedule, AnyView>", &mangle::ty("TimelineView").of(vec![mangle::ty("AnimationTimelineSchedule"), mangle::ty("AnyView")]), "7SwiftUI12TimelineViewVyAA09AnimationC8ScheduleVAA03AnyD0VG");
324        check("GeometryReader<AnyView>", &mangle::ty("GeometryReader").of(vec![mangle::ty("AnyView")]), "7SwiftUI14GeometryReaderVyAA7AnyViewVG");
325        check("NavigationLink<Text, AnyView>", &mangle::ty("NavigationLink").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI14NavigationLinkVyAA4TextVAA7AnyViewVG");
326        check("DisclosureGroup<Text, AnyView>", &mangle::ty("DisclosureGroup").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI15DisclosureGroupVyAA4TextVAA7AnyViewVG");
327        check("ModifiedContent<AnyView, _BlurEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BlurEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_BlurEffectVG");
328        check("ModifiedContent<AnyView, _ClipEffect<RoundedRectangle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("RoundedRectangle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA16RoundedRectangleVGG");
329        check("ModifiedContent<AnyView, _ClipEffect<Circle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Circle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA6CircleVGG");
330        check("ModifiedContent<AnyView, _ClipEffect<Capsule>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Capsule")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA7CapsuleVGG");
331        check("ModifiedContent<AnyView, _ClipEffect<Ellipse>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Ellipse")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA7EllipseVGG");
332        check("ModifiedContent<AnyView, _ClipEffect<Rectangle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Rectangle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA9RectangleVGG");
333        check("ModifiedContent<AnyView, _FrameLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FrameLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA12_FrameLayoutVG");
334        check("ModifiedContent<AnyView, _ScaleEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ScaleEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA12_ScaleEffectVG");
335        check("ModifiedContent<AnyView, _OffsetEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OffsetEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA13_OffsetEffectVG");
336        check("ModifiedContent<AnyView, _ShadowEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ShadowEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA13_ShadowEffectVG");
337        check("ModifiedContent<AnyView, _OpacityEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OpacityEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA14_OpacityEffectVG");
338        check("ModifiedContent<AnyView, _PaddingLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_PaddingLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA14_PaddingLayoutVG");
339        check("ModifiedContent<AnyView, _ContrastEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ContrastEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_ContrastEffectVG");
340        check("ModifiedContent<AnyView, _HiddenModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_HiddenModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_HiddenModifierVG");
341        check("ModifiedContent<AnyView, _RotationEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_RotationEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_RotationEffectVG");
342        check("ModifiedContent<AnyView, ToolbarModifier<(), TupleToolbarContent<ToolbarItem<(), AnyView>>>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("ToolbarModifier").of(vec![mangle::Type::Void, mangle::ty("TupleToolbarContent").of(vec![mangle::ty("ToolbarItem").of(vec![mangle::Type::Void, mangle::ty("AnyView")])])])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15ToolbarModifierVyytAA05TuplegD0VyAA0G4ItemVyytAEGGGG");
343        check("ModifiedContent<AnyView, _BlendModeEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BlendModeEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_BlendModeEffectVG");
344        check("ModifiedContent<AnyView, _FixedSizeLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FixedSizeLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_FixedSizeLayoutVG");
345        check("ModifiedContent<AnyView, _GrayscaleEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_GrayscaleEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_GrayscaleEffectVG");
346        check("ModifiedContent<AnyView, _OverlayModifier<_ShapeView<_StrokedShape<Rectangle._Inset>, Color>>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OverlayModifier").of(vec![mangle::ty("_ShapeView").of(vec![mangle::ty("_StrokedShape").of(vec![mangle::Type::Nominal { module: "SwiftUI", path: vec!["Rectangle", "_Inset"], kind: mangle::Kind::Struct }]), mangle::ty("Color")])])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_OverlayModifierVyAA06_ShapeF0VyAA08_StrokedI0VyAA9RectangleV6_InsetVGAA5ColorVGGG");
347        check("ModifiedContent<AnyView, _OverlayModifier<AnyView>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OverlayModifier").of(vec![mangle::ty("AnyView")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_OverlayModifierVyAEGG");
348        check("ModifiedContent<AnyView, _BrightnessEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BrightnessEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_BrightnessEffectVG");
349        check("ModifiedContent<AnyView, _Rotation3DEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_Rotation3DEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_Rotation3DEffectVG");
350        check("ModifiedContent<AnyView, _SaturationEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_SaturationEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_SaturationEffectVG");
351        check("ModifiedContent<AnyView, _AnimationModifier<Int>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_AnimationModifier").of(vec![mangle::Type::Verbatim("Si".into())])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18_AnimationModifierVySiGG");
352        check("ModifiedContent<AnyView, _AspectRatioLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_AspectRatioLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18_AspectRatioLayoutVG");
353        check("ModifiedContent<AnyView, _FocusableModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FocusableModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18_FocusableModifierVG");
354        check("ModifiedContent<AnyView, AddGestureModifier<DragGesture, DefaultGestureCombiner>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AddGestureModifier").of(vec![mangle::ty("DragGesture"), mangle::ty("DefaultGestureCombiner")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18AddGestureModifierVyAA04DragH0VAA07DefaultH8CombinerVGG");
355        check("ModifiedContent<AnyView, AddGestureModifier<RotateGesture, DefaultGestureCombiner>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AddGestureModifier").of(vec![mangle::ty("RotateGesture"), mangle::ty("DefaultGestureCombiner")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18AddGestureModifierVyAA06RotateH0VAA07DefaultH8CombinerVGG");
356        check("ModifiedContent<AnyView, AddGestureModifier<MagnifyGesture, DefaultGestureCombiner>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AddGestureModifier").of(vec![mangle::ty("MagnifyGesture"), mangle::ty("DefaultGestureCombiner")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18AddGestureModifierVyAA07MagnifyH0VAA07DefaultH8CombinerVGG");
357        check("ModifiedContent<AnyView, AddGestureModifier<LongPressGesture, DefaultGestureCombiner>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AddGestureModifier").of(vec![mangle::ty("LongPressGesture"), mangle::ty("DefaultGestureCombiner")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18AddGestureModifierVyAA09LongPressH0VAA07DefaultH8CombinerVGG");
358        check("ModifiedContent<AnyView, _DrawingGroupEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_DrawingGroupEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA19_DrawingGroupEffectVG");
359        check("ModifiedContent<AnyView, _ShaderFilterEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ShaderFilterEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA19_ShaderFilterEffectVG");
360        check("ModifiedContent<AnyView, _MaskAlignmentEffect<AnyView>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_MaskAlignmentEffect").of(vec![mangle::ty("AnyView")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA20_MaskAlignmentEffectVyAEGG");
361        check("ModifiedContent<AnyView, _TraitWritingModifier<TransitionTraitKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_TraitWritingModifier").of(vec![mangle::ty("TransitionTraitKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA21_TraitWritingModifierVyAA010TransitionG3KeyVGG");
362        check("ModifiedContent<AnyView, _TraitWritingModifier<ZIndexTraitKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_TraitWritingModifier").of(vec![mangle::ty("ZIndexTraitKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA21_TraitWritingModifierVyAA06ZIndexG3KeyVGG");
363        check("ModifiedContent<AnyView, _CompositingGroupEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_CompositingGroupEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA23_CompositingGroupEffectVG");
364        check("ModifiedContent<AnyView, _BackgroundStyleModifier<LinearGradient>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("LinearGradient")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA14LinearGradientVGG");
365        check("ModifiedContent<AnyView, _BackgroundStyleModifier<RadialGradient>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("RadialGradient")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA14RadialGradientVGG");
366        check("ModifiedContent<AnyView, _BackgroundStyleModifier<Color>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("Color")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA5ColorVGG");
367        check("ModifiedContent<AnyView, _BackgroundStyleModifier<Material>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("Material")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA8MaterialVGG");
368        check("ModifiedContent<AnyView, _ForegroundStyleModifier<Color>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ForegroundStyleModifier").of(vec![mangle::ty("Color")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_ForegroundStyleModifierVyAA5ColorVGG");
369        check("ModifiedContent<AnyView, _AppearanceActionModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_AppearanceActionModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA25_AppearanceActionModifierVG");
370        check("ModifiedContent<AnyView, _PreferenceWritingModifier<PreferredColorSchemeKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_PreferenceWritingModifier").of(vec![mangle::ty("PreferredColorSchemeKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA26_PreferenceWritingModifierVyAA23PreferredColorSchemeKeyVGG");
371        check("ModifiedContent<AnyView, PopoverPresentationModifier<PopoverConditionalStateProvider<AnyView>>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("PopoverPresentationModifier").of(vec![mangle::ty("PopoverConditionalStateProvider").of(vec![mangle::ty("AnyView")])])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA27PopoverPresentationModifierVyAA0G24ConditionalStateProviderVyAEGGG");
372        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<CoreGraphics.CGFloat>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::Type::Nominal { module: "CoreGraphics", path: vec!["CGFloat"], kind: mangle::Kind::Struct }])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVy12CoreGraphics7CGFloatVGG");
373        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<AnyShapeStyle?>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::opt(mangle::ty("AnyShapeStyle"))])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA0E10ShapeStyleVSgGG");
374        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<ControlSize>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::ty_enum("ControlSize")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA11ControlSizeOGG");
375        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<TextAlignment>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::ty_enum("TextAlignment")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA13TextAlignmentOGG");
376        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<Font?>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::opt(mangle::ty("Font"))])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA4FontVSgGG");
377        check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<Int?>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::opt(mangle::Type::Verbatim("Si".into()))])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVySiSgGG");
378        check("ModifiedContent<AnyView, AccessibilityAttachmentModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AccessibilityAttachmentModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA31AccessibilityAttachmentModifierVG");
379        check("ModifiedContent<AnyView, _EnvironmentKeyTransformModifier<[AnyFontModifier]>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyTransformModifier").of(vec![mangle::Type::Verbatim("Sa".into()).of(vec![mangle::ty_class("AnyFontModifier")])])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA32_EnvironmentKeyTransformModifierVySayAA0e4FontJ0CGGG");
380        check("ModifiedContent<AnyView, _EnvironmentKeyTransformModifier<Bool>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyTransformModifier").of(vec![mangle::Type::Verbatim("Sb".into())])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA32_EnvironmentKeyTransformModifierVySbGG");
381        check("ModifiedContent<AnyView, PrimitiveButtonStyleContainerModifier<GlassButtonStyle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("PrimitiveButtonStyleContainerModifier").of(vec![mangle::ty("GlassButtonStyle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA37PrimitiveButtonStyleContainerModifierVyAA05GlasshI0VGG");
382        check("ModifiedContent<AnyView, PrimitiveButtonStyleContainerModifier<BorderedButtonStyle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("PrimitiveButtonStyleContainerModifier").of(vec![mangle::ty("BorderedButtonStyle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA37PrimitiveButtonStyleContainerModifierVyAA08BorderedhI0VGG");
383        check("ModifiedContent<ModifiedContent<AnyView, _ValueActionModifier2<Int>>, _AppearanceActionModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ValueActionModifier2").of(vec![mangle::Type::Verbatim("Si".into())])]), mangle::ty("_AppearanceActionModifier")]), "7SwiftUI15ModifiedContentVyACyAA7AnyViewVAA21_ValueActionModifier2VySiGGAA011_AppearanceH8ModifierVG");
384        check("ModifiedContent<ModifiedContent<AnyView, TransactionalPreferenceTransformModifier<NavigationTitleKey>>, _PreferenceTransformModifier<ToolbarKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("TransactionalPreferenceTransformModifier").of(vec![mangle::ty("NavigationTitleKey")])]), mangle::ty("_PreferenceTransformModifier").of(vec![mangle::ty("ToolbarKey")])]), "7SwiftUI15ModifiedContentVyACyAA7AnyViewVAA40TransactionalPreferenceTransformModifierVyAA18NavigationTitleKeyVGGAA01_hiJ0VyAA07ToolbarM0VGG");
385        check("NavigationStack<NavigationPath, AnyView>", &mangle::ty("NavigationStack").of(vec![mangle::ty("NavigationPath"), mangle::ty("AnyView")]), "7SwiftUI15NavigationStackVyAA0C4PathVAA7AnyViewVG");
386        check("_ConditionalContent<AnyView, AnyView>", &mangle::ty("_ConditionalContent").of(vec![mangle::ty("AnyView"), mangle::ty("AnyView")]), "7SwiftUI19_ConditionalContentVyAA7AnyViewVAEG");
387        check("TupleToolbarContent<ToolbarItem<(), AnyView>>", &mangle::ty("TupleToolbarContent").of(vec![mangle::ty("ToolbarItem").of(vec![mangle::Type::Void, mangle::ty("AnyView")])]), "7SwiftUI19TupleToolbarContentVyAA0D4ItemVyytAA7AnyViewVGG");
388        check("Form<AnyView>", &mangle::ty("Form").of(vec![mangle::ty("AnyView")]), "7SwiftUI4FormVyAA7AnyViewVG");
389        check("Grid<AnyView>", &mangle::ty("Grid").of(vec![mangle::ty("AnyView")]), "7SwiftUI4GridVyAA7AnyViewVG");
390        check("List<Never, AnyView>", &mangle::ty("List").of(vec![mangle::Type::Verbatim("s5NeverO".into()), mangle::ty("AnyView")]), "7SwiftUI4ListVys5NeverOAA7AnyViewVG");
391        check("Menu<Text, AnyView>", &mangle::ty("Menu").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI4MenuVyAA4TextVAA7AnyViewVG");
392        check("Gauge<AnyView, EmptyView, EmptyView, EmptyView>", &mangle::ty("Gauge").of(vec![mangle::ty("AnyView"), mangle::ty("EmptyView"), mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI5GaugeVyAA7AnyViewVAA05EmptyE0VA2GG");
393        check("Label<Text, Image>", &mangle::ty("Label").of(vec![mangle::ty("Text"), mangle::ty("Image")]), "7SwiftUI5LabelVyAA4TextVAA5ImageVG");
394        check("Button<Text>", &mangle::ty("Button").of(vec![mangle::ty("Text")]), "7SwiftUI6ButtonVyAA4TextVG");
395        check("Slider<EmptyView, EmptyView>", &mangle::ty("Slider").of(vec![mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI6SliderVyAA9EmptyViewVAEG");
396        check("Toggle<Text>", &mangle::ty("Toggle").of(vec![mangle::ty("Text")]), "7SwiftUI6ToggleVyAA4TextVG");
397        check("Binding<TextSelection?>", &mangle::ty("Binding").of(vec![mangle::opt(mangle::ty("TextSelection"))]), "7SwiftUI7BindingVyAA13TextSelectionVSgG");
398        check("ForEach<Range<Int>, Int, AnyView>", &mangle::ty("ForEach").of(vec![mangle::Type::Verbatim("Sn".into()).of(vec![mangle::Type::Verbatim("Si".into())]), mangle::Type::Verbatim("Si".into()), mangle::ty("AnyView")]), "7SwiftUI7ForEachVySnySiGSiAA7AnyViewVG");
399        check("GridRow<AnyView>", &mangle::ty("GridRow").of(vec![mangle::ty("AnyView")]), "7SwiftUI7GridRowVyAA7AnyViewVG");
400        check("Stepper<Text>", &mangle::ty("Stepper").of(vec![mangle::ty("Text")]), "7SwiftUI7StepperVyAA4TextVG");
401        check("TabView<Int, AnyView>", &mangle::ty("TabView").of(vec![mangle::Type::Verbatim("Si".into()), mangle::ty("AnyView")]), "7SwiftUI7TabViewVySiAA03AnyD0VG");
402        check("GroupBox<EmptyView, AnyView>", &mangle::ty("GroupBox").of(vec![mangle::ty("EmptyView"), mangle::ty("AnyView")]), "7SwiftUI8GroupBoxVyAA9EmptyViewVAA03AnyF0VG");
403        check("TextField<Text>", &mangle::ty("TextField").of(vec![mangle::ty("Text")]), "7SwiftUI9TextFieldVyAA0C0VG");
404    }
405
406    /// A name the crate once got wrong by counting it by eye.
407    #[test]
408    fn counts_what_a_person_miscounted() {
409        assert_eq!("decimalDigits".len(), 13, "the bug was writing 14");
410        assert_eq!(
411            nominal(&["Foundation", "CharacterSet"], Kind::Struct),
412            "10Foundation12CharacterSetV"
413        );
414    }
415}
416
417// ── The compressor ─────────────────────────────────────────────────────────
418//
419// Swift does not write the same thing twice. Two separate schemes overlap:
420//
421// * **Word substitution.** An identifier is split into words at case
422//   boundaries, and any word already seen is written as a letter instead of
423//   spelled out. `AnimationTimelineSchedule` after `TimelineView` becomes
424//   `09AnimationC8Schedule` — "Animation", word #2 (`Timeline`), "Schedule".
425//   Lowercase continues a run of substitutions, uppercase ends one, and an
426//   identifier that *ends* on a substitution needs a trailing `0` so the kind
427//   character after it is not swallowed.
428//
429// * **Entity substitution.** Every identifier node and every nominal type node
430//   gets a slot, in the order they are mangled, and a repeat is written `A`
431//   plus a letter. So a type is always at an *odd* distance from its own
432//   identifier: in `ModifiedContent<AnyView, …>`, slot 3 is the identifier
433//   `AnyView` and slot 4 is the type. `A2G` is the third form — a count,
434//   meaning "substitution `G`, twice".
435//
436// None of that is guessable, and none of it is documented in a way that
437// survives contact with a real mangling. It was read off eighty-four strings
438// SwiftUI exports, which is what [`tests::compressor_reproduces_swiftui`]
439// checks it against.
440
441/// A Swift type, as a tree the compressor can walk.
442#[derive(Clone, Debug)]
443pub enum Type {
444    /// A nominal type: a module, the path to it, and what kind it is.
445    Nominal { module: &'static str, path: Vec<&'static str>, kind: Kind },
446    /// A generic type applied to arguments.
447    Bound(Box<Type>, Vec<Type>),
448    /// The empty tuple, `()`.
449    Void,
450    /// A type with a mangling of its own, such as `Si` or `s5NeverO`.
451    Verbatim(String),
452    /// `Optional<T>`, which Swift writes as `T` followed by `Sg` rather than
453    /// through the generic form.
454    Opt(Box<Type>),
455    /// A generic applied to a *parameter pack* — `Predicate<Pack{URL}>`. The
456    /// arguments are followed by `_QP` rather than closed directly.
457    Pack(Box<Type>, Vec<Type>),
458}
459
460/// A generic type applied to a parameter pack.
461pub fn pack(base: Type, args: Vec<Type>) -> Type {
462    Type::Pack(Box::new(base), args)
463}
464
465/// `T?`.
466pub fn opt(inner: Type) -> Type {
467    Type::Opt(Box::new(inner))
468}
469
470/// `SwiftUI.Name`, a struct.
471pub fn ty(name: &'static str) -> Type {
472    Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Struct }
473}
474
475/// `SwiftUI.Name`, an enum.
476pub fn ty_enum(name: &'static str) -> Type {
477    Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Enum }
478}
479
480/// `ModifiedContent<AnyView, M>` — what applying a modifier to an `AnyView`
481/// produces, and so the result type of most of this crate's bindings.
482pub fn modified(modifier: Type) -> Type {
483    ty("ModifiedContent").of(vec![ty("AnyView"), modifier])
484}
485
486/// `SwiftUI.Name`, a class.
487pub fn ty_class(name: &'static str) -> Type {
488    Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Class }
489}
490
491impl Type {
492    /// This type applied to generic arguments.
493    pub fn of(self, args: Vec<Type>) -> Type {
494        Type::Bound(Box::new(self), args)
495    }
496
497    /// Mangle it, with a fresh substitution state.
498    pub fn mangle(&self) -> String {
499        let mut m = Mangler::default();
500        let mut out = String::new();
501        m.write(self, &mut out);
502        out
503    }
504}
505
506/// The substitution state a single mangling accumulates.
507#[derive(Default)]
508struct Mangler {
509    words: Vec<String>,
510    /// One slot per identifier and per nominal type, in mangling order.
511    slots: Vec<String>,
512}
513
514/// Split an identifier into the words Swift substitutes on.
515fn split_words(ident: &str) -> Vec<String> {
516    let chars: Vec<char> = ident.chars().collect();
517    let mut out: Vec<String> = Vec::new();
518    let mut cur = String::new();
519    for (i, &c) in chars.iter().enumerate() {
520        // `_` is a boundary of its own: `_TraitWritingModifier` registers the
521        // word `Trait`, not `_Trait`, so a later `TransitionTraitKey` can reach
522        // it. The underscore still goes into the literal text.
523        if c == '_' {
524            if !cur.is_empty() {
525                out.push(std::mem::take(&mut cur));
526            }
527            out.push("_".to_string());
528            continue;
529        }
530        // A capital only starts a word when the character before it was not
531        // itself a capital. Swift does not break up an acronym even when a
532        // word follows it: `NSHostingView` is `NSHosting` and `View`, so the
533        // `AnyView` beside it substitutes the *second* word, not the third.
534        // Splitting it the way a person reads it shifts every later letter by
535        // one, and the result still demangles — to `AnyAny`.
536        let starts = if c.is_uppercase() {
537            let prev = if i > 0 { chars[i - 1] } else { '\0' };
538            prev.is_lowercase() || prev.is_ascii_digit()
539        } else {
540            false
541        };
542        if starts && !cur.is_empty() {
543            out.push(std::mem::take(&mut cur));
544        }
545        cur.push(c);
546    }
547    if !cur.is_empty() {
548        out.push(cur);
549    }
550    out
551}
552
553impl Mangler {
554    /// Mangle an identifier, substituting any word already seen.
555    fn identifier(&mut self, name: &str) -> String {
556        // `_` is not a word boundary and a one-letter word is not worth a slot.
557        let pieces = split_words(name);
558        let mut run: Vec<Result<usize, String>> = Vec::new();
559        let mut literal = String::new();
560        for w in &pieces {
561            let known = w.len() >= 2;
562            match self.words.iter().position(|k| k == w).filter(|_| known) {
563                Some(i) => {
564                    if !literal.is_empty() {
565                        run.push(Err(std::mem::take(&mut literal)));
566                    }
567                    run.push(Ok(i));
568                }
569                None => {
570                    literal.push_str(w);
571                    if known {
572                        self.words.push(w.clone());
573                    }
574                }
575            }
576        }
577        if !literal.is_empty() {
578            run.push(Err(literal));
579        }
580        if !run.iter().any(|p| p.is_ok()) {
581            return format!("{}{}", name.len(), name);
582        }
583        let mut out = String::from("0");
584        for (i, piece) in run.iter().enumerate() {
585            match piece {
586                Ok(idx) => {
587                    // Uppercase marks the *final* substitution in the
588                    // identifier — not the end of a run, and not the final
589                    // piece. `09AnimationC8Schedule` ends its only substitution
590                    // with `C` even though a literal follows; `0e4FontJ0` writes
591                    // the first of two as `e` even though a literal follows it
592                    // too. Only the last one is capitalised.
593                    let last = !run[i + 1..].iter().any(|p| p.is_ok());
594                    let base = if last { b'A' } else { b'a' };
595                    out.push((base + *idx as u8) as char);
596                }
597                Err(text) => out.push_str(&format!("{}{}", text.len(), text)),
598            }
599        }
600        if run.last().is_some_and(|p| p.is_ok()) {
601            // ended on a substitution: an empty literal keeps the next
602            // character from being read as part of it
603            out.push('0');
604        }
605        out
606    }
607
608    /// `A` plus a letter, if this entity has been mangled before.
609    fn substitute(&self, key: &str) -> Option<String> {
610        let i = self.slots.iter().position(|k| k == key)?;
611        (i < 26).then(|| format!("A{}", (b'A' + i as u8) as char))
612    }
613
614    fn write(&mut self, t: &Type, out: &mut String) {
615        match t {
616            Type::Void => out.push_str("yt"),
617            Type::Pack(base, args) => {
618                self.write(base, out);
619                out.push('y');
620                for a in args {
621                    self.write(a, out);
622                }
623                out.push_str("_QPG");
624            }
625            Type::Opt(inner) => {
626                self.write(inner, out);
627                out.push_str("Sg");
628            }
629            Type::Verbatim(s) => out.push_str(s),
630            Type::Bound(base, args) => {
631                self.write(base, out);
632                out.push('y');
633                // Adjacent identical substitutions collapse: `AGAG` is written
634                // `A2G`, a count rather than a repeat. Building each argument
635                // first is safe because a substituted one records no new slot.
636                let mut written: Vec<String> = Vec::with_capacity(args.len());
637                for a in args {
638                    let mut one = String::new();
639                    self.write(a, &mut one);
640                    written.push(one);
641                }
642                let mut i = 0;
643                while i < written.len() {
644                    let is_sub = written[i].len() == 2
645                        && written[i].starts_with('A')
646                        && written[i].as_bytes()[1].is_ascii_uppercase();
647                    let mut run = 1;
648                    if is_sub {
649                        while i + run < written.len() && written[i + run] == written[i] {
650                            run += 1;
651                        }
652                    }
653                    if run > 1 {
654                        out.push('A');
655                        out.push_str(&run.to_string());
656                        out.push(written[i].as_bytes()[1] as char);
657                    } else {
658                        out.push_str(&written[i]);
659                    }
660                    i += run;
661                }
662                out.push('G');
663            }
664            Type::Nominal { module, path, kind } => {
665                let key = format!("{module}.{}", path.join("."));
666                if let Some(s) = self.substitute(&key) {
667                    out.push_str(&s);
668                    return;
669                }
670                // the module: one slot, and only the first time
671                match self.substitute(module) {
672                    Some(s) => out.push_str(&s),
673                    None => {
674                        out.push_str(&self.identifier(module));
675                        self.slots.push((*module).to_string());
676                    }
677                }
678                for (i, seg) in path.iter().enumerate() {
679                    let id = self.identifier(seg);
680                    out.push_str(&id);
681                    self.slots.push(format!("{key}#ident{i}"));
682                    let k = if i + 1 == path.len() { *kind } else { Kind::Struct };
683                    out.push(k.suffix());
684                    // Every nominal *type* node takes a slot, and a nested path
685                    // walks through one per segment — `Circle._Inset` is two
686                    // types, `SwiftUI.Circle` and `SwiftUI.Circle._Inset`, not
687                    // one. Only the last used to be recorded, so every later
688                    // `A`-reference in a mangling containing a nested type came
689                    // out one short and the whole string stopped demangling.
690                    self.slots.push(format!("{module}.{}", path[..=i].join(".")));
691                }
692            }
693        }
694    }
695}