peachsim 0.1.2180 → 0.1.2185

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/cli/commands/shell-url.ts +28 -22
  2. package/cli/outbound-endpoints.ts +11 -1
  3. package/dist-lib/agent-daemon-client.cjs +1 -1
  4. package/dist-lib/agent-events.cjs +1 -1
  5. package/dist-lib/attached-projects.cjs +1 -1
  6. package/dist-lib/auth/shared-session.cjs +1 -1
  7. package/dist-lib/backend-origin.cjs +1 -1
  8. package/dist-lib/beta.cjs +1 -1
  9. package/dist-lib/beta.mjs +1 -1
  10. package/dist-lib/bridge-constants.cjs +1 -1
  11. package/dist-lib/bridge-contract-input.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.mjs +1 -1
  13. package/dist-lib/bridge-contract.cjs +1 -1
  14. package/dist-lib/bridge-contract.mjs +1 -1
  15. package/dist-lib/bundle-channel.cjs +457 -96
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cloud-contract.cjs +1 -1
  19. package/dist-lib/cloud-contract.mjs +1 -1
  20. package/dist-lib/cloud-test-args.cjs +1 -1
  21. package/dist-lib/cloud-test-args.mjs +1 -1
  22. package/dist-lib/cloud.cjs +1 -1
  23. package/dist-lib/cloud.mjs +1 -1
  24. package/dist-lib/config.cjs +1 -1
  25. package/dist-lib/config.mjs +1 -1
  26. package/dist-lib/detox/index.cjs +1 -1
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/expo-swift-bundle.cjs +352 -58
  29. package/dist-lib/expo-swift-metadata.cjs +1 -1
  30. package/dist-lib/expo-swift-metadata.mjs +1 -1
  31. package/dist-lib/home-paths.cjs +1 -1
  32. package/dist-lib/host/bridge-host.cjs +415 -85
  33. package/dist-lib/host/fetch-proxy-handler.cjs +7 -4
  34. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  36. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  37. package/dist-lib/host/websocket-proxy.cjs +1 -1
  38. package/dist-lib/index.cjs +7 -4
  39. package/dist-lib/jump-to-source-babel.cjs +1 -1
  40. package/dist-lib/jump-to-source-native.cjs +1 -1
  41. package/dist-lib/kotlin-host.cjs +1 -1
  42. package/dist-lib/menu.cjs +1 -1
  43. package/dist-lib/menu.mjs +1 -1
  44. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  46. package/dist-lib/metro-production-bundle.cjs +1 -1
  47. package/dist-lib/metro-production-bundle.mjs +1 -1
  48. package/dist-lib/metro.cjs +7 -4
  49. package/dist-lib/native-app-identity-project.cjs +6 -3
  50. package/dist-lib/native-app-identity-project.mjs +6 -3
  51. package/dist-lib/native-app-identity.cjs +1 -1
  52. package/dist-lib/native-app-identity.mjs +1 -1
  53. package/dist-lib/native-firebase-metadata.cjs +1 -1
  54. package/dist-lib/native-firebase-metadata.mjs +1 -1
  55. package/dist-lib/pdfium-raster.cjs +1 -1
  56. package/dist-lib/pdfium-raster.mjs +1 -1
  57. package/dist-lib/plist.cjs +1 -1
  58. package/dist-lib/plist.mjs +1 -1
  59. package/dist-lib/profiles.cjs +1 -1
  60. package/dist-lib/public-brand.cjs +1 -1
  61. package/dist-lib/public-origins-lib-esm.json +25 -24
  62. package/dist-lib/public-origins-lib.json +63 -53
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/reap-browser.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +1 -1
  68. package/dist-lib/sdk.mjs +1 -1
  69. package/dist-lib/skills.cjs +447 -93
  70. package/dist-lib/swift-compile.cjs +13 -4
  71. package/dist-lib/swift-compile.mjs +13 -4
  72. package/dist-lib/swift-package.cjs +26 -1
  73. package/dist-lib/swift-package.mjs +25 -1
  74. package/dist-lib/swift-submit.cjs +2 -2
  75. package/dist-lib/swift-submit.mjs +2 -2
  76. package/dist-lib/swift-toolchain.cjs +1 -1
  77. package/dist-lib/swift.cjs +1 -1
  78. package/dist-lib/vite-swift.cjs +298 -27
  79. package/dist-lib/vite.cjs +7 -4
  80. package/dist-lib/worklet-executable.cjs +1 -1
  81. package/dist-lib/worklet-executable.mjs +1 -1
  82. package/dist-types/packages/peach-engine/src/render-worker/map-command-channel.d.ts +14 -0
  83. package/package.json +1 -1
  84. package/sdk/ios/System/Library/Frameworks/ActivityKit.framework/Headers/ActivityKit.h +1 -0
  85. package/sdk/ios/System/Library/Frameworks/ActivityKit.framework/Modules/module.modulemap +4 -0
  86. package/sdk/ios/usr/lib/swift/ActivityKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +14 -0
  87. package/sdk/ios/usr/lib/swift/CoreGraphics.swiftmodule/arm64-apple-ios-simulator.swiftinterface +14 -0
  88. package/sdk/ios/usr/lib/swift/Dispatch.swiftmodule/arm64-apple-ios-simulator.swiftinterface +3 -0
  89. package/sdk/ios/usr/lib/swift/Foundation.swiftmodule/arm64-apple-ios-simulator.swiftinterface +147 -16
  90. package/sdk/ios/usr/lib/swift/UIKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +5 -0
  91. package/src/expo-native-workspace.ts +19 -21
  92. package/src/expo-swift-bundle.ts +6 -1
  93. package/src/expo-swift-host.ts +35 -6
  94. package/src/expoSwiftContract.ts +3 -0
  95. package/src/native-bundles.ts +30 -10
  96. package/src/one-sdk/source.ts +10 -4
  97. package/src/one-sdk-bundle.ts +56 -24
  98. package/src/swift-compile.ts +3 -1
  99. package/src/swift-interface-bodies.ts +165 -0
  100. package/src/swift-objc-standin.ts +73 -13
  101. package/src/swift-package.ts +26 -0
  102. package/src/swift-source-pods.ts +27 -4
  103. package/src/swift-standin-emit.ts +30 -3
  104. package/src/swift-standins.ts +75 -12
  105. package/src/swift-submit.ts +2 -1
  106. package/src/wasm-optimize.ts +11 -1
