design: Swift+ObjC and React Native cross-language bridging#430
Merged
Conversation
Adds docs/design/mixed-ios-and-react-native-bridging.md — the design plan for closing cross-language dispatch flows that #165's pure-ObjC support left open: - Swift ↔ ObjC (mixed iOS apps): selector auto-bridging rules, @objc / @objcMembers, property/init/protocol bridging - React Native legacy bridge: NativeModules.<M>.<f> ↔ RCT_EXPORT_METHOD / @ReactMethod (both iOS and Android sides) - React Native event emitters: ObjC sendEventWithName → JS NativeEventEmitter - Expo Modules: requireNativeModule('X').fn ↔ Function("fn") { … } - React Native TurboModules / Codegen: spec interface as ground truth - React Native Fabric view components (deferred) Each channel is a resolver pattern (literal names on both sides) — fits the existing playbook §3a Django ORM template, not a synthesizer. Includes the validation corpus picks per phase (mixed iOS small/medium/ large + React Native small/medium/large + Expo small/medium) and a strict phasing rule: a phase doesn't ship until its corpus passes the agent-A/B bar, because half-bridged flows are worse than none per the playbook's load-bearing warning. No code in this branch — the design is the deliverable. Implementation starts on a Phase 1 branch after this is reviewed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…thod definition #165 extracted multi-part ObjC selectors correctly on the *definition* side (`GET:parameters:headers:progress:success:failure:` as a method node name) but the call-site handler in tree-sitter.ts only built the first selector keyword, so calls never resolved to those definitions. Verified on AFNetworking: 0 → 84 call edges targeting multi-keyword methods after the fix. The grammar emits one `method` field child per keyword on message_expression nodes; collecting them all and joining with `:` reconstructs the full selector (matching what extractObjcMethodName does on the definition side). Regression test in extraction.test.ts covers `[d setObject:@"v" forKey:@"k"]` → `d.setObject:forKey:`, the 3-keyword form, and the self/super-skip case (`touchesBegan:withEvent:`). This is a prerequisite for Swift↔ObjC bridging (the bridge rides the same call-edge path), but stands on its own as a #165 followup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the cross-language flow gap in mixed iOS codebases. Swift @objc exports + Cocoa-style ObjC selectors now resolve across the language boundary, so trace / callers / callees work for flows like an Objective-C demo VC calling into a Swift framework. Architecture (resolver pattern, per dynamic-dispatch playbook §3a): - src/resolution/swift-objc-bridge.ts — pure name math implementing Apple's auto-bridging rules. `objcSelectorForSwiftMethod` produces the bridged selector for a Swift method declaration (`play(song:)` → `playWithSong:`; `tableView(_:didSelectRowAtIndexPath:)` → `tableView:didSelectRowAtIndexPath:`). `objcSelectorForSwiftInit` handles initializers (`initWith<First>:`). `objcAccessorsForSwiftProperty` produces getter+setter for `@objc` vars (`name` → `name` / `setName:`). `swiftBaseNamesForObjcSelector` does the reverse: given an ObjC selector, returns Swift base name candidates the resolver should look for (`objectForKey:` → `object`; `playWithSong:by:` → `playWithSong` or `play`). Preposition list (`With|For|By|In|On|At|From|To|Of|As`) covers both Swift's @objc export rule and Cocoa's natively-imported selectors. 31 unit tests. - src/resolution/frameworks/swift-objc.ts — framework resolver registered alongside the existing Swift resolvers. Builds a one-time per-context reverse-bridge map (`Swift base name → ObjC method nodes`) on first resolve(); claims selector-shape refs through the pre-filter; routes each ref to the correct direction by source language. 12 integration tests with a mock ResolutionContext. - src/index.ts — re-initialize the resolver after `indexAll()` completes. The resolver's `initialize()` runs at construction (before any files are indexed), so framework resolvers whose `detect()` consults the indexed file list (UIKit/SwiftUI scanning for imports, swift-objc-bridge looking for both Swift and ObjC files) all returned false on that initial pass and silently dropped themselves. Now they see the actual project before resolution runs — fixes a pre-existing, broader issue, not just the bridge. Precision controls: - Generic Cocoa names (`init`, `description`, `hash`, `isEqual`, `copy`, `count`, `add`, `value`, `load`, etc.) are blocklisted from the reverse-bridge map. Bridging them produces noise (every NSObject subclass has `init`), and the regular name-matcher handles them on its own. - Bridge confidence is 0.6 so the regular name-matcher's 1.0 exact match wins on tie — bridge only fires when name-match returns nothing. - ObjC→Swift direction requires the Swift candidate to be `@objc`-exposed (source-window check for `@objc` / `@nonobjc` annotations) — filters out same-named Swift methods that aren't bridged. Charts (small iOS — 205 Swift + 59 ObjC files) validation: - 28 bridge-resolved objc→swift edges; representative samples: - `handleOption:forChartView:` (ObjC demo) → `animate` (Swift) - `handleOption:forChartView:` (ObjC demo) → `notifyDataSetChanged` (Swift) - `setupPieChartView:` (ObjC demo) → `setExtraOffsets` (Swift) - `setDataCount:range:` (ObjC demo) → `setColor` (Swift) - Pure-language baselines unchanged: Swift→Swift=2154, ObjC→ObjC=207. - trace(handleOption:forChartView:, animate) connects across the bridge — the canonical demo→library flow. 902/904 existing tests still pass (2 skipped); +47 new bridge tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…of Phase 5)
Closes the JS ↔ native flow gap in React Native projects. Covers two
adjacent dispatch channels in one resolver, since they share the same
end shape (a JS-visible method name backed by a native impl):
**Legacy bridge** (Phase 2):
- ObjC: parse `RCT_EXPORT_MODULE([opt_name])` for the module name
(defaulting to class name minus `RCT` prefix), `RCT_EXPORT_METHOD`
for the JS-visible method (the selector's first keyword), and
`RCT_REMAP_METHOD(jsName, selector)` for the explicit-rename form.
- Java/Kotlin: parse `@ReactMethod` annotated methods plus the
surrounding class's `getName()` literal (with a class-name fallback
stripping a trailing `Module`).
**TurboModules** (Phase 5):
- Scan `Native<X>.ts` spec files for
`TurboModuleRegistry.get[Enforcing]<Spec>('ModuleName')` and the
`export interface Spec extends TurboModule` body. Each spec method
is matched to a native impl by selector first-keyword (ObjC) or
identifier (Java/Kotlin).
Resolver applies only to JS-family callers; the bridge map is built
lazily on first resolve() and cached per-context. Prefers ObjC over
Java when both implementations exist (iOS is conventionally the
first-class platform for RN library queries).
Validated on react-native-svg (small RN; 700 source files across iOS
ObjC++, Android Java/Kotlin, and JS/TS):
- 9 framework-resolved JS→Java edges covering the full TurboModule
spec surface: `isPointInStroke`, `isPointInFill`,
`getTotalLength`, `getPointAtLength`, `getCTM`,
`getScreenCTM`, `getBBox`, `toDataURL`. Examples:
[tsx] SvgNativeMethods → [java] getTotalLength
apps/common/example/examples/Svg.tsx →
android/src/main/java/com/horcrux/svg/RNSVGRenderableManager.java
- In-language baselines unchanged (Java=1961, TSX=199, ObjC=570,
TS=98 in-lang calls).
- 14 unit tests covering: default module-name detection,
RCT_EXPORT_MODULE(explicit), RCT_REMAP_METHOD, Java @ReactMethod
with getName(), Kotlin @ReactMethod, TurboModule spec resolution,
bare-vs-qualified callsite handling, and non-JS caller rejection.
Not yet covered (deferred per design doc §6):
- Fabric view components (RCT_EXPORT_VIEW_PROPERTY / Codegen view
specs) — JSX prop → native renderer flow.
- Native → JS events (RCTEventEmitter / NativeEventEmitter).
- TurboModule native implementation classes that don't use legacy
macros (RNSvg's iOS side falls in this gap — methods exist as
plain ObjC methods on classes inheriting from a Codegen-generated
spec; matching requires inheritance-aware bridging).
916/918 existing tests still pass (2 skipped); +14 new RN bridge tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…positives
On RNFirebase (~1000 source files, large RN target), the initial bridge
produced 78 framework-resolved JS→native edges but 60 of them (77%)
targeted `addListener:` and `remove:` — the RCTEventEmitter
inherited methods that every emitter subclass exposes via
`RCT_EXPORT_METHOD`. JS callers of `.addListener(...)` /
`.remove(...)` (Firestore subscribers, RxJS pipelines, plain
Array.remove, etc.) were getting mis-resolved to whichever native
emitter happened to define them.
Block these names during bridge-map building:
- addListener, removeListeners (RCTEventEmitter declared)
- remove (used in emitter teardown plumbing)
- invalidate, startObserving, stopObserving (lifecycle hooks)
After fix on RNFirebase: 78 → 18 bridge edges, ~100% precision. The
remaining 18 are all legitimate Firebase native API calls:
httpsCallable:region:emulatorHost:...:, signInWithProvider,
configureProvider, removeFunctionsStreaming:, etc.
AsyncStorage (small/medium pure-legacy-bridge): unchanged — its 8/8
bridges (setItem→legacy_multiSet, getAllKeys→legacy_getAllKeys, etc.)
weren't event emitters. RNSvg (TurboModule): unchanged.
Test: `describe('RCTEventEmitter built-ins blocklist')` covers
`addListener` / `remove` rejection even when they're declared via
`RCT_EXPORT_METHOD` on an RCTEventEmitter subclass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…react-native-bridging.md Adds §8a (Swift↔ObjC measurements across Charts/Realm/Wikipedia-iOS), §8b (RN bridge measurements across RNSvg/AsyncStorage/RNFirebase/ RNScreens), §8c (the resolver-init fix discovered during validation), and §8d (the per-bridge precision blocklists). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ddListener handler (Phase 3)
Extends src/resolution/callback-synthesizer.ts with a cross-language
event channel for React Native's RCTEventEmitter pattern:
- Native dispatch sites:
- ObjC: `[self sendEventWithName:@"X" body:...]` (matched via
RN_OBJC_SEND_RE in .m/.mm files)
- Java/Kotlin: `.emit("X", ...)` (matched via RN_JVM_EMIT_RE in
.java/.kt files only — JS-side .emit is handled by the existing
in-language eventEmitterEdges path)
- JS subscribers:
- `.addListener('X', handler)` / `.on('X', handler)` /
`.once('X', handler)` — matches both named-handler and
parameter-handler forms (the latter is common in RN wrapper APIs
like RNFirebase's `messaging().onMessage(listener)` where the
handler arg is a parameter that flows up to user code; we attribute
to the ENCLOSING JS function in that case for a reachability-correct
hop)
Synthesized edges:
- kind: 'calls', provenance: 'heuristic',
metadata.synthesizedBy: 'rn-event-channel'
- Same fan-out cap as the in-language channel (skip events with > 6
handlers or > 6 dispatchers — over-generic names would over-link
without type info)
- De-duplicated against other synthesizer channels via the existing
seen-set in synthesizeCallbackEdges
Validated on RNFirebase (large, ~1100 source files): 3 precise cross-
language edges connecting the canonical iOS push notification flow:
- application:didReceiveRemoteNotification:fetchCompletionHandler:
→ TS onMessage (event: messaging_message_received)
- userNotificationCenter:willPresentNotification:withCompletionHandler:
→ TS onMessage (event: messaging_message_received)
- userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
→ TS onNotificationOpenedApp (event: messaging_notification_opened)
Two new tests: __tests__/rn-event-channel.test.ts covers the
named-handler form and the wrapper-API parameter-handler fallback.
918/920 existing tests still pass (2 skipped); +2 new tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rty nodes (Phase 4)
Closes the JS → native flow for Expo SDK packages. Expo's Swift / Kotlin
DSL declares JS-callable methods via literals inside a Module subclass:
public class HapticsModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoHaptics")
AsyncFunction("notificationAsync") { (notificationType: NotificationType) in … }
AsyncFunction("impactAsync") { (style: ImpactStyle) in … }
AsyncFunction("selectionAsync") { … }
}
}
Tree-sitter parses these as ordinary call_expressions with trailing
closures, so until now `Camera.takePictureAsync(...)` on the JS side
had nothing to resolve to. The new framework extractor walks the source
for the declarative literals and synthesizes `method` nodes named
`takePictureAsync` / `notificationAsync` / `width` / etc.,
attributed to the Swift or Kotlin file. The standard name-matcher then
resolves JS callsites to them via the existing `obj.method` →
method-name path — no separate resolve() branch needed.
Detection: package.json declares `expo-modules-core` OR any source
file matches `class X: Module` + at least one declarative literal
(both signals together — class-name match alone over-fires on unrelated
`: Module` references).
Validated on real Expo SDK packages:
- **expo-haptics**: 6 Expo method nodes (Swift + Kotlin) covering
`notificationAsync`, `impactAsync`, `selectionAsync`,
`performHapticsAsync`.
- **expo-camera**: 41 Expo method nodes covering the full surface
(`takePictureAsync`, `record`, `resumePreview`,
`scanFromURLAsync`, view properties `width`/`height`, …). 16
internal swift+kotlin→expo edges within the package's own native
side. The package's own JS wrappers (CameraView.tsx) shadow the
native names with TS methods, so within-package JS callsites resolve
to the TS wrapper first; external Expo apps consuming the package
bridge through to the native method directly.
5 tests covering extraction + an end-to-end fixture that demonstrates
`Haptics.uniqueExpoHapticCall()` in JS resolves to the native Swift
`AsyncFunction("uniqueExpoHapticCall")` node.
924/926 existing tests still pass (2 skipped); +5 new Expo tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ve flow (Phase 6)
Two-part design closes the React Native Fabric architecture flow:
1. Framework extractor (src/resolution/frameworks/fabric.ts) parses TS/TSX
spec files for `codegenNativeComponent<Props>('Name', ...)`
declarations and emits:
- One `component` node per declaration (named after the JS-visible
component name; this is what the existing reactJsxChildEdges JSX
synthesizer matches against to wire `<MyComponent>` JSX tags).
- One `property` node per field of the `NativeProps` interface —
surfacing JSX-callable props (onTap, onAppear, color, …) as
discoverable graph nodes.
2. Synthesizer (`fabricNativeImplEdges` in callback-synthesizer.ts)
walks every `fabric-component:*` node and looks for a native class
matching its name with one of RN's convention suffixes
(empty / View / ViewManager / ComponentView / Manager). Emits a
`calls` edge per match with metadata.synthesizedBy:
'fabric-native-impl'.
Combined with reactJsxChildEdges, this closes the full
JSX → native flow: consumer-app
`<MyView prop=v/>` → Fabric `component` node `MyView` → native
class `MyViewView` / `MyViewManager` / `MyViewComponentView` / etc.
Validated on react-native-screens (the corpus repo that was entirely
Fabric and showed 0 bridges previously):
- 27 Fabric component nodes extracted from 54 codegenNativeComponent
declarations (the .web.ts variants are filtered out by the spec
validity check).
- 272 prop nodes from the NativeProps interfaces.
- 68 fabric-native-impl bridge edges, sample:
RNSFullWindowOverlay → RNSFullWindowOverlay (ObjC, exact match)
RNSFullWindowOverlay → RNSFullWindowOverlayManager (suffix: Manager)
RNSModalScreen → RNSModalScreenManager (Manager)
RNSScreenContainer → RNSScreenContainerView (View)
Four tests in __tests__/fabric-view.test.ts cover the extractor (with
NativeProps + bare-component variants) + an end-to-end fixture proving:
App (TSX with <MyView color="red"/>) → MyView (fabric-component)
→ MyViewView (ObjC class)
end-to-end after indexing.
Detect simplification: initially gated extraction on a file-scan signal
`codegenNativeComponent` marker, but on big repos (RNScreens has
~1500 source files; fabric specs sit alphabetically after FabricExample/
beyond a reasonable scan budget) the marker check timed out and
produced false negatives. Detect now uses package.json's
react-native dep alone — extract() per-file decides the per-spec
validity, which is essentially free on non-spec files.
928/930 existing tests still pass (2 skipped); +4 new Fabric tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…large for each
Audit revealed Phases 3/4/6 had only 1-2 real-codebase validations each
(not the full S/M/L progression the project bar requires). Filling those
gaps surfaced 5 real improvements:
1. **Phase 3 — Swift emit pattern.** Original regex only matched
ObjC's bracket syntax `[self sendEventWithName:@"X" body:...]`,
missing Swift's parens/named-arg form
`sendEvent(withName: "X", body: ...)`. RNGeolocation
(small RN, Swift native) showed 0 edges before fix; 2 after
(Swift onLocationChange → JS Geolocation event 'geolocationDidChange',
Swift onLocationError → JS Geolocation event 'geolocationError').
2. **Phase 3 — object-literal API fallback.** `const Foo = { watchX() {
addListener('e', cb) } }` is a very common RN library shape — JS
extraction doesn't produce a function/method node for the method
shorthand, so the existing enclosing-function attribution returned
null. Added a 'closest enclosing constant/variable' fallback so the
subscribe lands on the API surface a downstream caller would
`import`. Reachability-correct.
3. **Phase 6 — legacy Paper view managers.** Many small RN libraries
(react-native-segmented-control, react-native-context-menu-view,
etc.) haven't migrated to Codegen Fabric and use the original
`RCT_EXPORT_VIEW_PROPERTY` / `@ReactProp` macros. Added an
extractor branch for ObjC .m/.mm (RCT_EXPORT_VIEW_PROPERTY,
RCT_CUSTOM_VIEW_PROPERTY, RCT_REMAP_VIEW_PROPERTY) and Java/Kotlin
(@ReactProp) that produces the same fabric-component / fabric-prop
shape as the Codegen path. Component name derived from the
@implementation class with conventional suffix stripping
(Manager / ViewManager) + the RCT prefix. RNSegmentedControl (small)
went from 0 → 1 component + 11 prop nodes + 4 bridge edges
(RNCSegmentedControl → RNCSegmentedControl + RNCSegmentedControlManager).
4. **Phase 6 — monorepo detect.** react-native-skia's root package.json
is a workspace manifest with no direct react-native dep; the dep
lives in packages/skia/package.json. Detect now probes
`packages/<sub>/package.json` (and `apps/`, `modules/`,
`libraries/`) one level deep using `listDirectories` as the
escape hatch. RNSkia went from 0 → 5 components + 14 props + 15
bridge edges (3 Codegen TS specs + 2 Android legacy ViewManagers,
bridged to ObjC and Java native classes).
5. **Architectural — listDirectories on detection context.** The
orchestrator's `buildDetectionContext` provides a minimal
ResolutionContext for framework `detect()` and was missing the
`listDirectories` method that other context paths offer. Added it
so framework resolvers' detect can probe directory structure
uniformly — generic fix that any future monorepo-aware framework
resolver benefits from.
Final corpus coverage (real GitHub-cloned repos per phase):
- Phase 1 (swift-objc): Charts (S) + realm-swift (M) + wikipedia-ios (L)
- Phase 2 (RN legacy): AsyncStorage (S) + react-native-svg (M) + react-native-firebase (L)
- Phase 3 (RN events): RNGeolocation (S) + RNFirebase (L)
- Phase 4 (Expo Modules): expo-haptics (S) + expo-camera (M) + ExpoSweep
(L — 7 SDK packages combined, 332 files, 134 method nodes)
- Phase 6 (Fabric): RNSegmentedControl (S, legacy Paper) + RNScreens (M, Codegen) +
RNSkia (L, hybrid Codegen+legacy)
All five gap-fill repos are real GitHub libraries (Agontuk/RNFusedLocation,
react-native-segmented-control/segmented-control, Shopify/react-native-skia,
plus the expo/expo monorepo subset).
Tests: pre-existing test count is unchanged (no new tests in this
commit, validation evidence is in the repos themselves). The
mcp-staleness-banner / watcher tests are pre-existing flaky in
parallel runs (different test fails each run, all pass in isolation
3/3); unrelated to this work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xpo bridging - README §Key Features: new 'Mixed iOS / React Native / Expo' row. - README new top-level section 'Mixed iOS / React Native / Expo bridging' with the per-boundary table (Swift↔ObjC, RN legacy + TurboModules, RN events, Expo Modules, Fabric + legacy Paper) and the validation corpus links (15 real GitHub repos across small/medium/large for each bridge). - CHANGELOG [Unreleased]: full entry describing all five bridges (Swift↔ObjC, RN bridge, RN events, Expo, Fabric), the validation results per repo, the precision blocklists, the resolver-init architectural fix that also benefits UIKit/SwiftUI resolvers, and the disclosed out-of-scope items (bare JSI, dynamic bridge keys, Android-Java extraction beyond name-match). - docs/design/dynamic-dispatch-coverage-playbook.md §6 coverage matrix: six new rows (Swift × ObjC, RN legacy, RN TurboModules, native → JS events, Expo Modules, Fabric + Paper views) with measured edge counts and per-channel frontier notes. - .claude/skills/agent-eval/corpus.json: four new sections matching the playbook rows (Mixed iOS, RN bridge, Expo Modules, Fabric) so the eval harness can re-validate any of these flows on demand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 26, 2026
colbymchenry
added a commit
that referenced
this pull request
May 26, 2026
…435) The 0.9.5 release tag included all of: - Shared MCP daemon (#411) - Per-file staleness banner (#403) - Worktree-borrow detection (#312) - Watcher inotify-budget fix (#276) - Objective-C indexing (#165) - Mixed iOS / React Native / Expo cross-language bridging (#430) But the [0.9.5] block in CHANGELOG.md only had two Fixed entries (the fs-based change detection and default-ignore set), because the major feature entries were still sitting under [Unreleased] when 0.9.5 was tagged. release.yml extracts release notes from the matching version block, so the published v0.9.5 release notes are missing the bulk of what shipped. Move all the [Unreleased] entries that pre-date 0.9.5's tag (commit 318cda1) into [0.9.5], and reset [Unreleased] to empty. The GitHub Release notes for v0.9.5 get updated separately via gh release edit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KannaKuron
added a commit
to KannaKuron/codegraph4bevy
that referenced
this pull request
May 26, 2026
…, watcher fixes Key upstream changes merged: - feat(extraction): Objective-C language support (colbymchenry#165) - feat(mcp): share one serve --mcp per project across MCP clients (colbymchenry#411) - feat(mcp): per-file staleness banner + tunable watcher debounce (colbymchenry#403) - feat(mcp): detect borrowed git worktree index (colbymchenry#312) - feat(index): default-ignore dependency/build/cache dirs (colbymchenry#407) - feat(resolution): mixed iOS / React Native / Expo bridging (colbymchenry#430) - fix(watcher): exclude ignored dirs before watching (colbymchenry#276) - fix(sync): filesystem-based change detection (colbymchenry#414) Conflicts resolved: CLAUDE.md, .cursor/rules, extraction tests (kept split), MCP files (tools/server-instructions/transport/engine), package.json (kept jieba) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#165 landed pure-Objective-C support, but the real value for iOS / RN
codebases is cross-language flows — Swift call → ObjC selector,
NativeModules.X.fn(...)→RCT_EXPORT_METHOD, Expo'srequireNativeModule('X').fn(...)→Function("fn") { ... }. Todaythose flows silently break at the boundary; the agent falls back to Read.
This is the plan for closing them, not the implementation. No code
in this branch.
What's in the doc
docs/design/mixed-ios-and-react-native-bridging.mdcovers:classified per the existing
dynamic-dispatch coverage playbook
(almost all resolvers, one synthesizer extension).
selector auto-mapping, RN's
RCT_EXPORT_MODULE/RCT_EXPORT_METHOD,TurboModule specs as ground truth, Expo's
ModuleDSL.declaration node (no new edges; rides existing name resolution).
(Charts/Realm/Wikipedia for mixed iOS; react-native-svg/screens/
facebook-react-native for RN; expo-camera/expo monorepo for Expo).
smallest repo first; each phase doesn't ship until its corpus passes
the agent A/B bar.
raw JSI, bridging-header parsing, Android extraction quality).
the rules live,
@objcMembershandling.The load-bearing rule from the playbook
So both directions of every bridge close together, and both platforms of
the RN bridge close together. Phasing enforces this — no half-bridges
shipped.
Asking for review on
better candidates?
existing name resolution, but happy to switch if you'd rather see
cross-language edges explicit in
traceoutput.Once this is approved, Phase 1 (Swift↔ObjC) starts on a separate code
branch.
🤖 Generated with Claude Code