//! Swift type manglings, built rather than counted.
//!
//! A mangled type name is length-prefixed: `SwiftUI.ContentTransition` is
//! `7SwiftUI17ContentTransitionV`, where the `7` and the `17` are the lengths of
//! the two identifiers and the `V` says it is a struct. Writing those by hand is
//! arithmetic in a string literal, and getting one wrong does not fail to
//! compile — it resolves to nothing, or to the wrong type, at run time. This
//! crate shipped `14decimalDigits` for a name thirteen characters long, and
//! found out through a panic in an unrelated modifier.
//!
//! So the lengths are counted by the compiler's `len()` instead of by eye:
//!
//! ```ignore
//! swift_type!(SwiftUI.ContentTransition) // 7SwiftUI17ContentTransitionV
//! swift_type!(enum SwiftUI.Font.Design) // 7SwiftUI4FontV6DesignO
//! swift_type!(opt SwiftUI.Color) // 7SwiftUI5ColorVSg
//! swift_type!([SwiftUI.EdgeInsets]) // Say7SwiftUI10EdgeInsetsVG
//! ```
//!
//! Symbols — functions, getters, witness tables — are *not* covered. Their
//! mangling encodes argument types, substitutions and calling conventions, and
//! is read off the export table rather than constructed; see
//! [`crate::resolve`].
/// What kind of nominal type is being named.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
/// A struct — the `V` suffix.
Struct,
/// An enum — the `O` suffix.
Enum,
/// A class — the `C` suffix.
Class,
}
impl Kind {
const fn suffix(self) -> char {
match self {
Kind::Struct => 'V',
Kind::Enum => 'O',
Kind::Class => 'C',
}
}
}
/// A nominal type, named by its module and the path to it.
///
/// `nominal(&["SwiftUI", "Font", "Design"], Kind::Enum)` is
/// `7SwiftUI4FontV6DesignO` — note that the *outer* type keeps its own `V`, so a
/// nested type carries the kind of everything containing it.
pub fn nominal(path: &[&str], kind: Kind) -> String {
let mut out = String::new();
for (i, seg) in path.iter().enumerate() {
out.push_str(&seg.len().to_string());
out.push_str(seg);
// every segment but the last is a containing type, and a module has no
// suffix at all
if i > 0 && i + 1 < path.len() {
out.push(Kind::Struct.suffix());
}
}
out.push(kind.suffix());
out
}
/// A type imported from Objective-C or C.
///
/// These carry no module: `So` stands in for "the Clang importer", so
/// `NSItemProvider` is `So14NSItemProviderC` and `CGRect` is `So6CGRectV`,
/// however many frameworks away they actually live.
pub fn imported(name: &str, kind: Kind) -> String {
format!("So{}{}{}", name.len(), name, kind.suffix())
}
/// A SwiftUI type written where the module is already in the substitution table.
///
/// Swift does not repeat itself: once `7SwiftUI` has been mangled, every later
/// type from that module is `AA` followed by its own name. So the second
/// SwiftUI type in a mangling looks nothing like the first —
/// `7SwiftUI15ModifiedContentV` then `AA7AnyViewV`.
pub fn again(name: &str, kind: Kind) -> String {
format!("AA{}{}{}", name.len(), name, kind.suffix())
}
/// `ModifiedContent<AnyView, M>` — the result type of most modifiers.
///
/// `modifier` is already in substituted form, because it is the third SwiftUI
/// type in the name; [`again`] builds it.
pub fn modified_content(modifier: &str) -> String {
format!(
"{}y{}{modifier}G",
nominal(&["SwiftUI", "ModifiedContent"], Kind::Struct),
again("AnyView", Kind::Struct),
)
}
/// A standard-library type with no shorthand, such as `Result`.
///
/// The module is `s`, which is a shorthand of its own and carries no length.
pub fn stdlib(name: &str, kind: Kind) -> String {
format!("s{}{}{}", name.len(), name, kind.suffix())
}
/// A protocol existential — `any Error` is `s5Error_p`.
pub fn existential(module_and_name: &str) -> String {
format!("{module_and_name}_p")
}
/// `Range<T>`.
pub fn range(inner: &str) -> String {
format!("Sny{inner}G")
}
/// A type nested inside one of the shorthand types, such as `String.Index`.
///
/// The outer type is already a shorthand — `SS` — so only the inner name is
/// length-prefixed.
pub fn nested(shorthand: &str, name: &str, kind: Kind) -> String {
format!("{shorthand}{}{}{}", name.len(), name, kind.suffix())
}
/// `Optional<T>`, which is `T` with `Sg` after it.
pub fn optional(inner: &str) -> String {
format!("{inner}Sg")
}
/// `Array<T>`.
pub fn array(inner: &str) -> String {
format!("Say{inner}G")
}
/// `Set<T>`, which is `Sh` applied to one argument.
pub fn set(inner: &str) -> String {
format!("Shy{inner}G")
}
/// A generic type applied to arguments — `Binding<Bool>` is
/// `bound(BINDING, &[BOOL])`.
pub fn bound(generic: &str, args: &[&str]) -> String {
format!("{generic}y{}G", args.concat())
}
/// `Swift.String`.
pub const STRING: &str = "SS";
/// `Swift.Int`.
pub const INT: &str = "Si";
/// `Swift.Bool`.
pub const BOOL: &str = "Sb";
/// `Swift.Double`.
pub const DOUBLE: &str = "Sd";
/// `Foundation.Date` — one `Double` of seconds since the reference date.
pub const DATE: &str = "10Foundation4DateV";
/// `Swift.Never`.
pub const NEVER: &str = "s5NeverO";
/// Turn a mangled name back into something readable, for diagnostics.
pub fn demangle(mangled: &str) -> String {
// The runtime's demangler wants the `$s` a bare type mangling omits.
let full = if mangled.starts_with("$s") { mangled.to_string() } else { format!("$s{mangled}") };
match std::ffi::CString::new(full.clone()) {
Ok(c) => swift_runtime::demangle::demangle(&c).unwrap_or(full),
Err(_) => full,
}
}
/// Build a mangled Swift type name.
///
/// See the [module documentation](self) for the forms.
macro_rules! swift_type {
(opt $($rest:tt)+) => { $crate::mangle::optional(&swift_type!($($rest)+)) };
([$($rest:tt)+]) => { $crate::mangle::array(&swift_type!($($rest)+)) };
(again $name:ident) => { $crate::mangle::again(stringify!($name), $crate::mangle::Kind::Struct) };
(again enum $name:ident) => { $crate::mangle::again(stringify!($name), $crate::mangle::Kind::Enum) };
(stdlib enum $name:ident) => { $crate::mangle::stdlib(stringify!($name), $crate::mangle::Kind::Enum) };
(objc $name:ident) => { $crate::mangle::imported(stringify!($name), $crate::mangle::Kind::Class) };
(objc struct $name:ident) => { $crate::mangle::imported(stringify!($name), $crate::mangle::Kind::Struct) };
(enum $($seg:ident).+) => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Enum) };
(class $($seg:ident).+) => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Class) };
($($seg:ident).+) => { $crate::mangle::nominal(&[$(stringify!($seg)),+], $crate::mangle::Kind::Struct) };
}
pub(crate) use swift_type;
/// Declare the mangled result types of a group of modifiers.
///
/// Each is built by the compressor at first use rather than written out, so the
/// length prefixes and substitutions are the machine's arithmetic rather than
/// anyone's:
///
/// ```ignore
/// result_types! {
/// /// `.blur(radius:)`
/// &BLUR_T = modified(ty("_BlurEffect"));
/// /// `.clipShape(RoundedRectangle(...))`
/// CLIP_ROUNDED_T = modified(ty("_ClipEffect").of(vec![ty("RoundedRectangle")]));
/// }
/// ```
macro_rules! result_types {
($(
$(#[$meta:meta])*
$vis:vis $name:ident = $build:expr;
)*) => {$(
$(#[$meta])*
$vis static $name: $crate::views::types::CachedType =
$crate::views::types::CachedType::new(std::sync::LazyLock::new(|| {
#[allow(unused_imports)]
use $crate::mangle::{modified, opt, ty, ty_class, ty_enum, Kind, Type};
$build.mangle().into_bytes()
}));
)*};
}
pub(crate) use result_types;
/// Declare the result type of a *modifier*, by naming the modifier alone.
///
/// `result_types!` names a whole concrete type. A modifier cannot: its result is
/// `ModifiedContent<Self, Modifier>`, and `Self` is settled at the call, not
/// here. So these entries carry the modifier and compose the rest against
/// whatever they are applied to — see [`crate::views::types::ModifierType`].
macro_rules! modifier_types {
($(
$(#[$meta:meta])*
$vis:vis $name:ident = $build:expr;
)*) => {$(
$(#[$meta])*
$vis static $name: $crate::views::types::ModifierType =
$crate::views::types::ModifierType::new(std::sync::LazyLock::new(|| {
#[allow(unused_imports)]
use $crate::mangle::{modified, opt, ty, ty_class, ty_enum, Kind, Type};
$build.mangle().into_bytes()
}));
)*};
}
pub(crate) use modifier_types;
#[cfg(test)]
mod tests {
use super::*;
/// Every mangling this crate writes by hand, rebuilt by counting.
///
/// The point is not that the macro works — it is that these strings are
/// what SwiftUI actually exports, so a mismatch means one of the two is
/// wrong and the test says which.
#[test]
fn matches_the_hand_written_manglings() {
assert_eq!(swift_type!(SwiftUI.EmptyVisualEffect), "7SwiftUI17EmptyVisualEffectV");
assert_eq!(swift_type!(SwiftUI.ContentTransition), "7SwiftUI17ContentTransitionV");
assert_eq!(swift_type!(SwiftUI.EnvironmentValues), "7SwiftUI17EnvironmentValuesV");
assert_eq!(swift_type!(SwiftUI.UnitPoint), "7SwiftUI9UnitPointV");
assert_eq!(swift_type!(SwiftUI.EdgeInsets), "7SwiftUI10EdgeInsetsV");
assert_eq!(swift_type!(SwiftUI.TextSelection), "7SwiftUI13TextSelectionV");
assert_eq!(swift_type!(SwiftUI.LinearGradient), "7SwiftUI14LinearGradientV");
assert_eq!(swift_type!(SwiftUI.LocalizedStringKey), "7SwiftUI18LocalizedStringKeyV");
assert_eq!(swift_type!(SwiftUI.PinnedScrollableViews), "7SwiftUI21PinnedScrollableViewsV");
assert_eq!(swift_type!(Foundation.URL), "10Foundation3URLV");
assert_eq!(swift_type!(UniformTypeIdentifiers.UTType), "22UniformTypeIdentifiers6UTTypeV");
assert_eq!(swift_type!(objc NSItemProvider), "So14NSItemProviderC");
assert_eq!(swift_type!(objc struct CGRect), "So6CGRectV");
assert_eq!(swift_type!(enum SwiftUI.Axis), "7SwiftUI4AxisO");
assert_eq!(swift_type!(enum SwiftUI.Font.Design), "7SwiftUI4FontV6DesignO");
assert_eq!(swift_type!(SwiftUI.Namespace.ID), "7SwiftUI9NamespaceV2IDV");
assert_eq!(swift_type!(opt SwiftUI.Color), "7SwiftUI5ColorVSg");
assert_eq!(swift_type!(opt enum SwiftUI.Axis), "7SwiftUI4AxisOSg");
assert_eq!(swift_type!(opt SwiftUI.Namespace.ID), "7SwiftUI9NamespaceV2IDVSg");
assert_eq!(swift_type!(opt enum SwiftUI.Font.Design), "7SwiftUI4FontV6DesignOSg");
assert_eq!(swift_type!([SwiftUI.EdgeInsets]), "Say7SwiftUI10EdgeInsetsVG");
assert_eq!(array(STRING), "SaySSG");
assert_eq!(array(INT), "SaySiG");
assert_eq!(optional(STRING), "SSSg");
assert_eq!(optional(BOOL), "SbSg");
assert_eq!(optional(DOUBLE), "SdSg");
assert_eq!(bound("7SwiftUI7BindingV", &[BOOL]), "7SwiftUI7BindingVySbG");
assert_eq!(bound("7SwiftUI7BindingV", &[INT]), "7SwiftUI7BindingVySiG");
assert_eq!(bound("7Combine5EmptyV", &[INT, NEVER]), "7Combine5EmptyVySis5NeverOG");
// substituted forms
assert_eq!(swift_type!(again AnyView), "AA7AnyViewV");
assert_eq!(swift_type!(again _BlurEffect), "AA11_BlurEffectV");
assert_eq!(
modified_content(&swift_type!(again _BlurEffect)),
"7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_BlurEffectVG"
);
assert_eq!(
modified_content(&bound(&swift_type!(again _ClipEffect), &[&swift_type!(again RoundedRectangle)])),
"7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA16RoundedRectangleVGG"
);
// standard library corners
assert_eq!(range(&nested(STRING, "Index", Kind::Struct)), "SnySS5IndexVG");
assert_eq!(
bound(&swift_type!(stdlib enum Result), &[&swift_type!(Foundation.URL), &existential("s5Error")]),
"s6ResultOy10Foundation3URLVs5Error_pG"
);
}
/// The compressor, against the strings SwiftUI actually exports.
///
/// Each of these was copied out of the framework's export table; the
/// builder has to reproduce it character for character, including every
/// word substitution and every `A`-indexed back-reference. If Swift ever
/// changes the scheme, this is where it shows.
///
/// These are the only mangled type names left written out in the crate —
/// production builds every one of them — so this is also the last place a
/// hand-counted length prefix could hide.
#[test]
fn compressor_reproduces_swiftui() {
use crate::mangle;
fn check(readable: &str, built: &mangle::Type, expected: &str) {
assert_eq!(built.mangle(), expected, "building {readable}");
}
// 77 types SwiftUI exports, built rather than copied
check("SecureField<Text>", &mangle::ty("SecureField").of(vec![mangle::ty("Text")]), "7SwiftUI11SecureFieldVyAA4TextVG");
check("ToolbarItem<(), AnyView>", &mangle::ty("ToolbarItem").of(vec![mangle::Type::Void, mangle::ty("AnyView")]), "7SwiftUI11ToolbarItemVyytAA7AnyViewVG");
check("ProgressView<EmptyView, EmptyView>", &mangle::ty("ProgressView").of(vec![mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI12ProgressViewVyAA05EmptyD0VAEG");
check("TimelineView<AnimationTimelineSchedule, AnyView>", &mangle::ty("TimelineView").of(vec![mangle::ty("AnimationTimelineSchedule"), mangle::ty("AnyView")]), "7SwiftUI12TimelineViewVyAA09AnimationC8ScheduleVAA03AnyD0VG");
check("GeometryReader<AnyView>", &mangle::ty("GeometryReader").of(vec![mangle::ty("AnyView")]), "7SwiftUI14GeometryReaderVyAA7AnyViewVG");
check("NavigationLink<Text, AnyView>", &mangle::ty("NavigationLink").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI14NavigationLinkVyAA4TextVAA7AnyViewVG");
check("DisclosureGroup<Text, AnyView>", &mangle::ty("DisclosureGroup").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI15DisclosureGroupVyAA4TextVAA7AnyViewVG");
check("ModifiedContent<AnyView, _BlurEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BlurEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_BlurEffectVG");
check("ModifiedContent<AnyView, _ClipEffect<RoundedRectangle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("RoundedRectangle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA16RoundedRectangleVGG");
check("ModifiedContent<AnyView, _ClipEffect<Circle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Circle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA6CircleVGG");
check("ModifiedContent<AnyView, _ClipEffect<Capsule>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Capsule")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA7CapsuleVGG");
check("ModifiedContent<AnyView, _ClipEffect<Ellipse>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Ellipse")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA7EllipseVGG");
check("ModifiedContent<AnyView, _ClipEffect<Rectangle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ClipEffect").of(vec![mangle::ty("Rectangle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA11_ClipEffectVyAA9RectangleVGG");
check("ModifiedContent<AnyView, _FrameLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FrameLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA12_FrameLayoutVG");
check("ModifiedContent<AnyView, _ScaleEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ScaleEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA12_ScaleEffectVG");
check("ModifiedContent<AnyView, _OffsetEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OffsetEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA13_OffsetEffectVG");
check("ModifiedContent<AnyView, _ShadowEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ShadowEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA13_ShadowEffectVG");
check("ModifiedContent<AnyView, _OpacityEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OpacityEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA14_OpacityEffectVG");
check("ModifiedContent<AnyView, _PaddingLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_PaddingLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA14_PaddingLayoutVG");
check("ModifiedContent<AnyView, _ContrastEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ContrastEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_ContrastEffectVG");
check("ModifiedContent<AnyView, _HiddenModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_HiddenModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_HiddenModifierVG");
check("ModifiedContent<AnyView, _RotationEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_RotationEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA15_RotationEffectVG");
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");
check("ModifiedContent<AnyView, _BlendModeEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BlendModeEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_BlendModeEffectVG");
check("ModifiedContent<AnyView, _FixedSizeLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FixedSizeLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_FixedSizeLayoutVG");
check("ModifiedContent<AnyView, _GrayscaleEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_GrayscaleEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_GrayscaleEffectVG");
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");
check("ModifiedContent<AnyView, _OverlayModifier<AnyView>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_OverlayModifier").of(vec![mangle::ty("AnyView")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA16_OverlayModifierVyAEGG");
check("ModifiedContent<AnyView, _BrightnessEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BrightnessEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_BrightnessEffectVG");
check("ModifiedContent<AnyView, _Rotation3DEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_Rotation3DEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_Rotation3DEffectVG");
check("ModifiedContent<AnyView, _SaturationEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_SaturationEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA17_SaturationEffectVG");
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");
check("ModifiedContent<AnyView, _AspectRatioLayout>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_AspectRatioLayout")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18_AspectRatioLayoutVG");
check("ModifiedContent<AnyView, _FocusableModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_FocusableModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA18_FocusableModifierVG");
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");
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");
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");
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");
check("ModifiedContent<AnyView, _DrawingGroupEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_DrawingGroupEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA19_DrawingGroupEffectVG");
check("ModifiedContent<AnyView, _ShaderFilterEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ShaderFilterEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA19_ShaderFilterEffectVG");
check("ModifiedContent<AnyView, _MaskAlignmentEffect<AnyView>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_MaskAlignmentEffect").of(vec![mangle::ty("AnyView")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA20_MaskAlignmentEffectVyAEGG");
check("ModifiedContent<AnyView, _TraitWritingModifier<TransitionTraitKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_TraitWritingModifier").of(vec![mangle::ty("TransitionTraitKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA21_TraitWritingModifierVyAA010TransitionG3KeyVGG");
check("ModifiedContent<AnyView, _TraitWritingModifier<ZIndexTraitKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_TraitWritingModifier").of(vec![mangle::ty("ZIndexTraitKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA21_TraitWritingModifierVyAA06ZIndexG3KeyVGG");
check("ModifiedContent<AnyView, _CompositingGroupEffect>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_CompositingGroupEffect")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA23_CompositingGroupEffectVG");
check("ModifiedContent<AnyView, _BackgroundStyleModifier<LinearGradient>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("LinearGradient")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA14LinearGradientVGG");
check("ModifiedContent<AnyView, _BackgroundStyleModifier<RadialGradient>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("RadialGradient")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA14RadialGradientVGG");
check("ModifiedContent<AnyView, _BackgroundStyleModifier<Color>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("Color")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA5ColorVGG");
check("ModifiedContent<AnyView, _BackgroundStyleModifier<Material>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_BackgroundStyleModifier").of(vec![mangle::ty("Material")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_BackgroundStyleModifierVyAA8MaterialVGG");
check("ModifiedContent<AnyView, _ForegroundStyleModifier<Color>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_ForegroundStyleModifier").of(vec![mangle::ty("Color")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA24_ForegroundStyleModifierVyAA5ColorVGG");
check("ModifiedContent<AnyView, _AppearanceActionModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_AppearanceActionModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA25_AppearanceActionModifierVG");
check("ModifiedContent<AnyView, _PreferenceWritingModifier<PreferredColorSchemeKey>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_PreferenceWritingModifier").of(vec![mangle::ty("PreferredColorSchemeKey")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA26_PreferenceWritingModifierVyAA23PreferredColorSchemeKeyVGG");
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");
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");
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");
check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<ControlSize>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::ty_enum("ControlSize")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA11ControlSizeOGG");
check("ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<TextAlignment>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("_EnvironmentKeyWritingModifier").of(vec![mangle::ty_enum("TextAlignment")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA30_EnvironmentKeyWritingModifierVyAA13TextAlignmentOGG");
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");
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");
check("ModifiedContent<AnyView, AccessibilityAttachmentModifier>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("AccessibilityAttachmentModifier")]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA31AccessibilityAttachmentModifierVG");
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");
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");
check("ModifiedContent<AnyView, PrimitiveButtonStyleContainerModifier<GlassButtonStyle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("PrimitiveButtonStyleContainerModifier").of(vec![mangle::ty("GlassButtonStyle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA37PrimitiveButtonStyleContainerModifierVyAA05GlasshI0VGG");
check("ModifiedContent<AnyView, PrimitiveButtonStyleContainerModifier<BorderedButtonStyle>>", &mangle::ty("ModifiedContent").of(vec![mangle::ty("AnyView"), mangle::ty("PrimitiveButtonStyleContainerModifier").of(vec![mangle::ty("BorderedButtonStyle")])]), "7SwiftUI15ModifiedContentVyAA7AnyViewVAA37PrimitiveButtonStyleContainerModifierVyAA08BorderedhI0VGG");
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");
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");
check("NavigationStack<NavigationPath, AnyView>", &mangle::ty("NavigationStack").of(vec![mangle::ty("NavigationPath"), mangle::ty("AnyView")]), "7SwiftUI15NavigationStackVyAA0C4PathVAA7AnyViewVG");
check("_ConditionalContent<AnyView, AnyView>", &mangle::ty("_ConditionalContent").of(vec![mangle::ty("AnyView"), mangle::ty("AnyView")]), "7SwiftUI19_ConditionalContentVyAA7AnyViewVAEG");
check("TupleToolbarContent<ToolbarItem<(), AnyView>>", &mangle::ty("TupleToolbarContent").of(vec![mangle::ty("ToolbarItem").of(vec![mangle::Type::Void, mangle::ty("AnyView")])]), "7SwiftUI19TupleToolbarContentVyAA0D4ItemVyytAA7AnyViewVGG");
check("Form<AnyView>", &mangle::ty("Form").of(vec![mangle::ty("AnyView")]), "7SwiftUI4FormVyAA7AnyViewVG");
check("Grid<AnyView>", &mangle::ty("Grid").of(vec![mangle::ty("AnyView")]), "7SwiftUI4GridVyAA7AnyViewVG");
check("List<Never, AnyView>", &mangle::ty("List").of(vec![mangle::Type::Verbatim("s5NeverO".into()), mangle::ty("AnyView")]), "7SwiftUI4ListVys5NeverOAA7AnyViewVG");
check("Menu<Text, AnyView>", &mangle::ty("Menu").of(vec![mangle::ty("Text"), mangle::ty("AnyView")]), "7SwiftUI4MenuVyAA4TextVAA7AnyViewVG");
check("Gauge<AnyView, EmptyView, EmptyView, EmptyView>", &mangle::ty("Gauge").of(vec![mangle::ty("AnyView"), mangle::ty("EmptyView"), mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI5GaugeVyAA7AnyViewVAA05EmptyE0VA2GG");
check("Label<Text, Image>", &mangle::ty("Label").of(vec![mangle::ty("Text"), mangle::ty("Image")]), "7SwiftUI5LabelVyAA4TextVAA5ImageVG");
check("Button<Text>", &mangle::ty("Button").of(vec![mangle::ty("Text")]), "7SwiftUI6ButtonVyAA4TextVG");
check("Slider<EmptyView, EmptyView>", &mangle::ty("Slider").of(vec![mangle::ty("EmptyView"), mangle::ty("EmptyView")]), "7SwiftUI6SliderVyAA9EmptyViewVAEG");
check("Toggle<Text>", &mangle::ty("Toggle").of(vec![mangle::ty("Text")]), "7SwiftUI6ToggleVyAA4TextVG");
check("Binding<TextSelection?>", &mangle::ty("Binding").of(vec![mangle::opt(mangle::ty("TextSelection"))]), "7SwiftUI7BindingVyAA13TextSelectionVSgG");
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");
check("GridRow<AnyView>", &mangle::ty("GridRow").of(vec![mangle::ty("AnyView")]), "7SwiftUI7GridRowVyAA7AnyViewVG");
check("Stepper<Text>", &mangle::ty("Stepper").of(vec![mangle::ty("Text")]), "7SwiftUI7StepperVyAA4TextVG");
check("TabView<Int, AnyView>", &mangle::ty("TabView").of(vec![mangle::Type::Verbatim("Si".into()), mangle::ty("AnyView")]), "7SwiftUI7TabViewVySiAA03AnyD0VG");
check("GroupBox<EmptyView, AnyView>", &mangle::ty("GroupBox").of(vec![mangle::ty("EmptyView"), mangle::ty("AnyView")]), "7SwiftUI8GroupBoxVyAA9EmptyViewVAA03AnyF0VG");
check("TextField<Text>", &mangle::ty("TextField").of(vec![mangle::ty("Text")]), "7SwiftUI9TextFieldVyAA0C0VG");
}
/// A name the crate once got wrong by counting it by eye.
#[test]
fn counts_what_a_person_miscounted() {
assert_eq!("decimalDigits".len(), 13, "the bug was writing 14");
assert_eq!(
nominal(&["Foundation", "CharacterSet"], Kind::Struct),
"10Foundation12CharacterSetV"
);
}
}
// ── The compressor ─────────────────────────────────────────────────────────
//
// Swift does not write the same thing twice. Two separate schemes overlap:
//
// * **Word substitution.** An identifier is split into words at case
// boundaries, and any word already seen is written as a letter instead of
// spelled out. `AnimationTimelineSchedule` after `TimelineView` becomes
// `09AnimationC8Schedule` — "Animation", word #2 (`Timeline`), "Schedule".
// Lowercase continues a run of substitutions, uppercase ends one, and an
// identifier that *ends* on a substitution needs a trailing `0` so the kind
// character after it is not swallowed.
//
// * **Entity substitution.** Every identifier node and every nominal type node
// gets a slot, in the order they are mangled, and a repeat is written `A`
// plus a letter. So a type is always at an *odd* distance from its own
// identifier: in `ModifiedContent<AnyView, …>`, slot 3 is the identifier
// `AnyView` and slot 4 is the type. `A2G` is the third form — a count,
// meaning "substitution `G`, twice".
//
// None of that is guessable, and none of it is documented in a way that
// survives contact with a real mangling. It was read off eighty-four strings
// SwiftUI exports, which is what [`tests::compressor_reproduces_swiftui`]
// checks it against.
/// A Swift type, as a tree the compressor can walk.
#[derive(Clone, Debug)]
pub enum Type {
/// A nominal type: a module, the path to it, and what kind it is.
Nominal { module: &'static str, path: Vec<&'static str>, kind: Kind },
/// A generic type applied to arguments.
Bound(Box<Type>, Vec<Type>),
/// The empty tuple, `()`.
Void,
/// A type with a mangling of its own, such as `Si` or `s5NeverO`.
Verbatim(String),
/// `Optional<T>`, which Swift writes as `T` followed by `Sg` rather than
/// through the generic form.
Opt(Box<Type>),
/// A generic applied to a *parameter pack* — `Predicate<Pack{URL}>`. The
/// arguments are followed by `_QP` rather than closed directly.
Pack(Box<Type>, Vec<Type>),
}
/// A generic type applied to a parameter pack.
pub fn pack(base: Type, args: Vec<Type>) -> Type {
Type::Pack(Box::new(base), args)
}
/// `T?`.
pub fn opt(inner: Type) -> Type {
Type::Opt(Box::new(inner))
}
/// `SwiftUI.Name`, a struct.
pub fn ty(name: &'static str) -> Type {
Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Struct }
}
/// `SwiftUI.Name`, an enum.
pub fn ty_enum(name: &'static str) -> Type {
Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Enum }
}
/// `ModifiedContent<AnyView, M>` — what applying a modifier to an `AnyView`
/// produces, and so the result type of most of this crate's bindings.
pub fn modified(modifier: Type) -> Type {
ty("ModifiedContent").of(vec![ty("AnyView"), modifier])
}
/// `SwiftUI.Name`, a class.
pub fn ty_class(name: &'static str) -> Type {
Type::Nominal { module: "SwiftUI", path: vec![name], kind: Kind::Class }
}
impl Type {
/// This type applied to generic arguments.
pub fn of(self, args: Vec<Type>) -> Type {
Type::Bound(Box::new(self), args)
}
/// Mangle it, with a fresh substitution state.
pub fn mangle(&self) -> String {
let mut m = Mangler::default();
let mut out = String::new();
m.write(self, &mut out);
out
}
}
/// The substitution state a single mangling accumulates.
#[derive(Default)]
struct Mangler {
words: Vec<String>,
/// One slot per identifier and per nominal type, in mangling order.
slots: Vec<String>,
}
/// Split an identifier into the words Swift substitutes on.
fn split_words(ident: &str) -> Vec<String> {
let chars: Vec<char> = ident.chars().collect();
let mut out: Vec<String> = Vec::new();
let mut cur = String::new();
for (i, &c) in chars.iter().enumerate() {
// `_` is a boundary of its own: `_TraitWritingModifier` registers the
// word `Trait`, not `_Trait`, so a later `TransitionTraitKey` can reach
// it. The underscore still goes into the literal text.
if c == '_' {
if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
out.push("_".to_string());
continue;
}
// A capital only starts a word when the character before it was not
// itself a capital. Swift does not break up an acronym even when a
// word follows it: `NSHostingView` is `NSHosting` and `View`, so the
// `AnyView` beside it substitutes the *second* word, not the third.
// Splitting it the way a person reads it shifts every later letter by
// one, and the result still demangles — to `AnyAny`.
let starts = if c.is_uppercase() {
let prev = if i > 0 { chars[i - 1] } else { '\0' };
prev.is_lowercase() || prev.is_ascii_digit()
} else {
false
};
if starts && !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
cur.push(c);
}
if !cur.is_empty() {
out.push(cur);
}
out
}
impl Mangler {
/// Mangle an identifier, substituting any word already seen.
fn identifier(&mut self, name: &str) -> String {
// `_` is not a word boundary and a one-letter word is not worth a slot.
let pieces = split_words(name);
let mut run: Vec<Result<usize, String>> = Vec::new();
let mut literal = String::new();
for w in &pieces {
let known = w.len() >= 2;
match self.words.iter().position(|k| k == w).filter(|_| known) {
Some(i) => {
if !literal.is_empty() {
run.push(Err(std::mem::take(&mut literal)));
}
run.push(Ok(i));
}
None => {
literal.push_str(w);
if known {
self.words.push(w.clone());
}
}
}
}
if !literal.is_empty() {
run.push(Err(literal));
}
if !run.iter().any(|p| p.is_ok()) {
return format!("{}{}", name.len(), name);
}
let mut out = String::from("0");
for (i, piece) in run.iter().enumerate() {
match piece {
Ok(idx) => {
// Uppercase marks the *final* substitution in the
// identifier — not the end of a run, and not the final
// piece. `09AnimationC8Schedule` ends its only substitution
// with `C` even though a literal follows; `0e4FontJ0` writes
// the first of two as `e` even though a literal follows it
// too. Only the last one is capitalised.
let last = !run[i + 1..].iter().any(|p| p.is_ok());
let base = if last { b'A' } else { b'a' };
out.push((base + *idx as u8) as char);
}
Err(text) => out.push_str(&format!("{}{}", text.len(), text)),
}
}
if run.last().is_some_and(|p| p.is_ok()) {
// ended on a substitution: an empty literal keeps the next
// character from being read as part of it
out.push('0');
}
out
}
/// `A` plus a letter, if this entity has been mangled before.
fn substitute(&self, key: &str) -> Option<String> {
let i = self.slots.iter().position(|k| k == key)?;
(i < 26).then(|| format!("A{}", (b'A' + i as u8) as char))
}
fn write(&mut self, t: &Type, out: &mut String) {
match t {
Type::Void => out.push_str("yt"),
Type::Pack(base, args) => {
self.write(base, out);
out.push('y');
for a in args {
self.write(a, out);
}
out.push_str("_QPG");
}
Type::Opt(inner) => {
self.write(inner, out);
out.push_str("Sg");
}
Type::Verbatim(s) => out.push_str(s),
Type::Bound(base, args) => {
self.write(base, out);
out.push('y');
// Adjacent identical substitutions collapse: `AGAG` is written
// `A2G`, a count rather than a repeat. Building each argument
// first is safe because a substituted one records no new slot.
let mut written: Vec<String> = Vec::with_capacity(args.len());
for a in args {
let mut one = String::new();
self.write(a, &mut one);
written.push(one);
}
let mut i = 0;
while i < written.len() {
let is_sub = written[i].len() == 2
&& written[i].starts_with('A')
&& written[i].as_bytes()[1].is_ascii_uppercase();
let mut run = 1;
if is_sub {
while i + run < written.len() && written[i + run] == written[i] {
run += 1;
}
}
if run > 1 {
out.push('A');
out.push_str(&run.to_string());
out.push(written[i].as_bytes()[1] as char);
} else {
out.push_str(&written[i]);
}
i += run;
}
out.push('G');
}
Type::Nominal { module, path, kind } => {
let key = format!("{module}.{}", path.join("."));
if let Some(s) = self.substitute(&key) {
out.push_str(&s);
return;
}
// the module: one slot, and only the first time
match self.substitute(module) {
Some(s) => out.push_str(&s),
None => {
out.push_str(&self.identifier(module));
self.slots.push((*module).to_string());
}
}
for (i, seg) in path.iter().enumerate() {
let id = self.identifier(seg);
out.push_str(&id);
self.slots.push(format!("{key}#ident{i}"));
let k = if i + 1 == path.len() { *kind } else { Kind::Struct };
out.push(k.suffix());
// Every nominal *type* node takes a slot, and a nested path
// walks through one per segment — `Circle._Inset` is two
// types, `SwiftUI.Circle` and `SwiftUI.Circle._Inset`, not
// one. Only the last used to be recorded, so every later
// `A`-reference in a mangling containing a nested type came
// out one short and the whole string stopped demangling.
self.slots.push(format!("{module}.{}", path[..=i].join(".")));
}
}
}
}
}