Skip to content

[ECO-5577] Get rid of existential type usage in the public API#364

Merged
lawrence-forooghian merged 2 commits intomainfrom
remove-existential-from-public-api
Oct 6, 2025
Merged

[ECO-5577] Get rid of existential type usage in the public API#364
lawrence-forooghian merged 2 commits intomainfrom
remove-existential-from-public-api

Conversation

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

@lawrence-forooghian lawrence-forooghian commented Oct 3, 2025

Note: This PR is based on top of #363; please review that one first.

This was started in #362; here we address the remainder.

The only remaining usage is in ChatClientOptions.logHandler, to avoid making the options type generic.

(There's still a fair bit of usage of existential types internally but we don't need to sort that out pre-v1 release.)

Summary by CodeRabbit

  • Refactor

    • Public APIs now favor associated types and opaque return types over existential protocol types across messaging, presence, typing, occupancy, reactions, rooms, and connection; subscription returns and paginated navigation were narrowed/self-referential. Breaking API changes for adopters.
  • Documentation

    • CONTRIBUTING revised to discourage existential types in public APIs and removed the requirement to reference spec items in API behaviour comments; preserves an explicit exception example.
  • Tests

    • Mocks and test helpers updated to use concrete mock types and new generics.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 3, 2025

Walkthrough

Replaces many public-api existentials with associated types or opaque some returns, makes PaginatedResult self-referential (Self), converts several internal/default/subscription types to concrete Default* implementations, propagates generics through Messages/Subscriptions/Room, updates mocks, tests, and CONTRIBUTING guidance about existential types.

Changes

Cohort / File(s) Summary
Documentation
CONTRIBUTING.md
Removes requirement to reference spec items in public API comments; introduces guidance discouraging any existential types in public APIs (with noted exceptions and an explicit ChatClientOptions example).
Core protocols: associated/opaque types
Sources/AblyChat/Messages.swift, Sources/AblyChat/Presence.swift, Sources/AblyChat/Occupancy.swift, Sources/AblyChat/Typing.swift, Sources/AblyChat/MessageReactions.swift, Sources/AblyChat/RoomReactions.swift, Sources/AblyChat/Room.swift, Sources/AblyChat/Connection.swift, Sources/AblyChat/Subscription.swift
Adds associated types for subscription/response/history/result types; changes returns from any ... to associated types or some ...; MessageSubscriptionResponseProtocol gains HistoryResult; Messages protocol gains Reactions/SubscribeResponse/HistoryResult associated types; Room gets many associated component types.
Pagination API self-typing
Sources/AblyChat/PaginatedResult.swift
PaginatedResult navigation properties (next, first, current) now return Self; internal PaginatedResultWrapper updated to use PaginatedResultWrapper<Item> for navigation.
Default implementations: concrete returns
Sources/AblyChat/DefaultPresence.swift, Sources/AblyChat/DefaultTyping.swift, Sources/AblyChat/DefaultRoomReactions.swift, Sources/AblyChat/DefaultMessageReactions.swift, Sources/AblyChat/DefaultMessages.swift, Sources/AblyChat/DefaultOccupancy.swift, Sources/AblyChat/DefaultConnection.swift, Sources/AblyChat/SubscriptionStorage.swift, Sources/AblyChat/RoomLifecycleManager.swift
Switches subscribe/onStatusChange returns from any existentials to concrete Default* types or some opaque returns; DefaultMessages uses concrete DefaultMessageReactions and DefaultMessageSubscriptionResponse; DefaultMessageReactions.subscribeRaw now uses fatalError for unsupported raw path.
Chat client & API
Sources/AblyChat/ChatClient.swift, Sources/AblyChat/ChatAPI.swift
ChatClient adds associated Connection type; DefaultChatClient stores private concrete connection and exposes some Connection; ChatAPI paginated request/getMessages return some PaginatedResult<...>.
Room generics & lifecycle
Sources/AblyChat/Room.swift
Room protocol added associated types for Messages/Presence/Reactions/Typing/Occupancy/StatusSubscription; DefaultRoom and DefaultRoomFactory generalized over a LifecycleManager type; onStatusChange/onDiscontinuity now return lifecycle-managed subscription types.
Subscription internals & storage
Sources/AblyChat/Subscription.swift, Sources/AblyChat/SubscriptionStorage.swift
Renamed internal structs to Default* (Subscription → DefaultSubscription, StatusSubscription → DefaultStatusSubscription, MessageSubscriptionResponse → DefaultMessageSubscriptionResponse); MessageSubscriptionResponseProtocol historyBeforeSubscribe returns HistoryResult; storages switched to store/return concrete Default* types where applicable.
Examples / mocks
Example/AblyChatExample/Mocks/*, Example/AblyChatExample/Mocks/Misc.swift
Mocks refactored to concrete Mock/Default types; MockSubscriptionStorage made generic over PaginatedResult; Mock PaginatedResult and MockMessagesPaginatedResult updated to use Self-based navigation; many mock return types tightened to mock-specific types or some.
Tests
Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift, Tests/AblyChatTests/Mocks/MockRoom.swift, Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
Test mocks and instantiations updated to use concrete/mock types and explicit generics (e.g., MessageSubscriptionAsyncSequence); MockRoom and MockRoomLifecycleManager now return concrete DefaultStatusSubscription and Default* components.

Sequence Diagram(s)

(Skipped — changes are type/signature-level and do not introduce new runtime control-flow interactions.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • umair-ably
  • ttypic

Poem

Hoppity-hop through types I go,
From any to some my bindings flow.
Pages link to Self with cheer,
Subscriptions snug and clear.
A rabbit tidies mocks today — swift steps all the way. 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.07% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately reflects the main purpose of this changeset—removing existential type usage from the public API—and aligns with the PR summary and file modifications without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch remove-existential-from-public-api

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac378d and 5009845.

📒 Files selected for processing (28)
  • CONTRIBUTING.md (1 hunks)
  • Example/AblyChatExample/Mocks/Misc.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (15 hunks)
  • Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (8 hunks)
  • Sources/AblyChat/ChatAPI.swift (2 hunks)
  • Sources/AblyChat/ChatClient.swift (4 hunks)
  • Sources/AblyChat/Connection.swift (2 hunks)
  • Sources/AblyChat/DefaultConnection.swift (2 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (3 hunks)
  • Sources/AblyChat/DefaultMessages.swift (4 hunks)
  • Sources/AblyChat/DefaultOccupancy.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (3 hunks)
  • Sources/AblyChat/DefaultRoomReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultTyping.swift (2 hunks)
  • Sources/AblyChat/MessageReactions.swift (3 hunks)
  • Sources/AblyChat/Messages.swift (8 hunks)
  • Sources/AblyChat/Occupancy.swift (2 hunks)
  • Sources/AblyChat/PaginatedResult.swift (4 hunks)
  • Sources/AblyChat/Presence.swift (3 hunks)
  • Sources/AblyChat/Room.swift (13 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (4 hunks)
  • Sources/AblyChat/RoomReactions.swift (2 hunks)
  • Sources/AblyChat/Subscription.swift (5 hunks)
  • Sources/AblyChat/SubscriptionStorage.swift (2 hunks)
  • Sources/AblyChat/Typing.swift (2 hunks)
  • Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRoom.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • Sources/AblyChat/RoomLifecycleManager.swift
  • Sources/AblyChat/Occupancy.swift
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift
  • Sources/AblyChat/DefaultOccupancy.swift
  • Sources/AblyChat/DefaultMessages.swift
  • Sources/AblyChat/ChatClient.swift
  • Sources/AblyChat/DefaultPresence.swift
  • Sources/AblyChat/DefaultTyping.swift
  • Sources/AblyChat/DefaultMessageReactions.swift
  • Sources/AblyChat/DefaultRoomReactions.swift
🧰 Additional context used
🧬 Code graph analysis (15)
Sources/AblyChat/Connection.swift (4)
Example/AblyChatExample/Mocks/MockClients.swift (2)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Sources/AblyChat/Room.swift (3)
  • onStatusChange (143-157)
  • onStatusChange (160-162)
  • onStatusChange (386-389)
Tests/AblyChatTests/Mocks/MockRoom.swift (1)
  • onStatusChange (67-70)
Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (2)
Sources/AblyChat/Messages.swift (2)
  • next (381-383)
  • emit (361-363)
Sources/AblyChat/SubscriptionAsyncSequence.swift (4)
  • next (44-46)
  • next (119-130)
  • next (139-141)
  • emit (72-79)
Sources/AblyChat/Presence.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (7)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/Messages.swift (3)
Example/AblyChatExample/Mocks/MockClients.swift (8)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
  • history (157-159)
Sources/AblyChat/DefaultMessages.swift (2)
  • subscribe (39-115)
  • history (118-124)
Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (1)
  • mockGetPreviousMessages (49-58)
Sources/AblyChat/DefaultConnection.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (2)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
Sources/AblyChat/Connection.swift (2)
  • onStatusChange (43-57)
  • onStatusChange (60-62)
Sources/AblyChat/RoomReactions.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (5)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
Sources/AblyChat/DefaultRoomReactions.swift (1)
  • subscribe (36-79)
Example/AblyChatExample/Mocks/Misc.swift (1)
Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (2)
  • next (46-48)
  • first (50-52)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (1)
Sources/AblyChat/Subscription.swift (1)
  • historyBeforeSubscribe (93-108)
Sources/AblyChat/SubscriptionStorage.swift (1)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (3)
  • create (38-49)
  • create (96-107)
  • create (153-170)
Sources/AblyChat/Subscription.swift (1)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (1)
  • historyBeforeSubscribe (211-213)
Tests/AblyChatTests/Mocks/MockRoom.swift (4)
Example/AblyChatExample/Mocks/MockClients.swift (3)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
  • onDiscontinuity (107-110)
Sources/AblyChat/Room.swift (6)
  • onStatusChange (143-157)
  • onStatusChange (160-162)
  • onStatusChange (386-389)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • onDiscontinuity (226-229)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • onDiscontinuity (82-85)
Sources/AblyChat/Room.swift (5)
Example/AblyChatExample/Mocks/MockClients.swift (3)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
  • onDiscontinuity (107-110)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Tests/AblyChatTests/Mocks/MockRoom.swift (2)
  • onStatusChange (67-70)
  • onDiscontinuity (72-75)
Tests/AblyChatTests/DefaultRoomTests.swift (2)
  • onStatusChange (269-291)
  • onDiscontinuity (296-318)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • onDiscontinuity (226-229)
Sources/AblyChat/MessageReactions.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (8)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
  • subscribeRaw (306-308)
Sources/AblyChat/DefaultMessageReactions.swift (2)
  • subscribe (62-100)
  • subscribeRaw (103-149)
Sources/AblyChat/Typing.swift (5)
Example/AblyChatExample/Mocks/MockClients.swift (6)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/Occupancy.swift (2)
  • subscribe (55-69)
  • subscribe (72-74)
Sources/AblyChat/Presence.swift (4)
  • subscribe (139-153)
  • subscribe (166-180)
  • subscribe (183-185)
  • subscribe (188-190)
Sources/AblyChat/RoomReactions.swift (2)
  • subscribe (44-58)
  • subscribe (61-63)
Example/AblyChatExample/Mocks/MockClients.swift (9)
Sources/AblyChat/Connection.swift (2)
  • onStatusChange (43-57)
  • onStatusChange (60-62)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Sources/AblyChat/Room.swift (6)
  • onStatusChange (143-157)
  • onStatusChange (160-162)
  • onStatusChange (386-389)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Sources/AblyChat/DefaultMessages.swift (2)
  • subscribe (39-115)
  • history (118-124)
Sources/AblyChat/DefaultOccupancy.swift (1)
  • subscribe (20-58)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/DefaultMessageReactions.swift (1)
  • subscribeRaw (103-149)
Sources/AblyChat/MessageReactions.swift (2)
  • subscribeRaw (96-110)
  • subscribeRaw (113-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
🔇 Additional comments (39)
CONTRIBUTING.md (1)

43-44: Guidance update reads well.

Clear direction on preferring associated/opaque types, plus the concrete exception, matches the API changes and keeps contributors aligned. Nice addition.

Sources/AblyChat/Connection.swift (2)

8-9: LGTM! Associated type properly constrains subscription return type.

The introduction of associatedtype StatusSubscription: StatusSubscriptionProtocol successfully eliminates the existential type from the public API. This aligns with the PR's objective to replace any StatusSubscriptionProtocol with concrete associated types.


30-30: LGTM! Return type correctly uses associated type.

The method signature now returns the concrete StatusSubscription associated type instead of an existential, maintaining type safety while removing the existential from the public API surface.

Sources/AblyChat/RoomReactions.swift (2)

10-11: LGTM! Associated type introduction aligns with existential removal.

The associatedtype Subscription: SubscriptionProtocol correctly constrains the subscription type while eliminating the existential from the public API.


31-31: LGTM! Return type properly uses associated type.

The method signature now returns the concrete Subscription associated type, removing the existential any SubscriptionProtocol from the public API.

Sources/AblyChat/Typing.swift (2)

11-12: LGTM! Associated type properly introduced.

The associatedtype Subscription: SubscriptionProtocol correctly constrains the subscription type and removes the existential from the public API.


22-22: LGTM! Return type correctly uses associated type.

The method signature now returns the concrete Subscription associated type instead of an existential.

Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (2)

13-17: LGTM! Mock properties correctly use concrete self-referential types.

The properties next, first, and current now return MockPaginatedResult<Item> or Self, aligning with the self-referential PaginatedResult pattern introduced in this PR. This removes existential types from test mocks.


36-36: LGTM! Test instantiations correctly specify generic parameter.

The MessageSubscriptionAsyncSequence<MockPaginatedResult> instantiations explicitly parameterize the generic type, ensuring type safety and alignment with the concrete mock types.

Also applies to: 42-42

Example/AblyChatExample/Mocks/Misc.swift (1)

33-37: LGTM! Properties correctly use self-referential Self type.

The next, first, and current properties now return Self, aligning with the self-referential PaginatedResult pattern introduced in this PR. This removes existential types from example mocks.

Sources/AblyChat/MessageReactions.swift (2)

10-11: LGTM! Associated type properly constrains subscription return type.

The associatedtype Subscription: SubscriptionProtocol correctly eliminates the existential type from the public API.


41-41: LGTM! Return types correctly use associated type.

Both subscribe(_:) and subscribeRaw(_:) now return the concrete Subscription associated type instead of existentials.

Also applies to: 54-54

Sources/AblyChat/DefaultConnection.swift (2)

24-24: LGTM! Return type correctly uses opaque some type.

The method signature now returns some StatusSubscriptionProtocol instead of an existential, aligning with the protocol's associated type and removing existentials from the internal implementation.


75-75: LGTM! Concrete return type aligns with opaque signature.

The method returns DefaultStatusSubscription, which conforms to StatusSubscriptionProtocol and satisfies the some StatusSubscriptionProtocol signature.

Sources/AblyChat/Presence.swift (2)

13-14: LGTM! Associated type properly introduced.

The associatedtype Subscription: SubscriptionProtocol correctly constrains the subscription type and removes the existential from the public API.


86-86: LGTM! Return types correctly use associated type.

Both subscribe(event:_:) and subscribe(events:_:) now return the concrete Subscription associated type instead of existentials.

Also applies to: 100-100

Sources/AblyChat/ChatAPI.swift (1)

19-19: LGTM! Opaque return types appropriately replace existentials.

The change from any PaginatedResult to some PaginatedResult maintains type identity while eliminating existential types from internal APIs, consistent with the PR's objectives.

Also applies to: 245-245

Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (2)

9-9: LGTM! Concrete mock types replace protocol existentials.

Replacing protocol-typed stored subscriptions with concrete MockSubscription and MockStatusSubscription types aligns with the broader refactor to eliminate existential types while maintaining clear mock boundaries.

Also applies to: 42-42, 67-67, 100-100


122-122: LGTM! Generic parametrization enables type-safe mocking.

Making MockMessageSubscriptionStorage and MockMessageSubscriptionResponse generic over PaginatedResult provides type safety while supporting the shift away from existential types. The opaque some MessageSubscriptionResponseProtocol return type appropriately hides implementation details.

Also applies to: 125-125, 130-130, 155-155, 158-158, 207-211, 220-220

Sources/AblyChat/SubscriptionStorage.swift (1)

10-10: LGTM! Internal concrete types align with existential removal.

The shift to DefaultSubscription and DefaultStatusSubscription for internal storage is consistent with the PR's goals. Note the intentional asymmetry: SubscriptionStorage.create() still returns any SubscriptionProtocol (line 16), while StatusSubscriptionStorage.create() exposes the concrete DefaultStatusSubscription type (line 57).

Also applies to: 18-23, 51-51, 57-65

Sources/AblyChat/Subscription.swift (2)

37-37: LGTM! Associated type eliminates existential from protocol.

Adding associatedtype HistoryResult: PaginatedResult<Message> to MessageSubscriptionResponseProtocol is the key change enabling existential removal from the public API. This allows conforming types to specify their concrete pagination result type while maintaining protocol composability.

Also applies to: 56-56


59-69: LGTM! Default naming clarifies concrete implementations.*

Renaming to DefaultSubscription, DefaultStatusSubscription, and DefaultMessageSubscriptionResponse appropriately distinguishes these concrete internal types from their protocol counterparts, with the opaque some PaginatedResult<Message> return (line 93) maintaining type identity.

Also applies to: 71-81, 83-93

Sources/AblyChat/PaginatedResult.swift (2)

11-13: LGTM! Self-based navigation removes existential boxing.

Changing next, first, and current to return Self? or Self eliminates existential types from the pagination protocol while enabling type-preserving navigation. This is a key architectural improvement for the public API.


65-65: LGTM! Internal wrapper uses concrete types.

The internal PaginatedResultWrapper correctly uses concrete PaginatedResultWrapper<Item> types for navigation, consistent with the protocol's Self-based design and avoiding unnecessary type erasure.

Also applies to: 80-80, 95-95

Tests/AblyChatTests/Mocks/MockRoom.swift (2)

19-37: LGTM! Mock properties align with concrete Default types.*

Updating mock room component properties to concrete DefaultMessages, DefaultPresence, DefaultRoomReactions, DefaultTyping, and DefaultOccupancy types ensures test mocks accurately reflect the production API surface.


68-74: LGTM! Mock subscription returns align with production types.

Changing onStatusChange and onDiscontinuity to return concrete DefaultStatusSubscription maintains consistency with the production Room implementation and broader existential removal effort.

Sources/AblyChat/Messages.swift (2)

11-13: LGTM! Associated types comprehensively eliminate existentials.

Adding Reactions, SubscribeResponse, and HistoryResult associated types to the Messages protocol enables conforming types to specify concrete implementations while removing all existential types from the public API. This is a well-designed protocol evolution.

Also applies to: 23-23, 33-34, 83-83


95-95: LGTM! Generic propagation maintains type safety.

Making MessageSubscriptionAsyncSequence generic over HistoryResult and propagating SubscribeResponse.HistoryResult through the subscription flow maintains end-to-end type safety while supporting the existential removal effort.

Also applies to: 119-119, 338-338, 344-344, 349-350, 356-359, 370-370

Example/AblyChatExample/Mocks/MockClients.swift (2)

12-12: LGTM! Mock client uses concrete types throughout.

Updating connection to MockConnection and room component properties to concrete Mock* types ensures the example mock client accurately reflects the production API's shift away from existential types.

Also applies to: 56-60


91-104: LGTM! Subscription methods consistently use concrete/opaque types.

All subscription and history methods now return either concrete types (DefaultStatusSubscription, MockSubscription) or opaque types (some MessageSubscriptionResponseProtocol, some PaginatedResult<Message>), completing the mock infrastructure alignment with the existential removal effort.

Also applies to: 108-109, 127-155, 157-159, 280-304, 306-308, 336-352, 367-382, 420-437, 534-540, 555-565, 588-600

Sources/AblyChat/Room.swift (9)

10-16: LGTM! Well-structured associated types.

The associated types properly eliminate existential types from the public API while maintaining protocol constraints. The StatusSubscription associated type allows concrete implementations to specify their subscription type.


30-66: LGTM! Existential types successfully replaced with associated types.

The property declarations now use associated types instead of existential types (any Protocol), achieving the PR objective of removing existential types from the public API.


84-95: LGTM! Status subscription methods updated correctly.

Both onStatusChange and onDiscontinuity now return the StatusSubscription associated type instead of an existential type, maintaining consistency with the PR objective.


229-238: LGTM! Factory method returns concrete generic type.

The createRoom method now returns a concrete DefaultRoom<Realtime, DefaultRoomLifecycleManager> instead of an existential type, successfully propagating the type information through the factory pattern.


241-241: LGTM! Generic signature eliminates existential storage.

Adding LifecycleManager as a generic parameter with proper constraint removes the need to store the lifecycle manager as an existential type internally.


246-255: LGTM! Properties use concrete types throughout.

The feature properties (messages, reactions, presence, occupancy, typing) and lifecycleManager all use concrete types instead of existential types, completing the elimination of existential types from the implementation.


271-271: LGTM! Generic initializer preserves type safety and testability.

The initializer's generic LifecycleManagerFactory parameter with the equality constraint ensures type-safe factory injection while eliminating existential types.


387-389: LGTM! Status change subscription returns concrete type.

The method now returns LifecycleManager.StatusSubscription (a concrete associated type) instead of an existential, completing the elimination of existential types from the subscription mechanism.


398-400: LGTM! Discontinuity subscription returns concrete type.

Consistent with onStatusChange, this method returns LifecycleManager.StatusSubscription (a concrete associated type), successfully removing existential types.


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/364/AblyChat October 3, 2025 15:07 Inactive
Copy link
Copy Markdown

@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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (4)
Sources/AblyChat/DefaultOccupancy.swift (1)

21-26: Fix message: “presence events” → “occupancy events”.

User-facing fatalError text mentions presence; this is occupancy.

- fatalError("In order to be able to subscribe to presence events, please set enableEvents to true in the room's occupancy options.")
+ fatalError("In order to be able to subscribe to occupancy events, please set enableEvents to true in the room's occupancy options.")
Sources/AblyChat/Messages.swift (1)

11-24: Avoid exposing Messages as a protocol existential
Room.swift:30: change nonisolated var messages: Messages { get } to return some Messages or introduce a generic type parameter.
Audit all other public APIs returning or accepting Messages and update them similarly.

Sources/AblyChat/DefaultMessageReactions.swift (1)

104-111: Enforce spec CHA-MR7a: throw ARTErrorInfo instead of crashing

  • In Sources/AblyChat/DefaultMessageReactions.swift (around line 106), replace
    fatalError("Room is not configured to support raw message reactions")
    with
    throw ARTErrorInfo(chatError: .badRequest(message: "Room is not configured to support raw message reactions", code: 40000)).
  • Change the protocol definition in Sources/AblyChat/MessageReactions.swift from
    func subscribeRaw(_ callback: …) -> Subscription
    to
    func subscribeRaw(_ callback: …) throws -> Subscription
    and update both subscribeRaw(bufferingPolicy:) and the no-arg subscribeRaw() AsyncSequence variants to propagate the thrown error.
Sources/AblyChat/DefaultMessages.swift (1)

150-185: Filter once or call unsubscribe on your listener to avoid hanging

resolveSubscriptionStart() uses channel.once { … }, which can fire on an intermediate state (e.g. .attaching) and never resume. Two fixes:

  • Use the overload that listens only for .attached:

    - _ = channel.once { [weak self] stateChange in
    + _ = channel.once(.attached) { [weak self] stateChange in
        guard let self else { return }
        let serial = stateChange.resumed ? channel.properties.channelSerial : channel.properties.attachSerial
        if let serial {
            continuation.resume(returning: serial)
        } else {
            continuation.resume(throwing: ARTErrorInfo(chatError: .attachSerialIsNotDefined).toInternalError())
        }
    }
  • Or subscribe with on and call channel.unsubscribe(listener) inside terminal branches:

    return try await withCheckedContinuation { continuation in
        let listener = channel.on { [weak self] stateChange in
            guard let self else { return }
            switch stateChange.current {
            case .attached:
                channel.unsubscribe(listener)
                continuation.resume(returning: /* serial */)
            case .failed, .suspended:
                channel.unsubscribe(listener)
                continuation.resume(throwing: ARTErrorInfo(chatError: .channelFailedToAttach(cause: stateChange.reason)).toInternalError())
            default: break
            }
        }
    }.get()
