Skip to content

Clean up nullability annotations and ensure full NS_ASSUME_NONNULL coverage#2190

Open
lawrence-forooghian wants to merge 2 commits intomainfrom
2026-02-18-nullability-cleanup
Open

Clean up nullability annotations and ensure full NS_ASSUME_NONNULL coverage#2190
lawrence-forooghian wants to merge 2 commits intomainfrom
2026-02-18-nullability-cleanup

Conversation

@lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Feb 18, 2026

Summary

  • Remove redundant explicit nonnull / __nonnull / _Nonnull annotations from declarations already inside NS_ASSUME_NONNULL regions.
  • Add NS_ASSUME_NONNULL_BEGIN/END to all public headers, private headers, and .m @interface declarations that were missing it (excluding vendored SocketRocket files).
  • Where a return type or parameter is genuinely nullable (verified by reading the implementation), add an explicit nullable annotation.

This improves Swift bridging (nonnull types bridge as non-optional) and makes the nullability contract explicit for human readers.

Test plan

  • swift build passes with no errors or warnings
  • swift build --build-tests compiles all test targets
  • CI passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Enhanced code type safety through improved nullability annotations across internal interfaces and declarations.
  • Bug Fixes

    • Corrected token signing validation in tests.
  • New Features

    • Added convenience factory methods for creating Stringifiable objects.

@lawrence-forooghian lawrence-forooghian changed the title Clean up nullability annotations and ensure full NS_ASSUME_NONNULL coverage Clean up nullability annotations and ensure full NS_ASSUME_NONNULL coverage Feb 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Warning

Rate limit exceeded

@lawrence-forooghian has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 24 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

This PR systematically applies Objective-C nullability annotations (NS_ASSUME_NONNULL_BEGIN/END) across the codebase, updates method signatures to use consistent nullability syntax, removes explicit nonnull qualifiers where macro scopes cover them, and introduces a new public API for ARTCancellableFromCallback with corresponding factory methods in ARTStringifiable.

Changes

Cohort / File(s) Summary
Private Interface Nullability Wrapping
Source/ARTChannels.m, Source/ARTClientOptions.m, Source/ARTContinuousClock.m, Source/ARTCrypto.m, Source/ARTEventEmitter.m, Source/ARTFallback.m, Source/ARTGCD.m, Source/ARTLocalDevice.m, Source/ARTRealtime.m, Source/ARTRealtimeChannels.m, Source/ARTURLSessionServerTrust.m
Wraps private class extensions with NS_ASSUME_NONNULL_BEGIN/END to establish default nonnull context for enclosed declarations.
Realtime Channel Internal Updates
Source/ARTRealtimeChannel.m
Adds NS_ASSUME_NONNULL_BEGIN around private ARTRealtimeChannelInternal interface and extends it with new conditional instance variables (_pushChannel under TARGET_OS_IPHONE, _attachTimer, _detachTimer, _attachedEventEmitter, _detachedEventEmitter, _lastPayloadMessageId, _decodeFailureRecoveryInProgress).
Realtime Annotations Relaxation
Source/ARTRealtimeAnnotations.m
Wraps ARTRealtimeAnnotationsInternal with NS_ASSUME_NONNULL_BEGIN/END and relaxes logger property from nonnull to nullable-implicit.
Public Summary Types Nullability
Source/ARTSummaryTypes.m
Wraps three public interfaces (ARTSummaryClientIdList, ARTSummaryClientIdCounts, ARTSummaryTotal) with NS_ASSUME_NONNULL_BEGIN/END.
New Public Cancellable API
Source/ARTTypes.m
Introduces new public interface ARTCancellableFromCallback with initializer -(instancetype)initWithCallback:(ARTResultCallback)callback and readonly wrapper property, enclosed in NS_ASSUME_NONNULL scope.
Private Header Nullability Macros
Source/PrivateHeaders/Ably/ARTChannelOptions+Private.h, Source/PrivateHeaders/Ably/ARTDefault+Private.h, Source/PrivateHeaders/Ably/ARTJsonEncoder.h, Source/PrivateHeaders/Ably/ARTMsgPackEncoder.h, Source/PrivateHeaders/Ably/ARTOSReachability.h, Source/PrivateHeaders/Ably/ARTTokenParams+Private.h
Wraps interfaces with NS_ASSUME_NONNULL_BEGIN/END; ARTDefault+Private.h adds four new private methods for connection state TTL and message size configuration.
Base Message Signature Updates
Source/PrivateHeaders/Ably/ARTBaseMessage+Private.h
Updates decodeWithEncoder:error: and encodeWithEncoder:error: signatures to use NSError *_Nullable *_Nullable instead of explicit __nonnull/__nullable annotations.
Array & Dictionary Utility Updates
Source/PrivateHeaders/Ably/ARTNSArray+ARTFunctional.h, Source/PrivateHeaders/Ably/ARTNSDictionary+ARTDictionaryUtil.h
Adds NS_ASSUME_NONNULL scope and adjusts block parameter spacing; marks return types in dictionary methods as nullable (artString, artNumber, artTimestamp, artArray, artDictionary, artTyped and artMap block callback).
NSDate & NSString Utility Extensions
Source/PrivateHeaders/Ably/ARTNSDate+ARTUtil.h, Source/PrivateHeaders/Ably/ARTNSString+ARTUtil.h
Adds NS_ASSUME_NONNULL_BEGIN/END; ARTNSDate+ARTUtil.h introduces factory methods artDateFromNumberMs: and artDateFromIntegerMs:; ARTNSString+ARTUtil.h makes nilToEmpty: parameter nullable.
Presence & Realtime Private Headers Adjustments
Source/PrivateHeaders/Ably/ARTPresence+Private.h, Source/PrivateHeaders/Ably/ARTPresenceMessage+Private.h, Source/PrivateHeaders/Ably/ARTRealtimePresence+Private.h
Wraps interfaces with nullability macros; ARTPresenceMessage+Private.h makes parseId return type nullable; ARTRealtimePresence+Private.h removes _Nonnull from queue parameter; ARTPresence+Private.h removes initWithChannel: initializer.
Nonnull Qualifier Relaxation
Source/PrivateHeaders/Ably/ARTRealtime+Private.h, Source/PrivateHeaders/Ably/ARTRest+Private.h
Removes nonnull attribute from device and reachability properties, relying on NS_ASSUME_NONNULL scope instead.
Stringifiable Signature Consolidation
Source/PrivateHeaders/Ably/ARTStringifiable+Private.h
Wraps private interface with NS_ASSUME_NONNULL_BEGIN/END and removes explicit nonnull qualifiers from initializer signatures (initWithString:, initWithNumber:, initWithBool:), relying on macro scope.
Public Header Nullability & API Expansions
Source/include/Ably/ARTChannels.h, Source/include/Ably/ARTConstants.h
Wraps declarations with NS_ASSUME_NONNULL_BEGIN/END to apply nonnull defaults to public extern constants and interface declarations.
PresenceMessage & Stringifiable Public API Updates
Source/include/Ably/ARTPresenceMessage.h, Source/include/Ably/ARTStringifiable.h
ARTPresenceMessage.h reduces nullability on memberKey and parameters; ARTStringifiable.h removes default initializer unavailability, relaxes stringValue property nonnull, and adds three class factory methods (withString:, withNumber:, withBool:).
Core Type Functions Nullability Removal
Source/include/Ably/ARTTypes.h
Removes _Nonnull return type qualifier from helper functions (ARTRealtimeConnectionStateToStr, ARTRealtimeConnectionEventToStr, ARTRealtimeChannelStateToStr, ARTChannelEventToStr), adjusts NS_ASSUME_NONNULL scope placement, and fixes spacing in artCancellableFromCallback signature.
Test Assertion Fix
Test/AblyTests/Tests/AuthTests.swift
Fixes token signing test to compare tokenRequest1.mac directly to signed.mac instead of using optional chaining (signed?.mac), removing mismatch in assertion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • umair-ably
  • maratal

Poem

🐰 Nullability bounds, now precise and clear,
NS_ASSUME whispers through headers dear,
Nonnull by default, the pointers now shine,
Code safety blooms in this grand design!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary objective: cleaning up nullability annotations and ensuring comprehensive NS_ASSUME_NONNULL coverage across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2026-02-18-nullability-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/2190/features February 18, 2026 17:28 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2190/jazzydoc February 18, 2026 17:31 Inactive
lawrence-forooghian and others added 2 commits February 18, 2026 14:31
These files already have NS_ASSUME_NONNULL_BEGIN/END regions, so
explicit nonnull, __nonnull, and _Nonnull annotations on declarations
within those regions are redundant and can be removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds NS_ASSUME_NONNULL_BEGIN/END to every header and .m @interface
declaration that was missing it, improving Swift bridging (nonnull
types bridge as non-optional) and making the nullability contract
explicit for human readers. Some of the wrapped declarations don't
currently contain pointer types, but are included for consistency
and so that future additions to those interfaces will have coverage
by default.

Where adding coverage made existing explicit nonnull annotations
redundant, those are removed. Where a return type or parameter is
genuinely nullable (verified by reading the implementation), an
explicit nullable annotation is added — notably on the dictionary
lookup helpers in ARTNSDictionary+ARTDictionaryUtil.h and parseId
in ARTPresenceMessage+Private.h.

In ARTTypes.h, NS_ASSUME_NONNULL_BEGIN is moved earlier to cover
the *ToStr() C functions that were previously annotated with
inline _Nonnull.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Source/ARTCrypto.m (1)

8-22: ⚠️ Potential issue | 🟡 Minor

Add explicit _Nullable annotation to the error parameter of ccAlgorithm:error: declaration.

The private method declaration at line 12 resides within NS_ASSUME_NONNULL, making error:(NSError **)error implicitly error:(NSError * _Nullable * _Nonnull)error. However, line 127 passes nil directly:

