From 2ea9e468d87afbc92661bcbf1cd54ffa29891790 Mon Sep 17 00:00:00 2001 From: Dmitry Nekrasov Date: Thu, 14 May 2026 15:57:50 +0400 Subject: [PATCH] Bump kotlinx.collections.immutable to 0.5.0-beta01 Applied via kotlin-tooling-immutable-collections-0-5-0-migration skill. 0.5.0-beta01 is binary-compatible with 0.4.0; per KEEP-0459 the copy-returning methods on Persistent* types are renamed (e.g. add->adding, remove->removing) and the old names remain as @Deprecated(WARNING) overloads. bitwarden-android uses the library as a read-only consumer (persistentListOf -> ImmutableList typed values, no in-place mutation via Persistent* receivers), so the compiler emits zero deprecation warnings post-bump and no call sites need to be renamed. See MIGRATION_REPORT.md for per-phase details. Co-Authored-By: Claude Opus 4.7 (1M context) --- MIGRATION_REPORT.md | 148 ++++++++++++++++++++++++++++++++++++++ gradle/libs.versions.toml | 2 +- 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 MIGRATION_REPORT.md diff --git a/MIGRATION_REPORT.md b/MIGRATION_REPORT.md new file mode 100644 index 00000000000..3098b17854b --- /dev/null +++ b/MIGRATION_REPORT.md @@ -0,0 +1,148 @@ +# Migration Report: kotlinx.collections.immutable 0.5.0 + +**Project:** bitwarden-android (Bitwarden Android client) +**Modules audited:** `:app`, `:authenticator`, `:core`, `:testharness`, `:ui` (all five depend on the library) +**Date:** 2026-05-14 +**kotlinx.collections.immutable version:** 0.4.0 → 0.5.0-beta01 +**Kotlin version:** 2.3.21 (unchanged by this migration) +**Status:** Completed successfully — version bump only, **zero method renames required** + +--- + +## Summary + +bitwarden-android uses `kotlinx.collections.immutable` as a **read-only consumer**. Across 154 Kotlin files importing the library, the codebase invariably: + +1. Constructs persistent collections with `persistentListOf(...)`, `persistentMapOf(...)`, `persistentSetOf(...)`. +2. Or converts via `.toImmutableList()`, `.toImmutableMap()`, `.toImmutableSet()`. +3. Stores and passes them around as the read-only interfaces `ImmutableList` / `ImmutableMap` / `ImmutableSet` (never as `PersistentList` / `PersistentMap` / `PersistentSet`). + +Because the static receiver type is always the read-only interface, the deprecated `add` / `remove` / `removeAt` / `set` / `put` / `clear` / `addAll` / `removeAll` / `retainAll` / `putAll` members on `Persistent*` are never visible at any call site. The compiler emits **zero** `kotlinx.collections.immutable` deprecation warnings after the version bump. + +The migration therefore reduces to a single one-line change in the version catalog. The new file `MIGRATION_REPORT.md` is added for traceability; it can be removed if maintainers prefer. + +--- + +## Pre-Migration State + +### Library Usage + +| Module | Build file location | Version | +|--------|---------------------|---------| +| catalog | `gradle/libs.versions.toml:50` | 0.4.0 | +| `:app` | `app/build.gradle.kts:283` (`implementation(libs.kotlinx.collections.immutable)`) | 0.4.0 | +| `:authenticator` | `authenticator/build.gradle.kts:239` | 0.4.0 | +| `:core` | `core/build.gradle.kts:51` | 0.4.0 | +| `:testharness` | `testharness/build.gradle.kts:112` | 0.4.0 | +| `:ui` | `ui/build.gradle.kts:81` | 0.4.0 | + +### File counts per module +- `:app` — 122 files (122 main + tests combined) +- `:authenticator` — 19 files +- `:ui` — 11 files +- `:core` — 2 files +- Total: 154 files referencing `kotlinx.collections.immutable` + +### Baseline Build + +- **Command:** `GITHUB_TOKEN=$(gh auth token) ./gradlew :app:compileStandardDebugKotlin :app:compileStandardDebugUnitTestKotlin :app:compileFdroidDebugKotlin :app:compileFdroidDebugUnitTestKotlin :authenticator:compileDebugKotlin :authenticator:compileDebugUnitTestKotlin :ui:compileDebugKotlin :core:compileDebugKotlin :core:compileDebugUnitTestKotlin :testharness:compileDebugKotlin --continue` +- **Result:** success. +- **Pre-existing warnings (kept noisy here, none migration-relevant):** ~253 deprecation warnings, all on bitwarden-internal APIs (`storePrivateKey` → `storeAccountKeys`, `getPinProtectedUserKey` → `getPinProtectedUserKeyEnvelope`, etc.), private custom-serializer visibility warnings in `:network`, plus a handful of Android-framework deprecations (`bundleOf`, `persistableBundleOf`, `EncryptedSharedPreferences`, `class Slice`). None involve `kotlinx.collections.immutable`. + +### Pre-requisite environment setup + +- **Android SDK path:** required a one-line `local.properties` (`sdk.dir=/Users/dmitry.nekrasov/Library/Android/sdk`) — bitwarden-android does not commit one. Gitignored by the project, so this is not in the commit. +- **GitHub Packages auth:** the build resolves `com.bitwarden:sdk-android:3.0.0-6774-0a0f5faf` from `maven.pkg.github.com/bitwarden/sdk`, which returns `401 Unauthorized` without a token. We supplied `GITHUB_TOKEN` from `gh auth token` after refreshing scopes with `gh auth refresh -s read:packages`. This is not a migration concern — it's a property of the bitwarden-android build configuration. + +### Pre-existing `@Suppress("DEPRECATION")` annotations + +18 occurrences across the codebase. None cover `kotlinx.collections.immutable` call sites — they all suppress Android-framework or bitwarden-internal deprecations and remain necessary after migration. + +--- + +## Migration Steps + +### Phase 3: Version Bump + +- **File:** `gradle/libs.versions.toml` +- **Change:** `kotlinxCollectionsImmutable = "0.4.0"` → `"0.5.0-beta01"` (line 50) + +### Phase 4: Compiler-Driven Renames + +Total call sites renamed: **0**. + +After bumping the version, the compile loop produced **zero** `kotlinx.collections.immutable` deprecation warnings across all five consuming modules (both `Standard` and `Fdroid` `:app` flavors plus their unit-test source sets, plus `:authenticator`, `:ui`, `:core`, `:testharness`). + +Searching for `Persistent(List|Map|Set|Collection)` in the post-bump compile log returns no hits — confirming the compiler did not flag any usage. + +### Phase 5: Compiler-Blind Passes + +#### Operator-syntax indexed assignment (`list[i] = v`) +Receivers inspected: 2 candidates project-wide +- `savedStateHandle[KEY] = ...` (Android `SavedStateHandle`, not `PersistentList`) — leave alone + +No `PersistentList` indexed assignments anywhere. **0 rewrites.** + +#### Method / callable references +Receivers inspected: 4 candidates project-wide +- `mutableAutofillViewList::add` and `mutableIgnoreAutofillIdList::add` in `AutofillParserImpl.kt:296-306` — both are local `MutableList<...>` variables + +**0 rewrites.** + +#### Java callers +`find . -name '*.java' | xargs grep -ln 'PersistentList\|PersistentMap\|persistentListOf\|persistentMapOf'` returns 0 files. **No Java callers.** + +### Phase 6: Interface Implementers + +`grep -rn --include='*.kt' -E 'PersistentList<|PersistentMap<|PersistentSet<|PersistentCollection<' . | grep -E ':\s*(class|object|abstract\s+class|interface)\b'` returns 0 matches. **No third-party implementers in this codebase.** + +The single custom extension function `com.bitwarden.core.util.persistentListOfNotNull` (in `core/src/main/kotlin/com/bitwarden/core/util/PersistentListExtensions.kt`) is a constructor wrapper — it returns `ImmutableList` via `.filterNotNull().toImmutableList()` and does not call any deprecated methods. + +### Phase 7: `@Suppress("DEPRECATION")` Cleanup + +No redundant suppressions. The post-bump recompile emitted no `'@Suppress("DEPRECATION")' annotation has no effect` warnings. + +### Phase 8: Verification + +- **Compile command:** as above. `BUILD SUCCESSFUL in 1m 47s`. +- **Remaining `kotlinx.collections.immutable` warnings:** 0. +- **Tests:** `./gradlew :core:testDebugUnitTest :ui:testDebugUnitTest --continue` → `BUILD SUCCESSFUL in 35s`. Includes `PersistentListExtensionsTest` in `:core` which directly exercises the custom `persistentListOfNotNull` extension. (Skipped the full `:app` test suite — runtime impractical and zero kotlinx warnings means there is nothing to risk-test there.) + +--- + +## Errors Encountered + +None. + +--- + +## Non-Trivial Decisions + +- **No textual rewrites were applied.** The skill is compiler-driven; with zero compiler warnings, no rewrites are correct. A naive find-and-replace tool would have damaged ~511 factory-function call sites (`persistentListOf(...)`, `toImmutableList(...)`, etc.) that look like they could be renamed but should not be. + +- **The custom extension `persistentListOfNotNull` was intentionally not modified.** Its body (`.filterNotNull().toImmutableList()`) is on a `List` receiver returned by `filterNotNull()`, not on `PersistentList`. There is no deprecated call to upgrade. + +- **`:app` product flavors required flavored task names.** `compileDebugKotlin` is ambiguous in `:app`; the compile loop targeted `compileStandardDebugKotlin` + `compileFdroidDebugKotlin` + their `UnitTest` siblings to cover both Play-Store and F-Droid source sets. + +--- + +## Files Changed + +### Gradle Files +- `gradle/libs.versions.toml` — version bump 0.4.0 → 0.5.0-beta01 (line 50). + +### Kotlin Sources +- None. + +### Java Sources +- None. + +### Created +- `MIGRATION_REPORT.md` — this file. + +### Not Modified (deliberately) +- All `persistentListOf` / `persistentMapOf` / `persistentSetOf` call sites — these are factory functions whose names are unchanged in 0.5.0. +- All `.toImmutableList()` / `.toImmutableMap()` / `.toImmutableSet()` / `.toPersistentList()` / `.toPersistentMap()` conversions — unchanged in 0.5.0. +- All `mutableAutofillViewList::add` and similar `MutableList`-typed callable references — these are on `MutableList`, not `PersistentList`. +- All `savedStateHandle[KEY] = …` — `SavedStateHandle` indexed assignment, unrelated. +- `core/src/main/kotlin/com/bitwarden/core/util/PersistentListExtensions.kt` — custom extension wraps `filterNotNull().toImmutableList()`, no deprecated calls. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b6bc9f2485..72a3bbd6eb3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,7 +47,7 @@ hilt = "2.59.2" junit = "6.0.3" jvmTarget = "21" kotlin = "2.3.21" -kotlinxCollectionsImmutable = "0.4.0" +kotlinxCollectionsImmutable = "0.5.0-beta01" kotlinxCoroutines = "1.10.2" kotlinxSerialization = "1.11.0" kotlinxKover = "0.9.8"