objcjs-types 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/bin/objcjs-types.ts +31 -0
  2. package/dist/AVFoundation/functions.d.ts +21 -0
  3. package/dist/AVFoundation/functions.js +32 -0
  4. package/dist/Accessibility/functions.d.ts +16 -0
  5. package/dist/Accessibility/functions.js +35 -0
  6. package/dist/AddressBook/functions.d.ts +98 -0
  7. package/dist/AddressBook/functions.js +290 -0
  8. package/dist/AppKit/functions.d.ts +112 -0
  9. package/dist/AppKit/functions.js +272 -0
  10. package/dist/AudioToolbox/functions.d.ts +377 -0
  11. package/dist/AudioToolbox/functions.js +1124 -0
  12. package/dist/AuthenticationServices/functions.d.ts +2 -0
  13. package/dist/AuthenticationServices/functions.js +5 -0
  14. package/dist/BrowserEngineCore/functions.d.ts +3 -0
  15. package/dist/BrowserEngineCore/functions.js +11 -0
  16. package/dist/CoreAudio/functions.d.ts +60 -0
  17. package/dist/CoreAudio/functions.js +173 -0
  18. package/dist/CoreMIDI/functions.d.ts +96 -0
  19. package/dist/CoreMIDI/functions.js +287 -0
  20. package/dist/CoreML/functions.d.ts +2 -0
  21. package/dist/CoreML/functions.js +5 -0
  22. package/dist/CoreMediaIO/functions.d.ts +38 -0
  23. package/dist/CoreMediaIO/functions.js +107 -0
  24. package/dist/CoreText/functions.d.ts +209 -0
  25. package/dist/CoreText/functions.js +611 -0
  26. package/dist/CoreWLAN/functions.d.ts +23 -0
  27. package/dist/CoreWLAN/functions.js +56 -0
  28. package/dist/DeviceDiscoveryExtension/functions.d.ts +11 -0
  29. package/dist/DeviceDiscoveryExtension/functions.js +17 -0
  30. package/dist/DiscRecording/functions.d.ts +97 -0
  31. package/dist/DiscRecording/functions.js +290 -0
  32. package/dist/DiscRecordingUI/functions.d.ts +13 -0
  33. package/dist/DiscRecordingUI/functions.js +38 -0
  34. package/dist/ExceptionHandling/functions.d.ts +1 -0
  35. package/dist/ExceptionHandling/functions.js +5 -0
  36. package/dist/FSKit/functions.d.ts +4 -0
  37. package/dist/FSKit/functions.js +11 -0
  38. package/dist/Foundation/functions.d.ts +145 -0
  39. package/dist/Foundation/functions.js +386 -0
  40. package/dist/GLKit/functions.d.ts +51 -0
  41. package/dist/GLKit/functions.js +146 -0
  42. package/dist/GameController/functions.d.ts +18 -0
  43. package/dist/GameController/functions.js +44 -0
  44. package/dist/HealthKit/functions.d.ts +19 -0
  45. package/dist/HealthKit/functions.js +35 -0
  46. package/dist/IOSurface/functions.d.ts +53 -0
  47. package/dist/IOSurface/functions.js +155 -0
  48. package/dist/IOUSBHost/functions.d.ts +44 -0
  49. package/dist/IOUSBHost/functions.js +131 -0
  50. package/dist/InstantMessage/functions.d.ts +1 -0
  51. package/dist/InstantMessage/functions.js +5 -0
  52. package/dist/JavaRuntimeSupport/functions.d.ts +40 -0
  53. package/dist/JavaRuntimeSupport/functions.js +113 -0
  54. package/dist/JavaScriptCore/functions.d.ts +120 -0
  55. package/dist/JavaScriptCore/functions.js +359 -0
  56. package/dist/MLCompute/functions.d.ts +27 -0
  57. package/dist/MLCompute/functions.js +41 -0
  58. package/dist/MapKit/functions.d.ts +23 -0
  59. package/dist/MapKit/functions.js +56 -0
  60. package/dist/Matter/functions.d.ts +17 -0
  61. package/dist/Matter/functions.js +26 -0
  62. package/dist/MediaAccessibility/functions.d.ts +28 -0
  63. package/dist/MediaAccessibility/functions.js +83 -0
  64. package/dist/MediaPlayer/functions.d.ts +3 -0
  65. package/dist/MediaPlayer/functions.js +11 -0
  66. package/dist/Metal/functions.d.ts +14 -0
  67. package/dist/Metal/functions.js +26 -0
  68. package/dist/MetalKit/functions.d.ts +11 -0
  69. package/dist/MetalKit/functions.js +20 -0
  70. package/dist/MetalPerformanceShaders/functions.d.ts +7 -0
  71. package/dist/MetalPerformanceShaders/functions.js +14 -0
  72. package/dist/NearbyInteraction/functions.d.ts +3 -0
  73. package/dist/NearbyInteraction/functions.js +5 -0
  74. package/dist/ParavirtualizedGraphics/functions.d.ts +7 -0
  75. package/dist/ParavirtualizedGraphics/functions.js +14 -0
  76. package/dist/QuartzCore/functions.d.ts +19 -0
  77. package/dist/QuartzCore/functions.js +50 -0
  78. package/dist/SceneKit/functions.d.ts +17 -0
  79. package/dist/SceneKit/functions.js +38 -0
  80. package/dist/SensorKit/functions.d.ts +4 -0
  81. package/dist/SensorKit/functions.js +14 -0
  82. package/dist/ServiceManagement/functions.d.ts +7 -0
  83. package/dist/ServiceManagement/functions.js +20 -0
  84. package/dist/StoreKit/functions.d.ts +1 -0
  85. package/dist/StoreKit/functions.js +5 -0
  86. package/dist/VideoToolbox/functions.d.ts +81 -0
  87. package/dist/VideoToolbox/functions.js +236 -0
  88. package/dist/Vision/functions.d.ts +16 -0
  89. package/dist/Vision/functions.js +38 -0
  90. package/generator/ast-parser.ts +1368 -0
  91. package/generator/clang.ts +167 -0
  92. package/generator/custom.ts +936 -0
  93. package/generator/discover.ts +111 -0
  94. package/generator/emitter.ts +2020 -0
  95. package/generator/frameworks.ts +135 -0
  96. package/generator/index.ts +1334 -0
  97. package/generator/parse-worker.ts +263 -0
  98. package/generator/resolve-strings.ts +121 -0
  99. package/generator/struct-fields.ts +46 -0
  100. package/generator/templates/bind.ts +100 -0
  101. package/generator/templates/helpers.ts +70 -0
  102. package/generator/templates/nsdata.ts +97 -0
  103. package/generator/templates/osversion.ts +91 -0
  104. package/generator/type-mapper.ts +615 -0
  105. package/generator/worker-pool.ts +309 -0
  106. package/package.json +17 -4
