Utilities for writing Kotlin compiler plugins.
- Library:
com.github.rnett.compiler-plugin-utils:compiler-plugin-utils- Native:
compiler-plugin-utils-nativefor native compiler plugins. It will have to be shaded in. Useskotlin-compilerinstead ofkotlin-compiler-embedable.
- Native:
- Compiler plugin (actually the gradle plugin for it):
com.github.rnett.compiler-plugin-utils
The compiler plugin is optional, but is required for some features (it is explicitly noted when required).
Releases are on maven central, snapshots are on https://oss.sonatype.org/content/repositories/snapshots.
Names and stdlib builders are deprecated, and will be replaced by a reference generator I'm working on
Stdlib is fully tested on JVM, as is Naming. IR utilities are mostly tested.
The usage of Naming and some of the IR utilities can be seen in the stdlib code.
Using the Stdlib builders on JS may cause Not found Idx for public kotlin/to|9142910121690433229[0] like errors. These
are because the return types of irCalls are not sufficiently specified. Make an issue here and I'll see what I can do.
Testing JS linking is not feasible ATM, so I can't guarantee you won't run into these, but they shouldn't happen too
often. As a workaround, you can use the IR utility inferReturnType or implement something similar yourself.
The com.rnett.plugin.ir package contains a number of utilities for working with IR. This includes basic utilities such
as CompilerConfiguration.messageCollector, IrClass.addAnonymousInitializer, IrType.raiseTo,
and IrClass.typeWith(List<IrTypeArgument>), all of which are available as extension functions.
Many utilities require a IrPluginContext, so in lieu of multiple receivers, they are put in HasContext which has
a val context: IrPluginContext. It can be easily implemented by IrElementTransformers. Of special note are
the IrBuilderWithScope.buildLambda and lambdaArgument functions.
KnowsCurrentFile is a similar interface, but requires a IrFile and provides extensions for getting message locations
from IrElements using said file.
Both of these are implemented by IrTransformer, which is usable as a replacement for IrElementTransformerVoid
or IrElementTransformerVoidWithContext. In addition to implementing IrElementTransformerVoidWithContext
, KnowsCurrentFile, and HasContext, it modifies the file transformer so that new declarations can be added to the
current file without running into ConcurrentModificationException (it does so by running transforms on a copy of the
declaration list, and then on newly added declarations until no more are added).
Is deprecated, will be replaced by a reference generator.
The com.rnett.plugin.naming package provides ways to get FqNames and IR symbols for declarations. It primarily
provides a structured method based on nested objects, but also provides direct access. Each of these reference types
exposes the FqName of the element, and can be resolved with an IrPluginContext. Function, property, and constructor
references all accept filters (essentially (IrSimpleFunction) -> Boolean lambdas or the equivalent, but with some
helper methods for common conditions) to disambiguate overloads. Some utility functions are provided in HasContext for
working with references in IR, such as irCall(FunctionRef) or ClassRef.resolveTypeWith.
The structured name resolution can be seen in
the stdlib names. Objects
extending RootPackage, PackageRef, and ClassRef can be nested, each auto-detecting the name from the object named
and adding its parent's name as a prefix to its own. This requires the compiler plugin since there is
no inner object or object delegation. However, if both name
and parent are specified the compiler plugin is unnecessary. Property, function, and constructor references can be
created inside of class or package references (with constructor references in packages requiring the class name). Note
that just like in IR, extension receivers are not part of an element's FqName, only dispatch receivers.
Methods to directly get references are also provided for classes, functions, properties, and constructors. Literal
versions are also provided, that take a class, function, property, or class literal, respectively. Using the literal
versions requires the compiler plugin, since the FqNames of the literals will be resolved at compile time.
The getFqName(literal) and literal.fqName() functions work similarly, and also require the compiler plugin. Note
that all of these literal functions only work with literal arguments for the declaration being resolved (thus the name),
not variables or parameters.
typeOf()-like type resolution methods are also provided. They enable getting a TypeRef from a type literal
with typeRef<T>(), which can be resolved to an IrType later with an IrPluginContext or HasContext. They can also
be compared to IrTypes without resolving using eq. Methods using eq such as IrType.isType<T>()
and IrType.isClassifierOf<T>() are also provided. The classifier of a TypeRef is a ClassRef, which provides
another method for resolving classes.
Is deprecated, will be replaced by a reference generator.
The com.rnett.plugin.stdlib package provides builders for common standard library functions, using the Naming and IR
Utilities features (and providing good examples of how to use them). Collections, toString and hashCode, typeOf,
scope functions, numbers, and common exceptions are included. All builder methods are tested. Note that unlike for
Naming, extension functions are members of their extension receiver, i.e. Map.getValue. In some cases, builders will
resolve their functions based on the types of arguments, such as for number operators or nullable toString
and hashCode. The receiver arguments are type checked (in IR) in most cases, the other arguments aren't.
Note that using a lot of these functions is a bad idea. If you find yourself generating a lot of code using these methods, you should probably create a utility function and call it from IR instead. The number of builders is there to provide breadth, not depth.