Skip to main content

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.

DiagnosticINLINE_FUNCTION_WITH_LOGIC
Default severityError
Gradle propertyinlineFunctionWithLogic
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.length
return 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)
@PublishedApi
internal fun chooseImpl(value: Int): Int = if (value < 0) -1 else 1
@PublishedApi
internal fun cachedLengthImpl(tag: String): Int = withCache {
tag.length
}

Don't

@file:JvmName("Arrays")
// Supporting published state
@PublishedApi
internal val array1: List<Int> = emptyList()
@PublishedApi
internal val array2: List<Int> = emptyList()
/** Returns the combined size of the arrays. */
public inline val calculateArraysSize: Int
get() {
return array1.size + array2.size
}

Do

@file:JvmName("Arrays")
/** Returns the combined size of the arrays. */
public val calculateArraysSize: Int
get() = calculateArraysSizeImpl()

Don't

/** Returns the number of functions declared by [T]. */
@JvmSynthetic
public inline fun <reified T : Any> resolveFunctionsCount(): Int {
return T::class.memberFunctions.size
}

Do

@file:JvmName("Reflection")
@PublishedApi
internal fun <T : Any> resolveFunctionsCountImpl(kClass: KClass<T>): Int {
return kClass.memberFunctions.size
}
/** Returns the number of functions declared by [T]. */
@JvmSynthetic
public 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 internal inline 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

See also