Technical Documentation: square/kotlinpoet
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
KotlinPoet
KotlinPoet is a Kotlin and Java API for generating .kt source files.
[square.github.io/kotlinpoet](https://square.github.io/kotlinpoet)
License
Copyright 2017 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.Index (`docs/index.md`)
KotlinPoet
KotlinPoet is a Kotlin and Java API for generating .kt source files.
Source file generation can be useful when doing things such as annotation processing or interacting
with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate
the need to write boilerplate while also keeping a single source of truth for the metadata.
Example
Here's a HelloWorld file:
class Greeter(val name: String) {
fun greet() {
println("""Hello, $name""")
}
}
fun main(vararg args: String) {
Greeter(args[0]).greet()
}And this is the code to generate it with KotlinPoet:
val greeterClass = ClassName("", "Greeter")
val file = FileSpec.builder("", "HelloWorld")
.addType(
TypeSpec.classBuilder("Greeter")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build()
)
.addProperty(
PropertySpec.builder("name", String::class)
.initializer("name")
.build()
)
.addFunction(
FunSpec.builder("greet")
.addStatement("println(%P)", "Hello, \$name")
.build()
)
.build()
)
.addFunction(
FunSpec.builder("main")
.addParameter("args", String::class, VARARG)
.addStatement("%T(args[0]).greet()", greeterClass)
.build()
)
.build()
file.writeTo(System.out)The KDoc catalogs the complete KotlinPoet API, which is inspired by JavaPoet.
Note: In order to maximize portability, KotlinPoet generates code with explicit visibility
modifiers. This ensures compatibility with both standard Kotlin projects as well as projects
using explicit API mode. Examples in the documentation omit those modifiers for
brevity.
Download
Download the latest .jar or depend via Maven:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>kotlinpoet-jvm</artifactId>
<version>[version]</version>
</dependency>or Gradle:
implementation("com.squareup:kotlinpoet:[version]")Snapshots of the development version are available in the Central Portal Snapshots repository.
License
Copyright 2017 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.Annotations (`docs/annotations.md`)
Annotations
Simple annotations are easy:
val test = FunSpec.builder("test string equality")
.addAnnotation(Test::class)
.addStatement("assertThat(%1S).isEqualTo(%1S)", "foo")
.build()Which generates this function with an @Test annotation:
@Test
fun `test string equality`() {
assertThat("foo").isEqualTo("foo")
}Use AnnotationSpec.builder() to set properties on annotations:
val logRecord = FunSpec.builder("recordEvent")
.addModifiers(KModifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(Headers::class)
.addMember("accept = %S", "application/json; charset=utf-8")
.addMember("userAgent = %S", "Square Cash")
.build()
)
.addParameter("logRecord", LogRecord::class)
.returns(LogReceipt::class)
.build()Which generates this annotation with accept and userAgent properties:
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
abstract fun recordEvent(logRecord: LogRecord): LogReceiptWhen you get fancy, annotation values can be annotations themselves. Use %L for embedded
annotations:
val headerList = ClassName("", "HeaderList")
val header = ClassName("", "Header")
val logRecord = FunSpec.builder("recordEvent")
.addModifiers(KModifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(headerList)
.addMember(
"[\nโฅ%L,\n%Lโค\n]",
AnnotationSpec.builder(header)
.addMember("name = %S", "Accept")
.addMember("value = %S", "application/json; charset=utf-8")
.build(),
AnnotationSpec.builder(header)
.addMember("name = %S", "User-Agent")
.addMember("value = %S", "Square Cash")
.build()
)
.build()
)
.addParameter("logRecord", logRecordName)
.returns(logReceipt)
.build()Which generates this:
@HeaderList(
[
Header(name = "Accept", value = "application/json; charset=utf-8"),
Header(name = "User-Agent", value = "Square Cash")
]
)
abstract fun recordEvent(logRecord: LogRecord): LogReceiptKotlinPoet supports use-site targets for annotations:
val utils = FileSpec.builder("com.example", "Utils")
.addAnnotation(
AnnotationSpec.builder(JvmName::class)
.useSiteTarget(UseSiteTarget.FILE)
.build()
)
.addFunction(
FunSpec.builder("abs")
.receiver(Int::class)
.returns(Int::class)
.addStatement("return if (this < 0) -this else this")
.build()
)
.build()Will output this:
@file:JvmName
package com.example
import kotlin.Int
import kotlin.jvm.JvmName
fun Int.abs(): Int = if (this < 0) -this else thisAnnotating Types
KotlinPoet provides a convenient annotated() API for adding annotations to types:
// Add a single annotation.
val annotatedType = String::class.asTypeName()
.annotated(AnnotationSpec.builder(MyAnnotation::class).build())
// Add multiple annotations.
val multiAnnotatedType = Int::class.asTypeName()
.annotated(
AnnotationSpec.builder(Suppress::class).addMember("%S", "unused").build(),
AnnotationSpec.builder(Deprecated::class).addMember("%S", "Use something else").build()
)
// Add annotations by class.
val simpleAnnotated = String::class.asTypeName().annotated(Suppress::class, Deprecated::class)
// Chain multiple calls.
val chainedAnnotations = String::class.asTypeName()
.annotated(Suppress::class)
.annotated(Deprecated::class)This is especially useful when working with lambda types:
val composableType = LambdaTypeName.get(
receiver = null,
parameters = listOf(ParameterSpec.unnamed(ClassName("androidx.compose.ui", "Modifier"))),
returnType = UNIT,
).annotated(AnnotationSpec.builder(ClassName("androidx.compose.runtime", "Composable")).build())You can also pass annotations directly by class:
val suppressedType = String::class.asTypeName().annotated(Suppress::class)Or chain multiple annotations:
val chainedAnnotations = String::class.asTypeName()
.annotated(AnnotationSpec.builder(Suppress::class).addMember("%S", "unused").build())
.annotated(Deprecated::class)You can also update, replace, or clear annotations on a type with the copy() function:
val unannotatedString = String::class.asTypeName()
.annotated(Suppress::class)
.copy(annotations = listOf())Anonymous Inner Classes (`docs/anonymous-inner-classes.md`)
Anonymous Inner Classes
In the enum code, we used TypeSpec.anonymousClassBuilder(). Anonymous inner classes can also be
used in code blocks. They are values that can be referenced with %L:
val comparator = TypeSpec.anonymousClassBuilder()
.addSuperinterface(Comparator::class.parameterizedBy(String::class))
.addFunction(
FunSpec.builder("compare")
.addModifiers(KModifier.OVERRIDE)
.addParameter("a", String::class)
.addParameter("b", String::class)
.returns(Int::class)
.addStatement("return %N.length - %N.length", "a", "b")
.build()
)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addFunction(
FunSpec.builder("sortByLength")
.addParameter("strings", List::class.parameterizedBy(String::class))
.addStatement("%N.sortedWith(%L)", "strings", comparator)
.build()
)
.build()This generates a method that contains a class that contains a method:
class HelloWorld {
fun sortByLength(strings: List<String>) {
strings.sortedWith(object : Comparator<String> {
override fun compare(a: String, b: String): Int = a.length - b.length
})
}
}One particularly tricky part of defining anonymous inner classes is the arguments to the superclass
constructor. To pass them use TypeSpec.Builder's addSuperclassConstructorParameter() method.
Callable References (`docs/callable-references.md`)
Callable References
Callable references to constructors, functions, and properties may be emitted
via:
ClassName.constructorReference()for constructorsMemberName.reference()for functions and properties
For example,
val helloClass = ClassName("com.example.hello", "Hello")
val worldFunction: MemberName = helloClass.member("world")
val byeProperty: MemberName = helloClass.nestedClass("World").member("bye")
val factoriesFun = FunSpec.builder("factories")
.addStatement("val hello = %L", helloClass.constructorReference())
.addStatement("val world = %L", worldFunction.reference())
.addStatement("val bye = %L", byeProperty.reference())
.build()
FileSpec.builder("com.example", "HelloWorld")
.addFunction(factoriesFun)
.build()would generate:
package com.example
import com.example.hello.Hello
fun factories() {
val hello = ::Hello
val world = Hello::world
val bye = Hello.World::bye
}Top-level classes and members with conflicting names may require aliased imports, as with
member names.
Changelog (`docs/changelog.md`)
Change Log
Unreleased
- New: Support for companion extensions. (#2406)
- Fix: Carriage returns are no longer lost in
%Sand%Pstring literals. (#2380) - Fix:
Class.asClassName()no longer drops the package slot of a class in the default package. (#2382) - Fix: A trailing newline in a
%Sor%Pstring argument that ends aCodeBlockis no longer trimmed or added. (#2391)
Version 2.4.0
Thanks to @eyupcanakman and @arimu1 for contributing to this release.
2026-09-07
- New: Kotlin 2.4.20.
- New: KSP 2.3.11.
- New: Support for explicit backing fields. (#2325)
- New:
value classvalidations have been relaxed to support multi-field value classes. (#2329) - New: Extract
CodeBlockHolderinterface for constructs that can hold aCodeBlockbody and their builders. (#2331) - New: Add
CodeBlock.Builder.addComment()for adding//comments. (#2340) - New: Add
CodeBlockHolder.Builder.addComment(). (#2347) - New: Use
[]syntax instead ofarrayOfwhen emitting annotation arguments. (#2361) - Fix: Keep the
//prefix on wrapped file comment lines. (#2315) - Fix:
TypeVariableName.equals/hashCodeno longer overflow on recursively bound generics likeEnum<E : Enum<E>>. (#2320) - Fix:
getandsetoperator function names are no longer escaped with backticks. (#2338) - Fix: Don't special case varargs in
KSAnnotation.toAnnotationSpec. (#2360) - Fix:
KSType.toTypeName()with the defaultTypeParameterResolver.EMPTYnow resolves type parameters from their enclosing declarations instead of throwing. (#2368) - Fix: Emit context parameters after annotations in
FunSpecandPropertySpec. (#2374) - Fix: An expression body no longer leaks indentation into later declarations. (#2365)
Version 2.3.0
Thanks to @haruue, @hfhbd, @yz4230, @mina-jaff, @BoD,
@RaoPrashanth for contributing to this release.
2026-03-27
- New: Kotlin 2.3.20.
- New: KSP 2.3.6.
- New: Support for defining type aliases inside other types. (#2167)
- New: Add
TypeName.annotated()convenience API for adding annotations to types. (#2281) - New: Add
FileSpec.addImport(MemberName)to explicitly import a member. (#2198) - New: Add
NameAllocator.containsto check if a given tag is already allocated. (#2160) - New:
:interop:javapoetmodule has been discontinued and is not published starting with this release. (#2292) - New: In-development snapshots are now published to the Central Portal Snapshots repository at https://central.sonatype.com/repository/maven-snapshots/. (#2135)
- Fix: Annotation array parameters with annotation elements now correctly handled. (#2142)
- Fix:
KType.asTypeNamenow correctly handles recursively bound generics (e.g.T : Comparable<T>). (#2162) - Fix: Don't convert multi-statement function to expression body. (#2163)
- Fix:
FunSpec.beginControlFlownow accepts nullable arguments for consistency withCodeBlock.beginControlFlow. (#2174) - Fix: Ignore illegal characters inside escaped identifiers. (#2204)
- Fix:
emitNullablecorrectly when deferring type inCodeWriter. (#2217) - Fix: Escape
/*and*/when emitting Kdoc. (#2258) - Fix: Generate imports for extension members used via
%Min KDocs. (#2266)
Version 2.2.0
Thanks to @IRus for contributing to this release.
2025-05-16
- New: Kotlin 2.1.21.
- New: KSP 2.1.21-2.0.1.
- New: Add support for context parameters. (#2112)
- New: Eliminate Guava dependency. (#2110)
- New: Migrate to Dokka plugin V2. (#2113)
Version 2.1.0
Thanks to @ForteScarlet, @TrevorSStone,
@RaoPrashanth, @damianw, @ansehoon1999,
@TheMrMilchmann for contributing to this release.
2025-02-25
- New: Kotlin 2.1.10.
- New: KSP 2.1.10-1.0.30.
- Fix: Support delegates on anonymous classes. (#2034)
- Fix: Prevent aliased imports from colliding with existing imports. (#2052)
- Fix:
TypeName.MUTABLE_MAP_ENTRYnow correctly uses theMutableEntrynested class name. (#2061) - Fix: Use the same aliased import for both the nullable and non-nullable versions of a type. (#2068)
- Fix: Allow zero methods in a functional interface if it has a superinterface. (#2075)
Version 2.0.0
Thanks to @brokenhappy, @tajobe, @niyajali,
@ForteScarlet for contributing to this release.
2024-10-23
This release is source- and binary-compatible with KotlinPoet 1.x.
The most important behavior change in this release is that spaces in generated code don't wrap by
default anymore.
KotlinPoet 1.x used to replace space characters with newline characters whenever a given line of
code exceeded the length limit. This usually led to better code formatting, but could also lead to
compilation errors in generated code. Non-breaking spaces could be marked by the ยท character, but
the discoverability of this feature wasn't great.
KotlinPoet 2.0 does not wrap spaces, even if the line of code they occur in exceeds the length
limit. The newly introduced โข character can be used to mark spaces that are safe to wrap, which
can improve code formatting. The ยท character has been preserved for compatibility, but its
behavior is now equivalent to a regular space character.
- New: Kotlin 2.0.10.
- New: Spaces don't break by default.
- New: New
โขplaceholder representing a space that is safe to wrap. - New: Add
KSTypeAlias.toClassName(). (#1956) - New: Add
KSType.toClassNameOrNull(). (#1956) - Fix: Enum classes that only have an init block now also generate the required semicolon. (#1953)
- Fix: Preserve typealiases in
KSAnnotation.toAnnotationSpec(). (#1956) - Fix: Preserve nullability in
KSType.toClassName(). (#1956)
Version 1.18.1
Thanks to @mitasov-ra for contributing to this release.
2024-07-15
Fix: Workaround for KT-18706: KotlinPoet now generates import aliases without backticks (#1920).
kotlin// before, doesn't compile due to KT-18706 import com.example.one.`$Foo` as `One$Foo` import com.example.two.`$Foo` as `Two$Foo` // now, compiles import com.example.one.`$Foo` as One__Foo import com.example.two.`$Foo` as Two__Foo
Version 1.18.0
Thanks to @DanielGronau for contributing to this release.
2024-07-05
- New: Kotlin 2.0.0.
- New: KSP 2.0.0-1.0.22.
- New: Promote
kotlinpoet-metadataout of preview to stable. - New: Migrate
kotlinpoet-metadatato stableorg.jetbrains.kotlin:kotlin-metadata-jvmartifact for Metadata parsing. - New: Make enum entry references in
KSAnnotation.toAnnotationSpec()andKSClassDeclaration.toClassName()more robust. - Fix: Don't expand typealiases of function types to
LambdaTypeNames inKSTypeReference.toTypeName(). - Fix: Avoid rounding small double and float values in
%Ltranslation (#1927). - Fix: Fix typealias type argument resolution in KSP2 (#1929).
Version 1.17.0
Thanks to @jisungbin, @hfhbd, @evant, @sgjesse, @sebek64 for
contributing to this release.
2024-05-24
- Change: kotlinx-metadata 0.9.0. Note that the
KotlinClassMetadata.readis deprecated in 0.9.0 and replaced withreadStrict(#1830).- Note: we now also provide
lenientparameters to map to the underlyingreadStrict()andreadLenient()calls
(#1766). - We have also removed various
Class/TypeElement/Metadata-to-KmClassAPIs from the public API, as these are
trivial to write now with kotlinx-metadata's newer APIs and allows us to focus the API surface area of this artifact
better (#1891).
- Note: we now also provide
- New: Supertype list wraps to one-per-line if the primary constructor spans multiple lines (#1866).
- New: Extract
MemberSpecHolderinterface for constructs that can holdPropertySpecs andFunSpecs and their
builders (#1877). - New:
joinToCodevariant which operates on any type, but requires a transform lambda to convert each element into aCodeBlock(#1874). - New: Support annotation type arguments in
KSAnnotation.toAnnotationSpec()(#1889). - Fix: Prevent name clashes between a function in class and a function call in current scope (#1850).
- Fix: Fix extension function imports (#1814).
- Fix: Omit implicit modifiers on
FileSpec.scriptBuilder(#1813). - Fix: Fix trailing newline in
PropertySpec(#1827). - Fix:
KSAnnotation.toAnnotationSpecwrites varargs in place instead of making them an array to work around a Kotlin
issue withOptInannotations (#1833). - Fix:
MemberNames without a package are now correctly imported (#1841) - Fix: Throw if primary constructor delegates to other constructors (#1859).
- Fix: Aliased imports with nested class (#1876).
- Fix: Check for error types in
KSType.toClassName()(#1890). - Fix: Support generating a single import for overloaded
MemberNames (#1909).
Version 1.16.0
Thanks to @drawers, @rickclephas for contributing to this release.
2024-01-18
- New: Kotlin 1.9.22.
- New: KSP 1.9.22-1.0.16.
- New: Add
NameAllocatorAPI to control keyword pre-allocation (#1803). - Fix: Fix issue with missing
suspendmodifier inKSTypeReference.toTypeName(#1793). - Fix: Honour same-package import aliases (#1794).
- Fix: Always include parameter docs in the type header (#1800).
Version 1.15.3
Thanks to @gabrielittner for contributing to this release.
2023-12-04
- Fix: Fix nullability of lambdas in
KSTypeReference.toTypeName(#1756).
Version 1.15.2
Thanks to @evant for contributing to this release.
2023-11-30
- New: Kotlin 1.9.21.
- New: KSP 1.9.21-1.0.15.
- New: KSP: more accurately represent function types (#1742).
Version 1.15.1
2023-11-19
- Fix: Fix a regression introduced by #1637, where a superfluous newline is added to a type's KDoc
if it has a primary constructor with no docs (#1727).
Version 1.15.0
2023-11-18
Thanks to @drawers, @fejesjoco, @takahirom,
@martinbonnin, @mcarleio for contributing to this release.
In this release the :kotlinpoet module has been converted to a Kotlin Multiplatform module
(#1654), though for now it only supports the JVM target. Important: unless you're building
with Gradle, you will now need to depend on the kotlinpoet-jvm artifact, instead of kotlinpoet -
see Downloads for instructions.
- New: Kotlin 1.9.20.
- New: KSP 1.9.20-1.0.14.
- New: Extract
TypeSpecHolderinterface for constructs that can hold a TypeSpec and their builders (#1723). - New: Expose relative path from
FileSpec(#1720). - New: Return the generated path from
FileSpec.writeTo(). (#1514). - New: Remove default compatibility from unstable types (#1662).
- New: Deprecate
TypeSpec.expectClassBuilder()andTypeSpec.valueClassBuilder()(#1589). - New: Add option to convert
KSAnnotationtoAnnotationSpecwhile omitting default values (#1538). - New: Add
FileSpec.builderconvenience forMemberName(#1585). - Fix: Set
DecimalFormatSymbols.minusSignfor consistency across locales (#1658). - Fix: Fix link to incremental KSP in KDoc (#1638).
- Fix: Emit primary constructor KDoc (#1637).
- Change: kotlinx-metadata 0.7.0. This is a breaking change for users of the
:kotlinpoet-metadata
module, as mostFlags-API extensions have been removed in favor of the now-available first-party versions.
Version 1.14.2
2023-05-30
- Fix: Fix one more missing API in binary compatibility override in
Annotatable.Builder(#1581).
Version 1.14.1
2023-05-29
- Fix: Restore ABI stability for annotatable and documentable builders (#1580).
Version 1.14.0
2023-05-29
Thanks to @Omico, @drawers, @RBusarow for contributing to this release.
New: Kotlin 1.8.21.
New: KSP 1.8.21-1.0.11.
New: Enable default methods in Java bytecode (#1561).
New: Group Kotlin and Renovate updates together in Renovate (#1562).
New: Extract trait interface for annotatable constructs and their builders (#1564).
New: Extract trait interface for documentable constructs and their builders (#1571).
New: Document the usage of
STAR(#1572).New: Add builder for
FunSpecwhich accepts aMemberName(#1574).Fix: Omit public modifier on override function or constructor parameters (#1550).
Fix: Correct handling of members in various types (#1558).
Fix: Function return types now default to
Unitunless explicitly set (#1559).Previously, when
FunSpecdidn't have a return type specified and an expression body was produced, no return
type would be emitted. However, starting from1.14.0, KotlinPoet will not addUnitas a return type in such
cases. In order to correct the generated output, you are to specify the actual return type of theFunSpec.Before
1.14.0, if omitted, no return type is produced:kotlinval funSpec = FunSpec.builder("foo") .addStatement("return 1") .build()kotlinpublic fun foo() = 1From
1.14.0, the return type defaults toUnitif not otherwise set:kotlinval funSpec = FunSpec.builder("foo") .addStatement("return 1") .build()kotlinpublic fun foo(): Unit = 1 // โTo fix it, explicitly define the return type:
diffval funSpec = FunSpec.builder("foo") + .returns(INT) .addStatement("return 1") .build()kotlinpublic fun foo(): Int = 1 // โAdditionally, as part of this change,
FunSpec.returnTypehas changed to be non-nullable. This is a source- and
binary-compatible change, although if you were performing null-checks then new warnings may appear after upgrade.Fix: Append nested class names to alias during name lookup (#1568).
Fix: Allow PropertySpec with context receivers and without getter or setter (#1575).
Version 1.13.2
2023-05-05
Thanks to @Squiry for contributing to this release.
- Fix:
KSType.toTypeNamefixed to work with aliased types (#1534).
Version 1.13.1
2023-04-28
Thanks to @rickclephas for contributing to this release.
- Fix: Look at canonical names instead of just package names when generating import aliases (#1519).
- Fix: Ignore KSP annotation arguments without a value (#1523).
- Fix: Fix arguments handling in
KSType.toTypeName()(#1529).
Version 1.13.0
2023-04-06
Thanks to @popematt, @bitPogo, @mars885, @sjudd, @Sironheart,
@polarene, @DeoTimeTheGithubUser, @drawers for contributing to this release.
- New: Kotlin 1.8.0.
- New: KSP 1.8.0-1.0.9.
- New: Support context receivers on TypeSpecs + extract ContextReceivable API (#1269).
- New: Optimize
OriginatingElementsandTagMapimplementations (#1270). - New: Auto-generate import aliases for types and members (#1355).
- New: Insert underscores into large decimal literals (#1384).
- New: New factory function
FileSpec.builder(ClassName)(#1397). - Fix: Fix StackOverflowError when calling
KSTypeArgument.toTypeName()for a wildcard in a recursive type bound (#1272). - Fix: Fix transitive aliases (#1306).
- Fix: Fix Aliases as TypeArgument (#1321).
- Fix: Don't escape special characters inside raw strings (#1331).
- Fix: Fix KSP interop's output of the annotation parameter value of type Char (#1338).
- Fix: Fix KSP interop's output for primitive arrays (#1340).
- Fix: Avoid emitting public if
shouldEmitPublicModifierreturns false (#1342). - Fix: Fix context receivers being rendered in an incorrect position when on a nullable/suspending
LambdaTypeName(#1454). - Fix: Do not use
bestGuessforKClass.asClassName(#1469). - Fix: Handle fake nested types with platform mapped parents (#1472).
- Fix: Fix
TypeNameequals (#1477). - Fix: Make equals consistent with compareTo for
ClassName(#1506).
Version 1.12.0
2022-06-13
Thanks to @WhosNickDoglio, @sullis, @DRSchlaubi,
@martinbonnin, @seriouslyhypersonic, @ephemient,
@dkilmer, @aksh1618, @zsqw123, @roihershberg for
contributing to this release.
- New: Kotlin 1.7.0.
- New: Add support for context receivers.
- New: Add support for external property getter.
- New:
interop-kspAPI promoted to stable. - Fix: Resolve enum constants when emitting types.
- Fix: Fix type argument mapping when processing typealiases with KSP.
- Fix: Properly unwrap
KSTypeAliaswith an unused type parameter. - Fix: Unwrap nested
KSTypeAlias-es recursively. - Fix: Add support for context receivers
@PropertySpecand fix issues with annotations. - Fix: Treat
headerandimplas keywords (workaround for KT-52315). - Fix: Use
%Ninstead of%Lfor annotation arg names so keywords are handled. - Fix: Improve handling of long
returnexpressions.
Version 1.11.0
2022-03-24
Thanks to @liujingxing and @BoD for contributing to this release.
- New: Kotlin scripting support in
FileSpec.
val spec = FileSpec.scriptBuilder("Taco")
.addStatement("println(%S)", "hello world!")
.addKotlinDefaultImports()
.build()Generates a Taco.kts file with the following contents:
println("hello world!")- New: Emit trailing commas for multi-line parameters and annotations.
- New: Add
KSAnnotation.toAnnotationSpec(). - New: Add
UnitandCharSequenceconversions injavapoet-interop. - New: Add support for default imports in
FileSpec.- This is particularly oriented at scripting support, but can also be used in non-script files.
- New: Update to Kotlin 1.6.10.
- Fix: Fail compilation if you only pass one string to
ClassName. - Fix: Inline
valproperty if its getter isinline. - Fix: Add
yieldto the list of reserved keywords. - Fix: Enforce only allowed parameter modifiers in
ParameterSpec(i.e.crossinline,vararg, andnoinline). - Fix: Fix
CodeBlocks in class delegation gettingtoString()'d instead of participating in code writing. - Fix: Error when attempting to convert KSP error types (i.e. if
KSType.isErroris true) toTypeName.
Version 1.10.2
2021-10-22
Thanks to @glureau and @goooler for contributing to this release.
- New: Switch
AnnotationSpec.get()to use thearrayOf()syntax instead of[]. - Fix: Don't wrap aliasing imports with long package names.
- Fix: Don't wrap type names inside line comments.
- Fix: Ignore Java's
@Deprecatedannotations on synthetic methods for annotations.
Version 1.10.1
2021-09-21
Thanks to @evant for contributing to this release.
- Fix: Correct generation of typealiases with type args in KSP interop.
- Fix: Add missing default
TypeParameterResolver.EMPTYargument tofun KSTypeArgument.toTypeNamein KSP interop.
Version 1.10.0
2021-09-20
Thanks to @martinbonnin, @idanakav, @goooler, and
@anandwana001 for contributing to this release.
- New: Add a new KSP interop artifact. See docs for more details.
- New: Add a new JavaPoet interop artifact. See docs for more
details. - New: Allow copying a
ParameterizedTypeNamewith new type arguments via newcopy()overload. - kotlinx-metadata artifacts have been consolidated to a single
com.squareup:kotlinpoet-metadata
maven artifact. The previouskotlinpoet-metadata-*subartifacts are no longer published. - New:
TypeNameAliasTaghas been moved to KotlinPoet's main artifact underTypeAliasTag, for
reuse with KSP interop. ImmutableKm*classes have been removed. They were deemed to be a needless abstraction over the basekotlinx-metadataKm types. All usages of these should be substituted with their non-immutable base types.- Fix: Fix self-referencing type variables in metadata parsing.
- Fix: Use delicate APIs rather than noisy logging ones when converting annotation mirrors in
AnnotationSpec.get. - Fix: Update error message when metadata cannot be read to a more actionable one.
- Fix: Avoid escaping already escaped strings.
- Add docs about
kotlin-reflectusage. - Avoid using kotlin-reflect for looking up
Unittypes where possible. - Test all the way up to JDK 17.
- Update Kotlin to 1.5.31.
Version 1.9.0
2021-06-22
- New: Kotlin 1.5.10.
- New: Previously deprecated API to interop with Java reflection and Mirror API have been
un-deprecated and marked with@DelicateKotlinPoetApiannotation. - New:
CodeBlock.Builder.withIndenthelper function. - New: Allow changing initializers and default values in
ParameterSpec.BuilderandPropertySpec.Builderafter they were set. - New:
MemberName.isExtensionproperty that instructs KotlinPoet to always import the member,
even if conflicting declarations are present in the same scope. - Fix: Escape member names that only contain underscores.
- Fix: Always emit an empty primary constructor if it was set via
TypeSpec.primaryConstructor.
Version 1.8.0
2021-03-29
- New: Kotlin 1.4.31.
- New: Add
KModifier.VALUEto supportvalue classdeclarations. - New: Allow using a custom
ClassLoaderwithReflectiveClassInspector. - New: Update to kotlinx-metadata 0.2.0.
- Fix: Ensure
ImmutableKmProperty.toMutable()copiesfieldSignature. - Fix: Prevent name clashes between an imported
MemberNameand a member in current scope. - Fix: Prevent name clashes between a type and a supertype with the same name.
- Fix: Don't generate empty body for
expectandexternalfunctions. - Fix: Don't allow
expectorexternalclasses to initialize supertypes. - Fix: Disallow delegate constructor calls in
externalclasses. - Fix: Allow non-public primary constructors inside inline/value classes.
- Fix: Allow init blocks inside inline/value classes.
- Fix: Omit redundant
abstractmodifiers on members inside interfaces
Version 1.7.2
2020-10-20
- New: Detect expression bodies with
returnยทandthrowยทprefixes. - Fix: Omit visibility modifiers on custom accessors.
Version 1.7.1
2020-10-15
- Fix: 1.7.0 was published using JDK 11 which set
"org.gradle.jvm.version"to"11"in Gradle
metadata, making it impossible to use the library on earlier Java versions (see
#999). 1.7.1 is published with JDK 8, which fixes the problem.
Version 1.7.0
2020-10-14
- New: Kotlin 1.4.10.
- New: Generated code is now compatible with the explicit API mode by default.
- New: Escape soft and modifier keywords, in addition to hard keywords.
- New: Improve enum constants generation for cleaner diffs.
- New: Disallow setters on immutable properties.
- New: Ensure trailing new lines in expression bodies.
- New: Ensure trailing new lines after parameterless custom setters.
- Fix: Don't auto-convert properties with custom accessors to primary constructor properties.
- Fix: Don't allow parameterless setters with body.
- Fix: Prevent auto-wrapping spaces inside escaped keywords.
Version 1.6.0
2020-05-28
New: Deprecate Mirror API integrations.
Mirror API integrations, such as
TypeElement.asClassName()andFunSpec.overriding(ExecutableElement), are being deprecated in this release. These KotlinPoet
APIs are most often used in annotation processors. Since kapt runs annotation processors over
stubs, which are Java files, a lot of the Kotlin-specific information gets lost in translation
and cannot be accessed by KotlinPoet through the Mirror API integrations. Examples include:- Alias types, such as
kotlin.String, get converted to their JVM representations, such asjava.lang.String. - Type nullability information is not accessible.
suspendfunctions are seen as simple functions with an additionalContinuationparameter.
The correct solution is to switch to KotlinPoet-metadata or
KotlinPoet-metadata-specs API, which fetches Kotlin-specific
information from the@Metadataannotation and produces correct KotlinPoet Specs. We may explore
adding new metadata-based alternatives to the deprecated APIs in the future.- Alias types, such as
New: Kotlin 1.3.72.
New: Improve
MemberNameto support operator overloading.New: Support generics in
AnnotationSpec.New: Add support for functional interfaces.
New: Make more
FunSpec.Buildermembers public for easier mutation.Fix: Properly propagate implicit type and function modifiers in nested declarations.
Fix: Properly escape type names containing
$character.Fix: Don't emit
LambdaTypeNameannotations twice.Fix: Preserve tags in
TypeName.copy().
Version 1.5.0
2020-01-09
KotlinPoet now targets JDK8, which means that executing a build that includes KotlinPoet as a
dependency on a machine with an older version of JDK installed won't work. This has no effect on
the code that KotlinPoet produces: the code can still be compiled against JDK6, as long as it
doesn't use any features that were introduced in newer releases.
- New: Kotlin 1.3.61.
- New: Add support for processing FileFacades in KotlinPoet-metadata.
- New: Add support for inner nested and companion objects on annotation classes.
- New: Improve error messages for mismatched open/close statement characters.
- New: Tag
AnnotationSpecs with the annotation mirror when available. - New: Include annotations on enum entries when creating
TypeSpecs from metadata. - Fix: Fix metadata parsing for types.
- Fix: Allow file names that are Kotlin keywords.
- Fix: Properly escape type alias names with backticks.
- Fix: Allow creating
TypeSpecs with names that can be escaped with backticks. - Fix: Properly escape enum constant names with backticks.
- Fix: Maintain proper ordering of properties and initializers when emitting a
TypeSpec.
Note: with this change, any properties declared after any initializer blocks will not be
added to the primary constructor and will instead be emitted inside theTypeSpecbody. - Fix: Don't emit a leading new line if type KDoc is empty but parameter KDocs are present.
- Fix: Ensure KotlinPoet-metadata resolves package names properly.
Version 1.4.4
2019-11-16
- Fix: Support reified inline types in KotlinPoet-metadata.
Version 1.4.3
2019-10-30
- Fix: Don't emit stubs for abstract functions in KotlinPoet-metadata.
Version 1.4.2
2019-10-28
- Fix: Properly handle abstract elements in KotlinPoet-metadata.
- Fix: Properly handle typealiases in KotlinPoet-metadata.
- Fix: Properly render % symbols at the end of KDocs.
Version 1.4.1
2019-10-18
- New: Add annotations support to
TypeAliasSpec. - New: Read type annotations from Kotlin
Metadata. - New: Introduce
ImmutableKmDeclarationContainer. - Fix: Use full package name for shading
auto-common. - Fix: Support reading self-type variables (e.g.
Asset<A : Asset<A>>) from KotlinMetadata.
Version 1.4.0
2019-09-24
New: This release introduces the new KotlinPoet-metadata API that makes it easy to introspect
Kotlin types and build KotlinPoet Specs based on that data.The strategy for type introspection is driven by
ClassInspector, which is a basic interface for
looking up JVM information about a given Class. This optionally is used by thetoTypeSpec()/toFileSpec()APIs inkotlinpoet-metadata-specsartifact to inform about
Classes with information that isnโt present in metadata (overrides, JVM modifiers, etc). There
are two batteries-included implementations available inReflectiveClassInspector
(for reflection) andElementsClassInspector(for the javax Elements API in annotation
processing). These implementations are available through their respectivekotlinpoet-classinspector-*artifacts. For more information refer to the
KotlinPoet-metadata-specs README.At the time of this release the API is in experimental mode and has to be opted into via the
KotlinPoetMetadataPreviewannotation.New: Kotlin 1.3.50.
New: A new constructor to simplify creation of
ParameterSpecinstances.New: New
ClassNameconstructors.New:
TypeNameand subclasses can now store tags.New: Optional parameters added to
toBuilder()methods of most Specs.New:
Listoverrides for Spec methods that acceptvarargs.New:
CodeBlock.Builder.clear()helper method.New:
FunSpec.Builder.clearBody()helper method.Fix: Properly escape enum constant names.
Fix: Ensure trailing newlines in KDoc and function bodies.
Fix:
TypeVariableNames with empty bounds will now default toAny?.Fix: Don't emit parens for primary constructors.
Fix:
ClassNames with empty simple names are not allowed anymore.Fix: Throw if names contain illegal characters that can't be escaped with backticks.
Version 1.3.0
2019-05-30
- New: Don't inline annotations in the primary constructor.
- New: Force new lines when emitting primary constructors.
- New: Support using MemberNames as arguments to %N.
- New: Add more ClassName constants: ClassName.STRING, ClassName.LIST, etc.
- New: Add ClassName.constructorReference() and MemberName.reference().
- New: Make %N accept MemberNames.
- New: Escape spaces in import aliases.
- New: Escape spaces in ClassNames.
- New: Escape spaces in MemberNames.
- New: Escape imports containing spaces.
- New: Escape package name containing spaces.
- New: Use 2-space indents.
- New: Only indent one level on annotation values.
- Fix: Pass only unique originating elements to Filer.
- Fix: Fix bug with MemberNames in same package nested inside a class.
Version 1.2.0
2019-03-28
- New: Add writeTo(Filer) and originating element API.
- New: Make *Spec types taggable.
- New: Make FunSpec.Builder#addCode take vararg Any?.
- Fix: Import members from default package.
- Fix: Add non-wrapping spaces in control flow creation methods.
- Fix: Named "value" argument being omitted in annotation array types.
Version 1.1.0
2019-02-28
- New: Kotlin 1.3.21.
- New: Support referencing members using
%MandMemberNametype. - New: Add extensions for getting a
MemberNamefrom aClassName,KClassandClass. - New: Allow passing
CodeBlocks as arguments to%P. - New: Allow interface delegation for objects.
- Fix: Don't emit visible whitespace in
toString(). - Fix: Prevent line wrapping in weird places inside function signature.
- Fix: No line wrapping between val and property name.
- Fix: Allow passing line prefix into
LineWrapperto enable proper line wrapping in KDoc. - Fix: Add newline for
TypeSpecKdoc with no tags. - Fix: Add newline for remaining Specs.
- Fix: Fix kdoc formatting for property getter/setters.
- Fix: Don't wrap single line comments inside
FunSpec. - Fix: Add non-wrapping package name.
- Fix: Remove n^2 algorithm in
CodeWriter.resolve()by precomputing all of the nested simple names of aTypeSpec. - Fix: Fix edge case with empty enum classes.
- Fix: Fix Nullable Type Parameter handling in
KType.asTypeName(). - Fix: Fix incorrect long comment wrapping in
FileSpec. - Fix: Attach primary constructor param/property KDoc to the element vs emitting it inside the type header.
Version 1.0.1
2019-01-02
- New: Allow enums without constants.
- New: Improved formatting of TypeSpec KDoc.
- New: Support @property and @param KDoc tags in TypeSpec.
- Fix: Use pre-formatted strings for arguments to %P.
Version 1.0.0
2018-12-10
- New: Kotlin 1.3.11.
- Fix: Prevent wrapping in import statements.
Version 1.0.0-RC3
2018-11-28
- New: Kotlin 1.3.10.
- New: Add
%Pplaceholder for string templates. - New: Add support for receiver kdoc.
- New: Avoid emitting
Unitas return type. - New: Add support for empty setters.
- New: Add checks for inline classes.
- New: Escape property and variable names if keywords.
- New: Replace
%>,%<,%[,%]placeholders withโฅ,โค,ยซ,ยป. - New: Replace
%Wwith space, and addยทas a non-breaking space. - New: Change
TypeNameto sealed class. - New: Documentation improvements.
- New: Replace
TypeNamemodifier methods withcopy(). - New: Rename members of
WildcardTypeNameto match with the producer/consumer generics model. - New: Rename
TypeName.nullableintoTypeName.isNullable. - New: Rename
LambdaTypeName.suspendingintoLambdaTypeName.isSuspending. - New: Rename
TypeVariableName.reifiedintoTypeVariableName.isReified. - Fix: Emit star-projection only for types with
Any?upper bound. - Fix: Fold property with escaped name.
Version 1.0.0-RC2
2018-10-22
- New: Kotlin 1.2.71.
- New: README improvements.
- New: Allow opening braces and params in
beginControlFlow(). - New: Add KDoc to
ParameterSpec, collapse into parent KDoc. - New: Support
TypeVariables inPropertySpec. - New: Add parens for annotated types in
LambdaTypeName. - New: Improve error messaging and documentation for inline properties.
- New: Allow sealed classes to declare abstract properties.
- New: Added
buildCodeBlock()helper function. - New: Allow using
CodeBlocks with statements as property initializers and default parameter values. - New: Rename
NameAllocator.clone()into `NameAllocator.copy(). - New: Rename
TypeName.asNonNullable()toTypeName.asNonNull(). - New: Remove
PropertySpec.varBuilder()(usemutable()instead). - New: Allow importing top-level members in default package.
- New: Add overloads to add KDoc to return type.
- Fix: Distinguishing
IntArrayandArraywhen creatingTypeName. - Fix: Use
TypeNameinstead ofClassNameas parameter type ofplusParameter(). - Fix: Keep type-parameter variance when constructing
TypeNamefromKType. - Fix: Don't validate modifiers when merging properties with primary constructor parameters.
- Fix: Escape $ characters in formatted strings.
- Fix:
FileSpec.Builderblank package and subfolder fix. - Fix: Append new line at end of parameter KDoc.
- Fix: Add parameter KDoc in
toBuilder().
Version 1.0.0-RC1
2018-07-16
- New: Escape keywords in imports and canonical class names.
- New: Improve
externalsupport. - New: Extensions for
KTypeandKTypeParameter. - New: Add builder methods to simplify adding common kotlin.jvm annotations.
- New: Enums are able to have companion objects.
- New: Add missing primaryConstructor & companionObject to
TypeSpec#toBuilder(). - New: Make subtype checking vals inside Kind public.
- New: Escape (class/property/function/variable) names automatically if they contain space, hyphen, or other symbols.
- New: Improve
ParameterizedTypeNameAPI. - New: Add
WildcardTypeName.STARconstant. - New: Expose mutable builder properties and move their validations to build-time.
- Fix: Use regular indents for parameter lists.
- Fix: Inline annotations on properties defined in primary constructor.
- Fix: Use
Any?as the default type variable bounds. - Fix: Fix importing annotated
TypeName. - Fix: If any primary constructor property has KDoc, put properties on new lines.
- Fix: Properly emit where block in type signature.
- Fix: Avoid type name collisions in primary constructor.
- Fix: Remove implicit
TypeVariablebound when more bounds are added. - Fix: Combine annotations and modifiers from constructor params and properties.
- Fix: Replace delegate constructor args along with the constructor.
Version 0.7.0
2018-02-16
- New: Increase indent to 4 spaces.
- New: Delegate super interfaces as constructor parameters.
- New: Support
PropertySpecs asCodeBlockliterals. - New: Support KDoc for
TypeAliasSpec. - New: Allow for adding an initializer block inside a companion object.
- New: Escape name in
ParameterSpecwhich is also a keyword. - New: Escape names in statements.
- New: Set com.squareup.kotlinpoet as automatic module name.
- New: Support suspending lambda types.
- New: Support named
LambdaTypeNameparameters. - New: Support dynamic type.
- New: Disallow wildcard imports.
- New: Depend on Kotlin 1.2.21.
- Fix: Correct handling of super-classes/interfaces on anonymous classes.
- Fix: Fix boundary filtering to
Any?. - Fix: Wrap long property initializers.
- Fix: Fix formatting and indentation of parameter lists.
Version 0.6.0
2017-11-03
- New: Support lambda extensions.
- New: Support renames in imports like
import bar.Bar as bBar. - New: Support extension and inline properties.
- New: Support reified types.
- New: Expose enclosed types inside
LambdaTypeName. - New: Depend on Kotlin Kotlin 1.1.51.
- New: Improved API and formatting of annotations.
- New: Improved multiplatform support.
- Fix: Escape function and package names if they are a Kotlin keyword.
- Fix: Properly format WildcardTypeName's class declaration.
Version 0.5.0
2017-09-13
- New: Rename
addFun()toaddFunction(). - New: Rename
KotlinFiletoFileSpec. - New: Rename
KotlinFile.addFileAnnotation()toaddAnnotation(). - New: Rename
KotlinFile.addFileComment()toaddComment(). - New: Support cross-platform code, including
HEADERandIMPLmodifiers. - New: Support type variables for type aliases.
- New: Support constructor delegation.
- New: Support named companion objects.
- New: Depend on Kotlin 1.1.4-3.
- Fix: Format one parameter per line when there are more than two parameters.
- Fix: Don't emit braces when the constructor body is empty.
- Fix: Do not invoke superclass constructor when no primary constructor.
- Fix: Enforce the right modifiers on functions.
Version 0.4.0
2017-08-08
- New: Change KotlinPoet's extensions like
asClassName()to be top-level functions. - New: Add declaration-site variance support.
- New: Improve handling of single expression bodies.
- New: Support file annotations.
- New: Support imports from the top-level file.
- New: Accept superclass constructor parameters.
- New: Support primary constructors using the
constructorkeyword. - Fix: Don't emit setter parameter types.
- Fix: Support Kotlin keywords in
NameAllocator. - Fix: Emit the right default parameters for primary constructors.
- Fix: Format annotations properly when used as parameters.
- Fix: Recognize imports when emitting nullable types.
- Fix: Call through to the superclass constructor when superclass has a no-args constructor.
- Fix: Omit class braces if all properties are declared in primary constructor.
- Fix: Don't emit empty class bodies.
- Fix: Emit the right syntax for declaring multiple generic type constraints.
- Fix: Support properties on objects, companions and interfaces.
- Fix: Use
AnnotationSpecfor throws.
Version 0.3.0
2017-06-11
- New: Objects and companion objects.
- New:
TypeAliasSpecto create type aliases. - New:
LambdaTypeNameto create lambda types. - New: Collapse property declarations into constructor params.
- New: Extension and invoke functions for creating type names:
Runnable::class.asClassName(). - New: Basic support for expression bodies.
- New: Basic support for custom accessors.
- New: Remove
Filerwriting and originating elements concept. These stem fromjavacannotation
processors. - Fix: Generate valid annotation classes.
- Fix: Use
KModifierfor varargs. - Fix: Use
ParameterizedTypeNamefor array types. - Fix: Extract Kotlin name from
KClassinstead of Java name. - Fix: Emit valid class literals:
Double::classinstead ofDouble.class. - Fix: Emit modifiers in the expected order.
- Fix: Emit the correct syntax for enum classes and overridden members.
Version 0.2.0
2017-05-21
- New: Flip API signatures to be (name, type) instead of (type, name).
- New: Support for nullable types.
- New: Support delegated properties.
- New: Extension functions.
- New: Support top-level properties.
- Fix: Inheritance should use
:instead ofextendsandimplements. - Fix: Make initializerBlock emit
init {}.
Version 0.1.0
2017-05-16
- Initial public release.
Code Block Format Strings (`docs/code-block-format-strings.md`)
Code Block Format Strings
Code blocks may specify the values for their placeholders in a few ways. Only one style may be used
for each operation on a code block.
Relative Arguments
Pass an argument value for each placeholder in the format string to CodeBlock.add(). In each
example, we generate code to say "I ate 3 tacos"
CodeBlock.builder().add("I ate %L %L", 3, "tacos")Positional Arguments
Place an integer index (1-based) before the placeholder in the format string to specify which
argument to use.
CodeBlock.builder().add("I ate %2L %1L", "tacos", 3)Named Arguments
Use the syntax %argumentName:X where X is the format character and call CodeBlock.addNamed()
with a map containing all argument keys in the format string. Argument names use characters ina-z, A-Z, 0-9, and _, and must start with a lowercase character.
val map = LinkedHashMap<String, Any>()
map += "food" to "tacos"
map += "count" to 3
CodeBlock.builder().addNamed("I ate %count:L %food:L", map)Code Control Flow (`docs/code-control-flow.md`)
Code & Control Flow
Most of KotlinPoet's API uses immutable Kotlin objects. There's also builders, method chaining
and varargs to make the API friendly. KotlinPoet offers models for Kotlin files (FileSpec),
classes, interfaces & objects (TypeSpec), type aliases (TypeAliasSpec),
properties (PropertySpec), functions & constructors (FunSpec), parameters (ParameterSpec) and
annotations (AnnotationSpec).
But the body of methods and constructors is not modeled. There's no expression class, no
statement class or syntax tree nodes. Instead, KotlinPoet uses strings for code blocks, and you can
take advantage of Kotlin's multiline strings to make this look nice:
val main = FunSpec.builder("main")
.addCode("""
|var total = 0
|for (i in 0..<10) {
| total += i
|}
|""".trimMargin())
.build()Which generates this:
fun main() {
var total = 0
for (i in 0..<10) {
total += i
}
}There are additional APIs to assist with newlines, braces and indentation:
val main = FunSpec.builder("main")
.addStatement("var total = 0")
.beginControlFlow("for (i in 0..<10)")
.addStatement("total += i")
.endControlFlow()
.build()This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10,
we want to make the operation and range configurable. Here's a method that generates a method:
private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec {
return FunSpec.builder(name)
.returns(Int::class)
.addStatement("var result = 1")
.beginControlFlow("for (i in $from..<$to)")
.addStatement("result = result $op i")
.endControlFlow()
.addStatement("return result")
.build()
}And here's what we get when we call computeRange("multiply10to20", 10, 20, "*"):
fun multiply10to20(): kotlin.Int {
var result = 1
for (i in 10..<20) {
result = result * i
}
return result
}Methods generating methods! And since KotlinPoet generates source instead of bytecode, you can
read through it to make sure it's right.
Constructors (`docs/constructors.md`)
Constructors
FunSpec is a slight misnomer; it can also be used for constructors:
val flux = FunSpec.constructorBuilder()
.addParameter("greeting", String::class)
.addStatement("this.%N = %N", "greeting", "greeting")
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addProperty("greeting", String::class, KModifier.PRIVATE)
.addFunction(flux)
.build()Which generates this:
class HelloWorld {
private val greeting: String
constructor(greeting: String) {
this.greeting = greeting
}
}For the most part, constructors work just like methods. When emitting code, KotlinPoet will place
constructors before methods in the output file.
Often times you'll need to generate the primary constructor for a class:
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.primaryConstructor(flux)
.addProperty("greeting", String::class, KModifier.PRIVATE)
.build()This code, however, generates the following:
class HelloWorld(greeting: String) {
private val greeting: String
init {
this.greeting = greeting
}
}By default, KotlinPoet won't merge primary constructor parameters and properties, even if they share
the same name. To achieve the effect, you have to tell KotlinPoet that the property is initialized
via the constructor parameter:
val flux = FunSpec.constructorBuilder()
.addParameter("greeting", String::class)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.primaryConstructor(flux)
.addProperty(
PropertySpec.builder("greeting", String::class)
.initializer("greeting")
.addModifiers(KModifier.PRIVATE)
.build()
)
.build()Now we're getting the following output:
class HelloWorld(private val greeting: String)Notice that KotlinPoet omits {} for classes with empty bodies.
Context Parameters (`docs/context-parameters.md`)
Context Parameters
KotlinPoet supports Kotlin's context parameters feature, which allows you to add context parameters to functions and properties. Context parameters are added using the contextParameter() method:
val greet = FunSpec.builder("greet")
.contextParameter("user", String::class)
.addStatement("println(\"Hello, ${'$'}user!\")")
.build()The code above generates:
context(user: kotlin.String)
public fun greet() {
println("Hello, $user!")
}You can add multiple context parameters:
val loggerType = ClassName("java.util.logging", "Logger")
val configType = ClassName("com.example", "Config")
val logger = ContextParameter("logger", loggerType)
val config = ContextParameter("config", configType)
val processData = FunSpec.builder("processData")
.contextParameter(logger)
.contextParameter(config)
.addStatement("%N.info(\"Processing with config: ${'$'}%N\")", logger, config)
.build()This generates:
context(logger: com.example.Logger, config: com.example.Config)
public fun processData() {
logger.info("Processing with config: $config")
}Note: context receivers and context parameters cannot be used together.
Contributing (`docs/contributing.md`)
Contributing
If you would like to contribute code you can do so through GitHub by forking
the repository and sending a pull request.
When submitting code, please make every effort to follow existing conventions
and style in order to keep the code as readable as possible. Please also make
sure your code compiles by running ./gradlew clean build.
When creating a pull request, please add a row in the changelog with the
patch description and PR # to Unreleased section.
Before your code can be accepted into the project you must also sign the
Individual Contributor License Agreement (CLA).
When submitting a pull request, please do not delete the default contents of
the pull request template (see pull_request_template.md) and make sure
you've completed the checklist before submitting. Pull requests that are
missing the checklist will be closed.
Enums (`docs/enums.md`)
Enums
Use enumBuilder to create the enum type, and addEnumConstant() for each value:
val helloWorld = TypeSpec.enumBuilder("Roshambo")
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build()To generate this:
enum class Roshambo {
ROCK,
SCISSORS,
PAPER
}Fancy enums are supported, where the enum values override methods or call a superclass constructor.
Here's a comprehensive example:
val helloWorld = TypeSpec.enumBuilder("Roshambo")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("handsign", String::class)
.build()
)
.addEnumConstant(
"ROCK", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "fist")
.addFunction(
FunSpec.builder("toString")
.addModifiers(KModifier.OVERRIDE)
.addStatement("return %S", "avalanche!")
.returns(String::class)
.build()
)
.build()
)
.addEnumConstant(
"SCISSORS", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "peace")
.build()
)
.addEnumConstant(
"PAPER", TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", "flat")
.build()
)
.addProperty(
PropertySpec.builder("handsign", String::class, KModifier.PRIVATE)
.initializer("handsign")
.build()
)
.build()Which generates this:
enum class Roshambo(private val handsign: String) {
ROCK("fist") {
override fun toString(): String = "avalanche!"
},
SCISSORS("peace"),
PAPER("flat");
}Functions (`docs/functions.md`)
Functions
All of the above functions have a code body. Use KModifier.ABSTRACT to get a function without any
body. This is only legal if it is enclosed by an abstract class or an interface.
val flux = FunSpec.builder("flux")
.addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(KModifier.ABSTRACT)
.addFunction(flux)
.build()Which generates this:
abstract class HelloWorld {
protected abstract fun flux()
}The other modifiers work where permitted.
Methods also have parameters, varargs, KDoc, annotations, type variables, return type and receiver
type for extension functions. All of these are configured with FunSpec.Builder.
Extension functions
Extension functions can be generated by specifying a receiver.
val square = FunSpec.builder("square")
.receiver(Int::class)
.returns(Int::class)
.addStatement("var s = this * this")
.addStatement("return s")
.build()Which outputs:
fun Int.square(): Int {
val s = this * this
return s
}Single-expression functions
KotlinPoet can recognize single-expression functions and print them out properly. It treats
each function with a body that starts with return as a single-expression function:
val abs = FunSpec.builder("abs")
.addParameter("x", Int::class)
.returns(Int::class)
.addStatement("return if (x < 0) -x else x")
.build()Which outputs:
fun abs(x: Int): Int = if (x < 0) -x else xDefault function arguments
Consider the example below.
Function argument b has a default value of 0 to avoid overloading this function.
fun add(a: Int, b: Int = 0) {
print("a + b = ${a + b}")
}Use the defaultValue() builder function to declare default value for a function argument.
FunSpec.builder("add")
.addParameter("a", Int::class)
.addParameter(
ParameterSpec.builder("b", Int::class)
.defaultValue("%L", 0)
.build()
)
.addStatement("print(\"a + b = ${a + b}\")")
.build()Wrapping is explicit
In order to guarantee code correctness, starting in version 2.0, KotlinPoet will never replace
spaces found in blocks of code with new line symbols, even in cases when the line of code exceeds
the length limit. Let's take this function for example:
val funSpec = FunSpec.builder("foo")
.addStatement("return (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }")
.build()The function will always be printed out like this:
public fun foo() = (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }While the output is correct, the resulting line of code is quite long and hard to read. KotlinPoet
is unable to understand the context of the expression and adjust the formatting for you, but
there's a trick you can use to declare a breaking space - use the โข symbol where you know it's
safe to optionally wrap the line. Let's apply this to our example:
val funSpec = FunSpec.builder("foo")
.addStatement("return (100..10000).map { number ->โขnumber * numberโข}.map { number ->โขnumber.toString()โข}.also { string ->โขprintln(string)โข}")
.build()This will now produce the following result:
public fun foo(): Unit = (100..10000).map { number -> number * number }.map { number ->
number.toString() }.also { string -> println(string) }This is slightly better, but far from perfect - feel free to play around with other formatting
modifiers, such as the standard \n character which KotlinPoet honors, or the indentation
formatters (โฅ and โค) that the library ships with (see the documentation for CodeBlock for
more information).
Lastly, imperfect formatting is a known limitation of the library, as KotlinPoet by design
prioritizes correctness of generated code over style. If correct and clean formatting is important
for your use case, please consider post-processing KotlinPoet's output using a dedicated code
formatter.
Interfaces (`docs/interfaces.md`)
Interfaces
KotlinPoet has no trouble with interfaces. Note that interface methods must always be ABSTRACT.
The modifier is necessary when defining the interface:
val helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addProperty("buzz", String::class)
.addFunction(
FunSpec.builder("beep")
.addModifiers(KModifier.ABSTRACT)
.build()
)
.build()But these modifiers are omitted when the code is generated. These are the default so we don't need
to include them for kotlinc's benefit!
interface HelloWorld {
val buzz: String
fun beep()
}Kotlin 1.4 adds support for functional interfaces via fun interface syntax. To create this in
KotlinPoet, use TypeSpec.funInterfaceBuilder().
val helloWorld = TypeSpec.funInterfaceBuilder("HelloWorld")
.addFunction(
FunSpec.builder("beep")
.addModifiers(KModifier.ABSTRACT)
.build()
)
.build()
// Generates...
fun interface HelloWorld {
fun beep()
}Interop Kotlin Metadata (`docs/interop-kotlin-metadata.md`)
KotlinPoet-metadata
interop:kotlin-metadata is an API for working with Kotlin @Metadata annotations. Its API
sits atop kotlin-metadata,
offering extensions for its types + JVM metadata information. This can be used to read
Kotlin language semantics off of Class or TypeElement @Metadata annotations.
Example
data class Taco(val seasoning: String, val soft: Boolean) {
fun prepare() {
}
}
val kmClass = Taco::class.toKmClass()
// Now you can access misc information about Taco from a Kotlin lens
println(kmClass.name)
kmClass.properties.forEach { println(it.name) }
kmClass.functions.forEach { println(it.name) }Flags
There are a number of boolean flags available to types as well under Flags.kt. These read the
underlying kotlin-metadata Flags property.
Using the Taco example above, we can glean certain information:
println("Is class? ${kmClass.isClass}")
println("Is data class? ${kmClass.isData}")Interop with KotlinPoet
interop:kotlin-metadata offers an API for converting core kotlin-metadata Km types to
KotlinPoet source representations of their APIs. This includes full type resolution, signatures,
enclosed elements, and general stub source representations of the underlying API.
Example
data class Taco(val seasoning: String, val soft: Boolean) {
fun prepare() {
}
}
val typeSpec = Taco::class.toTypeSpec()
// Or FileSpec
val fileSpec = Taco::class.toFileSpec()Source representation
The generated representations are a best effort representation of the underlying source code.
This means that synthetic elements will be excluded from generation. Kotlin-specific language
features like lambdas or delegation will be coerced to their idiomatic source form.
To aid with this, toTypeSpec() and toFileSpec() accept optional ClassInspector instances
to assist in parsing/understanding the underlying JVM code. This is important for things like
annotations, companion objects, certain JVM modifiers, overrides, and more. While it is optional,
represented sources can be incomplete without this information available. Reflective and javaxElements implementations are available under thecom.squareup.kotlinpoet.metadata.classinspectors package.
Generated sources are solely stub implementations, meaning implementation details of elements
like functions, property getters, and delegated properties are simply stubbed with TODO()
placeholders.
Known limitations
- Only
KotlinClassMetadata.ClassandKotlinClassMetadata.FileFacadeare supported for now. No support forSyntheticClass,MultiFileClassFacade, orMultiFileClassPart @JvmOverloadsannotations are only supported withElementsClassInspectorand not reflection.- Non-const literal values are only supported with
ElementsClassInspectorand not reflection. - ClassInspector data sourced from
syntheticconstructs are only supported withReflectiveClassInspectorand not elements. This is because the javax Elements API does not model
synthetic constructs. This can yield some missing information, like static companion object properties
orproperty:site target annotations. - Annotations annotated with
AnnotationRetention.SOURCEare not parsable in reflection nor javax elements.
Interop Ksp (`docs/interop-ksp.md`)
KSP Extensions for KotlinPoet
interop:ksp is an interop API for converting
Kotlin Symbol Processing (KSP) types to KotlinPoet types and
writing to KSP CodeGenerator.
dependencies {
implementation("com.squareup:kotlinpoet-ksp:<version>")
}Examples
Examples are based on reading the following property as a KSProperty:
class Taco {
internal inline val seasoning: String get() = "spicy"
}Convert a KSType to a TypeName
// returns a `ClassName` of value `kotlin.String`
seasoningKsProperty.type.toTypeName()Convert a Modifier to a KModifier
// returns `[KModifier.INLINE]`
seasoningKsProperty.modifiers.mapNotNull { it.toKModifier() }Convert a Visibility to a KModifier
// returns `KModifier.INTERNAL`
seasoningKsProperty.getVisibility().toKModifier()Write to CodeGenerator
To write a FileSpec to a KSP CodeGenerator, simply call the FileSpec.writeTo(CodeGenerator, ...)
extension function.
fileSpec.writeTo(codeGenerator)Type Parameters
Type parameters can be declared on classes, functions, and typealiases. These parameters are then
available to all of its enclosed elements. In order for these elements to resolve these in KSP, you
must be able to reference these type parameters by their index.
In kotlinpoet-ksp this is orchestrated by the TypeParameterResolver API, which can be passed
into most toTypeName() (or similar) functions to give them access to enclosing type parameters
that they may reference.
The canonical way to create an instance of this is to call toTypeParameterResolver() on aList<KSTypeParameter>.
Consider the following class and function
abstract class Taco<T> {
abstract val seasoning: T
}To properly resolve the type of seasoning, we need to pass the class TypeParameterResolver totoTypeName() so that it can properly resolve it.
val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
// returns `T`
val seasoningType = seasoningKsProperty.type.toTypeName(classTypeParams)TypeParameterResolver is also composable to allow for multi-level nesting. toTypeParameterResolver()
has an optional parent parameter to provide a parent instance.
Consider our previous example again, but this time with a function that defines its own type parameters.
class Taco<T> {
fun <E> getShellOfType(param1: E, param2: T) {
}
}To resolve its parameters, we need to create a TypeParameterResolver from the function'stypeParameters and compose it with the enclosing class's type parameters as a parent.
val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
val functionTypeParams = ksFunction.typeParameters.toTypeParameterResolver(parent = classTypeParams)
// returns `[E, T]`
val seasoningType = ksFunction.parameterTypes.map { it.toTypeName(functionTypeParams) }Incremental Processing
KSP supports incremental processing as
long as symbol processors properly indicate originating files in generated new files and whether or
not they are aggregating. kotlinpoet-ksp supports this via OriginatingKSFiles, which is a simple
API that sits atop KotlinPoet's Taggable API. To use this, simply add relevant originating files to
any TypeSpec, TypeAliasSpec, PropertySpec, or FunSpec builders.
val functionBuilder = FunSpec.builder("sayHello")
.addOriginatingKSFile(sourceKsFile)
.build()Like KotlinPoet's originating elements support for javac annotation processors, calling theFileSpec.writeTo(CodeGenerator, ...) function will automatically collect and de-dupe these originatingKSFile references and automatically assemble them in the underlying Dependencies for KSP's reference.
Optionally you can define your own collection of files and pass them to the writeTo function, but usually
you don't need to do this manually.
Lastly - FileSpec.writeTo(CodeGenerator, ...) also requires you to specify if your processor is
aggregating or not via required parameter by the same name.
TypeAlias Handling
For typealias types, KSP interop will store a TypeAliasTag in the TypeName's tags with a reference to the abbreviated type. This can be useful for APIs that want to resolve all un-aliased types.
Kotlin Reflect (`docs/kotlin-reflect.md`)
kotlin-reflect
To generate source code from any KType, including information that's not accessible to
the builtin reflection APIs, KotlinPoet depends on kotlin-reflect. kotlin-reflect
can read the metadata of your classes and access this extra information. KotlinPoet can for an
example, read the type parameters and their variance from a generic KType and
generate appropriate source code.
kotlin-reflect is a relatively big dependency though and in some cases it is desirable to remove
it from the final executable to save some space and/or simplify the proguard/R8 setup (for example
for a Gradle plugin that generates Kotlin code). It is possible to do so and still use most of the
KotlinPoet APIs:
dependencies {
implementation("com.squareup:kotlinpoet:<version>") {
exclude(module = "kotlin-reflect")
}
}The main APIs that require kotlin-reflect are KType.asTypeName() andtypeNameOf<T>(). If you're calling one of these without kotlin-reflect in the
classpath and the type is generic or has annotations you will get a crash.
You can replace it with code that passes type parameters or annotations explicitly and doesn't
need kotlin-reflect. For example:
// Replace
// kotlin-reflect needed
val typeName = typeNameOf<List<Int?>>()
// With
// kotlin-reflect not needed
val typeName =
List::class.asClassName().parameterizedBy(Int::class.asClassName().copy(nullable = true))L For Literals (`docs/l-for-literals.md`)
%L for Literals
Although Kotlin's string templates usually work well in cases when you want to include literals into
generated code, KotlinPoet offers additional syntax inspired-by but incompatible-withString.format(). It accepts %L to emit a literal value in the output. This
works just like Formatter's %s:
private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec {
return FunSpec.builder(name)
.returns(Int::class)
.addStatement("var result = 0")
.beginControlFlow("for (i in %L..<%L)", from, to)
.addStatement("result = result %L i", op)
.endControlFlow()
.addStatement("return result")
.build()
}Literals are emitted directly to the output code with no escaping. Arguments for literals may be
strings, primitives, and a few KotlinPoet types described below.
M For Members (`docs/m-for-members.md`)
%M for Members
Similar to types, KotlinPoet has a special placeholder for members (functions and properties),
which comes handy when your code needs to access top-level members and members declared inside
objects. Use %M to reference members, pass an instance of MemberName as the argument for the
placeholder, and KotlinPoet will handle imports automatically:
val createTaco = MemberName("com.squareup.tacos", "createTaco")
val isVegan = MemberName("com.squareup.tacos", "isVegan")
val file = FileSpec.builder("com.squareup.example", "TacoTest")
.addFunction(
FunSpec.builder("main")
.addStatement("val taco = %M()", createTaco)
.addStatement("println(taco.%M)", isVegan)
.build()
)
.build()
println(file)The code above generates the following file:
package com.squareup.example
import com.squareup.tacos.createTaco
import com.squareup.tacos.isVegan
fun main() {
val taco = createTaco()
println(taco.isVegan)
}As you can see, it's also possible to use %M to reference extension functions and properties. You
just need to make sure the member can be imported without simple name collisions, otherwise
importing will fail and the code generator output will not pass compilation. There's a way to work
around such cases though - use FileSpec.addAliasedImport() to create an alias for a clashingMemberName:
val createTaco = MemberName("com.squareup.tacos", "createTaco")
val createCake = MemberName("com.squareup.cakes", "createCake")
val isTacoVegan = MemberName("com.squareup.tacos", "isVegan")
val isCakeVegan = MemberName("com.squareup.cakes", "isVegan")
val file = FileSpec.builder("com.squareup.example", "Test")
.addAliasedImport(isTacoVegan, "isTacoVegan")
.addAliasedImport(isCakeVegan, "isCakeVegan")
.addFunction(
FunSpec.builder("main")
.addStatement("val taco = %M()", createTaco)
.addStatement("val cake = %M()", createCake)
.addStatement("println(taco.%M)", isTacoVegan)
.addStatement("println(cake.%M)", isCakeVegan)
.build()
)
.build()
println(file)KotlinPoet will produce an aliased import for com.squareup.tacos2.isVegan:
package com.squareup.example
import com.squareup.cakes.createCake
import com.squareup.tacos.createTaco
import com.squareup.cakes.isVegan as isCakeVegan
import com.squareup.tacos.isVegan as isTacoVegan
fun main() {
val taco = createTaco()
val cake = createCake()
println(taco.isTacoVegan)
println(cake.isCakeVegan)
}MemberName and operators
MemberName also supports operators, you can use MemberName(String, KOperator)
or MemberName(ClassName, KOperator) to import and reference operators.
val taco = ClassName("com.squareup.tacos", "Taco")
val meat = ClassName("com.squareup.tacos.ingredient", "Meat")
val iterator = MemberName("com.squareup.tacos.internal", KOperator.ITERATOR)
val minusAssign = MemberName("com.squareup.tacos.internal", KOperator.MINUS_ASSIGN)
val file = FileSpec.builder("com.example", "Test")
.addFunction(
FunSpec.builder("makeTacoHealthy")
.addParameter("taco", taco)
.beginControlFlow("for (ingredient %M taco)", iterator)
.addStatement("if (ingredient is %T) taco %M ingredient", meat, minusAssign)
.endControlFlow()
.addStatement("return taco")
.build()
)
.build()
println(file)KotlinPoet will import the extension operator functions and emit the operator.
package com.example
import com.squareup.tacos.Taco
import com.squareup.tacos.ingredient.Meat
import com.squareup.tacos.internal.iterator
import com.squareup.tacos.internal.minusAssign
fun makeTacoHealthy(taco: Taco) {
for (ingredient in taco) {
if (ingredient is Meat) taco -= ingredient
}
return taco
}N For Names (`docs/n-for-names.md`)
%N for Names
Generated code is often self-referential. Use %N to refer to another generated declaration by
its name. Here's a method that calls another:
fun byteToHex(b: Int): String {
val result = CharArray(2)
result[0] = hexDigit((b ushr 4) and 0xf)
result[1] = hexDigit(b and 0xf)
return String(result)
}
fun hexDigit(i: Int): Char {
return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar()
}When generating the code above, we pass the hexDigit() method as an argument to the byteToHex()
method using %N:
val hexDigit = FunSpec.builder("hexDigit")
.addParameter("i", Int::class)
.returns(Char::class)
.addStatement("return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar()")
.build()
val byteToHex = FunSpec.builder("byteToHex")
.addParameter("b", Int::class)
.returns(String::class)
.addStatement("val result = CharArray(2)")
.addStatement("result[0] = %N((b ushr 4) and 0xf)", hexDigit)
.addStatement("result[1] = %N(b and 0xf)", hexDigit)
.addStatement("return String(result)")
.build()Another handy feature that %N provides is automatically escaping names that contain illegal
identifier characters with double ticks. Suppose your code creates a MemberName with a Kotlin
keyword as the simple name:
val taco = ClassName("com.squareup.tacos", "Taco")
val packager = ClassName("com.squareup.tacos", "TacoPackager")
val file = FileSpec.builder("com.example", "Test")
.addFunction(
FunSpec.builder("packageTacos")
.addParameter("tacos", LIST.parameterizedBy(taco))
.addParameter("packager", packager)
.addStatement("packager.%N(tacos)", packager.member("package"))
.build()
)
.build()%N will escape the name for you, ensuring that the output will pass compilation:
package com.example
import com.squareup.tacos.Taco
import com.squareup.tacos.TacoPackager
import kotlin.collections.List
fun packageTacos(tacos: List<Taco>, packager: TacoPackager) {
packager.`package`(tacos)
}Objects (`docs/objects.md`)
Objects
KotlinPoet supports objects:
val helloWorld = TypeSpec.objectBuilder("HelloWorld")
.addProperty(
PropertySpec.builder("buzz", String::class)
.initializer("%S", "buzz")
.build()
)
.addFunction(
FunSpec.builder("beep")
.addStatement("println(%S)", "Beep!")
.build()
)
.build()Similarly, you can create companion objects and add them to classes using addType():
val companion = TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder("buzz", String::class)
.initializer("%S", "buzz")
.build()
)
.addFunction(
FunSpec.builder("beep")
.addStatement("println(%S)", "Beep!")
.build()
)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addType(companion)
.build()You can provide an optional name for a companion object.
P For String Templates (`docs/p-for-string-templates.md`)
%P for String Templates
%S also handles the escaping of dollar signs ($), to avoid inadvertent creation of string
templates, which may fail to compile in generated code:
val stringWithADollar = "Your total is " + "$" + "50"
val funSpec = FunSpec.builder("printTotal")
.returns(String::class)
.addStatement("return %S", stringWithADollar)
.build()produces:
fun printTotal(): String = "Your total is ${'$'}50"If you need to generate string templates, use %P, which doesn't escape dollars:
val amount = 50
val stringWithADollar = "Your total is " + "$" + "amount"
val funSpec = FunSpec.builder("printTotal")
.returns(String::class)
.addStatement("return %P", stringWithADollar)
.build()produces:
fun printTotal(): String = "Your total is $amount"You can also use CodeBlocks as arguments to %P, which is handy when you need to reference
importable types or members inside the string template:
val file = FileSpec.builder("com.example", "Digits")
.addFunction(
FunSpec.builder("print")
.addParameter("digits", IntArray::class)
.addStatement("println(%P)", buildCodeBlock {
val contentToString = MemberName("kotlin.collections", "contentToString")
add("These are the digits: \${digits.%M()}", contentToString)
})
.build()
)
.build()
println(file)The snippet above will produce the following output, handling the imports properly:
package com.example
import kotlin.IntArray
import kotlin.collections.contentToString
fun print(digits: IntArray) {
println("""These are the digits: ${digits.contentToString()}""")
}Parameters (`docs/parameters.md`)
Parameters
Declare parameters on methods and constructors with either ParameterSpec.builder() orFunSpec's convenient addParameter() API:
val android = ParameterSpec.builder("android", String::class)
.defaultValue("\"pie\"")
.build()
val welcomeOverlords = FunSpec.builder("welcomeOverlords")
.addParameter(android)
.addParameter("robot", String::class)
.build()The code above generates:
fun welcomeOverlords(android: String = "pie", robot: String) {
}The extended Builder form is necessary when the parameter has annotations (such as @Inject).
Properties (`docs/properties.md`)
Properties
Like parameters, properties can be created either with builders or by using convenient helper
methods:
val android = PropertySpec.builder("android", String::class)
.addModifiers(KModifier.PRIVATE)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addProperty(android)
.addProperty("robot", String::class, KModifier.PRIVATE)
.build()Which generates:
class HelloWorld {
private val android: String
private val robot: String
}The extended Builder form is necessary when a field has KDoc, annotations, or a field
initializer. Field initializers use the same String.format()-like syntax as the code
blocks above:
val android = PropertySpec.builder("android", String::class)
.addModifiers(KModifier.PRIVATE)
.initializer("%S + %L", "Oreo v.", 8.1)
.build()Which generates:
private val android: String = "Oreo v." + 8.1By default PropertySpec.Builder produces val properties. Use mutable() if you need avar:
val android = PropertySpec.builder("android", String::class)
.mutable()
.addModifiers(KModifier.PRIVATE)
.initializer("%S + %L", "Oreo v.", 8.1)
.build()Inline properties
The way KotlinPoet models inline properties deserves special mention. The following snippet of code:
val android = PropertySpec.builder("android", String::class)
.mutable()
.addModifiers(KModifier.INLINE)
.build()will produce an error:
java.lang.IllegalArgumentException: KotlinPoet doesn't allow setting the inline modifier on
properties. You should mark either the getter, the setter, or both inline.Indeed, a property marked with inline should have at least one accessor which will be inlined by
the compiler. Let's add a getter to this property:
val android = PropertySpec.builder("android", String::class)
.mutable()
.getter(
FunSpec.getterBuilder()
.addModifiers(KModifier.INLINE)
.addStatement("return %S", "foo")
.build()
)
.build()The result is the following:
var android: kotlin.String
inline get() = "foo"Now, what if we wanted to add a non-inline setter to the property above? We can do so without
modifying any of the code we wrote previously:
val android = PropertySpec.builder("android", String::class)
.mutable()
.getter(
FunSpec.getterBuilder()
.addModifiers(KModifier.INLINE)
.addStatement("return %S", "foo")
.build()
)
.setter(
FunSpec.setterBuilder()
.addParameter("value", String::class)
.build()
)
.build()We get the expected result:
var android: kotlin.String
inline get() = "foo"
set(`value`) {
}Finally, if we go back and add KModifier.INLINE to the setter, KotlinPoet can wrap it nicely and
produce the following result:
inline var android: kotlin.String
get() = "foo"
set(`value`) {
}Removing the modifier from either the getter or the setter will unwrap the expression back.
If, on the other hand, KotlinPoet had allowed marking a property inline directly, the programmer
would have had to manually add/remove the modifier whenever the state of the accessors changes in
order to get correct and compilable output. We're solving this problem by making accessors the
source of truth for the inline modifier.
S For Strings (`docs/s-for-strings.md`)
%S for Strings
When emitting code that includes string literals, we can use %S to emit a string, complete
with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which
returns its own name:
fun main(args: Array<String>) {
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addFunction(whatsMyNameYo("slimShady"))
.addFunction(whatsMyNameYo("eminem"))
.addFunction(whatsMyNameYo("marshallMathers"))
.build()
val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld")
.addType(helloWorld)
.build()
kotlinFile.writeTo(System.out)
}
private fun whatsMyNameYo(name: String): FunSpec {
return FunSpec.builder(name)
.returns(String::class)
.addStatement("return %S", name)
.build()
}In this case, using %S gives us quotation marks:
class HelloWorld {
fun slimShady(): String = "slimShady"
fun eminem(): String = "eminem"
fun marshallMathers(): String = "marshallMathers"
}T For Types (`docs/t-for-types.md`)
%T for Types
KotlinPoet has rich built-in support for types, including automatic generation of import
statements. Just use %T to reference types:
val today = FunSpec.builder("today")
.returns(Date::class)
.addStatement("return %T()", Date::class)
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addFunction(today)
.build()
val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld")
.addType(helloWorld)
.build()
kotlinFile.writeTo(System.out)That generates the following .kt file, complete with the necessary import:
package com.example.helloworld
import java.util.Date
class HelloWorld {
fun today(): Date = Date()
}We passed Date::class to reference a class that just-so-happens to be available when we're
generating code. This doesn't need to be the case. Here's a similar example, but this one
references a class that doesn't exist (yet):
val hoverboard = ClassName("com.mattel", "Hoverboard")
val tomorrow = FunSpec.builder("tomorrow")
.returns(hoverboard)
.addStatement("return %T()", hoverboard)
.build()And that not-yet-existent class is imported as well:
package com.example.helloworld
import com.mattel.Hoverboard
class HelloWorld {
fun tomorrow(): Hoverboard = Hoverboard()
}The ClassName type is very important, and you'll need it frequently when you're using KotlinPoet.
It can identify any declared class. Declared types are just the beginning of Kotlin's rich type
system: we also have arrays, parameterized types, wildcard types, lambda types and type variables.
KotlinPoet has classes for building each of these:
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STAR
val hoverboard = ClassName("com.mattel", "Hoverboard")
val list = ClassName("kotlin.collections", "List")
val arrayList = ClassName("kotlin.collections", "ArrayList")
val listOfHoverboards = list.parameterizedBy(hoverboard)
val arrayListOfHoverboards = arrayList.parameterizedBy(hoverboard)
val thing = ClassName("com.misc", "Thing")
val array = ClassName("kotlin", "Array")
val producerArrayOfThings = array.parameterizedBy(WildcardTypeName.producerOf(thing))
val beyond = FunSpec.builder("beyond")
.returns(listOfHoverboards)
.addStatement("val result = %T()", arrayListOfHoverboards)
.addStatement("result += %T()", hoverboard)
.addStatement("result += %T()", hoverboard)
.addStatement("result += %T()", hoverboard)
.addStatement("return result")
.build()
val printThings = FunSpec.builder("printThings")
.addParameter("things", producerArrayOfThings)
.addStatement("println(things)")
.build()
val printKClass = FunSpec.builder("printKClass")
.addParameter("kClass", KClass::class.asClassName().parameterizedBy(STAR))
.addStatement("println(kClass)")
.build()The STAR is represented as * in KotlinPoet. You can find more in the KDoc.
KotlinPoet will decompose each type and import its components where possible.
package com.example.helloworld
import com.mattel.Hoverboard
import com.misc.Thing
import kotlin.Array
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.reflect.KClass
class HelloWorld {
fun beyond(): List<Hoverboard> {
val result = ArrayList<Hoverboard>()
result += Hoverboard()
result += Hoverboard()
result += Hoverboard()
return result
}
fun printThings(things: Array<out Thing>) {
println(things)
}
fun printKClass(kClass: KClass<*>) {
println(kClass)
}
}Nullable Types
KotlinPoet supports nullable types. To convert a TypeName into its nullable counterpart, use thecopy() method with nullable parameter set to true:
val java = PropertySpec.builder("java", String::class.asTypeName().copy(nullable = true))
.mutable()
.addModifiers(KModifier.PRIVATE)
.initializer("null")
.build()
val helloWorld = TypeSpec.classBuilder("HelloWorld")
.addProperty(java)
.addProperty("kotlin", String::class, KModifier.PRIVATE)
.build()generates:
class HelloWorld {
private var java: String? = null
private val kotlin: String
}Annotated Types
KotlinPoet supports annotated types. To add an annotation to a type, use the annotated() API:
val annotatedType = String::class.asTypeName()
.annotated(AnnotationSpec.builder(MyAnnotation::class).build())For comprehensive examples of annotating types, see Annotations.
Type Aliases (`docs/type-aliases.md`)
Type Aliases
KotlinPoet provides API for creating Type Aliases, which supports simple class names, parameterized
types and lambdas:
val k = TypeVariableName("K")
val t = TypeVariableName("T")
val fileTable = Map::class.asClassName()
.parameterizedBy(k, Set::class.parameterizedBy(File::class))
val predicate = LambdaTypeName.get(
parameters = arrayOf(t),
returnType = Boolean::class.asClassName()
)
val helloWorld = FileSpec.builder("com.example", "HelloWorld")
.addTypeAlias(TypeAliasSpec.builder("Word", String::class).build())
.addTypeAlias(
TypeAliasSpec.builder("FileTable", fileTable)
.addTypeVariable(k)
.build()
)
.addTypeAlias(
TypeAliasSpec.builder("Predicate", predicate)
.addTypeVariable(t)
.build()
)
.build()Which generates the following:
package com.example
import java.io.File
import kotlin.Boolean
import kotlin.String
import kotlin.collections.Map
import kotlin.collections.Set
typealias Word = String
typealias FileTable<K> = Map<K, Set<File>>
typealias Predicate<T> = (T) -> BooleanType aliases can also be added to types (classes, interfaces, objects) as well:
Note: Nested type aliases are a beta feature in Kotlin. See the official documentation
here for more details.
val taco = TypeSpec.classBuilder("Taco")
.addTypeAlias(TypeAliasSpec.builder("Topping", String::class).build())
.build()Which generates the following:
package com.squareup.tacos
import kotlin.String
public class Taco {
public typealias Topping = String
}--- METRICS ---
- Files Extracted: 27
- Estimated Token Budget: ~25372 tokens
- Canonical Reference: https://codewiki.google/github.com/square/kotlinpoet