@@ -0,0 +1,165 @@
1
+ // a swift interface without function bodies. a library-evolution interface
2
+ // keeps the bodies of its inlinable functions and accessors, and compiling it
3
+ // type-checks them against the sdk it is read with. the objective-c intake
4
+ // declares Foundation and CoreGraphics only as far as headers name them, so a
5
+ // body that builds a Date or reads CGRect.zero fails the whole module although
6
+ // no declaration does. an interface prints every non-inlinable function
7
+ // without a body, so dropping the rest leaves the same public surface. a
8
+ // default argument value is a body of its own kind: it type-checks against the
9
+ // same partial sdk, and the symbol graph spells it into the stand-in, so it
10
+ // stays unless the caller finds the interface does not load with it.
11
+
12
+ const INLINE_ATTRIBUTE =
13
+ /@(?:inlinable|_alwaysEmitIntoClient|_transparent|inline\(__always\)|_effects\([^()]*\)|_specialize\((?:[^()]|\([^()]*\))*\))\s*/g
14
+
15
+ const ACCESSOR = /^(?:mutating|nonmutating|async|throws|rethrows|get|set|_modify|_read)$/
16
+
17
+ type Frame = 'container' | 'accessors' | 'expression'
18
+
19
+ // the keyword that says what a `{` after this line opens.
20
+ function declarationKind(head: string): 'function' | 'property' | 'container' {
21
+ const words = head
22
+ .replace(/@\w+(?:\((?:[^()]|\([^()]*\))*\))?/g, ' ')
23
+ .match(/[A-Za-z_]\w*/g)
24
+ for (const word of words ?? []) {
25
+ if (word === 'func' || word === 'init' || word === 'deinit') return 'function'
26
+ if (word === 'var' || word === 'let' || word === 'subscript') return 'property'
27
+ if (
28
+ word === 'struct' ||
29
+ word === 'class' ||
30
+ word === 'enum' ||
31
+ word === 'protocol' ||
32
+ word === 'extension' ||
33
+ word === 'actor' ||
34
+ word === 'precedencegroup'
35
+ )
36
+ return 'container'
37
+ }
38
+ return 'container'
39
+ }
40
+
41
+ /** the index after the string, comment or character literal starting at `i`, or `i`. */
42
+ function skipLiteral(text: string, i: number): number {
43
+ if (text.startsWith('//', i)) {
44
+ const end = text.indexOf('\n', i)
45
+ return end < 0 ? text.length : end
46
+ }
47
+ if (text.startsWith('/*', i)) {
48
+ const end = text.indexOf('*/', i + 2)
49
+ return end < 0 ? text.length : end + 2
50
+ }
51
+ if (text[i] !== '"' && text[i] !== '#') return i
52
+ let hashes = 0
53
+ let j = i
54
+ while (text[j] === '#') {
55
+ hashes++
56
+ j++
57
+ }
58
+ if (text[j] !== '"') return i
59
+ const multiline = text.startsWith('"""', j)
60
+ const open = multiline ? '"""' : '"'
61
+ j += open.length
62
+ const close = `${open}${'#'.repeat(hashes)}`
63
+ const escape = `\\${'#'.repeat(hashes)}`
64
+ while (j < text.length) {
65
+ if (text.startsWith(escape, j)) j += escape.length + 1
66
+ else if (text.startsWith(close, j)) return j + close.length
67
+ else j++
68
+ }
69
+ return text.length
70
+ }
71
+
72
+ /** the index after the brace block opened at `open`. */
73
+ function skipBlock(text: string, open: number): number {
74
+ let depth = 0
75
+ for (let i = open; i < text.length; ) {
76
+ const after = skipLiteral(text, i)
77
+ if (after !== i) {
78
+ i = after
79
+ continue
80
+ }
81
+ if (text[i] === '{') depth++
82
+ else if (text[i] === '}' && --depth === 0) return i + 1
83
+ i++
84
+ }
85
+ return text.length
86
+ }
87
+
88
+ /** the index of the `,` or `)` that ends the default argument value starting at `i`. */
89
+ function skipExpression(text: string, start: number): number {
90
+ let depth = 0
91
+ for (let i = start; i < text.length; ) {
92
+ const after = skipLiteral(text, i)
93
+ if (after !== i) {
94
+ i = after
95
+ continue
96
+ }
97
+ const char = text[i]!
98
+ if (char === '(' || char === '[' || char === '{' || char === '<') depth++
99
+ else if (char === '>' && text[i - 1] !== '-') depth--
100
+ else if (char === ')' || char === ']' || char === '}') {
101
+ if (depth === 0) return i
102
+ depth--
103
+ } else if (char === ',' && depth === 0) return i
104
+ i++
105
+ }
106
+ return text.length
107
+ }
108
+
109
+ export function swiftInterfaceWithoutBodies(text: string, keepDefaults = false): string {
110
+ let out = ''
111
+ const frames: Frame[] = []
112
+ let parens = 0
113
+ let lineStart = 0
114
+ for (let i = 0; i < text.length; ) {
115
+ const after = skipLiteral(text, i)
116
+ if (after !== i) {
117
+ out += text.slice(i, after)
118
+ i = after
119
+ continue
120
+ }
121
+ const char = text[i]!
122
+ if (char === '=' && parens > 0 && text[i - 1] === ' ' && text[i + 1] === ' ') {
123
+ out = out.trimEnd()
124
+ i = skipExpression(text, i + 1)
125
+ continue
126
+ }
127
+ if (char === '\n') lineStart = out.length + 1
128
+ else if (char === '(') parens++
129
+ else if (char === ')') parens--
130
+ else if (char === '}') {
131
+ frames.pop()
132
+ } else if (char === '{') {
133
+ const parent = frames.at(-1)
134
+ const head = out.slice(lineStart)
135
+ if (parens > 0 || parent === 'expression') frames.push('expression')
136
+ else if (parent === 'accessors') {
137
+ // an accessor's body: keep its keyword, drop the attributes that need one
138
+ out = `${out.slice(0, lineStart)}${head.replace(INLINE_ATTRIBUTE, '')}`.trimEnd()
139
+ i = skipBlock(text, i)
140
+ continue
141
+ } else {
142
+ const kind = declarationKind(head)
143
+ const body = text.slice(i + 1, skipBlock(text, i) - 1)
144
+ if (kind === 'function') {
145
+ out =
146
+ `${out.slice(0, lineStart)}${head.replace(INLINE_ATTRIBUTE, '')}`.trimEnd()
147
+ i = skipBlock(text, i)
148
+ continue
149
+ }
150
+ // `{ get set }` keeps its keywords; `{ get { .. } }` is a property with bodies
151
+ const bare = body.split(/\s+/).every((word) => word === '' || ACCESSOR.test(word))
152
+ frames.push(kind === 'property' && !bare ? 'accessors' : 'container')
153
+ if (kind === 'property' && bare) {
154
+ out += `{${body}}`
155
+ i = skipBlock(text, i)
156
+ frames.pop()
157
+ continue
158
+ }
159
+ }
160
+ }
161
+ out += char
162
+ i++
163
+ }
164
+ return out.replace(INLINE_ATTRIBUTE, '')
165
+ }
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto'
19
19
  import fs from 'node:fs'
