fix(bindings): forwardMessage's priority crosses the iOS bridge as a required int - #418
Merged
Merged
Conversation
…required int React Native forces every `NSNumber` argument to non-null, because numbers are not nullable on Android, and refuses a null one before the Swift method is entered: neither the resolver nor the rejecter runs, so the promise never settles and `await forwardMessage(...)` hangs forever behind a redbox. The TypeScript passed `null` whenever a caller omitted the priority, which made `forwardMessage` the one method in the bridge relying on a nullable number. No spelling of the declaration repairs that, so the nullability is gone instead. The null carried no information in the first place: the core already resolves an absent priority to Medium, the TypeScript already documented that default, and `sendMessage` already resolves it in TypeScript and crosses as a required int. `forwardMessage` now does the same, so the shim takes `NSInteger`, Swift and Kotlin take `Int`, and each maps an unrecognised value back to Medium. No caller sees a behaviour change on either platform. The check that refused the null is compiled out of release builds, so only development was affected. The selector guard cannot catch a regression here, since a nullable number and a nullable object share an ABI class and the two halves therefore agree. Both bridge documents lose the paragraph describing the breakage and gain the rule, and a new JS harness case pins the TypeScript half: the argument is always a number, and `MessagePriority.Low` being 0 means the default has to be resolved with `??` rather than `||`. Closes #417
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Closes #417.
The problem
React Native forces every
NSNumberargument to non-null, because numbers are not nullable on Android, and refuses a null one before the Swift method is entered. Neither the resolver nor the rejecter runs, so the promise never settles andawait forwardMessage(...)hangs forever behind a redbox.The TypeScript passed
nullwhenever a caller omitted the priority, which madeforwardMessagethe one method in this bridge relying on a nullable number. As #417 documents, no spelling of the declaration fixes it:nullableis rejected at module load, unspecified andnonnullare both forced non-null, and a plainNSIntegerdid not match the SwiftNSNumber?.The fix
The nullability is removed rather than respelled, because the null carried no information in the first place:
priority.unwrap_or(MessagePriority::Medium)(crates/offline-protocol/src/protocol/send.rs:259),ForwardMessageParams.priorityalready documented "defaults to Medium", andsendMessagealready had the identical API and already resolves the default in TypeScript, crossing as a required int.forwardMessagenow matches its sibling exactly: TypeScript sendsparams.priority ?? MessagePriority.Medium, the shim takesNSInteger, Swift and Kotlin takeInt, and each maps an unrecognised value back to Medium.This is the RN bridge, not UniFFI output, so the UDL is untouched and no bindings were regenerated.
Impact
No caller sees a behaviour change on either platform: an omitted priority meant Medium before and means Medium now. The check that refused the null is inside
#if RCT_DEBUG, so only development was affected. The publicForwardMessageParamstype is unchanged.Why a guard cannot hold this
react_native_ios_objc_shim_and_swift_agree_on_every_selectorcompares ABI classes, and a nullable number and a nullable object share one. Both halves agreed while React Native rejected the call anyway. So the rule is written down instead: bothBRIDGE_MAINTENANCE.mdanddocs/bridges/swift.mdlose the paragraph describing the breakage and gain the rule that a nullable number never crosses this bridge.The TypeScript half is pinned by a new harness file,
js-ci-harness/forward-priority.test.js, wired intonpm run test:js. It covers the argument always being a number and theMessagePriority.Low-is-0 trap, where resolving the default with||instead of??would silently upgrade every Low forward to Medium.Verification
cargo test --workspace --libcargo clippy --workspace -- -D warningsswiftc -typecheck, full hand-written iOS source set:offlineprotocol:testDebugUnitTest(clean-dir copy)npm run test:js(all 6 harness files)?? nulland||each fail the case that covers them