🧹 Nitpick comments (14)
CONTRIBUTING.md (1)

43-44: Good clarification on avoiding public existentials; add one nuance for “some” usage.

Consider explicitly noting that “some Protocol” is only allowed in return positions; for stored properties/surfaces prefer associated types, generics, or concrete wrappers. This will prevent misuse when replacing any with some.

Sources/AblyChat/DefaultOccupancy.swift (2)

20-22: Consider marking the callback @sendable (consistency with other subscribe APIs).

Other subscribe methods annotate callbacks as @sendable to pacify concurrency diagnostics noted in CONTRIBUTING.

- internal func subscribe(_ callback: @escaping @MainActor (OccupancyEvent) -> Void) -> some SubscriptionProtocol {
+ internal func subscribe(_ callback: @escaping @MainActor @Sendable (OccupancyEvent) -> Void) -> some SubscriptionProtocol {

52-57: Use unsubscribe for message listener removal
In DefaultOccupancy.swift’s DefaultSubscription closure, replace:

-self.channel.off(eventListener)
+self.channel.unsubscribe(eventListener)
Sources/AblyChat/Messages.swift (2)

11-14: Tie associated types together to prevent mismatches.

Ensure SubscribeResponse.HistoryResult matches HistoryResult to keep API coherent.

-public protocol Messages: AnyObject, Sendable {
-    associatedtype Reactions: MessageReactions
-    associatedtype SubscribeResponse: MessageSubscriptionResponseProtocol
-    associatedtype HistoryResult: PaginatedResult<Message>
+public protocol Messages: AnyObject, Sendable {
+    associatedtype Reactions: MessageReactions
+    associatedtype HistoryResult: PaginatedResult<Message>
+    associatedtype SubscribeResponse: MessageSubscriptionResponseProtocol where SubscribeResponse.HistoryResult == HistoryResult

23-24: Consider marking the callback @sendable.

Matches other subscribe APIs and avoids concurrency diagnostics noted in CONTRIBUTING.

- func subscribe(_ callback: @escaping @MainActor (ChatMessageEvent) -> Void) -> SubscribeResponse
+ func subscribe(_ callback: @escaping @MainActor @Sendable (ChatMessageEvent) -> Void) -> SubscribeResponse
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (1)

133-133: Annotate onTerminate with @mainactor for consistency

Other storages mark onTerminate as @MainActor. This one doesn’t. For consistency and to avoid accidental cross-actor hops, annotate it.

-            onTerminate: @escaping () -> Void,
+            onTerminate: @escaping @MainActor () -> Void,
Sources/AblyChat/ChatClient.swift (1)

74-77: Mark connection accessor as nonisolated to match protocol

Protocol requires nonisolated var connection. Consider annotating the witness for clarity and to avoid unintended actor hops.

-    public var connection: some Connection {
+    public nonisolated var connection: some Connection {
         _connection
     }
Sources/AblyChat/Subscription.swift (1)

59-69: Preserve actor/sendable annotations on stored closures

Store closure properties as @mainactor @sendable to retain concurrency guarantees.

-internal struct DefaultSubscription: SubscriptionProtocol, Sendable {
-    private let _unsubscribe: () -> Void
+internal struct DefaultSubscription: SubscriptionProtocol, Sendable {
+    private let _unsubscribe: @MainActor @Sendable () -> Void
@@
-    internal init(unsubscribe: @MainActor @Sendable @escaping () -> Void) {
+    internal init(unsubscribe: @MainActor @Sendable @escaping () -> Void) {
         _unsubscribe = unsubscribe
     }
 }
 
-internal struct DefaultStatusSubscription: StatusSubscriptionProtocol, Sendable {
-    private let _off: () -> Void
+internal struct DefaultStatusSubscription: StatusSubscriptionProtocol, Sendable {
+    private let _off: @MainActor @Sendable () -> Void
@@
-    internal init(off: @MainActor @Sendable @escaping () -> Void) {
+    internal init(off: @MainActor @Sendable @escaping () -> Void) {
         _off = off
     }
 }

Also applies to: 71-81

Example/AblyChatExample/Mocks/MockClients.swift (2)

108-110: Avoid fatalError in onDiscontinuity mock.

Implement a no-op subscription to prevent crashes in example code paths.

-    func onDiscontinuity(_: @escaping @MainActor (DiscontinuityEvent) -> Void) -> DefaultStatusSubscription {
-        fatalError("Not yet implemented")
-    }
+    func onDiscontinuity(_: @escaping @MainActor (DiscontinuityEvent) -> Void) -> DefaultStatusSubscription {
+        var active = true
+        return DefaultStatusSubscription { active = false }
+    }

306-308: Avoid fatalError in subscribeRaw mock.

Provide a no-op subscription like other mocks to keep examples robust.

-    func subscribeRaw(_: @escaping @MainActor @Sendable (MessageReactionRawEvent) -> Void) -> MockSubscription {
-        fatalError("Not implemented")
-    }
+    func subscribeRaw(_: @escaping @MainActor @Sendable (MessageReactionRawEvent) -> Void) -> MockSubscription {
+        MockSubscription { /* no-op */ }
+    }
Sources/AblyChat/SubscriptionStorage.swift (2)

15-24: Unify return type with concrete DefaultSubscription.

create still returns any SubscriptionProtocol while the storage now constructs and stores DefaultSubscription (and the status storage returns DefaultStatusSubscription). To reduce existential usage and keep types consistent, return DefaultSubscription.

Apply:

-    internal func create(_ callback: @escaping @MainActor (Element) -> Void) -> any SubscriptionProtocol {
+    internal func create(_ callback: @escaping @MainActor (Element) -> Void) -> DefaultSubscription {
         let id = UUID()
         let subscription = DefaultSubscription { [weak self] in
             self?.subscriptionDidTerminate(id: id)
         }
         let subscriptionItem = SubscriptionItem(callback: callback, subscription: subscription)
         subscriptions[id] = subscriptionItem
         return subscription
     }

36-41: Doc nit: “receiver” spelling.

“reciever’s” → “receiver’s”. Optional cleanup.

Also applies to: 77-83

Sources/AblyChat/DefaultMessages.swift (2)

39-90: Ensure callback runs on MainActor.

callback is @MainActor. Confirm channel.subscribe invokes its closure on MainActor. If not guaranteed, hop explicitly to MainActor when calling callback.

Example:

-            let event = ChatMessageEvent(message: message)
-            callback(event)
+            let event = ChatMessageEvent(message: message)
+            Task { @MainActor in
+                callback(event)
+            }

Also consider renaming the inner message model var to avoid shadowing the parameter (readability).


95-115: Closure signature likely needs async (uses await).

subscriptionStartSerial explicitly annotates throws(InternalError) but calls try await. If the initializer expects an async throws closure, either:

  • remove the explicit signature and let the compiler infer async throws, or
  • add async explicitly.

Apply:

-            subscriptionStartSerial: { [weak self] () throws(InternalError) in
+            subscriptionStartSerial: { [weak self] () async throws(InternalError) in
                 guard let self else {
                     throw MessagesError.noReferenceToSelf.toInternalError()
                 }
                 if channel.state == .attached, let startSerial = subscriptionPoints[uuid] {
                     return startSerial
                 }
                 let startSerial = try await resolveSubscriptionStart()
                 subscriptionPoints[uuid] = startSerial
                 return startSerial
             },

If the initializer already infers async throws, prefer:

-            subscriptionStartSerial: { [weak self] () throws(InternalError) in
+            subscriptionStartSerial: { [weak self] in
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f59a5b1 and 7ac378d.

📒 Files selected for processing (28)
  • CONTRIBUTING.md (1 hunks)
  • Example/AblyChatExample/Mocks/Misc.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (15 hunks)
  • Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (8 hunks)
  • Sources/AblyChat/ChatAPI.swift (2 hunks)
  • Sources/AblyChat/ChatClient.swift (4 hunks)
  • Sources/AblyChat/Connection.swift (2 hunks)
  • Sources/AblyChat/DefaultConnection.swift (2 hunks)
  • Sources/AblyChat/DefaultMessageReactions.swift (3 hunks)
  • Sources/AblyChat/DefaultMessages.swift (4 hunks)
  • Sources/AblyChat/DefaultOccupancy.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (3 hunks)
  • Sources/AblyChat/DefaultRoomReactions.swift (2 hunks)
  • Sources/AblyChat/DefaultTyping.swift (2 hunks)
  • Sources/AblyChat/MessageReactions.swift (3 hunks)
  • Sources/AblyChat/Messages.swift (8 hunks)
  • Sources/AblyChat/Occupancy.swift (2 hunks)
  • Sources/AblyChat/PaginatedResult.swift (4 hunks)
  • Sources/AblyChat/Presence.swift (3 hunks)
  • Sources/AblyChat/Room.swift (13 hunks)
  • Sources/AblyChat/RoomLifecycleManager.swift (4 hunks)
  • Sources/AblyChat/RoomReactions.swift (2 hunks)
  • Sources/AblyChat/Subscription.swift (5 hunks)
  • Sources/AblyChat/SubscriptionStorage.swift (2 hunks)
  • Sources/AblyChat/Typing.swift (2 hunks)
  • Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRoom.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (24)
Sources/AblyChat/DefaultConnection.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (2)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
Sources/AblyChat/Connection.swift (2)
  • onStatusChange (43-57)
  • onStatusChange (60-62)
Sources/AblyChat/Connection.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (2)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Sources/AblyChat/DefaultOccupancy.swift (4)
Sources/AblyChat/DefaultMessageReactions.swift (1)
  • subscribe (62-100)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/DefaultRoomReactions.swift (1)
  • subscribe (36-79)
Sources/AblyChat/Occupancy.swift (2)
  • subscribe (55-69)
  • subscribe (72-74)
Example/AblyChatExample/Mocks/Misc.swift (1)
Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (2)
  • next (46-48)
  • first (50-52)
Sources/AblyChat/DefaultMessages.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (8)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
  • history (157-159)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/RoomLifecycleManager.swift (5)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
  • onRoomStatusChange (77-80)
  • onDiscontinuity (82-85)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (3)
  • onRoomStatusChange (6-20)
  • onRoomStatusChange (22-24)
  • onDiscontinuity (26-39)
Example/AblyChatExample/Mocks/MockClients.swift (1)
  • onDiscontinuity (107-110)
Sources/AblyChat/Room.swift (3)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Tests/AblyChatTests/Mocks/MockRoom.swift (1)
  • onDiscontinuity (72-75)
Sources/AblyChat/Occupancy.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (7)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
Sources/AblyChat/DefaultOccupancy.swift (1)
  • subscribe (20-58)
Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (2)
Sources/AblyChat/SubscriptionAsyncSequence.swift (4)
  • next (44-46)
  • next (119-130)
  • next (139-141)
  • emit (72-79)
Tests/AblyChatTests/SubscriptionAsyncSequenceTests.swift (1)
  • emit (13-23)
Sources/AblyChat/MessageReactions.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (8)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
  • subscribeRaw (306-308)
Sources/AblyChat/DefaultMessageReactions.swift (2)
  • subscribe (62-100)
  • subscribeRaw (103-149)
Sources/AblyChat/Typing.swift (5)
Example/AblyChatExample/Mocks/MockClients.swift (6)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/Occupancy.swift (2)
  • subscribe (55-69)
  • subscribe (72-74)
Sources/AblyChat/Presence.swift (4)
  • subscribe (139-153)
  • subscribe (166-180)
  • subscribe (183-185)
  • subscribe (188-190)
Sources/AblyChat/RoomReactions.swift (2)
  • subscribe (44-58)
  • subscribe (61-63)
Sources/AblyChat/Presence.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (7)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/RoomReactions.swift (6)
Example/AblyChatExample/Mocks/MockClients.swift (5)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
Sources/AblyChat/DefaultRoomReactions.swift (1)
  • subscribe (36-79)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/Occupancy.swift (2)
  • subscribe (55-69)
  • subscribe (72-74)
Sources/AblyChat/Presence.swift (4)
  • subscribe (139-153)
  • subscribe (166-180)
  • subscribe (183-185)
  • subscribe (188-190)
Sources/AblyChat/Typing.swift (2)
  • subscribe (63-77)
  • subscribe (80-82)
Tests/AblyChatTests/Mocks/MockRoom.swift (4)
Example/AblyChatExample/Mocks/MockClients.swift (3)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
  • onDiscontinuity (107-110)
Sources/AblyChat/Room.swift (6)
  • onStatusChange (143-157)
  • onStatusChange (160-162)
  • onStatusChange (386-389)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • onDiscontinuity (226-229)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • onDiscontinuity (82-85)
Sources/AblyChat/Messages.swift (3)
Example/AblyChatExample/Mocks/MockClients.swift (8)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
  • history (157-159)
Sources/AblyChat/DefaultMessages.swift (2)
  • subscribe (39-115)
  • history (118-124)
Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (1)
  • mockGetPreviousMessages (49-58)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (5)
Sources/AblyChat/RoomLifecycleManager.swift (2)
  • onRoomStatusChange (182-185)
  • onDiscontinuity (226-229)
Tests/AblyChatTests/Helpers/RoomLifecycleManager+AsyncSequence.swift (3)
  • onRoomStatusChange (6-20)
  • onRoomStatusChange (22-24)
  • onDiscontinuity (26-39)
Sources/AblyChat/SubscriptionStorage.swift (2)
  • create (16-24)
  • create (57-65)
Sources/AblyChat/Room.swift (3)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Tests/AblyChatTests/DefaultRoomTests.swift (1)
  • onDiscontinuity (296-318)
Example/AblyChatExample/Mocks/MockClients.swift (12)
Sources/AblyChat/Connection.swift (2)
  • onStatusChange (43-57)
  • onStatusChange (60-62)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Sources/AblyChat/Room.swift (6)
  • onStatusChange (143-157)
  • onStatusChange (160-162)
  • onStatusChange (386-389)
  • onDiscontinuity (172-185)
  • onDiscontinuity (188-190)
  • onDiscontinuity (397-400)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • onDiscontinuity (226-229)
Sources/AblyChat/DefaultMessages.swift (2)
  • subscribe (39-115)
  • history (118-124)
Sources/AblyChat/DefaultOccupancy.swift (1)
  • subscribe (20-58)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/DefaultRoomReactions.swift (1)
  • subscribe (36-79)
Sources/AblyChat/DefaultTyping.swift (1)
  • subscribe (37-108)
Sources/AblyChat/Messages.swift (2)
  • subscribe (95-116)
  • subscribe (119-121)
Sources/AblyChat/DefaultMessageReactions.swift (1)
  • subscribeRaw (103-149)
Sources/AblyChat/MessageReactions.swift (2)
  • subscribeRaw (96-110)
  • subscribeRaw (113-115)
Sources/AblyChat/DefaultPresence.swift (2)
Sources/AblyChat/Presence.swift (4)
  • subscribe (139-153)
  • subscribe (166-180)
  • subscribe (183-185)
  • subscribe (188-190)
Sources/AblyChat/Subscription.swift (2)
  • unsubscribe (62-64)
  • unsubscribe (89-91)
Sources/AblyChat/DefaultRoomReactions.swift (5)
Example/AblyChatExample/Mocks/MockClients.swift (7)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
Sources/AblyChat/DefaultOccupancy.swift (1)
  • subscribe (20-58)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/DefaultTyping.swift (1)
  • subscribe (37-108)
Sources/AblyChat/RoomReactions.swift (2)
  • subscribe (44-58)
  • subscribe (61-63)
Sources/AblyChat/SubscriptionStorage.swift (1)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (3)
  • create (38-49)
  • create (96-107)
  • create (153-170)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (1)
Sources/AblyChat/Subscription.swift (1)
  • historyBeforeSubscribe (93-108)
Sources/AblyChat/Subscription.swift (1)
Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (1)
  • historyBeforeSubscribe (211-213)
Sources/AblyChat/Room.swift (7)
Example/AblyChatExample/Mocks/MockClients.swift (3)
  • onStatusChange (90-105)
  • onStatusChange (587-600)
  • onDiscontinuity (107-110)
Sources/AblyChat/Connection.swift (2)
  • onStatusChange (43-57)
  • onStatusChange (60-62)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (23-82)
Tests/AblyChatTests/Mocks/MockRoom.swift (2)
  • onStatusChange (67-70)
  • onDiscontinuity (72-75)
Tests/AblyChatTests/DefaultRoomTests.swift (2)
  • onStatusChange (269-291)
  • onDiscontinuity (296-318)
Sources/AblyChat/RoomLifecycleManager.swift (1)
  • onDiscontinuity (226-229)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
  • onDiscontinuity (82-85)
Sources/AblyChat/DefaultMessageReactions.swift (6)
Sources/AblyChat/DefaultOccupancy.swift (1)
  • subscribe (20-58)
Sources/AblyChat/DefaultPresence.swift (2)
  • subscribe (202-222)
  • subscribe (224-247)
Sources/AblyChat/DefaultRoomReactions.swift (1)
  • subscribe (36-79)
Sources/AblyChat/MessageReactions.swift (4)
  • subscribe (67-81)
  • subscribe (84-86)
  • subscribeRaw (96-110)
  • subscribeRaw (113-115)
Sources/AblyChat/Subscription.swift (2)
  • unsubscribe (62-64)
  • unsubscribe (89-91)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (3)
  • unsubscribe (212-214)
  • unsubscribe (316-318)
  • unsubscribe (430-432)
Sources/AblyChat/DefaultTyping.swift (2)
Example/AblyChatExample/Mocks/MockClients.swift (7)
  • subscribe (127-155)
  • subscribe (280-304)
  • subscribe (335-352)
  • subscribe (366-382)
  • subscribe (534-536)
  • subscribe (538-540)
  • subscribe (554-565)
Sources/AblyChat/Typing.swift (2)
  • subscribe (63-77)
  • subscribe (80-82)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
🔇 Additional comments (47)
Sources/AblyChat/DefaultMessageReactions.swift (2)

63-100: LGTM on returning a concrete DefaultSubscription.

Removes the existential and keeps unsubscribe symmetrical.


146-148: LGTM on unsubscribe symmetry.

Sources/AblyChat/PaginatedResult.swift (3)

11-13: Good: navigation returns Self, eliminating public existentials.

This aligns with the PR goal.


65-98: Wrapper conforms correctly to Self-returning API.

The Self-typed next/first/current are coherent.


1-14: No remaining any PaginatedResult existentials
Search returned no matches.

Sources/AblyChat/Messages.swift (2)

95-121: AsyncSequence convenience correctly tracks SubscribeResponse.HistoryResult.

Unsubscribe on termination and event piping look sound.


338-371: Generic MessageSubscriptionAsyncSequence wiring is consistent.

getPreviousMessages closure typing and public API match the new associated types.

Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift (6)

9-9: Concrete MockSubscription in storage — LGTM

Switching from protocol existential to concrete mock reduces dynamic dispatch and clarifies mock wiring.


42-42: Factory now returns MockSubscription — LGTM

Consistent with the storage item type and eases use in tests.


67-67: Concrete MockStatusSubscription — LGTM

Matches the existential reduction strategy across mocks.


100-100: Status factory return type updated — LGTM

Return type aligns with stored mock and usage.


122-131: Generic Message subscription storage — LGTM

Propagating PaginatedResult as a generic greatly clarifies types and mirrors the production flow. Returning some MessageSubscriptionResponseProtocol keeps call sites stable.

Also applies to: 155-159


207-221: MockMessageSubscriptionResponse generic result — LGTM

Generic PaginatedResult plumbs through correctly; historyBeforeSubscribe returns the concrete associated type expected by the async-sequence wrapper.

Sources/AblyChat/ChatAPI.swift (2)

19-22: Opaque getMessages return — LGTM

Returning some PaginatedResult<Message> removes existential use while keeping call sites unchanged.


245-252: Opaque makePaginatedRequest return — LGTM

The opaque return paired with toPaginatedResult is appropriate; generic constraints ensure Sendable/Equatable safety.

Sources/AblyChat/ChatClient.swift (2)

6-6: Introduce Connection associated type and nonisolated accessor — LGTM

This removes the public existential and aligns with the broader associated-type pattern.

Also applies to: 22-22


108-109: Wire DefaultConnection into client — LGTM

Concrete storage with an opaque public accessor preserves API while removing existentials internally.

Sources/AblyChat/Connection.swift (1)

8-8: Associated StatusSubscription and concrete return — LGTM

Eliminates the existential in onStatusChange. The async-sequence helper remains valid via off() on the associated type.

Also applies to: 30-30

Sources/AblyChat/RoomReactions.swift (1)

10-10: Associated Subscription and typed subscribe — LGTM

Removes existential return from the public API and keeps the async-sequence adapter intact.

Also applies to: 31-31

Example/AblyChatExample/Mocks/Misc.swift (1)

33-33: Self-referential PaginatedResult properties — LGTM

Aligned with the new Self-based PaginatedResult. No issues.

Also applies to: 35-35, 37-37

Tests/AblyChatTests/MessageSubscriptionAsyncSequenceTests.swift (1)

13-17: Tests updated for self-referential PaginatedResult and explicit generic — LGTM

Mocks and instantiation align with the new typing.

Also applies to: 36-36, 42-42

Sources/AblyChat/DefaultRoomReactions.swift (1)

37-37: Opaque Subscription return — LGTM

Switch to returning DefaultSubscription via some SubscriptionProtocol is consistent and safe.

Also applies to: 76-76

Sources/AblyChat/DefaultTyping.swift (1)

38-38: Opaque Subscription return — LGTM

Return type updated and unsubscribe handled correctly.

Also applies to: 100-100

Sources/AblyChat/DefaultPresence.swift (1)

202-202: Concrete DefaultSubscription returns — LGTM

Good move away from existentials; unsubscribe closures are safe.

Also applies to: 217-217, 224-224, 240-240

Sources/AblyChat/DefaultConnection.swift (1)

24-24: Opaque StatusSubscription return — LGTM

Existential removed; unsubscribe closure handles both timer and listener cleanly.

Also applies to: 75-75

Sources/AblyChat/Occupancy.swift (1)

11-11: [scratchpad]
[tasks_1/2 verify_conformers]
[observations]

  • Protocol Occupancy defines associatedtype Subscription: SubscriptionProtocol.
  • DefaultOccupancy: Occupancy implements func subscribe(...) -> some SubscriptionProtocol without explicit typealias Subscription.
  • MockOccupancy: Occupancy exists but no evidence of typealias Subscription.
    [analysis]
  • Conformers must satisfy Subscription associatedtype: returning some SubscriptionProtocol implicitly infers associatedtype. Swift inference supports opaque return types for associatedtype conformance.
  • Both DefaultOccupancy and MockOccupancy use opaque (some SubscriptionProtocol) and MockSubscription respectively, satisfying requirement.
    [pending]
  • Confirm no missing conformers for Occupancy.
  • Check if any conformer fails to implement required func subscribe(_:) -> Subscription.
    [actions]
  • Run search for DefaultOccupancy definition to inspect conformance.
  • Run search for MockOccupancy definition to inspect conformance.
    [done]
    [/scratchpad]

All Occupancy conformers return a consistent concrete Subscription type via some SubscriptionProtocol or a specific MockSubscription, satisfying the associatedtype requirement.

Sources/AblyChat/Subscription.swift (2)

37-38: HistoryResult associated type — LGTM Swift tools version 6.2 supports opaque types satisfying associated type requirements.


93-108: Opaque history return in concrete type — OK; aligns with protocol associated type
Ensure minimum Swift version supports opaque return types (some).

Sources/AblyChat/Presence.swift (1)

86-100: Subscribe API return type change looks good; AsyncSequence adapter correctly unsubscribes.

Signatures now return the associated Subscription and the adapters call unsubscribe() on termination. Aligns with PR goals.

Sources/AblyChat/RoomLifecycleManager.swift (2)

5-5: Associated StatusSubscription is appropriate for internal API.

Conforms to the PR’s direction; keeps existentials out of returns while allowing internal existential use where needed.


12-12: Return types switched to concrete/associated subscription; LGTM.

onRoomStatusChange/onDiscontinuity now return StatusSubscription/DefaultStatusSubscription. AsyncSequence helpers upstream correctly call off().

Also applies to: 27-27, 183-183, 227-227

Example/AblyChatExample/Mocks/MockClients.swift (9)

12-12: Switch to MockConnection is fine in examples.

Assuming ChatClient.connection surface accepts concrete/opaque connection. No issues spotted here.


56-60: Concrete mock component properties align with associated type changes.

Using Mock* concretes avoids existential usage in examples. Looks good.


91-105: Status subscription mock returns DefaultStatusSubscription; good parity with production.

Periodic emitter + proper cancellation closure. LGTM.


127-155: Messages.subscribe mock updated to opaque response; state tracking changes LGTM.

Reactions bookkeeping updates (Lines 144,146) and some MessageSubscriptionResponseProtocol return are consistent with new API.


157-159: Opaque PaginatedResult return in history mock: good.


280-304: MessageReactions.subscribe mock returning MockSubscription matches protocol constraints.

Fits the new associatedtype Subscription. LGTM.


534-541: Presence.subscribe mocks returning MockSubscription align with new Presence PAT.

Consistent with MockSubscriptionStorage. LGTM.


555-565: Occupancy.subscribe mock now returns MockSubscription; consistent with API.

No issues.


588-600: Connection.onStatusChange mock uses opaque status subscription; good.

Matches updated Connection surface across the repo.

Sources/AblyChat/MessageReactions.swift (2)

41-41: Subscribe returns associated Subscription; AsyncSequence adapters correctly unsubscribe.

Matches implementations (DefaultMessageReactions). Good.

Also applies to: 54-54


10-11: Reactions exposures use associated types

Room.reactions and Messages.reactions return generic associated types rather than any MessageReactions, so no changes needed.

Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)

78-80: Mocks updated to return DefaultStatusSubscription; matches production API.

Simple, correct change.

Also applies to: 83-85

Sources/AblyChat/DefaultMessages.swift (1)

118-124: Opaque some PaginatedResult passthrough.

Forwarding an opaque result is fine as long as chatAPI.getMessages consistently returns the same concrete type. Please confirm it does.

Tests/AblyChatTests/Mocks/MockRoom.swift (1)

19-37: Mocks aligned with concrete Default types and subscriptions.*

Switch to DefaultMessages/Presence/Reactions/Typing/Occupancy and returning DefaultStatusSubscription matches the new API. LGTM.

Also applies to: 67-75

Sources/AblyChat/Room.swift (2)

10-17: Associated types migration looks consistent.

Public Room now uses associated types and returns StatusSubscription for status APIs. Extension helpers still use off() correctly. Looks good.

Confirm StatusSubscriptionProtocol exposes off() (and not e.g. unsubscribe()), since both styles exist elsewhere.

Also applies to: 30-67, 83-96


229-238: DefaultRoom generic + concrete Default composition reads well.*

Factory return type and stored properties align with the associated-type protocol. Status methods returning LifecycleManager.StatusSubscription are coherent.

Also applies to: 241-256, 271-330, 386-400

Comment thread Sources/AblyChat/Presence.swift
Comment thread Sources/AblyChat/Typing.swift
Comment thread Sources/AblyChat/Typing.swift
@lawrence-forooghian lawrence-forooghian force-pushed the 2025-10-03-enable-upcoming-language-features branch from f59a5b1 to a43b818 Compare October 3, 2025 16:06
This was started in f44e533; here we address the remainder.

The only remaining usage is in ChatClientOptions.logHandler, to avoid
making the options type generic.

(There's still a fair bit of usage of existential types internally but
we don't need to sort that out pre-v1 release.)
Copy link
Copy Markdown
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

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

A question

Comment thread Example/AblyChatExample/Mocks/MockClients.swift
Base automatically changed from 2025-10-03-enable-upcoming-language-features to main October 3, 2025 16:24
Copy link
Copy Markdown
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

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

Lgtm

@lawrence-forooghian lawrence-forooghian merged commit 53e1853 into main Oct 6, 2025
17 checks passed
@lawrence-forooghian lawrence-forooghian deleted the remove-existential-from-public-api branch October 6, 2025 12:07
@lawrence-forooghian lawrence-forooghian changed the title Get rid of existential type usage in the public API [ECO-5577] Get rid of existential type usage in the public API Oct 10, 2025
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.

2 participants