20
20
  import path from 'node:path'
21
21
  import { isRecord } from './is-record'
22
+ import { swiftInterfaceWithoutBodies } from './swift-interface-bodies'
22
23
  import { swiftObjCPlatformFlags } from './swift-objc-platform'
23
24
  import { IOS_SDK_HEADERS, IOS_SDK_OVERLAYS, IOS_SDK_TARGET } from './swift-objc-sdk'
24
25
  import { clangImportNames, moduleMapTokens } from './swift-package'
@@ -460,6 +461,63 @@ function sdkDeclaration(name: string): SdkDeclaration | null {
460
461
  return sdkDeclarations.get(name) ?? null
461
462
  }
462
463
 
464
+ // a swift interface as the intake sdk reads it. the intake reads
465
+ // CoreFoundation's headers textually, so a type the interface qualifies with
466
+ // CoreFoundation lives in CoreGraphics (CGSize, CGFloat) or Foundation, and
467
+ // inlinable bodies would type-check against its partial Foundation, so only
468
+ // the declarations load.
469
+ function intakeInterface(text: string, keepDefaults = false): string {
470
+ return (
471
+ swiftInterfaceWithoutBodies(text, keepDefaults)
472
+ // the intake modules have no clang submodules
473
+ .replace(/^((?:@_exported[ \t]+)?import[ \t]+)(\w+)\.[\w.]+[ \t]*$/gm, '$1$2')
474
+ .replace(/\bCoreFoundation\.(?=CG)/g, 'CoreGraphics.')
475
+ .replace(/\bCoreFoundation\.(?=\w)/g, 'Foundation.')
476
+ // the intake has no Darwin module; OSStatus is its Int32
477
+ .replace(/\bDarwin\.OSStatus\b/g, 'Swift.Int32')
478
+ // Data's base64 option sets live in the swift overlay; the
479
+ // intake only has their NSData spelling, which Data aliases.
480
+ .replace(
481
+ /\bFoundation\.Data\.(?=Base64(?:En|De)codingOptions\b)/g,
482
+ 'Foundation.NSData.',
483
+ )
484
+ )
485
+ }
486
+
487
+ // a vendor framework directory the headers import, its swift interfaces read
488
+ // as the intake reads the surface's own.
489
+ function stageVendorFrameworks(dir: string, stage: string): string {
490
+ fs.mkdirSync(stage, { recursive: true })
491
+ for (const entry of fs.readdirSync(dir)) {
492
+ if (!entry.endsWith('.framework')) continue
493
+ const source = path.join(dir, entry)
494
+ const target = path.join(stage, entry)
495
+ fs.mkdirSync(target, { recursive: true })
496
+ for (const child of fs.readdirSync(source)) {
497
+ if (child !== 'Modules') {
498
+ fs.symlinkSync(path.join(source, child), path.join(target, child))
499
+ continue
500
+ }
501
+ for (const file of fs.readdirSync(path.join(source, child), {
502
+ recursive: true,
503
+ encoding: 'utf8',
504
+ })) {
505
+ const from = path.join(source, child, file)
506
+ const to = path.join(target, child, file)
507
+ if (fs.statSync(from).isDirectory()) {
508
+ fs.mkdirSync(to, { recursive: true })
509
+ continue
510
+ }
511
+ fs.mkdirSync(path.dirname(to), { recursive: true })
512
+ if (file.endsWith('.swiftinterface'))
513
+ fs.writeFileSync(to, intakeInterface(fs.readFileSync(from, 'utf8')))
514
+ else fs.copyFileSync(from, to)
515
+ }
516
+ }
517
+ }
518
+ return stage
519
+ }
520
+
463
521
  /** the symbol graphs, enum values and imports of an objective-c module. */