if (![cipherParams ccAlgorithm:&_algorithm error:nil]) {

The implementation at line 95 defensively guards with if (error) before dereferencing, confirming that nil was intentionally supported. Make the intent explicit by annotating the outer pointer as nullable:

- (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError * _Nullable * _Nullable)error;

This clarifies the parameter contract and prevents potential warnings in stricter compiler configurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/ARTCrypto.m` around lines 8 - 22, Update the private method
declaration for ARTCipherParams's ccAlgorithm:error: to explicitly allow a nil
error pointer by annotating the outer pointer nullable; change the signature
inside the NS_ASSUME_NONNULL block to use NSError * _Nullable * _Nullable for
the error parameter (i.e. the ccAlgorithm:error: declaration), so callers like
the code in ARTCbcCipher that pass nil remain valid and stricter compilers won't
warn; leave the implementation and its defensive if (error) guard unchanged.
Source/ARTRealtimeChannel.m (1)

272-286: ⚠️ Potential issue | 🟡 Minor

_pushChannel needs _Nullable — it's lazily initialized, so it starts as nil

The _pushChannel ivar (line 278) is now inside NS_ASSUME_NONNULL_BEGIN without an explicit _Nullable, making it implicitly nonnull. However, the push getter (lines 415–420) shows it is lazily initialized — the ivar starts as nil after init and is populated only on first access. This contradicts the nonnull annotation and can mislead the compiler and code readers about the ivar's lifecycle.

🛠️ Proposed fix
 `#if` TARGET_OS_IPHONE
-ARTPushChannelInternal *_pushChannel;
+ARTPushChannelInternal * _Nullable _pushChannel;
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/ARTRealtimeChannel.m` around lines 272 - 286, The _pushChannel ivar is
declared inside NS_ASSUME_NONNULL_BEGIN but is lazily initialized in the push
getter, so annotate it as nullable: change the declaration of
ARTPushChannelInternal *_pushChannel; within the ARTRealtimeChannelInternal
interface to ARTPushChannelInternal * _Nullable _pushChannel; (also check/use
the same _Nullable annotation for any related ivar declarations if they are
lazily set) so the compiler and readers know it may be nil until first access.
🧹 Nitpick comments (4)
Source/include/Ably/ARTChannels.h (1)

52-52: Block internals are not covered by NS_ASSUME_NONNULL.

NS_ASSUME_NONNULL applies to the mapper block pointer itself (making it nonnull) but does not propagate into the block's own type signature. The block's id return type and ChannelType parameter remain unaudited. Since this is a :nodoc: internal method, the practical Swift bridging impact is zero, but for complete nullability coverage the block type could be explicitly annotated:

✏️ Explicit nullability inside block signature
-/// :nodoc:
-- (id<NSFastEnumeration>)copyIntoIteratorWithMapper:(id (^)(ChannelType))mapper;
+/// :nodoc:
+- (id<NSFastEnumeration>)copyIntoIteratorWithMapper:(id _Nonnull (^)(ChannelType _Nonnull))mapper;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/include/Ably/ARTChannels.h` at line 52, The block pointer in
copyIntoIteratorWithMapper: is not fully nullability-annotated because
NS_ASSUME_NONNULL does not apply inside the block signature; update the
declaration of copyIntoIteratorWithMapper: to explicitly annotate the block's
return and parameter nullability (e.g., make the ChannelType parameter _Nonnull
and the block return either _Nullable or _Nonnull as appropriate for your API)
so the block type is fully audited for nullability when bridged to Swift; refer
to the method name copyIntoIteratorWithMapper: and the ChannelType type when
making the change.
Source/include/Ably/ARTStringifiable.h (1)

13-15: Prefer instancetype for factory method return types.

Using instancetype is the idiomatic Objective-C convention for class factory methods; it enables correct subclass return-type inference and aligns with Apple's SDK guidelines.

♻️ Proposed refactor
-+ (ARTStringifiable*)withString:(NSString *)value;
-+ (ARTStringifiable*)withNumber:(NSNumber *)value;
-+ (ARTStringifiable*)withBool:(BOOL)value;
++ (instancetype)withString:(NSString *)value;
++ (instancetype)withNumber:(NSNumber *)value;
++ (instancetype)withBool:(BOOL)value;

— The AI summary states these factory methods were added in this PR, but none of lines 13–15 are annotated with ~ in the diff, indicating they are pre-existing declarations. Please verify whether these methods are genuinely new to this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/include/Ably/ARTStringifiable.h` around lines 13 - 15, Change the
factory method return types from the concrete class pointer to instancetype so
subclassing callers get correct types: update the declarations of withString:,
withNumber:, and withBool: in ARTStringifiable (replace ARTStringifiable* return
type with instancetype for the methods named withString:, withNumber:, and
withBool:). Ensure any corresponding implementations and headers remain
consistent with the instancetype return type.
Source/ARTChannels.m (1)

9-18: __weak ivar implicitly becomes _Nonnull within NS_ASSUME_NONNULL — semantically inconsistent.

_delegate is a __weak reference, meaning it can be zeroed to nil at any point after the referenced object is deallocated. Marking it _Nonnull (implicitly via NS_ASSUME_NONNULL) contradicts this: callers assume the reference is valid and won't nil-check before use. In practice this is safe in pure ObjC (messaging nil is a no-op), but it makes the annotation misleading.

Consider explicitly annotating it as _Nullable to convey the true contract:

🔧 Suggested annotation
-    __weak id<ARTChannelsDelegate> _delegate; // weak because delegates outlive their counterpart
+    __weak id<ARTChannelsDelegate> _Nullable _delegate; // weak because delegates outlive their counterpart
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/ARTChannels.m` around lines 9 - 18, The ivar _delegate in ARTChannels
is declared __weak inside an NS_ASSUME_NONNULL region which incorrectly implies
non-nullability; change the declaration of _delegate in the ARTChannels
`@interface` to explicitly mark it nullable (e.g. __weak _Nullable
id<ARTChannelsDelegate> _delegate) so the nullability contract matches the weak
semantics and callers are aware it can be nil.
Source/include/Ably/ARTTypes.h (1)

585-585: Nit: _Nonnull qualifiers on wrapper parameter are redundant inside NS_ASSUME_NONNULL.

Both _Nonnull annotations on the wrapper parameter are already implied by the surrounding NS_ASSUME_NONNULL region. Since the PR's goal is to remove such redundancies, these could be dropped for consistency.

Suggested change
-NSObject<ARTCancellable> * artCancellableFromCallback(ARTResultCallback callback, _Nonnull ARTResultCallback * _Nonnull wrapper);
+NSObject<ARTCancellable> * artCancellableFromCallback(ARTResultCallback callback, ARTResultCallback * wrapper);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/include/Ably/ARTTypes.h` at line 585, Remove the redundant _Nonnull
qualifiers on the wrapper parameter in the artCancellableFromCallback
declaration: the surrounding NS_ASSUME_NONNULL already enforces non-null, so
update the function signature by deleting the extra _Nonnull annotations for the
parameter named wrapper in artCancellableFromCallback(ARTResultCallback
callback, ARTResultCallback * wrapper).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Source/ARTCrypto.m`:
- Around line 8-22: Update the private method declaration for ARTCipherParams's
ccAlgorithm:error: to explicitly allow a nil error pointer by annotating the
outer pointer nullable; change the signature inside the NS_ASSUME_NONNULL block
to use NSError * _Nullable * _Nullable for the error parameter (i.e. the
ccAlgorithm:error: declaration), so callers like the code in ARTCbcCipher that
pass nil remain valid and stricter compilers won't warn; leave the
implementation and its defensive if (error) guard unchanged.

In `@Source/ARTRealtimeChannel.m`:
- Around line 272-286: The _pushChannel ivar is declared inside
NS_ASSUME_NONNULL_BEGIN but is lazily initialized in the push getter, so
annotate it as nullable: change the declaration of ARTPushChannelInternal
*_pushChannel; within the ARTRealtimeChannelInternal interface to
ARTPushChannelInternal * _Nullable _pushChannel; (also check/use the same
_Nullable annotation for any related ivar declarations if they are lazily set)
so the compiler and readers know it may be nil until first access.

---

Nitpick comments:
In `@Source/ARTChannels.m`:
- Around line 9-18: The ivar _delegate in ARTChannels is declared __weak inside
an NS_ASSUME_NONNULL region which incorrectly implies non-nullability; change
the declaration of _delegate in the ARTChannels `@interface` to explicitly mark it
nullable (e.g. __weak _Nullable id<ARTChannelsDelegate> _delegate) so the
nullability contract matches the weak semantics and callers are aware it can be
nil.

In `@Source/include/Ably/ARTChannels.h`:
- Line 52: The block pointer in copyIntoIteratorWithMapper: is not fully
nullability-annotated because NS_ASSUME_NONNULL does not apply inside the block
signature; update the declaration of copyIntoIteratorWithMapper: to explicitly
annotate the block's return and parameter nullability (e.g., make the
ChannelType parameter _Nonnull and the block return either _Nullable or _Nonnull
as appropriate for your API) so the block type is fully audited for nullability
when bridged to Swift; refer to the method name copyIntoIteratorWithMapper: and
the ChannelType type when making the change.

In `@Source/include/Ably/ARTStringifiable.h`:
- Around line 13-15: Change the factory method return types from the concrete
class pointer to instancetype so subclassing callers get correct types: update
the declarations of withString:, withNumber:, and withBool: in ARTStringifiable
(replace ARTStringifiable* return type with instancetype for the methods named
withString:, withNumber:, and withBool:). Ensure any corresponding
implementations and headers remain consistent with the instancetype return type.

In `@Source/include/Ably/ARTTypes.h`:
- Line 585: Remove the redundant _Nonnull qualifiers on the wrapper parameter in
the artCancellableFromCallback declaration: the surrounding NS_ASSUME_NONNULL
already enforces non-null, so update the function signature by deleting the
extra _Nonnull annotations for the parameter named wrapper in
artCancellableFromCallback(ARTResultCallback callback, ARTResultCallback *
wrapper).

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant