Skip to content

Update baseline swiftinterface files for pallares/hashable-configuration-via-storage#6812

Merged
ajpallares merged 1 commit into
pallares/hashable-configuration-via-storagefrom
generated_swiftinterface/pallares/hashable-configuration-via-storage-574628
May 18, 2026
Merged

Update baseline swiftinterface files for pallares/hashable-configuration-via-storage#6812
ajpallares merged 1 commit into
pallares/hashable-configuration-via-storagefrom
generated_swiftinterface/pallares/hashable-configuration-via-storage-574628

Conversation

@RCGitBot

@RCGitBot RCGitBot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Requested by @ajpallares for pallares/hashable-configuration-via-storage


Note

Medium Risk
Updates the public Swift/ObjC interface for core configuration-related types, including equality/hash exposure and property mutability, which can affect how SDK objects behave in collections and comparisons. While mostly baseline/interface regeneration, it touches widely-used public API surfaces across all Apple platforms.

Overview
Refreshes the generated .swiftinterface baselines across iOS/macOS/tvOS/watchOS/visionOS (and simulators).

Notable API surface changes include DangerousSettings switching autoSyncPurchases and customEntitlementComputation from stored let to getter-only var, and adding Objective-C isEqual(_:)/hash overrides (guarded by compiler(>=5.3) && $NonescapableTypes) for DangerousSettings, Purchases.PlatformInfo, and Configuration.

Reviewed by Cursor Bugbot for commit ded1c2c. Bugbot is set up for automated code reviews on this repo. Configure here.

@RCGitBot RCGitBot requested a review from a team as a code owner May 18, 2026 07:28
@ajpallares ajpallares merged commit c40c1fd into pallares/hashable-configuration-via-storage May 18, 2026
5 checks passed
@ajpallares ajpallares deleted the generated_swiftinterface/pallares/hashable-configuration-via-storage-574628 branch May 18, 2026 09:03
ajpallares added a commit that referenced this pull request May 22, 2026
…6811)

* Expand DangerousSettings API tester coverage

Adds explicit property-read and constructor coverage for `DangerousSettings`
in both the Swift and Obj-C API testers so future refactors of the type
(e.g. moving stored properties into an internal value-type backing) are
guaranteed to remain source- and ABI-compatible for SDK consumers.

Swift (SwiftAPITester/DangerousSettingsAPI.swift):
- Exercise `DangerousSettings()` and `DangerousSettings(autoSyncPurchases:)`
  in addition to the existing `DangerousSettings(uiPreviewMode:)` case.
- Read all three publicly exposed properties (`autoSyncPurchases`,
  `customEntitlementComputation`, `uiPreviewMode`).

Obj-C (ObjcAPITester/RCDangerousSettingsAPI.{h,m}):
- New file mirroring the `RCConfigurationAPI` pattern (one
  `+ (void)checkAPI` per public type), wired into `main.m` and the
  `AllAPITests.xcodeproj` project file.
- Covers `-init`, `-initWithAutoSyncPurchases:`, and reads of the two
  Obj-C-exposed properties (`autoSyncPurchases`, `customEntitlementComputation`).
  `uiPreviewMode` is intentionally omitted since it is `@_spi(Internal)` and
  not bridged to Obj-C.

These additions are landed first so we can verify the existing public
surface compiles on `main`, before any refactor of `DangerousSettings`
internals is layered on top.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Back Configuration, DangerousSettings, PlatformInfo with Hashable Storage struct

Moves the comparable stored properties of `Configuration`, `DangerousSettings`,
and `Purchases.PlatformInfo` into internal `Storage` value types that
automatically synthesize `Hashable`. The class-level `isEqual(_:)` and `hash`
overrides delegate to the storage, so adding a new field only requires touching
`Storage`—no parallel updates to equality boilerplate.

Notes:
- Public `let` properties on `DangerousSettings` are now `var { get }` computed
  from `storage`. This is callsite-compatible for both Swift and Obj-C
  consumers, but `.swiftinterface` baselines under `api/` will need to be
  refreshed via the existing `update_swiftinterface_baselines` Fastlane lane.
- `UserDefaults?` is intentionally part of `Configuration.Storage`'s equality
  via `NSObject` reference identity. This dedups the common case (both `nil`,
  both `.standard`, or the same cached suite) while still treating a different
  `UserDefaults` instance as a real configuration change.
- `internalSettings` on `DangerousSettings` is excluded from equality; it's an
  internal/debug mechanism containing non-`Hashable` closures and has no
  observable effect on the public configuration.
- `Signing.ResponseVerificationMode` is left untouched globally; a small
  case-only `ResponseVerificationModeKey` proxy inside `Configuration` is used
  in `Storage`, since the associated `PublicKey` is always the hardcoded value
  loaded by `Signing.loadPublicKey()`.

Foundation for an upcoming change that deduplicates `Purchases.configure(...)`
calls with identical configurations, mirroring purchases-android's behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Dedupe `Purchases.configure(with:)` calls with equal `Configuration`

Mirrors `purchases-android`'s behavior of returning the existing
`Purchases` instance when `configure` is invoked again with a
`Configuration` equal to the one used previously.

Implementation:
- Store the `Configuration` used to create each `Purchases` instance on
  the instance itself (`currentConfiguration`).
- Add a `dedupingAgainst:` parameter to `setDefaultInstance`. When the
  current singleton's `currentConfiguration` equals the supplied one, the
  existing instance is returned and `purchases` is not invoked, avoiding
  an unnecessary `Purchases` allocation. A dedicated `ConfigureStrings`
  case logs that the existing instance is being reused.
- Route the public `Purchases.configure(with:)` directly through the new
  `setDefaultInstance(_:dedupingAgainst:)` and drop the legacy internal
  `configure(withAPIKey:...)` helper, which had no remaining callers.
  All other `configure(...)` overloads continue to funnel through
  `configure(with:)`, so every configure path now benefits from
  deduplication.

Tests:
- Add equality unit tests for `Configuration`, `DangerousSettings`, and
  `Purchases.PlatformInfo` (the three types now backed by `Hashable`
  storage structs), including `UserDefaults` reference-identity coverage
  for `Configuration`.
- Add dedup behavior tests covering repeated `Purchases.configure(with:)`
  with equal/equivalent `Configuration`s, with same/different
  `UserDefaults` references, plus a direct check of the equality
  short-circuit used by `setDefaultInstance(_:dedupingAgainst:)`.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add new Misc unit tests to RevenueCat.xcodeproj

`Tests/UnitTests/Misc/DangerousSettingsTests.swift` and
`Tests/UnitTests/Misc/PlatformInfoTests.swift` were picked up by the
Tuist-generated `UnitTests` target via globbing, but the hand-maintained
`RevenueCat.xcodeproj` enumerates each file explicitly, so the Danger
project-sync check flagged them as missing.

Wire them into the `Misc` group, file references, and the `UnitTests`
sources build phase, mirroring how `ClockTests.swift` is registered.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Match `purchases-android`'s dedup log message verbatim

The new info log is what developers will actually see when they
configure twice with the same `Configuration`. Talking about "returning
the existing instance" is misleading because the SDK does not surface a
return value to callers that hit the dedup path at runtime (the public
`configure(with:)` is `@discardableResult`).

Align with `purchases-android`'s `INSTANCE_ALREADY_EXISTS_WITH_SAME_CONFIG`:

- Rename `purchase_instance_already_set_with_same_config` to
  `instance_already_exists_with_same_config`.
- Use Android's exact wording: "Purchases instance already set with the
  same configuration. Ignoring duplicate call."

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop redundant `Storage` rationale doc comments