464
522
  export function objcView(
465
523
  surface: ObjcSurface,
@@ -507,6 +565,7 @@ export function objcView(
507
565
  fs.writeFileSync(file, text)
508
566
  }
509
567
  const interfaceCopies: string[] = []
568
+ let keepDefaults = true
510
569
  if (framework) {
511
570
  fs.mkdirSync(path.join(frameworks, `${surface.module}.framework`, 'Modules'), {
512
571
  recursive: true,
@@ -541,18 +600,7 @@ export function objcView(
541
600
  interfaceCopies.push(path.join(to, name))
542
601
  fs.writeFileSync(
543
602
  path.join(to, name),
544
- fs
545
- .readFileSync(path.join(from, name), 'utf8')
546
- .replace(/\bCoreFoundation\.(?=CG)/g, 'CoreGraphics.')
547
- .replace(/\bCoreFoundation\.(?=\w)/g, 'Foundation.')
548
- // the intake has no Darwin module; OSStatus is its Int32
549
- .replace(/\bDarwin\.OSStatus\b/g, 'Swift.Int32')
550
- // Data's base64 option sets live in the swift overlay; the
551
- // intake only has their NSData spelling, which Data aliases.
552
- .replace(
553
- /\bFoundation\.Data\.(?=Base64(?:En|De)codingOptions\b)/g,
554
- 'Foundation.NSData.',
555
- ),
603
+ intakeInterface(fs.readFileSync(path.join(from, name), 'utf8'), true),
556
604
  )
557
605
  }
558
606
  }
@@ -619,7 +667,10 @@ export function objcView(
619
667
  stubs,
620
668
  '-I',
621
669
  stubIncludes,
622
- ...vendorFrameworks.flatMap((dir) => ['-F', dir]),
670
+ ...vendorFrameworks.flatMap((dir, index) => [
671
+ '-F',
672
+ stageVendorFrameworks(dir, path.join(work, 'vendor-frameworks', String(index))),
673
+ ]),
623
674
  ]
624
675
  const missing: Missing = new Map()
625
676
  const stubbed = new Set<string>()
@@ -853,6 +904,15 @@ export function objcView(
853
904
  )
854
905
  }
855
906
  }
907
+ // a default argument value the intake cannot type-check fails the whole
908
+ // interface, so the declarations load without any
909
+ if ((!progress || round === ROUNDS) && keepDefaults && interfaceCopies.length > 0) {
910
+ keepDefaults = false
911
+ for (const file of interfaceCopies)
912
+ fs.writeFileSync(file, swiftInterfaceWithoutBodies(fs.readFileSync(file, 'utf8')))
913
+ writeHeaders()
914
+ continue
915
+ }
856
916
  if (!progress || round === ROUNDS)
857
917
  throw new Error(
858
918
  `its headers do not import as Swift: ${
@@ -321,6 +321,32 @@ export function moduleMapName(text: string): string | undefined {
321
321
  return index < 0 ? undefined : tokens[index + 1]
322
322
  }
323
323
 
324
+ // the top-level modules a clang module map declares, each with its own text.
325
+ // a uniffi xcframework ships one map holding a module per generated header.
326
+ export function moduleMapModules(text: string): { name: string; text: string }[] {
327
+ const modules: { name: string; text: string }[] = []
328
+ const token = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|"[^"]*"|\{|\}|\bmodule\s+([\w.]+)/g
329
+ let depth = 0
330
+ let start = -1
331
+ let name = ''
332
+ for (const match of text.matchAll(token)) {
333
+ if (match[0] === '{') depth++
334
+ else if (match[0] === '}') {
335
+ depth--
336
+ if (depth === 0 && start >= 0) {
337
+ modules.push({ name, text: text.slice(start, match.index + 1) })
338
+ start = -1
339
+ }
340
+ } else if (match[1] !== undefined && depth === 0) {
341
+ const before = text.slice(0, match.index).trimEnd()
342
+ if (/\bextern$/.test(before)) continue
343
+ start = match.index
344
+ name = match[1]
345
+ }
346
+ }
347
+ return modules
348
+ }
349
+
324
350
  // swiftpm's module name for a target: its name as a c99 identifier, so
325
351
  // target `cmark-gfm` is imported as `cmark_gfm`.
326
352
  export function swiftTargetModuleName(name: string): string {
@@ -96,6 +96,7 @@ async function trunkSpecification(
96
96
  'specifications',
97
97
  `${checksum}.json`,
98
98
  )
99
+ const sha1 = (value: string) => createHash('sha1').update(value).digest('hex')
99
100
  let text: string
100
101
  if (fs.existsSync(file)) text = fs.readFileSync(file, 'utf8')
101
102
  else {
@@ -105,16 +106,38 @@ async function trunkSpecification(
105
106
  .slice(0, 3)
106
107
  .split('')
107
108
  .join('/')
108
- const response = await fetch(
109
- `https://cdn.cocoapods.org/Specs/${shard}/${root}/${version}/${root}.podspec.json`,
110
- )
109
+ const specPath = `Specs/${shard}/${root}/${version}/${root}.podspec.json`
110
+ const response = await fetch(`https://cdn.cocoapods.org/${specPath}`)
111
111
  if (!response.ok)
112
112
  throw new Error(
113
113
  `CocoaPods specification ${root} ${version}: HTTP ${response.status}`,
114
114
  )
115
115
  text = await response.text()
116
+ // trunk edits a published specification in place (a deprecation adds a
117
+ // field), so a lock written earlier names an older revision of the file.
118
+ if (sha1(text) !== checksum) {
119
+ const history = await fetch(
120
+ `https://api.github.com/repos/CocoaPods/Specs/commits?path=${specPath}&per_page=100`,
121
+ )
122
+ if (!history.ok)
123
+ throw new Error(`CocoaPods specification ${root} history: HTTP ${history.status}`)
124
+ const commits: unknown = await history.json()
125
+ for (const commit of Array.isArray(commits) ? commits : []) {
126
+ const sha = record(commit) ? commit.sha : undefined
127
+ if (typeof sha !== 'string') continue
128
+ const revision = await fetch(
129
+ `https://raw.githubusercontent.com/CocoaPods/Specs/${sha}/${specPath}`,
130
+ )
131
+ if (!revision.ok) continue
132
+ const revisionText = await revision.text()
133
+ if (sha1(revisionText) === checksum) {
134
+ text = revisionText
135
+ break
136
+ }
137
+ }
138
+ }
116
139
  }
117
- if (createHash('sha1').update(text).digest('hex') !== checksum)
140
+ if (sha1(text) !== checksum)
118
141
  throw new Error(`${root} podspec checksum differs from ${lockFile}`)
119
142
  const spec: unknown = JSON.parse(text)
120
143
  if (!record(spec) || spec.name !== root || spec.version !== version)
@@ -74,7 +74,7 @@ export function restoreSwiftInterfaceSPI(
74
74
  for (const line of interfaceText.split('\n')) {
75
75
  const enclosing = scopes.at(-1)?.path ?? []
76
76
  const declaration =
77
- /^\s*(?:@[\w.]+(?:\([^)]*\))?\s+|\w+\s+)*?(class(?!\s+(?:func|var|subscript)\b)|struct|enum|protocol|actor|extension|func|var|let|init|subscript|typealias|case)\b\s*([\w.`]*)/.exec(
77
+ /^\s*(?:@[\w.]+(?:\([^)]*\))?\s+|\w+\s+)*?(class(?!\s+(?:func|var|subscript)\b)|struct|enum|protocol|actor|extension|func|var|let|init|subscript|typealias|case)\b\s*([/=\-+!*%<>&|^~?]+|[\w.`]*)/.exec(
78
78
  line.replace(/::/g, '.'),
79
79
  )
80
80
  if (declaration) {
@@ -178,6 +178,8 @@ export type StandinOptions = {
178
178
  plain?: ReadonlySet<string>
179
179
  /** paths whose type the guest cannot represent in objective-c: emitted without @objc */
180
180
  unobjc?: ReadonlySet<string>
181
+ /** class path → objective-c protocols the compiler found its witnesses mismatch (nullability a swift protocol checks); the class drops the conformance */
182
+ unconformed?: ReadonlyMap<string, ReadonlySet<string>>
181
183
  /** paths the compiler says must be marked override */
182
184
  forcedOverride?: ReadonlySet<string>
183
185
  /** class path → its generic superclass with arguments, from the swiftinterface */
@@ -267,6 +269,25 @@ const KEEP_EXTERNAL = new Map([
267
269
  ['s:Se', 'Decodable'],
268
270
  ['s:s23CustomStringConvertibleP', 'CustomStringConvertible'],
269
271
  ['s:s9CodingKeyP', 'CodingKey'],
272
+ ['s:SL', 'Comparable'],
273
+ ['s:ST', 'Sequence'],
274
+ ['s:St', 'IteratorProtocol'],
275
+ ['s:Sl', 'Collection'],
276
+ ['s:SK', 'BidirectionalCollection'],
277
+ ['s:Sk', 'RandomAccessCollection'],
278
+ ['s:SM', 'MutableCollection'],
279
+ ['s:Sm', 'RangeReplaceableCollection'],
280
+ ['s:s28CustomDebugStringConvertibleP', 'CustomDebugStringConvertible'],
281
+ ['s:s25LosslessStringConvertibleP', 'LosslessStringConvertible'],
282
+ ['s:s20TextOutputStreamableP', 'TextOutputStreamable'],
283
+ ['s:Sx', 'Strideable'],
284
+ ['s:s18AdditiveArithmeticP', 'AdditiveArithmetic'],
285
+ ['s:s25ExpressibleByArrayLiteralP', 'ExpressibleByArrayLiteral'],
286
+ ['s:s30ExpressibleByDictionaryLiteralP', 'ExpressibleByDictionaryLiteral'],
287
+ ['s:s26ExpressibleByStringLiteralP', 'ExpressibleByStringLiteral'],
288
+ ['s:s27ExpressibleByIntegerLiteralP', 'ExpressibleByIntegerLiteral'],
289
+ ['s:s25ExpressibleByFloatLiteralP', 'ExpressibleByFloatLiteral'],
290
+ ['s:s27ExpressibleByBooleanLiteralP', 'ExpressibleByBooleanLiteral'],
270
291
  ])
271
292
  const LEADING_ATTRS = /^((?:@\w+(?:\([^)]*\))?\s*|nonisolated\s+)*)(.*)$/s
272
293
 
@@ -1488,9 +1509,15 @@ export function emitSwiftStandin(
1488
1509
  !(
1489
1510
  parent &&
1490
1511
  objectConformers.has(id) &&
1491
- ['Hashable', 'Equatable', 'CustomStringConvertible'].includes(name)
1512
+ [
1513
+ 'Hashable',
1514
+ 'Equatable',
1515
+ 'CustomStringConvertible',
1516
+ 'CustomDebugStringConvertible',
1517
+ ].includes(name)
1492
1518
  ) &&
1493
- !(name === nsObjectProtocol && externalRoot(id) === nsObject),
1519
+ !(name === nsObjectProtocol && externalRoot(id) === nsObject) &&
1520
+ !options.unconformed?.get(path)?.has(name),
1494
1521
  ),
1495
1522
  ].filter((name, at, all) => all.indexOf(name) === at)
1496
1523
  const parentSymbol = superId.has(id) ? byId.get(superId.get(id)!) : undefined
@@ -45,6 +45,7 @@ import {
45
45
  } from './swift-objc-standin'
46
46
  import {
47
47
  clangImportNames,
48
+ moduleMapModules,
48
49
  moduleMapName,
49
50
  swiftManifestDeclarations,
50
51
  swiftTargetModuleName,
@@ -674,6 +675,22 @@ function cxxOnly(surface: Surface): boolean {
674
675
  })
675
676
  }
676
677
 
678
+ // the surfaces of a library slice's module map, one per module it declares: a
679
+ // uniffi xcframework ships a module per generated header in one map, and an
680
+ // app imports each by name.
681
+ function mapSurfaces(modulemap: string): SliceSurface[] {
682
+ const text = fs.readFileSync(modulemap, 'utf8')
683
+ const headers = path.dirname(modulemap)
684
+ const modules = moduleMapModules(text)
685
+ if (modules.length <= 1) {
686
+ const module = moduleMapName(text)
687
+ return module ? [headerSurface(module, modulemap, headers)] : []
688
+ }
689
+ return modules.map((module) =>
690
+ headerSurface(module.name, generatedModuleMap(`${module.text}\n`, false), headers),
691
+ )
692
+ }
693
+
677
694
  function simulatorSlice(xcframeworkDir: string): string | null {
678
695
  const slice = fs
679
696
  .readdirSync(xcframeworkDir)
@@ -762,8 +779,7 @@ function surfaces(
762
779
  const headers = path.join(root, 'Headers')
763
780
  for (const map of files(headers, (name) => name === 'module.modulemap')) {
764
781
  const modulemap = path.join(headers, map)
765
- const module = moduleMapName(fs.readFileSync(modulemap, 'utf8'))
766
- if (module) found.push(headerSurface(module, modulemap, path.dirname(modulemap)))
782
+ found.push(...mapSurfaces(modulemap))
767
783
  }
768
784
  if (found.length === 0 && files(headers, (name) => name.endsWith('.h')).length) {
769
785
  const module = swiftTargetModuleName(target)
@@ -964,13 +980,22 @@ export function swiftInterfaceForWasm(text: string, cFlags: readonly string[]):
964
980
  ])
965
981
  return `${interop} ${xcc.join(' ')}`
966
982
  })
967
- return retargeted
968
- .split('\n')
983
+ const lines = retargeted.split('\n')
984
+ // an optional requirement stays @objc, so a type its signature names must be
985
+ // objective-c representable: an enum it names keeps its own @objc
986
+ const required = lines.filter((line) => OPTIONAL_REQUIREMENT.test(line)).join('\n')
987
+ return lines
969
988
  .map((line) => {
970
989
  // an enum's @objc only bridges it to objective-c, which wasm has no
971
990
  // runtime for, and an @objc error enum loses its raw-value Hashable.
972
- if (/\benum\s/.test(line) && TYPE_DECLARATION.test(line))
973
- return line.replace(/@objc(?:\s*\([^()]*\))?\s+/g, '')
991
+ if (/\benum\s/.test(line) && TYPE_DECLARATION.test(line)) {
992
+ const name = /\benum\s+(\w+)/.exec(line)?.[1]
993
+ return name &&
994
+ new RegExp(`\\b${name}\\b`).test(required) &&
995
+ !/\bSwift\.Error\b/.test(line)
996
+ ? line
997
+ : line.replace(/@objc(?:\s*\([^()]*\))?\s+/g, '')
998
+ }
974
999
  if (TYPE_DECLARATION.test(line)) return line.replace(/@objcMembers\b/g, '')
975
1000
  if (OPTIONAL_REQUIREMENT.test(line)) return line
976
1001
  return line.replace(OBJC_ATTRIBUTE, '')
@@ -1522,6 +1547,18 @@ function graphStandin(
1522
1547
  const plain = new Set<string>()
1523
1548
  const unobjc = new Set<string>()
1524
1549
  const trapped = new Set<string>()
1550
+ const unconformed = new Map<string, Set<string>>()
1551
+ const objcProtocols = new Set(
1552
+ graphs.flatMap((graph) =>
1553
+ graph.symbols
1554
+ .filter(
1555
+ (symbol) =>
1556
+ symbol.kind.identifier === 'swift.protocol' &&
1557
+ symbol.identifier.precise.startsWith('c:'),
1558
+ )
1559
+ .map((symbol) => symbol.pathComponents.join('.')),
1560
+ ),
1561
+ )
1525
1562
  const file = path.join(work, `${module}.swift`)
1526
1563
  // a source target whose implementation compiles keeps inert bodies, and a
1527
1564
  // placeholder sends nothing. a target replaced because assembly cannot
@@ -1540,6 +1577,7 @@ function graphStandin(
1540
1577
  omit,
1541
1578
  plain,
1542
1579
  unobjc,
1580
+ unconformed,
1543
1581
  trapped,
1544
1582
  inert,
1545
1583
  imports: [...(placeholder?.peachImports ?? []), ...imports],
@@ -1589,7 +1627,7 @@ function graphStandin(
1589
1627
  files: { [`${module}.swift`]: source },
1590
1628
  ...(imports.length ? { dependencies: imports } : {}),
1591
1629
  }
1592
- const before = omit.size + plain.size + unobjc.size + trapped.size
1630
+ const before = omit.size + plain.size + unobjc.size + trapped.size + unconformed.size
1593
1631
  const elsewhere: string[] = []
1594
1632
  for (const match of check.stderr.matchAll(/^(\S+?):(\d+):\d+: error: (.*)$/gm)) {
1595
1633
  const owner = match[1] === file ? owners[Number(match[2]) - 1] : undefined
@@ -1599,6 +1637,15 @@ function graphStandin(
1599
1637
  unobjc.add(owner)
1600
1638
  continue
1601
1639
  }
1640
+ // an objective-c protocol the guest adapts to a swift one checks the
1641
+ // nullability a swift class's exported witness differs in
1642
+ const conformance = /^type '\w+' does not conform to protocol '(\w+)'$/.exec(
1643
+ match[3]!,
1644
+ )?.[1]
1645
+ if (owner && conformance && objcProtocols.has(conformance)) {
1646
+ unconformed.set(owner, new Set([...(unconformed.get(owner) ?? []), conformance]))
1647
+ continue
1648
+ }
1602
1649
  if (owner && swiftPaths.has(owner))
1603
1650
  throw new Error(
1604
1651
  `Swift declaration ${module}.${owner} does not typecheck: ${match[3]}`,
@@ -1615,7 +1662,7 @@ function graphStandin(
1615
1662
  else if (owner) omit.set(owner, `rejected by swiftc: ${match[3]}`)
1616
1663
  else elsewhere.push(match[3]!)
1617
1664
  }
1618
- if (omit.size + plain.size + unobjc.size + trapped.size === before)
1665
+ if (omit.size + plain.size + unobjc.size + trapped.size + unconformed.size === before)
1619
1666
  throw new Error(
1620
1667
  `its generated source does not typecheck: ${elsewhere.slice(0, 3).join('; ') || check.stderr.trim().slice(0, 400)}`,
1621
1668
  )
@@ -1788,7 +1835,20 @@ function cStandin(
1788
1835
  toolchain: Toolchain,
1789
1836
  work: string,
1790
1837
  ): SwiftStandin {
1791
- const headers = files(surface.headers, (name) => name.endsWith('.h'))
1838
+ const everyHeader = files(surface.headers, (name) => name.endsWith('.h'))
1839
+ const moduleRoots = moduleMapHeaders(surface.modulemap, everyHeader)
1840
+ // the module's headers and the local headers they include: a map of several modules
1841
+ // (one per uniffi header) gives each its own
1842
+ const headers = [...moduleRoots]
1843
+ for (let at = 0; at < headers.length; at++)
1844
+ for (const [, local] of fs
1845
+ .readFileSync(path.join(surface.headers, headers[at]!), 'utf8')
1846
+ .matchAll(/^\s*#\s*(?:include|import)\s*["<]([^">]+)[">]/gm)) {
1847
+ const file = path.posix.normalize(
1848
+ path.posix.join(path.posix.dirname(headers[at]!), local!),
1849
+ )
1850
+ if (everyHeader.includes(file) && !headers.includes(file)) headers.push(file)
1851
+ }
1792
1852
  const framework = path.basename(path.dirname(surface.modulemap)) === 'Modules'
1793
1853
  const headerDirectory = framework ? `${surface.module}.framework/Headers` : '.'
1794
1854
  const texts = new Map(
@@ -1804,9 +1864,7 @@ function cStandin(
1804
1864
  fs.mkdirSync(path.dirname(path.join(include, file)), { recursive: true })
1805
1865
  fs.writeFileSync(path.join(include, file), text)
1806
1866
  }
1807
- const roots = moduleMapHeaders(surface.modulemap, headers).map((file) =>
1808
- path.posix.join(headerDirectory, file),
1809
- )
1867
+ const roots = moduleRoots.map((file) => path.posix.join(headerDirectory, file))
1810
1868
  const frameworkMap = `${surface.module}.framework/Modules/module.modulemap`
1811
1869
  const modulemap = `module ${surface.module} {\n${roots.map((file) => ` header "${file}"\n`).join('')} export *\n}\n`
1812
1870
  const probe = path.join(work, 'declarations.c')
@@ -2728,9 +2786,14 @@ async function binaryStandins(
2728
2786
  fs.rmSync(work, { recursive: true, force: true })
2729
2787
  return error instanceof Error ? error.message : String(error)
2730
2788
  }
2789
+ // a framework whose module map names headers it does not ship declares nothing
2731
2790
  const found = all.filter(
2732
2791
  (surface) =>
2733
2792
  !provided.has(surface.module) &&
2793
+ (surface.kind === 'swift' ||
2794
+ surface.kind === 'mixed' ||
2795
+ surface.kind === 'source' ||
2796
+ files(surface.headers, (name) => name.endsWith('.h')).length > 0) &&
2734
2797
  !(binary.cxxHeaders === undefined && cxxOnly(surface)),
2735
2798
  )
2736
2799
  if (found.length === 0) {
@@ -146,7 +146,8 @@ export const MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES = 16 * 1024 * 1024
146
146
  // 68: fetch headers replies carry `setCookies`, the response's Set-Cookie list.
147
147
  // 69: `peach.icu_item_v1` replaces `icu_data_v1`: the host fills icu's table of
148
148
  // contents and places each item's pack the first time icu opens it.
149
- export const SWIFT_HOST_ABI = 69
149
+ // 70: full Objective-C forwarding uses exact typed IMPs (`peach.imp_forwarder_mint`).
150
+ export const SWIFT_HOST_ABI = 70
150
151
 
151
152
  export interface SwiftImageUpload {
152
153
  /** the raster file picked for the asset (has the extension). */
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { installVerifiedTarball } from '@contrast/runtime-delivery'
4
4
  import { BINARYEN_VERSION, wasmOpt } from 'wasm-opt'
5
+ import { windowAdmission } from './admitted-mac-build'
5
6
  import { rnxHomeDir } from './home-paths'
6
7
 
7
8
  // packaged native executables, size-optimized. clang's own
@@ -60,6 +61,15 @@ async function runWasmOpt(
60
61
  skipPasses: readonly string[],
61
62
  binary: string,
62
63
  ): Promise<void> {
64
+ const admittedCores = windowAdmission()?.cores ?? Number(process.env.TM_EXEC_CPU_CORES)
65
+ const requestedCores = Number(process.env.BINARYEN_CORES)
66
+ const environment: NodeJS.ProcessEnv = { ...process.env, WASM_OPT_PATH: binary }
67
+ if (Number.isInteger(admittedCores) && admittedCores > 0)
68
+ environment.BINARYEN_CORES = String(
69
+ Number.isInteger(requestedCores) && requestedCores > 0
70
+ ? Math.min(admittedCores, requestedCores)
71
+ : admittedCores,
72
+ )
63
73
  const result = await wasmOpt(
64
74
  [
65
75
  '-Oz',
@@ -69,7 +79,7 @@ async function runWasmOpt(
69
79
  '-o',
70
80
  output,
71
81
  ],
72
- { env: { ...process.env, WASM_OPT_PATH: binary } },
82
+ { env: environment },
73
83
  )
74
84
  if (result.status !== 0)
75
85
  throw new Error(`wasm-opt exited ${result.status}: ${result.stderr}`)