@@ -0,0 +1,91 @@
1
+ /**
2
+ * macOS operating system version utilities.
3
+ *
4
+ * Provides cached access to the current OS version and helpers for comparing
5
+ * it against a target version. All reads after the first go through the cache
6
+ * so `NSProcessInfo` is only called once per process lifetime.
7
+ */
8
+
9
+ import { NSProcessInfo } from "./Foundation/index.js";
10
+
11
+ // --- Types ---
12
+
13
+ export interface OSVersion {
14
+ readonly major: number;
15
+ readonly minor: number;
16
+ readonly patch: number;
17
+ }
18
+
19
+ // --- Cache ---
20
+
21
+ let cached: OSVersion | undefined;
22
+
23
+ // --- Core ---
24
+
25
+ /** Return the current macOS version, reading from cache after the first call. */
26
+ export function getOSVersion(): OSVersion {
27
+ if (cached !== undefined) return cached;
28
+ const raw = NSProcessInfo.processInfo().operatingSystemVersion();
29
+ cached = { major: raw.field0, minor: raw.field1, patch: raw.field2 };
30
+ return cached;
31
+ }
32
+
33
+ /** Invalidate the cached version (useful in tests). */
34
+ export function clearOSVersionCache(): void {
35
+ cached = undefined;
36
+ }
37
+
38
+ // --- Comparison ---
39
+
40
+ /**
41
+ * Compare two OS versions.
42
+ *
43
+ * Returns a negative number if `a < b`, 0 if equal, positive if `a > b`.
44
+ */
45
+ export function compareVersions(a: OSVersion, b: OSVersion): number {
46
+ if (a.major !== b.major) return a.major - b.major;
47
+ if (a.minor !== b.minor) return a.minor - b.minor;
48
+ return a.patch - b.patch;
49
+ }
50
+
51
+ /** Returns true if the current OS version is at least `target`. */
52
+ export function isAtLeast(target: OSVersion): boolean {
53
+ return compareVersions(getOSVersion(), target) >= 0;
54
+ }
55
+
56
+ /** Returns true if the current OS version is strictly before `target`. */
57
+ export function isBefore(target: OSVersion): boolean {
58
+ return compareVersions(getOSVersion(), target) < 0;
59
+ }
60
+
61
+ /** Returns true if the current OS version exactly matches `target`. */
62
+ export function isExactly(target: OSVersion): boolean {
63
+ return compareVersions(getOSVersion(), target) === 0;
64
+ }
65
+
66
+ // --- Convenience constructors ---
67
+
68
+ /** Build a version object from major/minor/patch components. */
69
+ export function version(major: number, minor: number = 0, patch: number = 0): OSVersion {
70
+ return { major, minor, patch };
71
+ }
72
+
73
+ /** Format a version as a human-readable string, e.g. `"15.3.1"`. */
74
+ export function formatVersion(v: OSVersion): string {
75
+ return `${v.major}.${v.minor}.${v.patch}`;
76
+ }
77
+
78
+ // --- Named macOS release versions ---
79
+
80
+ export const macOS = {
81
+ Tahoe: version(26),
82
+ Sequoia: version(15),
83
+ Sonoma: version(14),
84
+ Ventura: version(13),
85
+ Monterey: version(12),
86
+ BigSur: version(11),
87
+ Catalina: version(10, 15),
88
+ Mojave: version(10, 14),
89
+ HighSierra: version(10, 13),
90
+ Sierra: version(10, 12)
91
+ } as const;
@@ -0,0 +1,615 @@
1
+ /**
2
+ * Maps Objective-C types (from clang AST qualType strings) to TypeScript types.
3
+ */
4
+
5
+ /** Set of all class names we're generating types for (across all frameworks) */
6
+ let knownClasses: Set<string> = new Set();
7
+
8
+ /** Set of all known protocol names across all frameworks */
9
+ let knownProtocols: Set<string> = new Set();
10
+
11
+ /** Maps protocol name → set of known conforming class names */
12
+ let protocolConformers: Map<string, Set<string>> = new Map();
13
+
14
+ /** Set of all known integer enum names (NS_ENUM / NS_OPTIONS) across all frameworks */
15
+ let knownIntegerEnums: Set<string> = new Set();
16
+
17
+ /** Set of all known string enum names (NS_TYPED_EXTENSIBLE_ENUM etc.) across all frameworks */
18
+ let knownStringEnums: Set<string> = new Set();
19
+
20
+ /** General typedef resolution table: typedef name → underlying qualType string */
21
+ let knownTypedefs: Map<string, string> = new Map();
22
+
23
+ /** Read-only access to the known integer enum names set (for use by the emitter). */
24
+ export function getKnownIntegerEnums(): Set<string> {
25
+ return knownIntegerEnums;
26
+ }
27
+
28
+ /** Read-only access to the known string enum names set (for use by the emitter). */
29
+ export function getKnownStringEnums(): Set<string> {
30
+ return knownStringEnums;
31
+ }
32
+
33
+ export function setKnownClasses(classes: Set<string>): void {
34
+ knownClasses = classes;
35
+ }
36
+
37
+ export function setKnownProtocols(protocols: Set<string>): void {
38
+ knownProtocols = protocols;
39
+ }
40
+
41
+ export function setProtocolConformers(conformers: Map<string, Set<string>>): void {
42
+ protocolConformers = conformers;
43
+ }
44
+
45
+ export function setKnownIntegerEnums(enums: Set<string>): void {
46
+ knownIntegerEnums = enums;
47
+ }
48
+
49
+ export function setKnownStringEnums(enums: Set<string>): void {
50
+ knownStringEnums = enums;
51
+ }
52
+
53
+ export function setKnownTypedefs(typedefs: Map<string, string>): void {
54
+ knownTypedefs = typedefs;
55
+ }
56
+
57
+ /**
58
+ * Numeric ObjC types that map to `number` in TypeScript.
59
+ */
60
+ const NUMERIC_TYPES = new Set([
61
+ "int",
62
+ "unsigned int",
63
+ "short",
64
+ "unsigned short",
65
+ "long",
66
+ "unsigned long",
67
+ "long long",
68
+ "unsigned long long",
69
+ "float",
70
+ "double",
71
+ "NSInteger",
72
+ "NSUInteger",
73
+ "CGFloat",
74
+ "NSTimeInterval",
75
+ "unichar",
76
+ "size_t",
77
+ "ssize_t",
78
+ "uint8_t",
79
+ "uint16_t",
80
+ "uint32_t",
81
+ "uint64_t",
82
+ "int8_t",
83
+ "int16_t",
84
+ "int32_t",
85
+ "int64_t",
86
+ "CFIndex",
87
+ "CFTimeInterval",
88
+ "CGWindowLevel",
89
+ "NSWindowLevel",
90
+ "NSModalResponse",
91
+ "NSComparisonResult",
92
+ "NSStringEncoding"
93
+ ]);
94
+
95
+ /**
96
+ * ObjC types that map to specific TS types.
97
+ */
98
+ const DIRECT_MAPPINGS: Record<string, string> = {
99
+ void: "void",
100
+ BOOL: "boolean",
101
+ bool: "boolean",
102
+ _Bool: "boolean",
103
+ id: "NobjcObject",
104
+ SEL: "string",
105
+ Class: "NobjcObject",
106
+ "char *": "string",
107
+ "const char *": "string",
108
+ "unsigned char *": "string",
109
+ "const unsigned char *": "string",
110
+ // Typedef'd NSString * aliases — clang resolves the typedef name, not the underlying type
111
+ NSAttributedStringKey: "_NSString",
112
+ NSStringTransform: "_NSString",
113
+ NSNotificationName: "_NSString",
114
+ NSRunLoopMode: "_NSString",
115
+ NSPasteboardType: "_NSString",
116
+ NSTouchBarItemIdentifier: "_NSString",
117
+ NSToolbarIdentifier: "_NSString",
118
+ NSToolbarItemIdentifier: "_NSString",
119
+ NSAccessibilityRole: "_NSString",
120
+ NSAccessibilitySubrole: "_NSString",
121
+ NSAccessibilityNotificationName: "_NSString",
122
+ NSUserInterfaceItemIdentifier: "_NSString",
123
+ NSStoryboardName: "_NSString",
124
+ NSStoryboardSceneIdentifier: "_NSString",
125
+ NSNibName: "_NSString",
126
+ NSBindingName: "_NSString",
127
+ NSColorName: "_NSString",
128
+ NSColorListName: "_NSString",
129
+ NSImageName: "_NSString",
130
+ NSSoundName: "_NSString",
131
+ NSAppearanceName: "_NSString",
132
+ NSPasteboardReadingOptionKey: "_NSString",
133
+ // Pointer-to-struct typedefs — these are C pointer types, not value types
134
+ NSRectArray: "NobjcObject",
135
+ NSRectPointer: "NobjcObject",
136
+ NSPointArray: "NobjcObject",
137
+ NSPointPointer: "NobjcObject",
138
+ NSSizeArray: "NobjcObject",
139
+ NSSizePointer: "NobjcObject",
140
+ NSRangePointer: "NobjcObject"
141
+ // CoreFoundation opaque types — these are C struct pointers (type encoding `^{...}`),
142
+ // NOT ObjC objects. Handled separately in mapParamType/mapReturnType.
143
+ };
144
+
145
+ /**
146
+ * CoreFoundation opaque types — these are C struct pointers (type encoding
147
+ * `^{CGContext=}` etc.), NOT ObjC objects. The objc-js bridge handles `^`
148
+ * params with Buffer/TypedArray and pointer returns throw TypeError.
149
+ */
150
+ const CF_OPAQUE_TYPES = new Set([
151
+ "CGContextRef",
152
+ "CGImageRef",
153
+ "CGColorRef",
154
+ "CGColorSpaceRef",
155
+ "CGPathRef",
156
+ "CGEventRef",
157
+ "CGLayerRef",
158
+ "CFRunLoopRef",
159
+ "CFStringRef",
160
+ "CFTypeRef",
161
+ "SecTrustRef",
162
+ "SecIdentityRef",
163
+ "IOSurfaceRef"
164
+ ]);
165
+
166
+ /**
167
+ * ObjC generic type parameters that should map to NobjcObject.
168
+ * These are erased at runtime; the bridge just sees "id".
169
+ */
170
+ const GENERIC_TYPE_PARAMS = new Set(["ObjectType", "KeyType", "ValueType", "ElementType", "ResultType", "ContentType"]);
171
+
172
+ /**
173
+ * Maps ObjC struct type names to their TypeScript interface names (defined in src/structs.ts).
174
+ * Built dynamically from parsed struct data by setKnownStructs().
175
+ * NS geometry aliases map to their CG counterparts (identical layout).
176
+ */
177
+ let STRUCT_TYPE_MAP: Record<string, string> = {};
178
+
179
+ /** The set of all TS struct type names (for use by the emitter to generate imports). */
180
+ export let STRUCT_TS_TYPES = new Set<string>();
181
+
182
+ /**
183
+ * Set the known struct types from parsed AST data.
184
+ * Builds STRUCT_TYPE_MAP (ObjC name → TS name) and STRUCT_TS_TYPES (set of TS names).
185
+ *
186
+ * @param structNames - All struct definition names (e.g., "CGPoint", "NSRange")
187
+ * @param aliases - Map of alias name → target name (e.g., "NSPoint" → "CGPoint")
188
+ * @param internalNames - Map of public name → internal name (e.g., "NSRange" → "_NSRange")
189
+ */
190
+ export function setKnownStructs(
191
+ structNames: Set<string>,
192
+ aliases: Map<string, string>,
193
+ internalNames: Map<string, string>
194
+ ): void {
195
+ STRUCT_TYPE_MAP = {};
196
+
197
+ // Add direct struct names — they map to themselves as TS types
198
+ for (const name of structNames) {
199
+ STRUCT_TYPE_MAP[name] = name;
200
+ }
201
+
202
+ // Add internal names mapping to the public typedef name
203
+ // e.g., _NSRange → NSRange
204
+ // But don't overwrite if the internal name is itself a known public struct name
205
+ // (that case should have been handled as an alias in the parser).
206
+ for (const [publicName, internalName] of internalNames) {
207
+ if (!structNames.has(internalName)) {
208
+ STRUCT_TYPE_MAP[internalName] = publicName;
209
+ }
210
+ }
211
+
212
+ // Add aliases — they map to the target TS type
213
+ // e.g., NSPoint → CGPoint (the TS type is CGPoint)
214
+ for (const [aliasName, targetName] of aliases) {
215
+ STRUCT_TYPE_MAP[aliasName] = targetName;
216
+ }
217
+
218
+ // Rebuild the TS types set
219
+ STRUCT_TS_TYPES = new Set(Object.values(STRUCT_TYPE_MAP));
220
+ }
221
+
222
+ /**
223
+ * Convert an ObjC selector string to the objc-js `$` convention.
224
+ * e.g., "arrayByAddingObject:" -> "arrayByAddingObject$"
225
+ * e.g., "initWithFrame:styleMask:" -> "initWithFrame$styleMask$"
226
+ */
227
+ export function selectorToJS(selector: string): string {
228
+ return selector.replace(/:/g, "$");
229
+ }
230
+
231
+ /**
232
+ * Parse the nullable status from a qualType string.
233
+ */
234
+ function isNullableType(qualType: string): boolean {
235
+ return qualType.includes("_Nullable") || qualType.includes("nullable") || qualType.startsWith("nullable ");
236
+ }
237
+
238
+ /**
239
+ * Clean up a qualType string by removing annotations.
240
+ *
241
+ * Clang qualType strings can contain attribute macros that are not part of the
242
+ * actual type. These include nullability qualifiers, ownership qualifiers,
243
+ * availability macros (API_AVAILABLE, API_DEPRECATED, etc.), CoreFoundation
244
+ * ownership macros (CF_CONSUMED, CF_RETURNS_RETAINED, etc.), and Swift interop
245
+ * macros (NS_REFINED_FOR_SWIFT, NS_SWIFT_UI_ACTOR, etc.).
246
+ */
247
+ function cleanQualType(qualType: string): string {
248
+ return (
249
+ qualType
250
+ .replace(/_Nonnull/g, "")
251
+ .replace(/_Nullable/g, "")
252
+ .replace(/_Null_unspecified/g, "")
253
+ .replace(/\b__kindof\b/g, "")
254
+ .replace(/\b__unsafe_unretained\b/g, "")
255
+ .replace(/\b__strong\b/g, "")
256
+ .replace(/\b__weak\b/g, "")
257
+ .replace(/\b__autoreleasing\b/g, "")
258
+ // Attribute macros: NS_*, API_*, CF_*, XPC_* (e.g. API_AVAILABLE, CF_RETURNS_RETAINED)
259
+ .replace(/\b(?:NS|API|CF|XPC)_[A-Z_]+\b/g, "")
260
+ // Legacy availability macros: AVAILABLE_MAC_OS_X_VERSION_*_AND_LATER etc.
261
+ .replace(/\bAVAILABLE_MAC_OS_X_VERSION_[A-Z0-9_]+\b/g, "")
262
+ .replace(/^struct\s+/, "")
263
+ .trim()
264
+ );
265
+ }
266
+
267
+ /**
268
+ * Extract the base class name from a pointer type like "NSArray<ObjectType> *".
269
+ */
270
+ function extractClassName(cleaned: string): string | null {
271
+ // Match "ClassName *" or "ClassName<...> *" (with optional const prefix).
272
+ // The generic parameter section may contain nested angle brackets
273
+ // (e.g., "NSArray<id<NSAccessibilityRow>> *"), so we match from the
274
+ // first '<' to the last '>' instead of using [^>]*.
275
+ const match = cleaned.match(/^(?:const\s+)?(\w+)\s*(?:<.*>)?\s*\*$/);
276
+ if (match) return match[1]!;
277
+
278
+ return null;
279
+ }
280
+
281
+ /**
282
+ * Map an Objective-C type string to a TypeScript type string.
283
+ *
284
+ * @param qualType The clang qualType string (e.g., "NSArray<ObjectType> * _Nonnull")
285
+ * @param containingClass The class this type appears in (for resolving `instancetype`)
286
+ * @param isReturnType Whether this type is used in a return position (enables conformer union expansion)
287
+ * @returns The TypeScript type string
288
+ */
289
+ export function mapType(qualType: string, containingClass: string, isReturnType = false): string {
290
+ const nullable = isNullableType(qualType);
291
+ const cleaned = cleanQualType(qualType);
292
+
293
+ let tsType = mapTypeInner(cleaned, containingClass, undefined, isReturnType);
294
+
295
+ if (nullable && tsType !== "void") {
296
+ tsType = `${tsType} | null`;
297
+ }
298
+
299
+ return tsType;
300
+ }
301
+
302
+ function mapTypeInner(cleaned: string, containingClass: string, resolving?: Set<string>, isReturnType = false): string {
303
+ // Direct mappings
304
+ if (cleaned in DIRECT_MAPPINGS) {
305
+ return DIRECT_MAPPINGS[cleaned]!;
306
+ }
307
+
308
+ // Numeric types
309
+ if (NUMERIC_TYPES.has(cleaned)) {
310
+ return "number";
311
+ }
312
+
313
+ // ObjC generic type parameters (ObjectType, KeyType, etc.)
314
+ if (GENERIC_TYPE_PARAMS.has(cleaned)) {
315
+ return "NobjcObject";
316
+ }
317
+
318
+ // instancetype -> the containing class
319
+ if (cleaned === "instancetype" || cleaned === "instancetype _Nonnull" || cleaned === "instancetype _Nullable") {
320
+ return `_${containingClass}`;
321
+ }
322
+
323
+ // Struct types -> typed interfaces from src/structs.ts
324
+ if (cleaned in STRUCT_TYPE_MAP) {
325
+ return STRUCT_TYPE_MAP[cleaned]!;
326
+ }
327
+
328
+ // Block types: "void (^)(Type1, Type2)" or "ReturnType (^)(Type1, Type2)"
329
+ // At runtime, blocks have type encoding `@?` which the bridge handles via
330
+ // the `@` (id) case — only NobjcObject is accepted/returned, not JS functions.
331
+ if (cleaned.includes("(^") || cleaned.includes("Block_")) {
332
+ return "NobjcObject";
333
+ }
334
+
335
+ // Function pointer types — same runtime limitation as blocks
336
+ if (cleaned.includes("(*)") || cleaned.includes("(*")) {
337
+ return "NobjcObject";
338
+ }
339
+
340
+ // Pointer to pointer (e.g., "NSError **") - out parameters
341
+ if (cleaned.match(/\w+\s*\*\s*\*/)) {
342
+ return "NobjcObject";
343
+ }
344
+
345
+ // Void pointer
346
+ if (cleaned === "void *" || cleaned === "const void *") {
347
+ return "NobjcObject";
348
+ }
349
+
350
+ // ObjC object pointer (e.g., "NSString *", "NSArray<ObjectType> *")
351
+ const className = extractClassName(cleaned);
352
+ if (className) {
353
+ // instancetype check again
354
+ if (className === "instancetype") {
355
+ return `_${containingClass}`;
356
+ }
357
+
358
+ // Known class -> use typed reference
359
+ if (knownClasses.has(className)) {
360
+ return `_${className}`;
361
+ }
362
+
363
+ // Unknown class -> NobjcObject
364
+ return "NobjcObject";
365
+ }
366
+
367
+ // Enum / typedef types — use the enum type name directly for type safety.
368
+ // First check if this is a known enum type — this is more precise than the
369
+ // typedef resolution below and handles prefixes like AS that aren't in the list.
370
+ if (knownIntegerEnums.has(cleaned)) {
371
+ return cleaned;
372
+ }
373
+ if (knownStringEnums.has(cleaned)) {
374
+ return cleaned;
375
+ }
376
+
377
+ // Resolve typedefs: look up the underlying type and recursively map it.
378
+ // This handles cases like NSWindowPersistableFrameDescriptor → NSString *,
379
+ // NSWindowFrameAutosaveName → NSString *, FourCharCode → unsigned int, etc.
380
+ // without needing hardcoded heuristics or manual DIRECT_MAPPINGS entries.
381
+ if (knownTypedefs.has(cleaned)) {
382
+ // Guard against circular typedef chains (e.g., typedef struct X X → cleanQualType → X)
383
+ const seen = resolving ?? new Set<string>();
384
+ if (!seen.has(cleaned)) {
385
+ seen.add(cleaned);
386
+ const underlying = knownTypedefs.get(cleaned)!;
387
+ return mapTypeInner(cleanQualType(underlying), containingClass, seen, isReturnType);
388
+ }
389
+ }
390
+
391
+ // Protocol-qualified id: "id<ASAuthorizationCredential>" or "id<Proto1, Proto2>"
392
+ //
393
+ // For RETURN types: if the protocol has a small number of known conforming classes
394
+ // (≤ MAX_CONFORMERS_FOR_UNION), expand to a union of those concrete classes.
395
+ // This gives callers useful type information — e.g., credential() returns the
396
+ // union of all ASAuthorizationCredential-conforming classes rather than an empty
397
+ // protocol interface. The conformers set is deterministic (same for parent and
398
+ // child classes), so override compatibility is preserved.
399
+ //
400
+ // For PARAMETER types: keep the protocol interface type. Expanding to a union
401
+ // would incorrectly restrict which objects can be passed (any conforming object
402
+ // should be accepted, not just known SDK conformers).
403
+ const MAX_CONFORMERS_FOR_UNION = 30;
404
+ const protoMatch = cleaned.match(/^id<(.+)>$/);
405
+ if (protoMatch) {
406
+ const protoNames = protoMatch[1]!.split(/,\s*/);
407
+
408
+ // For return types with a single protocol, try conformer union expansion
409
+ if (isReturnType && protoNames.length === 1) {
410
+ const protoName = protoNames[0]!;
411
+ const conformers = protocolConformers.get(protoName);
412
+ if (conformers && conformers.size > 0 && conformers.size <= MAX_CONFORMERS_FOR_UNION) {
413
+ const sorted = [...conformers].sort();
414
+ return sorted.map((c) => `_${c}`).join(" | ");
415
+ }
416
+ }
417
+
418
+ // Fallback: use the protocol interface type(s)
419
+ const unionParts: string[] = [];
420
+
421
+ for (const protoName of protoNames) {
422
+ if (knownProtocols.has(protoName)) {
423
+ unionParts.push(`_${protoName}`);
424
+ } else if (knownClasses.has(protoName)) {
425
+ // Sometimes a "protocol" name is actually a class name
426
+ unionParts.push(`_${protoName}`);
427
+ }
428
+ }
429
+
430
+ if (unionParts.length > 0) {
431
+ // Deduplicate (a class conforming to multiple listed protocols)
432
+ return [...new Set(unionParts)].join(" | ");
433
+ }
434
+ return "NobjcObject";
435
+ }
436
+
437
+ // Array type (C array parameters like "const ObjectType [_Nonnull]")
438
+ if (cleaned.includes("[")) {
439
+ return "NobjcObject";
440
+ }
441
+
442
+ // Fallback: unknown type -> NobjcObject
443
+ return "NobjcObject";
444
+ }
445
+
446
+ /**
447
+ * Map a return type, handling instancetype specially.
448
+ *
449
+ * CF opaque types (CGContextRef, etc.) are struct pointers at the ABI level.
450
+ * The objc-js bridge throws TypeError for pointer return types, so we type
451
+ * them as NobjcObject (the call will fail at runtime regardless).
452
+ *
453
+ * For protocol-qualified id types (e.g., id<ASAuthorizationCredential>),
454
+ * returns a union of known conforming classes when the conformer set is small
455
+ * enough, providing concrete type information to callers.
456
+ */
457
+ export function mapReturnType(qualType: string, containingClass: string): string {
458
+ const cleaned = cleanQualType(qualType);
459
+ if (CF_OPAQUE_TYPES.has(cleaned)) {
460
+ return "NobjcObject";
461
+ }
462
+ return mapType(qualType, containingClass, true);
463
+ }
464
+
465
+ /**
466
+ * Map an ObjC qualType string to its ObjC type encoding character(s).
467
+ *
468
+ * Used by the function emitter to generate `{ returns: "..." }` and
469
+ * `{ args: [...] }` options for `callFunction()`. The type encodings
470
+ * follow the ObjC runtime convention:
471
+ * - `v` = void, `B` = BOOL, `i` = int, `I` = unsigned int
472
+ * - `s` = short, `S` = unsigned short, `l` = long, `L` = unsigned long
473
+ * - `q` = long long, `Q` = unsigned long long
474
+ * - `f` = float, `d` = double
475
+ * - `@` = ObjC object pointer (id, NSString *, etc.)
476
+ * - `:` = SEL, `#` = Class
477
+ * - `*` = char *, `^v` = void *
478
+ * - `^` = generic pointer (CF opaque types, etc.)
479
+ *
480
+ * @returns The type encoding string, or null if the type cannot be encoded
481
+ * (e.g., block types, function pointers, pointer-to-pointer).
482
+ */
483
+ export function qualTypeToEncoding(qualType: string): string | null {
484
+ const cleaned = cleanQualType(qualType);
485
+
486
+ // Void
487
+ if (cleaned === "void") return "v";
488
+
489
+ // Boolean
490
+ if (cleaned === "BOOL" || cleaned === "bool" || cleaned === "_Bool") return "B";
491
+
492
+ // Integer types
493
+ if (cleaned === "char" || cleaned === "signed char") return "c";
494
+ if (cleaned === "unsigned char") return "C";
495
+ if (cleaned === "short" || cleaned === "unsigned short") return cleaned.startsWith("unsigned") ? "S" : "s";
496
+ if (cleaned === "int") return "i";
497
+ if (cleaned === "unsigned int") return "I";
498
+ if (cleaned === "long") return "l";
499
+ if (cleaned === "unsigned long") return "L";
500
+ if (cleaned === "long long") return "q";
501
+ if (cleaned === "unsigned long long") return "Q";
502
+
503
+ // Fixed-width integer types
504
+ if (cleaned === "int8_t") return "c";
505
+ if (cleaned === "uint8_t") return "C";
506
+ if (cleaned === "int16_t") return "s";
507
+ if (cleaned === "uint16_t") return "S";
508
+ if (cleaned === "int32_t") return "i";
509
+ if (cleaned === "uint32_t") return "I";
510
+ if (cleaned === "int64_t") return "q";
511
+ if (cleaned === "uint64_t") return "Q";
512
+
513
+ // Platform-dependent integer types (arm64 macOS: NSInteger = long, NSUInteger = unsigned long)
514
+ if (cleaned === "NSInteger" || cleaned === "CFIndex" || cleaned === "ssize_t") return "q";
515
+ if (cleaned === "NSUInteger" || cleaned === "size_t") return "Q";
516
+
517
+ // Floating point
518
+ if (cleaned === "float") return "f";
519
+ if (cleaned === "double" || cleaned === "CGFloat" || cleaned === "NSTimeInterval" || cleaned === "CFTimeInterval")
520
+ return "d";
521
+
522
+ // Unicode character
523
+ if (cleaned === "unichar") return "S";
524
+
525
+ // Selector
526
+ if (cleaned === "SEL") return ":";
527
+
528
+ // Class
529
+ if (cleaned === "Class") return "#";
530
+
531
+ // C string types
532
+ if (
533
+ cleaned === "char *" ||
534
+ cleaned === "const char *" ||
535
+ cleaned === "unsigned char *" ||
536
+ cleaned === "const unsigned char *"
537
+ )
538
+ return "*";
539
+
540
+ // Void pointer
541
+ if (cleaned === "void *" || cleaned === "const void *") return "^v";
542
+
543
+ // ObjC id (bare)
544
+ if (cleaned === "id") return "@";
545
+
546
+ // ObjC object pointers (NSString *, NSArray<...> *, id<Protocol>, etc.)
547
+ if (cleaned.endsWith("*") || cleaned.startsWith("id<")) return "@";
548
+
549
+ // Enum types that are known integers — map to their underlying integer encoding
550
+ if (knownIntegerEnums.has(cleaned)) return "q"; // Most NS_ENUM/NS_OPTIONS use NSInteger/NSUInteger
551
+
552
+ // String enum types (NSString * typedef aliases)
553
+ if (knownStringEnums.has(cleaned)) return "@";
554
+
555
+ // NSString typedef aliases from DIRECT_MAPPINGS
556
+ if (cleaned in DIRECT_MAPPINGS) {
557
+ const mapped = DIRECT_MAPPINGS[cleaned]!;
558
+ if (mapped === "_NSString" || mapped === "string") return "@";
559
+ if (mapped === "boolean") return "B";
560
+ if (mapped === "number") return "q";
561
+ if (mapped === "void") return "v";
562
+ if (mapped === "NobjcObject") return "@";
563
+ }
564
+
565
+ // Struct types
566
+ if (cleaned in STRUCT_TYPE_MAP) return "{" + cleaned + "}";
567
+
568
+ // CF opaque types (struct pointers)
569
+ if (CF_OPAQUE_TYPES.has(cleaned)) return "^";
570
+
571
+ // Typedef resolution
572
+ if (knownTypedefs.has(cleaned)) {
573
+ const underlying = knownTypedefs.get(cleaned)!;
574
+ return qualTypeToEncoding(underlying);
575
+ }
576
+
577
+ // Numeric types catch-all
578
+ if (NUMERIC_TYPES.has(cleaned)) return "d";
579
+
580
+ // Block types, function pointers, pointer-to-pointer — cannot encode simply
581
+ if (
582
+ cleaned.includes("(^") ||
583
+ cleaned.includes("Block_") ||
584
+ cleaned.includes("(*)") ||
585
+ cleaned.match(/\w+\s*\*\s*\*/)
586
+ ) {
587
+ return null;
588
+ }
589
+
590
+ // Unknown — default to object pointer
591
+ return "@";
592
+ }
593
+
594
+ /**
595
+ * Map a parameter type.
596
+ *
597
+ * For raw pointer parameters (`void *`, `const void *`) and CF opaque types
598
+ * (CGContextRef, etc.), the objc-js bridge expects `Buffer` or `TypedArray`
599
+ * at runtime (type encoding `^`). We type these as `Uint8Array` so callers
600
+ * can pass `Buffer` or `Uint8Array` without casting.
601
+ */
602
+ export function mapParamType(qualType: string, containingClass: string): string {
603
+ const cleaned = cleanQualType(qualType);
604
+ // Raw void pointers
605
+ if (cleaned === "void *" || cleaned === "const void *") {
606
+ const nullable = isNullableType(qualType);
607
+ return nullable ? "Uint8Array | null" : "Uint8Array";
608
+ }
609
+ // CF opaque types (struct pointers with `^{...}` encoding)
610
+ if (CF_OPAQUE_TYPES.has(cleaned)) {
611
+ const nullable = isNullableType(qualType);
612
+ return nullable ? "Uint8Array | null" : "Uint8Array";
613
+ }
614
+ return mapType(qualType, containingClass);
615
+ }