# Kotlin rules

Apply these rules to every relevant file in this project.

- Do not assume this is safe: a function returning `List` does not prove that the result stays unchanged.
  Why: A caller may receive an alias to a list that the object's internals still modify.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- With `Map`, `map[key] == null` cannot tell whether the key is absent or present with `null` as its value.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- `associateBy { it.id }` looks like index construction, but a duplicate ID lets the later element overwrite the earlier one and shrinks the result.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- Calling `items.remove(item)` inside `for (item in items)` makes iterator state disagree with collection state and commonly produces `ConcurrentModificationException`.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- Do not assume this is safe: a `HashSet` or `HashMap` printing in a stable order for one sample does not make that order part of the interface contract.
  Why: Generated tests often copy the current representation straight into an expected string.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- Do not assume this is safe: `generateSequence()` can have no end, and calling `toList()`, `count()`, or `sorted()` on such a sequence does not complete normally.
  Why: A file line sequence that escapes the reader-closing scope also fails when it is eventually consumed.
  Source: [Collections](https://codewiki.com/kotlin/collections/)
- Do not assume this is safe: putting business state in the class body makes generated equality and `copy()` ignore it.
  Why: Instances with different state can compare equal, and a copy resets the property to its initializer.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- `val` only prevents property reassignment, while `List` restricts only the current interface; neither freezes the backing object.
  Why: Generated code often stores an external `MutableList` and then calls `copy()`, assuming it has made an independent snapshot.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- After a `var` or nested mutable object used by `equals()` and `hashCode()` changes, a key already stored in a hash table may no longer be found under its new state.
  Why: The entry remains at its old hash position while lookup uses its changed hash code.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- `Array.equals()` uses reference semantics, so two data class instances with `Array` properties aren't automatically equal just because the arrays contain equal elements.
  Why: A `ByteArray` property is particularly easy to mistake for value semantics: independently allocated payloads with identical bytes still compare unequal under the generated data-class implementation.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- Generated `toString()` displays every primary-constructor property.
  Why: Once an access token, password, session identifier, or protected personal value enters a data class, interpolating the whole object into a log can disclose it without explicit field access.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- Do not assume this is safe: destructuring follows `componentN()` positions, not local variable names.
  Why: Reordering constructor properties can silently swap the meaning of same-typed components while call sites still compile.
  Source: [Data classes](https://codewiki.com/kotlin/data-classes/)
- Turning a mutable delegate with one `value` field into an `object` makes every property bound to it share state.
  Why: Two form instances overwrite each other as well, producing data leakage that looks intermittent.
  Source: [Delegated properties](https://codewiki.com/kotlin/delegated-properties/)
- Do not treat `observable` as a transaction hook leaves partial state.
  Why: Its callback runs after the new value is stored, and an exception from a database write or listener doesn't roll the property back.
  Source: [Delegated properties](https://codewiki.com/kotlin/delegated-properties/)
- Do not assume this is safe: when `vetoable` returns `false`, an ordinary assignment statement still finishes without giving the caller a rejection result or reason.
  Why: Generated code often continues as if the new value took effect.
  Source: [Delegated properties](https://codewiki.com/kotlin/delegated-properties/)
- Map delegation uses the property name as a runtime key and casts `Any?` to the declared type.
  Why: A missing key raises `NoSuchElementException`, a wrong type raises a cast error, and a property rename silently changes the data contract.
  Source: [Delegated properties](https://codewiki.com/kotlin/delegated-properties/)
- `LazyThreadSafetyMode.PUBLICATION` may run the initializer concurrently on several threads and guarantees only that one result is published.
  Why: `NONE` has unspecified behavior under multithreaded access; choosing it as a generic faster mode bases correctness on an unproved thread assumption.
  Source: [Delegated properties](https://codewiki.com/kotlin/delegated-properties/)
- Marking only the top-level builder doesn't restrict an unmarked child builder.
  Why: Inside the omitted type, members of outer receivers may become visible again.
  Source: [DSLs](https://codewiki.com/kotlin/dsl/)
- Returning a builder-owned `MutableList` directly from `build()` lets completed results keep changing with the builder.
  Why: Declaring the property as `List` doesn't undo an alias that already escaped.
  Source: [DSLs](https://codewiki.com/kotlin/dsl/)
- Representing every required step with nullable properties and reading them with `!!` at the end turns a clear configuration error into a late null-pointer failure.
  Why: The error also fails to identify the omitted DSL operation.
  Source: [DSLs](https://codewiki.com/kotlin/dsl/)
- Infix functions and operators change code shape but don't guarantee intuitive meaning.
  Why: Infix calls also have their own precedence, so mixing them with arithmetic, casts, or Boolean expressions can make the reading differ from the parse.
  Source: [DSLs](https://codewiki.com/kotlin/dsl/)
- A type-safe builder checks Kotlin types, but it doesn't escape HTML, parameterize SQL, or validate URLs.
  Why: Interpolating untrusted strings into the final text can still cause injection or broken output.
  Source: [DSLs](https://codewiki.com/kotlin/dsl/)
- Do not treat extensions as virtual members; doing so makes a base-typed parameter ignore extensions for runtime subtypes.
  Why: The code usually compiles, and the mistake appears only in behavior for polymorphic inputs.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- Declaring an extension with the same signature as a member doesn't override or replace that member.
  Why: If a later dependency version adds a matching member, recompilation can silently change the target.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- Publishing many generic extensions from broad packages pollutes completion and may create import ambiguity.
  Why: Call syntax doesn't reveal the declaring package, so reviewers can also mistake an extension for a real member.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- An extension property can't store new per-receiver state.
  Why: Simulating a backing field with a global mutable map creates retention, race, and cleanup problems.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- Do not assume this is safe: using `?.` on a nullable-receiver extension skips the body when the receiver is `null`.
  Why: That can bypass the fallback, logging label, or normalization policy the extension was meant to own.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- An extension can use public receiver methods but can't access private or protected receiver state.
  Why: Generated code often equates “member-call syntax” with “member privileges” and fails to compile.
  Source: [Extensions](https://codewiki.com/kotlin/extensions/)
- Describing a value class as "guaranteed zero allocation" turns a compiler preference into a language contract.
  Why: Generics, interfaces, `Any`, and some nullable positions can all require wrappers.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- Do not assume this is safe: using a mutable collection or mutable domain object as the underlying value does not make it immutable because `value class` surrounds it.
  Why: Content changes can also change value-class equality and hashing.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- Putting validation only in a companion factory while leaving the primary constructor public lets other Kotlin code construct unnormalized values directly.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- Do not assume this is safe: comparing a value class with `===` assumes stable object identity that does not exist.
  Why: Inlining and boxing can leave a domain value with no object in one place and distinct wrapper objects elsewhere.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- Do not assume this is safe: copying `box-impl`, `unbox-impl`, or hyphenated mangled names from old bytecode examples into Java source usually does not compile.
  Why: They are compiler-generated details, not a stable Java source API.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- Do not assume this is safe: putting a token or password in a value class does not redact logs automatically.
  Why: The default `toString()` displays the class name and underlying property, and a generic logger boxes the value before calling it.
  Source: [Inline value classes](https://codewiki.com/kotlin/inline-classes/)
- `val` only prevents reassignment of the name.
  Why: An array, mutable collection, or object with mutable properties can still change through the same reference.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- Java accepts `long total = count` through widening, but Kotlin rejects `val total: Long = count`.
  Why: Simply changing the result to `Int` may reduce its representable range.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- `value!!` doesn't prove that a value is non-null.
  Why: It asks the runtime to throw `NullPointerException` when the value is `null` and discards the business meaning of absence.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- `==` checks structural equality and handles `null` safely; `===` asks whether two references point at the same object.
  Why: String interning or JVM boxing caches can make an incorrect `===` happen to pass for some test values.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- Do not assume this is safe: adding `else` to a `when` over an enum or sealed hierarchy makes the code shorter, but it keeps compiling after a new state appears and may send that state down a fallback that was never designed for it.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- `if`, `when`, Elvis, and scope functions can all nest into one expression.
  Why: Generated code sometimes combines logging, state changes, early returns, and result computation until execution order becomes hard to review.
  Source: [Kotlin fundamentals](https://codewiki.com/kotlin/fundamentals/)
- Do not treat every bare `return` in a lambda as “return only from the lambda”; doing so can exit an enclosing function early; putting it in an ordinary callback may instead fail compilation.
  Source: [Lambda expressions](https://codewiki.com/kotlin/lambdas/)
- Do not assume this is safe: when nested lambdas all use `it`, the inner parameter shadows the outer one, and generated code can read or update the wrong object.
  Source: [Lambda expressions](https://codewiki.com/kotlin/lambdas/)
- Do not assume this is safe: when several listeners capture one mutable variable, they share state rather than independent creation-time snapshots; concurrent calls can also lose updates.
  Source: [Lambda expressions](https://codewiki.com/kotlin/lambdas/)
- With several defaulted function parameters, a trailing lambda binds the last parameter and can put success-handling code in the failure-handler position.
  Source: [Lambda expressions](https://codewiki.com/kotlin/lambdas/)
- Do not treat a function type, `typealias`, and `fun interface` as the same type breaks overload, extension, or Java interoperability boundaries.
  Source: [Lambda expressions](https://codewiki.com/kotlin/lambdas/)
- Do not assume this is safe: using `!!` to remove a compile error on a production path turns an unproven invariant into a delayed `NullPointerException`, usually without domain context.
  Source: [Null safety](https://codewiki.com/kotlin/null-safety/)
- A long `?.` chain with one generic default collapses different failures into the same result, such as displaying `unknown` for a missing account, missing address, and blank city.
  Source: [Null safety](https://codewiki.com/kotlin/null-safety/)
- Checking and then rereading a mutable property may either prevent a smart cast or observe another value through a custom getter or concurrent mutation; generated repairs often append `!!` directly.
  Source: [Null safety](https://codewiki.com/kotlin/null-safety/)
- `List?`, `List`, and `List?` describe different contracts; casual use of `orEmpty()` or `filterNotNull()` erases container absence and element absence respectively.
  Source: [Null safety](https://codewiki.com/kotlin/null-safety/)
- Do not assume this is safe: a platform type from Java can be used as if non-null even though the Java implementation may return `null`; code with no visible nullable type can still fail at an assignment check or member call.
  Source: [Null safety](https://codewiki.com/kotlin/null-safety/)
- Do not assume this is safe: a domain property declared as a public `var` lets callers skip validation and related state updates.
  Why: The object may have methods, but it doesn't control its own state.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- Do not assume this is safe: code ported from Java often assumes classes and methods are naturally overridable, then fails to compile at a test double or derived implementation.
  Why: Making every declaration `open` instead expands an extension surface that has no designed contract.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- When a base property initializer or `init` block calls an open member, runtime dispatch can enter a derived override before derived properties finish initializing.
  Why: The result may be a default value, `null`, an exception, or behavior that depends on declaration order.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- Making two classes parent and child merely because they share logging or retry code also gives the child unwanted state, lifetime, and open members.
  Why: A later base-class change then puts every subclass in the regression scope.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- `object` guarantees one instance.
  Why: It doesn't make mutable operations atomic or reset state between tests. A cache, current user, or counter stored there can leak across requests and race under concurrency.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- Do not assume this is safe: unless an ordinary class overrides `equals()`, `==` eventually uses the implementation inherited from `Any`, so two separate instances with matching fields may still be unequal.
  Why: `===` checks only whether two references point to one instance.
  Source: [Object-oriented programming](https://codewiki.com/kotlin/oop/)
- Do not assume this is safe: memorizing only "`let` is for nulls" and "`apply` is for configuration" ignores the actual return type.
  Why: Generated refactors are especially prone to replacing a function name while leaving the rest of the chain intact, so later calls land on the lambda result or the wrong object.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- Do not treat `nullable.let {}` as null-safe executes the lambda normally, and `it` remains nullable.
  Why: The short-circuit comes from `?.` in `nullable?.let {}`.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- Nested `run`, `apply`, or `with` blocks stack implicit receivers.
  Why: Add lambdas using `it`, and short names can denote entirely different objects. When those objects have members with the same name, the mistake may still compile.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- `also` returns the original object, but it doesn't guarantee that the block only observes it or that a business effect occurs only once.
  Why: Network retries, repeated callers, or an exception in the block can make logging, message sends, and database writes disagree with the chain's innocent appearance.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- `let`, `run`, and `with` can all feed a nullable lambda result into an Elvis branch, so a compact chain may erase distinct failure reasons.
  Why: Several transformations can also make the intermediate type hard to see at the call site.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- Scope functions are inline, so a bare `return` in the block may be a non-local return that exits the surrounding named function.
  Why: A reviewer who reads it as "finish this lambda" misses a path where all following code is skipped.
  Source: [Scope functions](https://codewiki.com/kotlin/scope-functions/)
- Generated and handwritten handlers often add `else -> "unknown"` just to compile.
  Why: After a sealed subtype is added, that branch keeps catching the new state, so the compiler can't identify business decisions with undefined behavior.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
- A sealed parent restricts direct subtypes.
  Why: If a direct subclass is explicitly `open`, new indirect subclasses may still appear wherever it is visible, and one `is` check for that open node counts as covering the whole subtree.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
- A model generator may put the parent interface in a `domain` package and spread direct implementations across `network`, `storage`, or separate feature modules.
  Why: Even with sensible dependency direction, that code violates sealed direct-inheritance restrictions.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
- An `object` or `data object` represents one payload-free instance in a process.
  Why: Putting request IDs, progress, or error details in mutable properties makes separate events share state and prevents equality or logs from representing each occurrence.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
- Listing `Created`, `Paid`, and `Cancelled` closes the state set but doesn't prevent a transition from `Cancelled` back to `Created`.
  Why: Invalid edges can still appear at runtime when a transition function uses `else` or mutates fields in place.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
- The set of implementations for plugins, drivers, and cross-team extension points usually can't be listed by one module in advance.
  Why: Once their interface is sealed, consumers can't implement it in their own modules; they must modify the module that owns it.
  Source: [Sealed classes and interfaces](https://codewiki.com/kotlin/sealed-classes/)