The doc paragraphs only narrated the obvious mechanic ("a `Hashable`
struct lets the synthesized conformances drive `isEqual`/`hash`"), which
the code itself already conveys. Keep the non-obvious bit: the comment
explaining why `internalSettings` is excluded from `DangerousSettings`'s
storage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop `purchases-android` reference from setDefaultInstance doc

The doc comment for `setDefaultInstance(_:dedupingAgainst:)` already
describes the behavior on its own terms; pointing at another SDK as the
canonical reference adds noise.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Clarify dedup doc: only test paths leave configuration `nil`

There is no production "legacy" caller of
`setDefaultInstance(_:dedupingAgainst:)` — every public configure
overload routes through `configure(with: Configuration)`. The only
callers that pass `nil` are `BasePurchasesTests` and
`PurchasesSubscriberAttributesTests`, which build a `Purchases` directly
with mocks. Tighten the doc on both `setDefaultInstance` and
`currentConfiguration` to reflect that.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Require `currentConfiguration` to be passed explicitly

Drop the `= nil` default on `currentConfiguration` on both `Purchases`
initializers and pass `nil` explicitly from the two test helpers that
build `Purchases` directly with mocks. This way, future changes that
add a new code path producing a `Purchases` instance will be forced to
decide whether that instance participates in `configure(with:)`
deduplication instead of silently opting out.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop redundant `Storage` rationale doc on `Configuration`

Matches the cleanup already done on `DangerousSettings` and
`PlatformInfo`: the "Hashable struct synthesizes equality" paragraph
just narrates the obvious mechanic. Keep the `UserDefaults`
reference-identity note since that documents a non-obvious decision.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Store `EntitlementVerificationMode` directly on `Configuration.Storage`

`Configuration.Storage` previously held a `ResponseVerificationModeKey`
proxy enum because `Signing.ResponseVerificationMode` is not `Hashable`
(its associated `PublicKey` is a CryptoKit type without `Hashable`
conformance) and the developer-facing `EntitlementVerificationMode`
could not be used as a destination in a `Signing.ResponseVerificationMode`
mapping switch (`.enforced` is `@available(*, unavailable)`).

The proxy is unnecessary if we keep the developer-facing setting on
both the `Builder` and `Configuration`, and only expand it into a
`Signing.ResponseVerificationMode` (which carries the hardcoded
`PublicKey`) on demand:

- `Builder.responseVerificationMode: Signing.ResponseVerificationMode`
  becomes `Builder.entitlementVerificationMode: EntitlementVerificationMode`,
  defaulting to `.informational` (preserving the previous runtime
  default of `Signing.ResponseVerificationMode.default`).
- `Configuration.Storage.entitlementVerificationMode` replaces
  `responseVerificationMode: ResponseVerificationModeKey`, so `Storage`
  keeps free synthesized `Hashable` conformance without a proxy.
- `Configuration.responseVerificationMode` becomes a `lazy var` that
  calls `Signing.verificationMode(with:)` (and thus
  `Signing.loadPublicKey()`) on first access. The only production
  reader is `Purchases.configure(with:)`, so the lazy resolution
  happens at the same point the eager resolution used to.

Drops the proxy enum and its rationale doc comment.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop `responseVerificationMode` lazy resolution doc

The doc explains an internal mechanic (when the lazy var resolves)
that the reader can see from the `lazy` keyword itself.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update baseline swiftinterface files (#6812)

* Merge the same-configuration dedup unit tests

The instance-identity check and the log-message check exercised the
same `configure → configure` setup. Collapse them into one test so we
only build the dedup scenario once and the assertion list reads in one
pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore internal `configure(withAPIKey:...)` helper for backend integration tests

`BaseBackendIntegrationTests.configurePurchases()` calls the internal
long-form `Purchases.configure(withAPIKey:..., responseVerificationMode:
Signing.ResponseVerificationMode, ...)` helper to opt into strict signing
verification without going through `Configuration` (whose public
`EntitlementVerificationMode.enforced` case is marked
`@available(*, unavailable)`).

The previous dedup commit inadvertently removed this helper while
consolidating `configure(with:)` paths, breaking compilation of all five
`backend-integration-tests-*` CI jobs.

Restore the helper and forward `currentConfiguration: nil` so this path
opts out of dedup, mirroring the behavior of the other test-only call
sites that construct `Purchases` directly with mocks.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants