aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt
blob: 26e568431838b0cde6f226609d4967579b5d9226 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package at.hannibal2.skyhanni.utils.const

@JvmInline
/**
 * Immutable view of an object.
 * This class wraps an [T] indicating that it should not be modified. This allows multiple users to share an
 * object instance without having to fear that the internal mutability of the object causes unexpected behaviour.
 * More specifically, as long as the invariants of the methods and constructors are followed by all users then data
 * contained within will never change.
 *
 * For specific [T]s there are extension methods allowing to safely access the data.
 *
 * This is a [JvmInline] class, so at
 * runtime [Const] is a 0 cost wrapper.
 */
value class Const<T> private constructor(
    /**
     * Unsafely access the underlying object. Callers of this method promise not to modify the returned instance, or to
     * leak this instance or references/views into this instance to any other codepaths which modify it.
     * Whenever possible callers should wrap objects they return which offer a view into this object into a [Const] of
     * its own.
     */
    @PublishedApi
    internal val unsafeMutable: T,
) {
    companion object {
        /**
         * Create a new [Const] instance. Callers of this method guarantee that the given object will not be mutated
         * internally. This should ideally be done by every instance of [value] being wrapped in a [Const] (and other
         * references to be discarded as quickly as possible).
         */
        fun <T> newUnchecked(value: T): Const<T> {
            return Const(value)
        }
    }

    /**
     * Map the contained object into a new [Const].
     */
    inline fun <U> unsafeMap(
        /**
         * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable].
         * It can however assume that the return value will never be modified by other code.
         */
        mapper: (T) -> U
    ): Const<U> {
        return unsafeMutable
            .let(mapper)
            .let(::newUnchecked)
    }

    /**
     * Flat map the contained object into a new [Const]. Behaves like [unsafeMap] but allowing the [mapper] to return a [Const]
     */
    inline fun <U> unsafeFlatMap(
        /**
         * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable].
         */
        mapper: (T) -> Const<U>
    ): Const<U> {
        return unsafeMutable
            .let(mapper)
    }
}

/**
 * Flatten two nested [Const] into just one.
 */
fun <T> Const<Const<T>>.flatten(): Const<T> = unsafeFlatMap { it }

/**
 * Lift nullability out of a [Const] to allow for easier `?.` operations.
 */
fun <T : Any> Const<T?>.liftNull(): Const<T>? = unsafeMutable?.let(Const.Companion::newUnchecked)

inline fun <reified U : T, T> Const<T>.tryCast(): Const<U>? = (unsafeMutable as? U)?.let(Const.Companion::newUnchecked)

/**
 * List a list out of a const. This is legal since [List] does not allow for mutating unless it's elements are individually
 * mutable as long. The caller may never cast the returned instance to [MutableList].
 */
fun <T> Const<List<T>>.liftList(): List<Const<T>> {
    return unsafeMutable.map(Const.Companion::newUnchecked)
}

/**
 * Lift const out of a [List]. The caller must guarantee that the list instance is never modified. This means it was
 * either constructed directly as a [List] or if it originally comes from a [MutableList], mutations operations on this
 * instance are never used.
 */
fun <T> List<Const<T>>.liftConst(): Const<List<T>> {
    return Const.newUnchecked(this.map { it.unsafeMutable })
}