kotlin-acyclic¶
kotlin-acyclic is the annotation library for the one.wabbit.acyclic Kotlin compiler plugin family.
It contains only source-retained annotations and enums. There are no compiler internals here. The library is intended to be added as a normal dependency by projects that want to opt into, override, or explicitly exempt acyclicity rules in source.
Start with the root README for the overview, user guide for setup and rule semantics, and API reference for the public surface.
Status¶
This module is pre-1.0 and follows the repository Kotlin compatibility matrix in ../gradle.properties.
Repository Role¶
The acyclic project family is split into three parts:
kotlin-acyclic: source annotations used by application and library codekotlin-acyclic-plugin: the Kotlin compiler plugin that enforces the ruleskotlin-acyclic-gradle-plugin: the Gradle bridge that wires the compiler plugin into Kotlin compilations
Most consumers need this repository together with the Gradle plugin.
The annotations library keeps the base project version, while the compiler plugin is published as a Kotlin-specific variant such as one.wabbit:kotlin-acyclic-plugin:0.1.0-kotlin-2.3.10.
Artifact¶
- coordinates:
one.wabbit:kotlin-acyclic:0.1.0
Installation¶
Most users add this library alongside the Gradle plugin:
plugins {
kotlin("jvm") version "2.3.10"
id("one.wabbit.acyclic") version "0.1.0"
}
dependencies {
implementation("one.wabbit:kotlin-acyclic:0.1.0")
}
Run ./gradlew compileKotlin after adding the dependency to confirm the annotations resolve in source.
Annotations¶
@Acyclic¶
@Acyclic opts a file or declaration into checking and can optionally override declaration order.
Targets:
- file
- class
- function
- property
- typealias
Declaration analysis currently applies to top-level declarations and declarations nested inside classes, and declaration dependencies are evaluated only within the current file. Local declarations inside functions, accessors, and initializer bodies are not tracked by the compiler plugin as separate declaration nodes. Their resolved dependencies still contribute to the enclosing tracked declaration.
Example:
@file:one.wabbit.acyclic.Acyclic(
order = one.wabbit.acyclic.AcyclicOrder.TOP_DOWN,
)
package sample
AcyclicOrder¶
AcyclicOrder controls declaration-order policy for @Acyclic.
Values:
DEFAULTUses the module-level default order policy, even when a file-level@Acyclic(order = ...)override is present.NONETOP_DOWNBOTTOM_UP
When @file:Acyclic(order = ...) is present, that file-level order becomes the default for
tracked declarations in the file unless a declaration-level @Acyclic(order = ...) replaces it or
resets it with DEFAULT.
Precedence Within One Compilation¶
The annotations in this module sit on top of build-level defaults from the Gradle plugin or direct compiler-plugin options.
The effective policy is resolved in this order:
- build-level defaults from
acyclic {}orplugin:one.wabbit.acyclic:*options - file annotations such as
@file:Acyclicand@file:AllowCompilationUnitCycles - declaration annotations such as
@Acyclic,@AllowSelfRecursion, and@AllowMutualRecursion
For declaration order specifically:
- the module-level default comes from the build configuration
@file:Acyclic(order = ...)overrides that default for tracked declarations in the file@Acyclic(order = DEFAULT)on a declaration resets that declaration back to the module-level default
@AllowCompilationUnitCycles¶
@AllowCompilationUnitCycles explicitly permits a file to participate in a file-level cycle.
A compilation-unit cycle is exempt only when every file in the reported cycle opts out with this annotation.
Example:
@file:one.wabbit.acyclic.AllowCompilationUnitCycles
package sample
@AllowSelfRecursion¶
@AllowSelfRecursion explicitly permits direct self-recursion for the annotated declaration or file.
Targets:
- file
- class
- function
- property
- typealias
Example:
import one.wabbit.acyclic.AllowSelfRecursion
@AllowSelfRecursion
fun loop(n: Int): Int =
if (n <= 0) 0 else loop(n - 1)
@AllowMutualRecursion¶
@AllowMutualRecursion explicitly permits mutual recursion for the annotated declaration or file.
A mutual-recursion component is exempt only when every declaration in the cycle opts out with this annotation.
Targets:
- file
- class
- function
- property
- typealias
Example:
import one.wabbit.acyclic.AllowMutualRecursion
@AllowMutualRecursion
fun even(n: Int): Boolean =
if (n == 0) true else odd(n - 1)
@AllowMutualRecursion
fun odd(n: Int): Boolean =
if (n == 0) false else even(n - 1)
Source Retention¶
All annotations in this module use AnnotationRetention.SOURCE.
These annotations are source-retained because they are consumed during compilation, do not need to remain in runtime metadata, and are part of the static structure policy rather than runtime behavior.
Typical Usage¶
With the companion Gradle plugin:
plugins {
kotlin("jvm") version "2.3.10"
id("one.wabbit.acyclic")
}
dependencies {
implementation("one.wabbit:kotlin-acyclic:0.1.0")
}
Then use annotations in source only where you want to:
- opt files or declarations into checking
- override the default declaration-order policy
- carve out narrow, explicit recursion exceptions
Release notes live in ../CHANGELOG.md. If you run into unexpected diagnostics, start with ../docs/troubleshooting.md and the contribution/support guidance in the root README.