Inline functions with logic
INLINE_FUNCTION_WITH_LOGIC reports public inline functions and inline property accessors whose
body does more than delegate to a non-inline function.
| Diagnostic | INLINE_FUNCTION_WITH_LOGIC |
| Default severity | Error |
| Gradle property | inlineFunctionWithLogic |
| Exemption | @IntentionallyInlinedLogic |
What it reports
Any public inline function and any inline property accessor are reported unless their body is a thin wrapper: a single
statement - besides an optional contract - that only performs simple operations and delegates to a
non-inline call:
@file:JvmName("Numbers")/** Classifies [value] by its sign. */public inline fun classifySign(value: Int): Int = if (value < 0) -1 else 1
Rationale
The compiler copies an inline function's body into every call site at compile time. Once a user
compiles against a library version, that copy - and any bug in it - is frozen in the user's
binary until the user recompiles against a fixed version. A regular function call would instead
pick up the fix at runtime by relinking against the new library binary. See the Kotlin library
authors' guide on
@PublishedApi considerations.
Don't
@file:JvmName("Values")/** Chooses a sign for [value]. */public inline fun choose(value: Int): Int = if (value < 0) -1 else 1/** Returns the cached length of [tag]. */public inline fun cachedLength(tag: String): Int {val cached = tag.lengthreturn cached}
Do
@file:JvmName("Values")/** Chooses a sign for [value]. */public inline fun choose(value: Int): Int = chooseImpl(value)/** Returns the cached length of [tag]. */public inline fun cachedLength(tag: String): Int =cachedLengthImpl(tag)@PublishedApiinternal fun chooseImpl(value: Int): Int = if (value < 0) -1 else 1@PublishedApiinternal fun cachedLengthImpl(tag: String): Int = withCache {tag.length}
Don't
@file:JvmName("Arrays")// Supporting published state@PublishedApiinternal val array1: List<Int> = emptyList()@PublishedApiinternal val array2: List<Int> = emptyList()/** Returns the combined size of the arrays. */public inline val calculateArraysSize: Intget() {return array1.size + array2.size}
Do
@file:JvmName("Arrays")/** Returns the combined size of the arrays. */public val calculateArraysSize: Intget() = calculateArraysSizeImpl()
Don't
/** Returns the number of functions declared by [T]. */@JvmSyntheticpublic inline fun <reified T : Any> resolveFunctionsCount(): Int {return T::class.memberFunctions.size}
Do
@file:JvmName("Reflection")@PublishedApiinternal fun <T : Any> resolveFunctionsCountImpl(kClass: KClass<T>): Int {return kClass.memberFunctions.size}/** Returns the number of functions declared by [T]. */@JvmSyntheticpublic inline fun <reified T : Any> resolveFunctionsCount(): Int {return resolveFunctionsCountImpl(T::class)}
Notes
- A contract declared with
contract { ... }doesn't count as a statement. - Calling another inline function, or reading or writing through an inline accessor, is logic: the inliner drags that body into the user transitively even with no visible control flow.
@PublishedApi internalinline functions and accessors are checked exactly like public ones: a public inline wrapper can call them, which inlines their body into users just as transitively.
Exemption
Apply @IntentionallyInlinedLogic when inlining the logic is intended, for example when a lambda
must run inline for non-local returns or a hot path must not pay for an extra call:
@file:JvmName("Values")/** Chooses a sign for [value]. */@IntentionallyInlinedLogic(reason = ExemptionReason.API_DESIGN)public inline fun choose(value: Int): Int = if (value < 0) -1 else 1
Configuration
apiWatchdog {inlineFunctionWithLogic = WatchdogSeverity.WARNING}
With direct compiler invocation:
-P plugin:org.jetbrains.kotlin.library.api.watchdog:diagnosticSeverity=INLINE_FUNCTION_WITH_LOGIC:warning