Skip to content

Add explicit nullability annotations to RCTBridgeModule.h - #59405

Closed
javache wants to merge 1 commit into
react:mainfrom
javache:export-D126391299
Closed

javache wants to merge 1 commit into
react:mainfrom
javache:export-D126391299

Conversation

@javache

@javache javache commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Summary:
RCTBridgeModule.h had no nullability annotations at all, so viewRegistry_DEPRECATED and the other module properties had no explicit nullability qualifier. Once a header has any nullability specifier, clang's -Wnullability-completeness requires every pointer in it to be annotated. So this wraps the header in NS_ASSUME_NONNULL_BEGIN/END and marks as nullable only the pointers that the implementations and callers show can actually be nil:

  • Weak module properties (moduleRegistry, viewRegistry_DEPRECATED, bundleManager, callableJSModules, bridge) are nullable, because a weak reference can become nil.
  • RCTPromiseResolveBlock's result is nullable: the header comment already says nil resolves to undefined, and modules call resolve(nil).
  • All three RCTPromiseRejectBlock parameters are nullable. RCTJSErrorFromCodeMessageAndNSError substitutes defaults for a nil code or message, and modules call reject(nil, nil, error).
  • RCTResponseSenderBlock's response is nullable, because the TurboModule callback converts a nil array into a call with no arguments. RCTResponseErrorBlock's error is nonnull, because it exists to report an error.
  • methodsToExport returns nullable, because callers fall back when they get nil, and nothing in fbsource implements it.
  • constantsToExport is nonnull. Typed implementations return ModuleConstants<...> inside NS_ASSUME_NONNULL regions, Expo's EXNativeModulesProxy declares it nonnull, and RCTTypedModuleConstants.h documents it as nonnull. A nullable declaration is a hard "nullability specifier 'nonnull' conflicts with existing specifier 'nullable'" error in those implementations. No implementation in fbsource declares it nullable.
  • +moduleName has no nullability specifier. It sits in a small #pragma clang assume_nonnull end/begin gap, with -Wnullability-completeness suppressed. RCT_EXPORT_MODULE expansions inside NS_ASSUME_NONNULL files are implicitly nonnull, but Expo's EXModuleRegistryHolderReactModule declares + (nullable NSString *)moduleName and returns nil. So nonnull, nullable, and null_unspecified would each conflict with one of them. RCTBridgeModuleNameForClass already handles a nil or empty name.
  • RCTModuleRegistry moduleForName: / moduleForName:lazilyLoadIfNecessary: / moduleForClass: return nullable, because they return nil when no TurboModule registry is set or the module isn't found. The const char *moduleName argument stays nonnull: a module lookup needs a name.
  • methodQueue is nonnull. An override exists to return a queue. Any explicit specifier other than nonnull (including null_unspecified) is a hard error ("nullability specifier 'nonnull' conflicts with existing specifier") for Objective-C overrides inside NS_ASSUME_NONNULL regions, such as Twilight's TWMHEConfigurationTurboModule.
  • RCTViewRegistry viewForReactTag: returns nullable, because the bridgeless provider returns nil once the surface presenter is gone, and RCTBridgelessComponentViewProvider's return type is nullable to match. addUIBlock: takes a nullable block, because the implementation returns early on nil.
  • The args and onComplete parameters of RCTCallableJSModules invokeModule:... and RCTBridgelessJSModuleMethodInvoker are nullable. The 3-argument overload forwards NULL for onComplete, and the invoker substitutes @[] when args is nil.
  • Everything else (module/method names, tags, setters, RCTMethodInfo names, which always come from string literals) is nonnull through NS_ASSUME_NONNULL. There is no null_unspecified.

The macro bodies (RCT_EXPORT_MODULE, _RCT_EXTERN_REMAP_METHOD, ...) expand in implementation files outside the audited region, so they are unaffected.

Changelog:

[iOS][Changed] - Add nullability annotations to RCTBridgeModule.h. Module registry and view registry lookups, methodsToExport, promise resolve/reject arguments, and weak module properties such as bridge and moduleRegistry are now nullable. methodQueue is now nonnull, so Swift overrides must be declared as DispatchQueue rather than DispatchQueue!.

Reviewed By: christophpurrer, javache

Differential Revision: D126391299

Summary:
`RCTBridgeModule.h` had no nullability annotations at all, so `viewRegistry_DEPRECATED` and the other module properties had no explicit nullability qualifier. Once a header has any nullability specifier, clang's `-Wnullability-completeness` requires every pointer in it to be annotated. So this wraps the header in `NS_ASSUME_NONNULL_BEGIN/END` and marks as `nullable` only the pointers that the implementations and callers show can actually be nil:

- Weak module properties (`moduleRegistry`, `viewRegistry_DEPRECATED`, `bundleManager`, `callableJSModules`, `bridge`) are `nullable`, because a weak reference can become nil.
- `RCTPromiseResolveBlock`'s `result` is `nullable`: the header comment already says nil resolves to `undefined`, and modules call `resolve(nil)`.
- All three `RCTPromiseRejectBlock` parameters are `nullable`. `RCTJSErrorFromCodeMessageAndNSError` substitutes defaults for a nil code or message, and modules call `reject(nil, nil, error)`.
- `RCTResponseSenderBlock`'s `response` is `nullable`, because the TurboModule callback converts a nil array into a call with no arguments. `RCTResponseErrorBlock`'s `error` is nonnull, because it exists to report an error.
- `methodsToExport` returns `nullable`, because callers fall back when they get nil, and nothing in fbsource implements it.
- `constantsToExport` is nonnull. Typed implementations return `ModuleConstants<...>` inside `NS_ASSUME_NONNULL` regions, Expo's `EXNativeModulesProxy` declares it `nonnull`, and `RCTTypedModuleConstants.h` documents it as `nonnull`. A `nullable` declaration is a hard "nullability specifier 'nonnull' conflicts with existing specifier 'nullable'" error in those implementations. No implementation in fbsource declares it `nullable`.
- `+moduleName` has no nullability specifier. It sits in a small `#pragma clang assume_nonnull end/begin` gap, with `-Wnullability-completeness` suppressed. `RCT_EXPORT_MODULE` expansions inside `NS_ASSUME_NONNULL` files are implicitly nonnull, but Expo's `EXModuleRegistryHolderReactModule` declares `+ (nullable NSString *)moduleName` and returns nil. So `nonnull`, `nullable`, and `null_unspecified` would each conflict with one of them. `RCTBridgeModuleNameForClass` already handles a nil or empty name.
- `RCTModuleRegistry` `moduleForName:` / `moduleForName:lazilyLoadIfNecessary:` / `moduleForClass:` return `nullable`, because they return nil when no TurboModule registry is set or the module isn't found. The `const char *moduleName` argument stays nonnull: a module lookup needs a name.
- `methodQueue` is nonnull. An override exists to return a queue. Any explicit specifier other than `nonnull` (including `null_unspecified`) is a hard error ("nullability specifier 'nonnull' conflicts with existing specifier") for Objective-C overrides inside `NS_ASSUME_NONNULL` regions, such as Twilight's `TWMHEConfigurationTurboModule`.
- `RCTViewRegistry` `viewForReactTag:` returns `nullable`, because the bridgeless provider returns nil once the surface presenter is gone, and `RCTBridgelessComponentViewProvider`'s return type is `nullable` to match. `addUIBlock:` takes a `nullable` block, because the implementation returns early on nil.
- The `args` and `onComplete` parameters of `RCTCallableJSModules invokeModule:...` and `RCTBridgelessJSModuleMethodInvoker` are `nullable`. The 3-argument overload forwards `NULL` for `onComplete`, and the invoker substitutes `@[]` when `args` is nil.
- Everything else (module/method names, tags, setters, `RCTMethodInfo` names, which always come from string literals) is nonnull through `NS_ASSUME_NONNULL`. There is no `null_unspecified`.

The macro bodies (`RCT_EXPORT_MODULE`, `_RCT_EXTERN_REMAP_METHOD`, ...) expand in implementation files outside the audited region, so they are unaffected.

Changelog:

[iOS][Changed] - Add nullability annotations to `RCTBridgeModule.h`. Module registry and view registry lookups, `methodsToExport`, promise resolve/reject arguments, and weak module properties such as `bridge` and `moduleRegistry` are now `nullable`. `methodQueue` is now nonnull, so Swift overrides must be declared as `DispatchQueue` rather than `DispatchQueue!`.

Reviewed By: christophpurrer, javache

Differential Revision: D126391299
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 23, 2026
@meta-codesync

meta-codesync Bot commented Oct 23, 2026

Copy link
Copy Markdown

@javache has exported this pull request. If you are a Meta employee, you can view the originating Diff in D126391299.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by generatedunixname1608173377072046 in 08f96fe

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Oct 23, 2026
@meta-codesync

meta-codesync Bot commented Oct 23, 2026

Copy link
Copy Markdown

This pull request has been merged in 08f96fe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants